mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
feat: 添加 CCR (Claude Code Router) 账户类型支持
实现通过供应商前缀语法进行 CCR 后端路由的完整支持。 用户现在可以在 Claude Code 中使用 `/model ccr,model_name` 将请求路由到 CCR 后端。 暂时没有实现`/v1/messages/count_tokens`,因为这需要在CCR后端支持。 CCR类型的账户也暂时没有考虑模型的支持情况 ## 核心实现 ### 供应商前缀路由 - 添加 modelHelper 工具用于解析模型名称中的 `ccr,` 供应商前缀 - 检测到前缀时自动路由到 CCR 账户池 - 转发到 CCR 后端前移除供应商前缀 ### 账户管理 - 创建 ccrAccountService 实现 CCR 账户的完整 CRUD 操作 - 支持账户属性:名称、API URL、API Key、代理、优先级、配额 - 实现账户状态:active、rate_limited、unauthorized、overloaded - 支持模型映射和支持模型配置 ### 请求转发 - 实现 ccrRelayService 处理 CCR 后端通信 - 支持流式和非流式请求 - 从 SSE 流中解析和捕获使用数据 - 支持 Bearer 和 x-api-key 两种认证格式 ### 统一调度 - 将 CCR 账户集成到 unifiedClaudeScheduler - 添加 \_selectCcrAccount 方法用于 CCR 特定账户选择 - 支持 CCR 账户的会话粘性 - 防止跨类型会话映射(CCR 会话仅用于 CCR 请求) ### 错误处理 - 实现全面的错误状态管理 - 处理 401(未授权)、429(速率限制)、529(过载)错误 - 成功请求后自动从错误状态恢复 - 支持可配置的速率限制持续时间 ### Web 管理界面 - 添加 CcrAccountForm 组件用于创建/编辑 CCR 账户 - 将 CCR 账户集成到 AccountsView 中,提供完整管理功能 - 支持账户切换、重置和使用统计 - 在界面中显示账户状态和错误信息 ### API 端点 - POST /admin/ccr-accounts - 创建 CCR 账户 - GET /admin/ccr-accounts - 列出所有 CCR 账户 - PUT /admin/ccr-accounts/:id - 更新 CCR 账户 - DELETE /admin/ccr-accounts/:id - 删除 CCR 账户 - PUT /admin/ccr-accounts/:id/toggle - 切换账户启用状态 - PUT /admin/ccr-accounts/:id/toggle-schedulable - 切换可调度状态 - POST /admin/ccr-accounts/:id/reset-usage - 重置每日使用量 - POST /admin/ccr-accounts/:id/reset-status - 重置错误状态 ## 技术细节 - CCR 账户使用 'ccr' 作为 accountType 标识符 - 带有 `ccr,` 前缀的请求绕过普通账户池 - 转发到 CCR 后端前清理模型名称内的`ccr,` - 从流式和非流式响应中捕获使用数据 - 支持缓存令牌跟踪(创建和读取)
This commit is contained in:
@@ -79,7 +79,7 @@ class ClaudeRelayService {
|
||||
requestedModel: requestBody.model
|
||||
})
|
||||
|
||||
// 检查模型限制
|
||||
// 检查模型限制(restrictedModels 作为允许列表)
|
||||
if (
|
||||
apiKeyData.enableModelRestriction &&
|
||||
apiKeyData.restrictedModels &&
|
||||
@@ -87,12 +87,12 @@ class ClaudeRelayService {
|
||||
) {
|
||||
const requestedModel = requestBody.model
|
||||
logger.info(
|
||||
`🔒 Model restriction check - Requested model: ${requestedModel}, Restricted models: ${JSON.stringify(apiKeyData.restrictedModels)}`
|
||||
`🔒 Model restriction check - Requested model: ${requestedModel}, Allowed models: ${JSON.stringify(apiKeyData.restrictedModels)}`
|
||||
)
|
||||
|
||||
if (requestedModel && apiKeyData.restrictedModels.includes(requestedModel)) {
|
||||
if (requestedModel && !apiKeyData.restrictedModels.includes(requestedModel)) {
|
||||
logger.warn(
|
||||
`🚫 Model restriction violation for key ${apiKeyData.name}: Attempted to use restricted model ${requestedModel}`
|
||||
`🚫 Model restriction violation for key ${apiKeyData.name}: Attempted model ${requestedModel} not in allowed list`
|
||||
)
|
||||
return {
|
||||
statusCode: 403,
|
||||
@@ -844,7 +844,7 @@ class ClaudeRelayService {
|
||||
requestedModel: requestBody.model
|
||||
})
|
||||
|
||||
// 检查模型限制
|
||||
// 检查模型限制(restrictedModels 作为允许列表)
|
||||
if (
|
||||
apiKeyData.enableModelRestriction &&
|
||||
apiKeyData.restrictedModels &&
|
||||
@@ -852,12 +852,12 @@ class ClaudeRelayService {
|
||||
) {
|
||||
const requestedModel = requestBody.model
|
||||
logger.info(
|
||||
`🔒 [Stream] Model restriction check - Requested model: ${requestedModel}, Restricted models: ${JSON.stringify(apiKeyData.restrictedModels)}`
|
||||
`🔒 [Stream] Model restriction check - Requested model: ${requestedModel}, Allowed models: ${JSON.stringify(apiKeyData.restrictedModels)}`
|
||||
)
|
||||
|
||||
if (requestedModel && apiKeyData.restrictedModels.includes(requestedModel)) {
|
||||
if (requestedModel && !apiKeyData.restrictedModels.includes(requestedModel)) {
|
||||
logger.warn(
|
||||
`🚫 Model restriction violation for key ${apiKeyData.name}: Attempted to use restricted model ${requestedModel}`
|
||||
`🚫 Model restriction violation for key ${apiKeyData.name}: Attempted model ${requestedModel} not in allowed list`
|
||||
)
|
||||
|
||||
// 对于流式响应,需要写入错误并结束流
|
||||
|
||||
Reference in New Issue
Block a user