diff --git a/README.md b/README.md index d00e91c9..179cb280 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,17 @@ This project only accepts pull requests that relate to third-party provider supp If you need to submit any non-third-party provider changes, please open them against the mainline repository. +## More choices + +Those projects are ports of CLIProxyAPI or inspired by it: + +### [9Router](https://github.com/decolua/9router) + +A Next.js implementation inspired by CLIProxyAPI, easy to install and use, built from scratch with format translation (OpenAI/Claude/Gemini/Ollama), combo system with auto-fallback, multi-account management with exponential backoff, a Next.js web dashboard, and support for CLI tools (Cursor, Claude Code, Cline, RooCode) - no API keys needed. + +> [!NOTE] +> If you have developed a port of CLIProxyAPI or a project inspired by it, please open a PR to add it to this list. + ## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/README_CN.md b/README_CN.md index 21132b86..7838937f 100644 --- a/README_CN.md +++ b/README_CN.md @@ -19,6 +19,17 @@ 如果需要提交任何非第三方供应商支持的 Pull Request,请提交到主线版本。 +## 更多选择 + +以下项目是 CLIProxyAPI 的移植版或受其启发: + +### [9Router](https://github.com/decolua/9router) + +基于 Next.js 的实现,灵感来自 CLIProxyAPI,易于安装使用;自研格式转换(OpenAI/Claude/Gemini/Ollama)、组合系统与自动回退、多账户管理(指数退避)、Next.js Web 控制台,并支持 Cursor、Claude Code、Cline、RooCode 等 CLI 工具,无需 API 密钥。 + +> [!NOTE] +> 如果你开发了 CLIProxyAPI 的移植或衍生项目,请提交 PR 将其添加到此列表中。 + ## 许可证 此项目根据 MIT 许可证授权 - 有关详细信息,请参阅 [LICENSE](LICENSE) 文件。 \ No newline at end of file diff --git a/internal/translator/antigravity/openai/chat-completions/antigravity_openai_request.go b/internal/translator/antigravity/openai/chat-completions/antigravity_openai_request.go index baf76eef..7ca01b07 100644 --- a/internal/translator/antigravity/openai/chat-completions/antigravity_openai_request.go +++ b/internal/translator/antigravity/openai/chat-completions/antigravity_openai_request.go @@ -184,7 +184,7 @@ func ConvertOpenAIRequestToAntigravity(modelName string, inputRawJSON []byte, _ role := m.Get("role").String() content := m.Get("content") - if role == "system" && len(arr) > 1 { + if (role == "system" || role == "developer") && len(arr) > 1 { // system -> request.systemInstruction as a user message style if content.Type == gjson.String { out, _ = sjson.SetBytes(out, "request.systemInstruction.role", "user") @@ -201,7 +201,7 @@ func ConvertOpenAIRequestToAntigravity(modelName string, inputRawJSON []byte, _ } } } - } else if role == "user" || (role == "system" && len(arr) == 1) { + } else if role == "user" || ((role == "system" || role == "developer") && len(arr) == 1) { // Build single user content node to avoid splitting into multiple contents node := []byte(`{"role":"user","parts":[]}`) if content.Type == gjson.String { diff --git a/internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_request.go b/internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_request.go index c511644d..98188835 100644 --- a/internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_request.go +++ b/internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_request.go @@ -152,7 +152,7 @@ func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bo role := m.Get("role").String() content := m.Get("content") - if role == "system" && len(arr) > 1 { + if (role == "system" || role == "developer") && len(arr) > 1 { // system -> request.systemInstruction as a user message style if content.Type == gjson.String { out, _ = sjson.SetBytes(out, "request.systemInstruction.role", "user") @@ -169,7 +169,7 @@ func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bo } } } - } else if role == "user" || (role == "system" && len(arr) == 1) { + } else if role == "user" || ((role == "system" || role == "developer") && len(arr) == 1) { // Build single user content node to avoid splitting into multiple contents node := []byte(`{"role":"user","parts":[]}`) if content.Type == gjson.String { diff --git a/internal/translator/gemini/openai/chat-completions/gemini_openai_request.go b/internal/translator/gemini/openai/chat-completions/gemini_openai_request.go index 5c96b847..57e150c1 100644 --- a/internal/translator/gemini/openai/chat-completions/gemini_openai_request.go +++ b/internal/translator/gemini/openai/chat-completions/gemini_openai_request.go @@ -170,7 +170,7 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool) role := m.Get("role").String() content := m.Get("content") - if role == "system" && len(arr) > 1 { + if (role == "system" || role == "developer") && len(arr) > 1 { // system -> system_instruction as a user message style if content.Type == gjson.String { out, _ = sjson.SetBytes(out, "system_instruction.role", "user") @@ -187,7 +187,7 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool) } } } - } else if role == "user" || (role == "system" && len(arr) == 1) { + } else if role == "user" || ((role == "system" || role == "developer") && len(arr) == 1) { // Build single user content node to avoid splitting into multiple contents node := []byte(`{"role":"user","parts":[]}`) if content.Type == gjson.String {