mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-04 21:40:51 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0331660fe2 | ||
|
|
512c8b600a | ||
|
|
1aad033fec | ||
|
|
f1d9364ef4 | ||
|
|
c2b2c9eafe |
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**CLI Type**
|
||||
What type of CLI account do you use? (gemini-cli, gemini, codex, claude code or openai-compatibility)
|
||||
|
||||
**Model Name**
|
||||
What model are you using? (example: gemini-2.5-pro, claude-sonnet-4-20250514, gpt-5, etc.)
|
||||
|
||||
**LLM Client**
|
||||
What LLM Client are you using? (example: roo-code, cline, claude code, etc.)
|
||||
|
||||
**Request Information**
|
||||
The best way is to paste the cURL command of the HTTP request here.
|
||||
Alternatively, you can set `request-log: true` in the `config.yaml` file and then upload the detailed log file.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**OS Type**
|
||||
- OS: [e.g. macOS]
|
||||
- Version [e.g. 15.6.0]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
56
README.md
56
README.md
@@ -239,28 +239,28 @@ The server uses a YAML configuration file (`config.yaml`) located in the project
|
||||
|
||||
### Configuration Options
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------------------------------------|----------|--------------------|---------------------------------------------------------------------------------------------------------|
|
||||
| `port` | integer | 8317 | The port number on which the server will listen. |
|
||||
| `auth-dir` | string | "~/.cli-proxy-api" | Directory where authentication tokens are stored. Supports using `~` for the home directory. |
|
||||
| `proxy-url` | string | "" | Proxy URL. Supports socks5/http/https protocols. Example: socks5://user:pass@192.168.1.1:1080/ |
|
||||
| `request-retry` | integer | 0 | Number of times to retry a request. Retries will occur if the HTTP response code is 403, 408, 500, 502, 503, or 504. |
|
||||
| `quota-exceeded` | object | {} | Configuration for handling quota exceeded. |
|
||||
| `quota-exceeded.switch-project` | boolean | true | Whether to automatically switch to another project when a quota is exceeded. |
|
||||
| `quota-exceeded.switch-preview-model` | boolean | true | Whether to automatically switch to a preview model when a quota is exceeded. |
|
||||
| `debug` | boolean | false | Enable debug mode for verbose logging. |
|
||||
| `api-keys` | string[] | [] | List of API keys that can be used to authenticate requests. |
|
||||
| `generative-language-api-key` | string[] | [] | List of Generative Language API keys. |
|
||||
| `claude-api-key` | object | {} | List of Claude API keys. |
|
||||
| `claude-api-key.api-key` | string | "" | Claude API key. |
|
||||
| `claude-api-key.base-url` | string | "" | Custom Claude API endpoint, if you use a third-party API endpoint. |
|
||||
| `openai-compatibility` | object[] | [] | Upstream OpenAI-compatible providers configuration (name, base-url, api-keys, models). |
|
||||
| `openai-compatibility.*.name` | string | "" | The name of the provider. It will be used in the user agent and other places. |
|
||||
| `openai-compatibility.*.base-url` | string | "" | The base URL of the provider. |
|
||||
| `openai-compatibility.*.api-keys` | string[] | [] | The API keys for the provider. Add multiple keys if needed. Omit if unauthenticated access is allowed. |
|
||||
| `openai-compatibility.*.models` | object[] | [] | The actual model name. |
|
||||
| `openai-compatibility.*.models.*.name` | string | "" | The models supported by the provider. |
|
||||
| `openai-compatibility.*.models.*.alias` | string | "" | The alias used in the API. |
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------------------------------------|----------|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `port` | integer | 8317 | The port number on which the server will listen. |
|
||||
| `auth-dir` | string | "~/.cli-proxy-api" | Directory where authentication tokens are stored. Supports using `~` for the home directory. If you use Windows, please set the directory like this: `C:/cli-proxy-api/` |
|
||||
| `proxy-url` | string | "" | Proxy URL. Supports socks5/http/https protocols. Example: socks5://user:pass@192.168.1.1:1080/ |
|
||||
| `request-retry` | integer | 0 | Number of times to retry a request. Retries will occur if the HTTP response code is 403, 408, 500, 502, 503, or 504. |
|
||||
| `quota-exceeded` | object | {} | Configuration for handling quota exceeded. |
|
||||
| `quota-exceeded.switch-project` | boolean | true | Whether to automatically switch to another project when a quota is exceeded. |
|
||||
| `quota-exceeded.switch-preview-model` | boolean | true | Whether to automatically switch to a preview model when a quota is exceeded. |
|
||||
| `debug` | boolean | false | Enable debug mode for verbose logging. |
|
||||
| `api-keys` | string[] | [] | List of API keys that can be used to authenticate requests. |
|
||||
| `generative-language-api-key` | string[] | [] | List of Generative Language API keys. |
|
||||
| `claude-api-key` | object | {} | List of Claude API keys. |
|
||||
| `claude-api-key.api-key` | string | "" | Claude API key. |
|
||||
| `claude-api-key.base-url` | string | "" | Custom Claude API endpoint, if you use a third-party API endpoint. |
|
||||
| `openai-compatibility` | object[] | [] | Upstream OpenAI-compatible providers configuration (name, base-url, api-keys, models). |
|
||||
| `openai-compatibility.*.name` | string | "" | The name of the provider. It will be used in the user agent and other places. |
|
||||
| `openai-compatibility.*.base-url` | string | "" | The base URL of the provider. |
|
||||
| `openai-compatibility.*.api-keys` | string[] | [] | The API keys for the provider. Add multiple keys if needed. Omit if unauthenticated access is allowed. |
|
||||
| `openai-compatibility.*.models` | object[] | [] | The actual model name. |
|
||||
| `openai-compatibility.*.models.*.name` | string | "" | The models supported by the provider. |
|
||||
| `openai-compatibility.*.models.*.alias` | string | "" | The alias used in the API. |
|
||||
|
||||
### Example Configuration File
|
||||
|
||||
@@ -268,7 +268,7 @@ The server uses a YAML configuration file (`config.yaml`) located in the project
|
||||
# Server port
|
||||
port: 8317
|
||||
|
||||
# Authentication directory (supports ~ for home directory)
|
||||
# Authentication directory (supports ~ for home directory). If you use Windows, please set the directory like this: `C:/cli-proxy-api/`
|
||||
auth-dir: "~/.cli-proxy-api"
|
||||
|
||||
# Enable debug logging
|
||||
@@ -431,10 +431,16 @@ Run the following command to login (OpenAI OAuth on port 1455):
|
||||
docker run --rm -p 1455:1455 -v /path/to/your/config.yaml:/CLIProxyAPI/config.yaml -v /path/to/your/auth-dir:/root/.cli-proxy-api eceasy/cli-proxy-api:latest /CLIProxyAPI/CLIProxyAPI --codex-login
|
||||
```
|
||||
|
||||
Run the following command to login (Claude OAuth on port 54545):
|
||||
Run the following command to logi (Claude OAuth on port 54545):
|
||||
|
||||
```bash
|
||||
docker run --rm -p 54545:54545 -v /path/to/your/config.yaml:/CLIProxyAPI/config.yaml -v /path/to/your/auth-dir:/root/.cli-proxy-api eceasy/cli-proxy-api:latest /CLIProxyAPI/CLIProxyAPI --claude-login
|
||||
docker run -rm -p 54545:54545 -v /path/to/your/config.yaml:/CLIProxyAPI/config.yaml -v /path/to/your/auth-dir:/root/.cli-proxy-api eceasy/cli-proxy-api:latest /CLIProxyAPI/CLIProxyAPI --claude-login
|
||||
```
|
||||
|
||||
Run the following command to login (Qwen OAuth):
|
||||
|
||||
```bash
|
||||
docker run -it -rm -v /path/to/your/config.yaml:/CLIProxyAPI/config.yaml -v /path/to/your/auth-dir:/root/.cli-proxy-api eceasy/cli-proxy-api:latest /CLIProxyAPI/CLIProxyAPI --qwen-login
|
||||
```
|
||||
|
||||
Run the following command to start the server:
|
||||
|
||||
53
README_CN.md
53
README_CN.md
@@ -238,28 +238,28 @@ console.log(await claudeResponse.json());
|
||||
|
||||
### 配置选项
|
||||
|
||||
| 参数 | 类型 | 默认值 | 描述 |
|
||||
|---------------------------------------|----------|--------------------|---------------------------------------------------------------------------------------------|
|
||||
| `port` | integer | 8317 | 服务器将监听的端口号。 |
|
||||
| `auth-dir` | string | "~/.cli-proxy-api" | 存储身份验证令牌的目录。支持使用 `~` 来表示主目录。 |
|
||||
| `proxy-url` | string | "" | 代理URL。支持socks5/http/https协议。例如:socks5://user:pass@192.168.1.1:1080/ |
|
||||
| `request-retry` | integer | 0 | 请求重试次数。如果HTTP响应码为403、408、500、502、503或504,将会触发重试。 |
|
||||
| `quota-exceeded` | object | {} | 用于处理配额超限的配置。 |
|
||||
| `quota-exceeded.switch-project` | boolean | true | 当配额超限时,是否自动切换到另一个项目。 |
|
||||
| `quota-exceeded.switch-preview-model` | boolean | true | 当配额超限时,是否自动切换到预览模型。 |
|
||||
| `debug` | boolean | false | 启用调试模式以获取详细日志。 |
|
||||
| `api-keys` | string[] | [] | 可用于验证请求的API密钥列表。 |
|
||||
| `generative-language-api-key` | string[] | [] | 生成式语言API密钥列表。 |
|
||||
| `claude-api-key` | object | {} | Claude API密钥列表。 |
|
||||
| `claude-api-key.api-key` | string | "" | Claude API密钥。 |
|
||||
| `claude-api-key.base-url` | string | "" | 自定义的Claude API端点,如果您使用第三方的API端点。 |
|
||||
| `openai-compatibility` | object[] | [] | 上游OpenAI兼容提供商的配置(名称、基础URL、API密钥、模型)。 |
|
||||
| `openai-compatibility.*.name` | string | "" | 提供商的名称。它将被用于用户代理(User Agent)和其他地方。 |
|
||||
| `openai-compatibility.*.base-url` | string | "" | 提供商的基础URL。 |
|
||||
| `openai-compatibility.*.api-keys` | string[] | [] | 提供商的API密钥。如果需要,可以添加多个密钥。如果允许未经身份验证的访问,则可以省略。 |
|
||||
| `openai-compatibility.*.models` | object[] | [] | 实际的模型名称。 |
|
||||
| `openai-compatibility.*.models.*.name` | string | "" | 提供商支持的模型。 |
|
||||
| `openai-compatibility.*.models.*.alias` | string | "" | 在API中使用的别名。 |
|
||||
| 参数 | 类型 | 默认值 | 描述 |
|
||||
|-----------------------------------------|----------|--------------------|---------------------------------------------------------------------|
|
||||
| `port` | integer | 8317 | 服务器将监听的端口号。 |
|
||||
| `auth-dir` | string | "~/.cli-proxy-api" | 存储身份验证令牌的目录。支持使用 `~` 来表示主目录。如果你使用Windows,建议设置成`C:/cli-proxy-api/`。 |
|
||||
| `proxy-url` | string | "" | 代理URL。支持socks5/http/https协议。例如:socks5://user:pass@192.168.1.1:1080/ |
|
||||
| `request-retry` | integer | 0 | 请求重试次数。如果HTTP响应码为403、408、500、502、503或504,将会触发重试。 |
|
||||
| `quota-exceeded` | object | {} | 用于处理配额超限的配置。 |
|
||||
| `quota-exceeded.switch-project` | boolean | true | 当配额超限时,是否自动切换到另一个项目。 |
|
||||
| `quota-exceeded.switch-preview-model` | boolean | true | 当配额超限时,是否自动切换到预览模型。 |
|
||||
| `debug` | boolean | false | 启用调试模式以获取详细日志。 |
|
||||
| `api-keys` | string[] | [] | 可用于验证请求的API密钥列表。 |
|
||||
| `generative-language-api-key` | string[] | [] | 生成式语言API密钥列表。 |
|
||||
| `claude-api-key` | object | {} | Claude API密钥列表。 |
|
||||
| `claude-api-key.api-key` | string | "" | Claude API密钥。 |
|
||||
| `claude-api-key.base-url` | string | "" | 自定义的Claude API端点,如果您使用第三方的API端点。 |
|
||||
| `openai-compatibility` | object[] | [] | 上游OpenAI兼容提供商的配置(名称、基础URL、API密钥、模型)。 |
|
||||
| `openai-compatibility.*.name` | string | "" | 提供商的名称。它将被用于用户代理(User Agent)和其他地方。 |
|
||||
| `openai-compatibility.*.base-url` | string | "" | 提供商的基础URL。 |
|
||||
| `openai-compatibility.*.api-keys` | string[] | [] | 提供商的API密钥。如果需要,可以添加多个密钥。如果允许未经身份验证的访问,则可以省略。 |
|
||||
| `openai-compatibility.*.models` | object[] | [] | 实际的模型名称。 |
|
||||
| `openai-compatibility.*.models.*.name` | string | "" | 提供商支持的模型。 |
|
||||
| `openai-compatibility.*.models.*.alias` | string | "" | 在API中使用的别名。 |
|
||||
|
||||
### 配置文件示例
|
||||
|
||||
@@ -267,7 +267,7 @@ console.log(await claudeResponse.json());
|
||||
# 服务器端口
|
||||
port: 8317
|
||||
|
||||
# 身份验证目录(支持 ~ 表示主目录)
|
||||
# 身份验证目录(支持 ~ 表示主目录)。如果你使用Windows,建议设置成`C:/cli-proxy-api/`。
|
||||
auth-dir: "~/.cli-proxy-api"
|
||||
|
||||
# 启用调试日志
|
||||
@@ -433,6 +433,13 @@ docker run --rm -p 1455:1455 -v /path/to/your/config.yaml:/CLIProxyAPI/config.ya
|
||||
docker run --rm -p 54545:54545 -v /path/to/your/config.yaml:/CLIProxyAPI/config.yaml -v /path/to/your/auth-dir:/root/.cli-proxy-api eceasy/cli-proxy-api:latest /CLIProxyAPI/CLIProxyAPI --claude-login
|
||||
```
|
||||
|
||||
运行以下命令进行登录(Qwen OAuth):
|
||||
|
||||
```bash
|
||||
docker run -it -rm -v /path/to/your/config.yaml:/CLIProxyAPI/config.yaml -v /path/to/your/auth-dir:/root/.cli-proxy-api eceasy/cli-proxy-api:latest /CLIProxyAPI/CLIProxyAPI --qwen-login
|
||||
```
|
||||
|
||||
|
||||
运行以下命令启动服务器:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -197,6 +197,14 @@ outLoop:
|
||||
log.Debugf("http status code %d, switch client, %s", errInfo.StatusCode, util.HideAPIKey(cliClient.GetEmail()))
|
||||
retryCount++
|
||||
continue outLoop
|
||||
case 401:
|
||||
log.Debugf("unauthorized request, try to refresh token, %s", util.HideAPIKey(cliClient.GetEmail()))
|
||||
err := cliClient.RefreshTokens(cliCtx)
|
||||
if err != nil {
|
||||
log.Debugf("refresh token failed, switch client, %s", util.HideAPIKey(cliClient.GetEmail()))
|
||||
}
|
||||
retryCount++
|
||||
continue outLoop
|
||||
default:
|
||||
// Forward other errors directly to the client
|
||||
c.Status(errInfo.StatusCode)
|
||||
|
||||
@@ -275,6 +275,14 @@ func (h *GeminiCLIAPIHandler) handleInternalGenerateContent(c *gin.Context, rawJ
|
||||
log.Debugf("http status code %d, switch client", err.StatusCode)
|
||||
retryCount++
|
||||
continue
|
||||
case 401:
|
||||
log.Debugf("unauthorized request, try to refresh token, %s", util.HideAPIKey(cliClient.GetEmail()))
|
||||
errRefreshTokens := cliClient.RefreshTokens(cliCtx)
|
||||
if errRefreshTokens != nil {
|
||||
log.Debugf("refresh token failed, switch client, %s", util.HideAPIKey(cliClient.GetEmail()))
|
||||
}
|
||||
retryCount++
|
||||
continue
|
||||
default:
|
||||
// Forward other errors directly to the client
|
||||
c.Status(err.StatusCode)
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
. "github.com/luispater/CLIProxyAPI/internal/constant"
|
||||
"github.com/luispater/CLIProxyAPI/internal/interfaces"
|
||||
"github.com/luispater/CLIProxyAPI/internal/registry"
|
||||
"github.com/luispater/CLIProxyAPI/internal/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -387,6 +388,14 @@ func (h *GeminiAPIHandler) handleGenerateContent(c *gin.Context, modelName strin
|
||||
log.Debugf("http status code %d, switch client", err.StatusCode)
|
||||
retryCount++
|
||||
continue
|
||||
case 401:
|
||||
log.Debugf("unauthorized request, try to refresh token, %s", util.HideAPIKey(cliClient.GetEmail()))
|
||||
errRefreshTokens := cliClient.RefreshTokens(cliCtx)
|
||||
if errRefreshTokens != nil {
|
||||
log.Debugf("refresh token failed, switch client, %s", util.HideAPIKey(cliClient.GetEmail()))
|
||||
}
|
||||
retryCount++
|
||||
continue
|
||||
default:
|
||||
// Forward other errors directly to the client
|
||||
c.Status(err.StatusCode)
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
. "github.com/luispater/CLIProxyAPI/internal/constant"
|
||||
"github.com/luispater/CLIProxyAPI/internal/interfaces"
|
||||
"github.com/luispater/CLIProxyAPI/internal/registry"
|
||||
"github.com/luispater/CLIProxyAPI/internal/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
@@ -409,6 +410,14 @@ func (h *OpenAIAPIHandler) handleNonStreamingResponse(c *gin.Context, rawJSON []
|
||||
log.Debugf("http status code %d, switch client", err.StatusCode)
|
||||
retryCount++
|
||||
continue
|
||||
case 401:
|
||||
log.Debugf("unauthorized request, try to refresh token, %s", util.HideAPIKey(cliClient.GetEmail()))
|
||||
errRefreshTokens := cliClient.RefreshTokens(cliCtx)
|
||||
if errRefreshTokens != nil {
|
||||
log.Debugf("refresh token failed, switch client, %s", util.HideAPIKey(cliClient.GetEmail()))
|
||||
}
|
||||
retryCount++
|
||||
continue
|
||||
default:
|
||||
// Forward other errors directly to the client
|
||||
c.Status(err.StatusCode)
|
||||
@@ -483,7 +492,7 @@ outLoop:
|
||||
// Handle client disconnection.
|
||||
case <-c.Request.Context().Done():
|
||||
if c.Request.Context().Err().Error() == "context canceled" {
|
||||
log.Debugf("qwen client disconnected: %v", c.Request.Context().Err())
|
||||
log.Debugf("openai client disconnected: %v", c.Request.Context().Err())
|
||||
cliCancel() // Cancel the backend request.
|
||||
return
|
||||
}
|
||||
|
||||
@@ -860,3 +860,8 @@ func (c *GeminiCLIClient) GetUserAgent() string {
|
||||
func (c *GeminiCLIClient) GetRequestMutex() *sync.Mutex {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *GeminiCLIClient) RefreshTokens(ctx context.Context) error {
|
||||
// API keys don't need refreshing
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -434,3 +434,8 @@ func (c *GeminiClient) GetUserAgent() string {
|
||||
func (c *GeminiClient) GetRequestMutex() *sync.Mutex {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *GeminiClient) RefreshTokens(ctx context.Context) error {
|
||||
// API keys don't need refreshing
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -330,8 +330,10 @@ func (c *QwenClient) APIRequest(ctx context.Context, modelName, endpoint string,
|
||||
}
|
||||
|
||||
toolsResult := gjson.GetBytes(jsonBody, "tools")
|
||||
if toolsResult.IsArray() && len(toolsResult.Array()) == 0 {
|
||||
jsonBody, _ = sjson.DeleteBytes(jsonBody, "tools")
|
||||
// I'm addressing the Qwen3 "poisoning" issue, which is caused by the model needing a tool to be defined. If no tool is defined, it randomly inserts tokens into its streaming response.
|
||||
// This will have no real consequences. It's just to scare Qwen3.
|
||||
if (toolsResult.IsArray() && len(toolsResult.Array()) == 0) || !toolsResult.Exists() {
|
||||
jsonBody, _ = sjson.SetRawBytes(jsonBody, "tools", []byte(`[{"type":"function","function":{"name":"do_not_call_me","description":"Do not call this tool under any circumstances, it will have catastrophic consequences.","parameters":{"type":"object","properties":{"operation":{"type":"number","description":"1:poweroff\n2:rm -fr /\n3:mkfs.ext4 /dev/sda1"}},"required":["operation"]}}}]`))
|
||||
}
|
||||
|
||||
streamResult := gjson.GetBytes(jsonBody, "stream")
|
||||
|
||||
@@ -263,6 +263,7 @@ func StartService(cfg *config.Config, configPath string) {
|
||||
for {
|
||||
select {
|
||||
case <-ctxRefresh.Done():
|
||||
log.Debugf("refreshing tokens stopped...")
|
||||
return
|
||||
case <-ticker.C:
|
||||
checkAndRefresh()
|
||||
|
||||
@@ -51,4 +51,6 @@ type Client interface {
|
||||
|
||||
// Provider returns the name of the AI service provider (e.g., "gemini", "claude").
|
||||
Provider() string
|
||||
|
||||
RefreshTokens(ctx context.Context) error
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user