mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 20:40:52 +08:00
feat(auth): add per-key proxy support and enhance API key configuration handling
- Introduced `ProxyURL` field to Claude and Codex API key configurations. - Added support for `api-key-entries` in OpenAI compatibility section with per-key proxy configuration. - Maintained backward compatibility for legacy `api-keys` format. - Updated logic to prioritize `api-key-entries` where applicable. - Improved documentation and examples to reflect new proxy support.
This commit is contained in:
65
README.md
65
README.md
@@ -285,19 +285,24 @@ The server uses a YAML configuration file (`config.yaml`) located in the project
|
|||||||
| `usage-statistics-enabled` | boolean | true | Enable in-memory usage aggregation for management APIs. Disable to drop all collected usage metrics. |
|
| `usage-statistics-enabled` | boolean | true | Enable in-memory usage aggregation for management APIs. Disable to drop all collected usage metrics. |
|
||||||
| `api-keys` | string[] | [] | Legacy shorthand for inline API keys. Values are mirrored into the `config-api-key` provider for backwards compatibility. |
|
| `api-keys` | string[] | [] | Legacy shorthand for inline API keys. Values are mirrored into the `config-api-key` provider for backwards compatibility. |
|
||||||
| `generative-language-api-key` | string[] | [] | List of Generative Language API keys. |
|
| `generative-language-api-key` | string[] | [] | List of Generative Language API keys. |
|
||||||
| `codex-api-key` | object | {} | List of Codex API keys. |
|
| `codex-api-key` | object | {} | List of Codex API keys. |
|
||||||
| `codex-api-key.api-key` | string | "" | Codex API key. |
|
| `codex-api-key.api-key` | string | "" | Codex API key. |
|
||||||
| `codex-api-key.base-url` | string | "" | Custom Codex API endpoint, if you use a third-party API endpoint. |
|
| `codex-api-key.base-url` | string | "" | Custom Codex API endpoint, if you use a third-party API endpoint. |
|
||||||
| `claude-api-key` | object | {} | List of Claude API keys. |
|
| `codex-api-key.proxy-url` | string | "" | Proxy URL for this specific API key. Overrides the global proxy-url setting. Supports socks5/http/https protocols. |
|
||||||
| `claude-api-key.api-key` | string | "" | Claude API key. |
|
| `claude-api-key` | object | {} | List of Claude API keys. |
|
||||||
| `claude-api-key.base-url` | string | "" | Custom Claude API endpoint, if you use a third-party API endpoint. |
|
| `claude-api-key.api-key` | string | "" | Claude API key. |
|
||||||
| `openai-compatibility` | object[] | [] | Upstream OpenAI-compatible providers configuration (name, base-url, api-keys, models). |
|
| `claude-api-key.base-url` | string | "" | Custom Claude API endpoint, if you use a third-party API endpoint. |
|
||||||
| `openai-compatibility.*.name` | string | "" | The name of the provider. It will be used in the user agent and other places. |
|
| `claude-api-key.proxy-url` | string | "" | Proxy URL for this specific API key. Overrides the global proxy-url setting. Supports socks5/http/https protocols. |
|
||||||
| `openai-compatibility.*.base-url` | string | "" | The base URL of the provider. |
|
| `openai-compatibility` | object[] | [] | Upstream OpenAI-compatible providers configuration (name, base-url, api-keys, models). |
|
||||||
| `openai-compatibility.*.api-keys` | string[] | [] | The API keys for the provider. Add multiple keys if needed. Omit if unauthenticated access is allowed. |
|
| `openai-compatibility.*.name` | string | "" | The name of the provider. It will be used in the user agent and other places. |
|
||||||
| `openai-compatibility.*.models` | object[] | [] | The actual model name. |
|
| `openai-compatibility.*.base-url` | string | "" | The base URL of the provider. |
|
||||||
| `openai-compatibility.*.models.*.name` | string | "" | The models supported by the provider. |
|
| `openai-compatibility.*.api-keys` | string[] | [] | (Deprecated) The API keys for the provider. Use api-key-entries instead for per-key proxy support. |
|
||||||
| `openai-compatibility.*.models.*.alias` | string | "" | The alias used in the API. |
|
| `openai-compatibility.*.api-key-entries` | object[] | [] | API key entries with optional per-key proxy configuration. Preferred over api-keys. |
|
||||||
|
| `openai-compatibility.*.api-key-entries.*.api-key` | string | "" | The API key for this entry. |
|
||||||
|
| `openai-compatibility.*.api-key-entries.*.proxy-url` | string | "" | Proxy URL for this specific API key. Overrides the global proxy-url setting. Supports socks5/http/https protocols. |
|
||||||
|
| `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. |
|
||||||
| `gemini-web` | object | {} | Configuration specific to the Gemini Web client. |
|
| `gemini-web` | object | {} | Configuration specific to the Gemini Web client. |
|
||||||
| `gemini-web.context` | boolean | true | Enables conversation context reuse for continuous dialogue. |
|
| `gemini-web.context` | boolean | true | Enables conversation context reuse for continuous dialogue. |
|
||||||
| `gemini-web.code-mode` | boolean | false | Enables code mode for optimized responses in coding-related tasks. |
|
| `gemini-web.code-mode` | boolean | false | Enables code mode for optimized responses in coding-related tasks. |
|
||||||
@@ -361,20 +366,28 @@ generative-language-api-key:
|
|||||||
codex-api-key:
|
codex-api-key:
|
||||||
- api-key: "sk-atSM..."
|
- api-key: "sk-atSM..."
|
||||||
base-url: "https://www.example.com" # use the custom codex API endpoint
|
base-url: "https://www.example.com" # use the custom codex API endpoint
|
||||||
|
proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override
|
||||||
|
|
||||||
# Claude API keys
|
# Claude API keys
|
||||||
claude-api-key:
|
claude-api-key:
|
||||||
- api-key: "sk-atSM..." # use the official claude API key, no need to set the base url
|
- api-key: "sk-atSM..." # use the official claude API key, no need to set the base url
|
||||||
- api-key: "sk-atSM..."
|
- api-key: "sk-atSM..."
|
||||||
base-url: "https://www.example.com" # use the custom claude API endpoint
|
base-url: "https://www.example.com" # use the custom claude API endpoint
|
||||||
|
proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override
|
||||||
|
|
||||||
# OpenAI compatibility providers
|
# OpenAI compatibility providers
|
||||||
openai-compatibility:
|
openai-compatibility:
|
||||||
- name: "openrouter" # The name of the provider; it will be used in the user agent and other places.
|
- name: "openrouter" # The name of the provider; it will be used in the user agent and other places.
|
||||||
base-url: "https://openrouter.ai/api/v1" # The base URL of the provider.
|
base-url: "https://openrouter.ai/api/v1" # The base URL of the provider.
|
||||||
api-keys: # The API keys for the provider. Add multiple keys if needed. Omit if unauthenticated access is allowed.
|
# New format with per-key proxy support (recommended):
|
||||||
- "sk-or-v1-...b780"
|
api-key-entries:
|
||||||
- "sk-or-v1-...b781"
|
- api-key: "sk-or-v1-...b780"
|
||||||
|
proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override
|
||||||
|
- api-key: "sk-or-v1-...b781" # without proxy-url
|
||||||
|
# Legacy format (still supported, but cannot specify proxy per key):
|
||||||
|
# api-keys:
|
||||||
|
# - "sk-or-v1-...b780"
|
||||||
|
# - "sk-or-v1-...b781"
|
||||||
models: # The models supported by the provider.
|
models: # The models supported by the provider.
|
||||||
- name: "moonshotai/kimi-k2:free" # The actual model name.
|
- name: "moonshotai/kimi-k2:free" # The actual model name.
|
||||||
alias: "kimi-k2" # The alias used in the API.
|
alias: "kimi-k2" # The alias used in the API.
|
||||||
@@ -386,10 +399,26 @@ Configure upstream OpenAI-compatible providers (e.g., OpenRouter) via `openai-co
|
|||||||
|
|
||||||
- name: provider identifier used internally
|
- name: provider identifier used internally
|
||||||
- base-url: provider base URL
|
- base-url: provider base URL
|
||||||
- api-keys: optional list of API keys (omit if provider allows unauthenticated requests)
|
- api-key-entries: list of API key entries with optional per-key proxy configuration (recommended)
|
||||||
|
- api-keys: (deprecated) simple list of API keys without proxy support
|
||||||
- models: list of mappings from upstream model `name` to local `alias`
|
- models: list of mappings from upstream model `name` to local `alias`
|
||||||
|
|
||||||
Example:
|
Example with per-key proxy support:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
openai-compatibility:
|
||||||
|
- name: "openrouter"
|
||||||
|
base-url: "https://openrouter.ai/api/v1"
|
||||||
|
api-key-entries:
|
||||||
|
- api-key: "sk-or-v1-...b780"
|
||||||
|
proxy-url: "socks5://proxy.example.com:1080"
|
||||||
|
- api-key: "sk-or-v1-...b781"
|
||||||
|
models:
|
||||||
|
- name: "moonshotai/kimi-k2:free"
|
||||||
|
alias: "kimi-k2"
|
||||||
|
```
|
||||||
|
|
||||||
|
Legacy format (still supported):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
openai-compatibility:
|
openai-compatibility:
|
||||||
|
|||||||
67
README_CN.md
67
README_CN.md
@@ -297,19 +297,24 @@ console.log(await claudeResponse.json());
|
|||||||
| `usage-statistics-enabled` | boolean | true | 是否启用内存中的使用统计;设为 false 时直接丢弃所有统计数据。 |
|
| `usage-statistics-enabled` | boolean | true | 是否启用内存中的使用统计;设为 false 时直接丢弃所有统计数据。 |
|
||||||
| `api-keys` | string[] | [] | 兼容旧配置的简写,会自动同步到默认 `config-api-key` 提供方。 |
|
| `api-keys` | string[] | [] | 兼容旧配置的简写,会自动同步到默认 `config-api-key` 提供方。 |
|
||||||
| `generative-language-api-key` | string[] | [] | 生成式语言API密钥列表。 |
|
| `generative-language-api-key` | string[] | [] | 生成式语言API密钥列表。 |
|
||||||
| `codex-api-key` | object | {} | Codex API密钥列表。 |
|
| `codex-api-key` | object | {} | Codex API密钥列表。 |
|
||||||
| `codex-api-key.api-key` | string | "" | Codex API密钥。 |
|
| `codex-api-key.api-key` | string | "" | Codex API密钥。 |
|
||||||
| `codex-api-key.base-url` | string | "" | 自定义的Codex API端点 |
|
| `codex-api-key.base-url` | string | "" | 自定义的Codex API端点 |
|
||||||
| `claude-api-key` | object | {} | Claude API密钥列表。 |
|
| `codex-api-key.proxy-url` | string | "" | 针对该API密钥的代理URL。会覆盖全局proxy-url设置。支持socks5/http/https协议。 |
|
||||||
| `claude-api-key.api-key` | string | "" | Claude API密钥。 |
|
| `claude-api-key` | object | {} | Claude API密钥列表。 |
|
||||||
| `claude-api-key.base-url` | string | "" | 自定义的Claude API端点,如果您使用第三方的API端点。 |
|
| `claude-api-key.api-key` | string | "" | Claude API密钥。 |
|
||||||
| `openai-compatibility` | object[] | [] | 上游OpenAI兼容提供商的配置(名称、基础URL、API密钥、模型)。 |
|
| `claude-api-key.base-url` | string | "" | 自定义的Claude API端点,如果您使用第三方的API端点。 |
|
||||||
| `openai-compatibility.*.name` | string | "" | 提供商的名称。它将被用于用户代理(User Agent)和其他地方。 |
|
| `claude-api-key.proxy-url` | string | "" | 针对该API密钥的代理URL。会覆盖全局proxy-url设置。支持socks5/http/https协议。 |
|
||||||
| `openai-compatibility.*.base-url` | string | "" | 提供商的基础URL。 |
|
| `openai-compatibility` | object[] | [] | 上游OpenAI兼容提供商的配置(名称、基础URL、API密钥、模型)。 |
|
||||||
| `openai-compatibility.*.api-keys` | string[] | [] | 提供商的API密钥。如果需要,可以添加多个密钥。如果允许未经身份验证的访问,则可以省略。 |
|
| `openai-compatibility.*.name` | string | "" | 提供商的名称。它将被用于用户代理(User Agent)和其他地方。 |
|
||||||
| `openai-compatibility.*.models` | object[] | [] | 实际的模型名称。 |
|
| `openai-compatibility.*.base-url` | string | "" | 提供商的基础URL。 |
|
||||||
| `openai-compatibility.*.models.*.name` | string | "" | 提供商支持的模型。 |
|
| `openai-compatibility.*.api-keys` | string[] | [] | (已弃用) 提供商的API密钥。建议改用api-key-entries以获得每密钥代理支持。 |
|
||||||
| `openai-compatibility.*.models.*.alias` | string | "" | 在API中使用的别名。 |
|
| `openai-compatibility.*.api-key-entries` | object[] | [] | API密钥条目,支持可选的每密钥代理配置。优先于api-keys。 |
|
||||||
|
| `openai-compatibility.*.api-key-entries.*.api-key` | string | "" | 该条目的API密钥。 |
|
||||||
|
| `openai-compatibility.*.api-key-entries.*.proxy-url` | string | "" | 针对该API密钥的代理URL。会覆盖全局proxy-url设置。支持socks5/http/https协议。 |
|
||||||
|
| `openai-compatibility.*.models` | object[] | [] | 实际的模型名称。 |
|
||||||
|
| `openai-compatibility.*.models.*.name` | string | "" | 提供商支持的模型。 |
|
||||||
|
| `openai-compatibility.*.models.*.alias` | string | "" | 在API中使用的别名。 |
|
||||||
| `gemini-web` | object | {} | Gemini Web 客户端的特定配置。 |
|
| `gemini-web` | object | {} | Gemini Web 客户端的特定配置。 |
|
||||||
| `gemini-web.context` | boolean | true | 是否启用会话上下文重用,以实现连续对话。 |
|
| `gemini-web.context` | boolean | true | 是否启用会话上下文重用,以实现连续对话。 |
|
||||||
| `gemini-web.code-mode` | boolean | false | 是否启用代码模式,优化代码相关任务的响应。 |
|
| `gemini-web.code-mode` | boolean | false | 是否启用代码模式,优化代码相关任务的响应。 |
|
||||||
@@ -373,20 +378,28 @@ generative-language-api-key:
|
|||||||
codex-api-key:
|
codex-api-key:
|
||||||
- api-key: "sk-atSM..."
|
- api-key: "sk-atSM..."
|
||||||
base-url: "https://www.example.com" # 第三方 Codex API 中转服务端点
|
base-url: "https://www.example.com" # 第三方 Codex API 中转服务端点
|
||||||
|
proxy-url: "socks5://proxy.example.com:1080" # 可选:针对该密钥的代理设置
|
||||||
|
|
||||||
# Claude API 密钥
|
# Claude API 密钥
|
||||||
claude-api-key:
|
claude-api-key:
|
||||||
- api-key: "sk-atSM..." # 如果使用官方 Claude API,无需设置 base-url
|
- api-key: "sk-atSM..." # 如果使用官方 Claude API,无需设置 base-url
|
||||||
- api-key: "sk-atSM..."
|
- api-key: "sk-atSM..."
|
||||||
base-url: "https://www.example.com" # 第三方 Claude API 中转服务端点
|
base-url: "https://www.example.com" # 第三方 Claude API 中转服务端点
|
||||||
|
proxy-url: "socks5://proxy.example.com:1080" # 可选:针对该密钥的代理设置
|
||||||
|
|
||||||
# OpenAI 兼容提供商
|
# OpenAI 兼容提供商
|
||||||
openai-compatibility:
|
openai-compatibility:
|
||||||
- name: "openrouter" # 提供商的名称;它将被用于用户代理和其它地方。
|
- name: "openrouter" # 提供商的名称;它将被用于用户代理和其它地方。
|
||||||
base-url: "https://openrouter.ai/api/v1" # 提供商的基础URL。
|
base-url: "https://openrouter.ai/api/v1" # 提供商的基础URL。
|
||||||
api-keys: # 提供商的API密钥。如果需要,可以添加多个密钥。如果允许未经身份验证的访问,则可以省略。
|
# 新格式:支持每密钥代理配置(推荐):
|
||||||
- "sk-or-v1-...b780"
|
api-key-entries:
|
||||||
- "sk-or-v1-...b781"
|
- api-key: "sk-or-v1-...b780"
|
||||||
|
proxy-url: "socks5://proxy.example.com:1080" # 可选:针对该密钥的代理设置
|
||||||
|
- api-key: "sk-or-v1-...b781" # 不进行额外代理设置
|
||||||
|
# 旧格式(仍支持,但无法为每个密钥指定代理):
|
||||||
|
# api-keys:
|
||||||
|
# - "sk-or-v1-...b780"
|
||||||
|
# - "sk-or-v1-...b781"
|
||||||
models: # 提供商支持的模型。
|
models: # 提供商支持的模型。
|
||||||
- name: "moonshotai/kimi-k2:free" # 实际的模型名称。
|
- name: "moonshotai/kimi-k2:free" # 实际的模型名称。
|
||||||
alias: "kimi-k2" # 在API中使用的别名。
|
alias: "kimi-k2" # 在API中使用的别名。
|
||||||
@@ -398,10 +411,26 @@ openai-compatibility:
|
|||||||
|
|
||||||
- name:内部识别名
|
- name:内部识别名
|
||||||
- base-url:提供商基础地址
|
- base-url:提供商基础地址
|
||||||
- api-keys:可选,多密钥轮询(若提供商支持无鉴权可省略)
|
- api-key-entries:API密钥条目列表,支持可选的每密钥代理配置(推荐)
|
||||||
|
- api-keys:(已弃用) 简单的API密钥列表,不支持代理配置
|
||||||
- models:将上游模型 `name` 映射为本地可用 `alias`
|
- models:将上游模型 `name` 映射为本地可用 `alias`
|
||||||
|
|
||||||
示例:
|
支持每密钥代理配置的示例:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
openai-compatibility:
|
||||||
|
- name: "openrouter"
|
||||||
|
base-url: "https://openrouter.ai/api/v1"
|
||||||
|
api-key-entries:
|
||||||
|
- api-key: "sk-or-v1-...b780"
|
||||||
|
proxy-url: "socks5://proxy.example.com:1080"
|
||||||
|
- api-key: "sk-or-v1-...b781"
|
||||||
|
models:
|
||||||
|
- name: "moonshotai/kimi-k2:free"
|
||||||
|
alias: "kimi-k2"
|
||||||
|
```
|
||||||
|
|
||||||
|
旧格式(仍支持):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
openai-compatibility:
|
openai-compatibility:
|
||||||
|
|||||||
@@ -51,20 +51,28 @@ quota-exceeded:
|
|||||||
#codex-api-key:
|
#codex-api-key:
|
||||||
# - api-key: "sk-atSM..."
|
# - api-key: "sk-atSM..."
|
||||||
# base-url: "https://www.example.com" # use the custom codex API endpoint
|
# base-url: "https://www.example.com" # use the custom codex API endpoint
|
||||||
|
# proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override
|
||||||
|
|
||||||
# Claude API keys
|
# Claude API keys
|
||||||
#claude-api-key:
|
#claude-api-key:
|
||||||
# - api-key: "sk-atSM..." # use the official claude API key, no need to set the base url
|
# - api-key: "sk-atSM..." # use the official claude API key, no need to set the base url
|
||||||
# - api-key: "sk-atSM..."
|
# - api-key: "sk-atSM..."
|
||||||
# base-url: "https://www.example.com" # use the custom claude API endpoint
|
# base-url: "https://www.example.com" # use the custom claude API endpoint
|
||||||
|
# proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override
|
||||||
|
|
||||||
# OpenAI compatibility providers
|
# OpenAI compatibility providers
|
||||||
#openai-compatibility:
|
#openai-compatibility:
|
||||||
# - name: "openrouter" # The name of the provider; it will be used in the user agent and other places.
|
# - name: "openrouter" # The name of the provider; it will be used in the user agent and other places.
|
||||||
# base-url: "https://openrouter.ai/api/v1" # The base URL of the provider.
|
# base-url: "https://openrouter.ai/api/v1" # The base URL of the provider.
|
||||||
# api-keys: # The API keys for the provider. Add multiple keys if needed. Omit if unauthenticated access is allowed.
|
# # New format with per-key proxy support (recommended):
|
||||||
# - "sk-or-v1-...b780"
|
# api-key-entries:
|
||||||
# - "sk-or-v1-...b781"
|
# - api-key: "sk-or-v1-...b780"
|
||||||
|
# proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override
|
||||||
|
# - api-key: "sk-or-v1-...b781" # without proxy-url
|
||||||
|
# # Legacy format (still supported, but cannot specify proxy per key):
|
||||||
|
# # api-keys:
|
||||||
|
# # - "sk-or-v1-...b780"
|
||||||
|
# # - "sk-or-v1-...b781"
|
||||||
# models: # The models supported by the provider.
|
# models: # The models supported by the provider.
|
||||||
# - name: "moonshotai/kimi-k2:free" # The actual model name.
|
# - name: "moonshotai/kimi-k2:free" # The actual model name.
|
||||||
# alias: "kimi-k2" # The alias used in the API.
|
# alias: "kimi-k2" # The alias used in the API.
|
||||||
|
|||||||
@@ -107,6 +107,9 @@ type ClaudeKey struct {
|
|||||||
// BaseURL is the base URL for the Claude API endpoint.
|
// BaseURL is the base URL for the Claude API endpoint.
|
||||||
// If empty, the default Claude API URL will be used.
|
// If empty, the default Claude API URL will be used.
|
||||||
BaseURL string `yaml:"base-url" json:"base-url"`
|
BaseURL string `yaml:"base-url" json:"base-url"`
|
||||||
|
|
||||||
|
// ProxyURL overrides the global proxy setting for this API key if provided.
|
||||||
|
ProxyURL string `yaml:"proxy-url" json:"proxy-url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CodexKey represents the configuration for a Codex API key,
|
// CodexKey represents the configuration for a Codex API key,
|
||||||
@@ -118,6 +121,9 @@ type CodexKey struct {
|
|||||||
// BaseURL is the base URL for the Codex API endpoint.
|
// BaseURL is the base URL for the Codex API endpoint.
|
||||||
// If empty, the default Codex API URL will be used.
|
// If empty, the default Codex API URL will be used.
|
||||||
BaseURL string `yaml:"base-url" json:"base-url"`
|
BaseURL string `yaml:"base-url" json:"base-url"`
|
||||||
|
|
||||||
|
// ProxyURL overrides the global proxy setting for this API key if provided.
|
||||||
|
ProxyURL string `yaml:"proxy-url" json:"proxy-url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenAICompatibility represents the configuration for OpenAI API compatibility
|
// OpenAICompatibility represents the configuration for OpenAI API compatibility
|
||||||
@@ -130,12 +136,25 @@ type OpenAICompatibility struct {
|
|||||||
BaseURL string `yaml:"base-url" json:"base-url"`
|
BaseURL string `yaml:"base-url" json:"base-url"`
|
||||||
|
|
||||||
// APIKeys are the authentication keys for accessing the external API services.
|
// APIKeys are the authentication keys for accessing the external API services.
|
||||||
APIKeys []string `yaml:"api-keys" json:"api-keys"`
|
// Deprecated: Use APIKeyEntries instead to support per-key proxy configuration.
|
||||||
|
APIKeys []string `yaml:"api-keys,omitempty" json:"api-keys,omitempty"`
|
||||||
|
|
||||||
|
// APIKeyEntries defines API keys with optional per-key proxy configuration.
|
||||||
|
APIKeyEntries []OpenAICompatibilityAPIKey `yaml:"api-key-entries,omitempty" json:"api-key-entries,omitempty"`
|
||||||
|
|
||||||
// Models defines the model configurations including aliases for routing.
|
// Models defines the model configurations including aliases for routing.
|
||||||
Models []OpenAICompatibilityModel `yaml:"models" json:"models"`
|
Models []OpenAICompatibilityModel `yaml:"models" json:"models"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenAICompatibilityAPIKey represents an API key configuration with optional proxy setting.
|
||||||
|
type OpenAICompatibilityAPIKey struct {
|
||||||
|
// APIKey is the authentication key for accessing the external API services.
|
||||||
|
APIKey string `yaml:"api-key" json:"api-key"`
|
||||||
|
|
||||||
|
// ProxyURL overrides the global proxy setting for this API key if provided.
|
||||||
|
ProxyURL string `yaml:"proxy-url,omitempty" json:"proxy-url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// OpenAICompatibilityModel represents a model configuration for OpenAI compatibility,
|
// OpenAICompatibilityModel represents a model configuration for OpenAI compatibility,
|
||||||
// including the actual model name and its alias for API routing.
|
// including the actual model name and its alias for API routing.
|
||||||
type OpenAICompatibilityModel struct {
|
type OpenAICompatibilityModel struct {
|
||||||
|
|||||||
@@ -746,11 +746,13 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
|
|||||||
if ck.BaseURL != "" {
|
if ck.BaseURL != "" {
|
||||||
attrs["base_url"] = ck.BaseURL
|
attrs["base_url"] = ck.BaseURL
|
||||||
}
|
}
|
||||||
|
proxyURL := strings.TrimSpace(ck.ProxyURL)
|
||||||
a := &coreauth.Auth{
|
a := &coreauth.Auth{
|
||||||
ID: id,
|
ID: id,
|
||||||
Provider: "claude",
|
Provider: "claude",
|
||||||
Label: "claude-apikey",
|
Label: "claude-apikey",
|
||||||
Status: coreauth.StatusActive,
|
Status: coreauth.StatusActive,
|
||||||
|
ProxyURL: proxyURL,
|
||||||
Attributes: attrs,
|
Attributes: attrs,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
@@ -772,11 +774,13 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
|
|||||||
if ck.BaseURL != "" {
|
if ck.BaseURL != "" {
|
||||||
attrs["base_url"] = ck.BaseURL
|
attrs["base_url"] = ck.BaseURL
|
||||||
}
|
}
|
||||||
|
proxyURL := strings.TrimSpace(ck.ProxyURL)
|
||||||
a := &coreauth.Auth{
|
a := &coreauth.Auth{
|
||||||
ID: id,
|
ID: id,
|
||||||
Provider: "codex",
|
Provider: "codex",
|
||||||
Label: "codex-apikey",
|
Label: "codex-apikey",
|
||||||
Status: coreauth.StatusActive,
|
Status: coreauth.StatusActive,
|
||||||
|
ProxyURL: proxyURL,
|
||||||
Attributes: attrs,
|
Attributes: attrs,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
@@ -790,33 +794,70 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
|
|||||||
providerName = "openai-compatibility"
|
providerName = "openai-compatibility"
|
||||||
}
|
}
|
||||||
base := strings.TrimSpace(compat.BaseURL)
|
base := strings.TrimSpace(compat.BaseURL)
|
||||||
for j := range compat.APIKeys {
|
|
||||||
key := strings.TrimSpace(compat.APIKeys[j])
|
// Handle new APIKeyEntries format (preferred)
|
||||||
if key == "" {
|
if len(compat.APIKeyEntries) > 0 {
|
||||||
continue
|
for j := range compat.APIKeyEntries {
|
||||||
|
entry := &compat.APIKeyEntries[j]
|
||||||
|
key := strings.TrimSpace(entry.APIKey)
|
||||||
|
if key == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
proxyURL := strings.TrimSpace(entry.ProxyURL)
|
||||||
|
idKind := fmt.Sprintf("openai-compatibility:%s", providerName)
|
||||||
|
id, token := idGen.next(idKind, key, base, proxyURL)
|
||||||
|
attrs := map[string]string{
|
||||||
|
"source": fmt.Sprintf("config:%s[%s]", providerName, token),
|
||||||
|
"base_url": base,
|
||||||
|
"api_key": key,
|
||||||
|
"compat_name": compat.Name,
|
||||||
|
"provider_key": providerName,
|
||||||
|
}
|
||||||
|
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
|
||||||
|
attrs["models_hash"] = hash
|
||||||
|
}
|
||||||
|
a := &coreauth.Auth{
|
||||||
|
ID: id,
|
||||||
|
Provider: providerName,
|
||||||
|
Label: compat.Name,
|
||||||
|
Status: coreauth.StatusActive,
|
||||||
|
ProxyURL: proxyURL,
|
||||||
|
Attributes: attrs,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
out = append(out, a)
|
||||||
}
|
}
|
||||||
idKind := fmt.Sprintf("openai-compatibility:%s", providerName)
|
} else {
|
||||||
id, token := idGen.next(idKind, key, base)
|
// Handle legacy APIKeys format for backward compatibility
|
||||||
attrs := map[string]string{
|
for j := range compat.APIKeys {
|
||||||
"source": fmt.Sprintf("config:%s[%s]", providerName, token),
|
key := strings.TrimSpace(compat.APIKeys[j])
|
||||||
"base_url": base,
|
if key == "" {
|
||||||
"api_key": key,
|
continue
|
||||||
"compat_name": compat.Name,
|
}
|
||||||
"provider_key": providerName,
|
idKind := fmt.Sprintf("openai-compatibility:%s", providerName)
|
||||||
|
id, token := idGen.next(idKind, key, base)
|
||||||
|
attrs := map[string]string{
|
||||||
|
"source": fmt.Sprintf("config:%s[%s]", providerName, token),
|
||||||
|
"base_url": base,
|
||||||
|
"api_key": key,
|
||||||
|
"compat_name": compat.Name,
|
||||||
|
"provider_key": providerName,
|
||||||
|
}
|
||||||
|
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
|
||||||
|
attrs["models_hash"] = hash
|
||||||
|
}
|
||||||
|
a := &coreauth.Auth{
|
||||||
|
ID: id,
|
||||||
|
Provider: providerName,
|
||||||
|
Label: compat.Name,
|
||||||
|
Status: coreauth.StatusActive,
|
||||||
|
Attributes: attrs,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
out = append(out, a)
|
||||||
}
|
}
|
||||||
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
|
|
||||||
attrs["models_hash"] = hash
|
|
||||||
}
|
|
||||||
a := &coreauth.Auth{
|
|
||||||
ID: id,
|
|
||||||
Provider: providerName,
|
|
||||||
Label: compat.Name,
|
|
||||||
Status: coreauth.StatusActive,
|
|
||||||
Attributes: attrs,
|
|
||||||
CreatedAt: now,
|
|
||||||
UpdatedAt: now,
|
|
||||||
}
|
|
||||||
out = append(out, a)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -937,7 +978,12 @@ func BuildAPIKeyClients(cfg *config.Config) (int, int, int, int) {
|
|||||||
if len(cfg.OpenAICompatibility) > 0 {
|
if len(cfg.OpenAICompatibility) > 0 {
|
||||||
// Do not construct legacy clients for OpenAI-compat providers; these are handled by the stateless executor.
|
// Do not construct legacy clients for OpenAI-compat providers; these are handled by the stateless executor.
|
||||||
for _, compatConfig := range cfg.OpenAICompatibility {
|
for _, compatConfig := range cfg.OpenAICompatibility {
|
||||||
openAICompatCount += len(compatConfig.APIKeys)
|
// Count from new APIKeyEntries format if present, otherwise fall back to legacy APIKeys
|
||||||
|
if len(compatConfig.APIKeyEntries) > 0 {
|
||||||
|
openAICompatCount += len(compatConfig.APIKeyEntries)
|
||||||
|
} else {
|
||||||
|
openAICompatCount += len(compatConfig.APIKeys)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return glAPIKeyCount, claudeAPIKeyCount, codexAPIKeyCount, openAICompatCount
|
return glAPIKeyCount, claudeAPIKeyCount, codexAPIKeyCount, openAICompatCount
|
||||||
@@ -980,9 +1026,9 @@ func diffOpenAICompatibility(oldList, newList []config.OpenAICompatibility) []st
|
|||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case !oldOk:
|
case !oldOk:
|
||||||
changes = append(changes, fmt.Sprintf("provider added: %s (api-keys=%d, models=%d)", label, countNonEmptyStrings(newEntry.APIKeys), countOpenAIModels(newEntry.Models)))
|
changes = append(changes, fmt.Sprintf("provider added: %s (api-keys=%d, models=%d)", label, countAPIKeys(newEntry), countOpenAIModels(newEntry.Models)))
|
||||||
case !newOk:
|
case !newOk:
|
||||||
changes = append(changes, fmt.Sprintf("provider removed: %s (api-keys=%d, models=%d)", label, countNonEmptyStrings(oldEntry.APIKeys), countOpenAIModels(oldEntry.Models)))
|
changes = append(changes, fmt.Sprintf("provider removed: %s (api-keys=%d, models=%d)", label, countAPIKeys(oldEntry), countOpenAIModels(oldEntry.Models)))
|
||||||
default:
|
default:
|
||||||
if detail := describeOpenAICompatibilityUpdate(oldEntry, newEntry); detail != "" {
|
if detail := describeOpenAICompatibilityUpdate(oldEntry, newEntry); detail != "" {
|
||||||
changes = append(changes, fmt.Sprintf("provider updated: %s %s", label, detail))
|
changes = append(changes, fmt.Sprintf("provider updated: %s %s", label, detail))
|
||||||
@@ -993,8 +1039,8 @@ func diffOpenAICompatibility(oldList, newList []config.OpenAICompatibility) []st
|
|||||||
}
|
}
|
||||||
|
|
||||||
func describeOpenAICompatibilityUpdate(oldEntry, newEntry config.OpenAICompatibility) string {
|
func describeOpenAICompatibilityUpdate(oldEntry, newEntry config.OpenAICompatibility) string {
|
||||||
oldKeyCount := countNonEmptyStrings(oldEntry.APIKeys)
|
oldKeyCount := countAPIKeys(oldEntry)
|
||||||
newKeyCount := countNonEmptyStrings(newEntry.APIKeys)
|
newKeyCount := countAPIKeys(newEntry)
|
||||||
oldModelCount := countOpenAIModels(oldEntry.Models)
|
oldModelCount := countOpenAIModels(oldEntry.Models)
|
||||||
newModelCount := countOpenAIModels(newEntry.Models)
|
newModelCount := countOpenAIModels(newEntry.Models)
|
||||||
details := make([]string, 0, 2)
|
details := make([]string, 0, 2)
|
||||||
@@ -1010,6 +1056,21 @@ func describeOpenAICompatibilityUpdate(oldEntry, newEntry config.OpenAICompatibi
|
|||||||
return "(" + strings.Join(details, ", ") + ")"
|
return "(" + strings.Join(details, ", ") + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func countAPIKeys(entry config.OpenAICompatibility) int {
|
||||||
|
// Prefer new APIKeyEntries format
|
||||||
|
if len(entry.APIKeyEntries) > 0 {
|
||||||
|
count := 0
|
||||||
|
for _, keyEntry := range entry.APIKeyEntries {
|
||||||
|
if strings.TrimSpace(keyEntry.APIKey) != "" {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
// Fall back to legacy APIKeys format
|
||||||
|
return countNonEmptyStrings(entry.APIKeys)
|
||||||
|
}
|
||||||
|
|
||||||
func countNonEmptyStrings(values []string) int {
|
func countNonEmptyStrings(values []string) int {
|
||||||
count := 0
|
count := 0
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
|
|||||||
Reference in New Issue
Block a user