mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +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:
78
src/utils/modelHelper.js
Normal file
78
src/utils/modelHelper.js
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Model Helper Utility
|
||||
*
|
||||
* Provides utilities for parsing vendor-prefixed model names.
|
||||
* Supports parsing model strings like "ccr,model_name" to extract vendor type and base model.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parse vendor-prefixed model string
|
||||
* @param {string} modelStr - Model string, potentially with vendor prefix (e.g., "ccr,gemini-2.5-pro")
|
||||
* @returns {{vendor: string|null, baseModel: string}} - Parsed vendor and base model
|
||||
*/
|
||||
function parseVendorPrefixedModel(modelStr) {
|
||||
if (!modelStr || typeof modelStr !== 'string') {
|
||||
return { vendor: null, baseModel: modelStr || '' }
|
||||
}
|
||||
|
||||
// Trim whitespace and convert to lowercase for comparison
|
||||
const trimmed = modelStr.trim()
|
||||
const lowerTrimmed = trimmed.toLowerCase()
|
||||
|
||||
// Check for ccr prefix (case insensitive)
|
||||
if (lowerTrimmed.startsWith('ccr,')) {
|
||||
const parts = trimmed.split(',')
|
||||
if (parts.length >= 2) {
|
||||
// Extract base model (everything after the first comma, rejoined in case model name contains commas)
|
||||
const baseModel = parts.slice(1).join(',').trim()
|
||||
return {
|
||||
vendor: 'ccr',
|
||||
baseModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No recognized vendor prefix found
|
||||
return {
|
||||
vendor: null,
|
||||
baseModel: trimmed
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a model string has a vendor prefix
|
||||
* @param {string} modelStr - Model string to check
|
||||
* @returns {boolean} - True if the model has a vendor prefix
|
||||
*/
|
||||
function hasVendorPrefix(modelStr) {
|
||||
const { vendor } = parseVendorPrefixedModel(modelStr)
|
||||
return vendor !== null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the effective model name for scheduling and processing
|
||||
* This removes vendor prefixes to get the actual model name used for API calls
|
||||
* @param {string} modelStr - Original model string
|
||||
* @returns {string} - Effective model name without vendor prefix
|
||||
*/
|
||||
function getEffectiveModel(modelStr) {
|
||||
const { baseModel } = parseVendorPrefixedModel(modelStr)
|
||||
return baseModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the vendor type from a model string
|
||||
* @param {string} modelStr - Model string to parse
|
||||
* @returns {string|null} - Vendor type ('ccr') or null if no prefix
|
||||
*/
|
||||
function getVendorType(modelStr) {
|
||||
const { vendor } = parseVendorPrefixedModel(modelStr)
|
||||
return vendor
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseVendorPrefixedModel,
|
||||
hasVendorPrefix,
|
||||
getEffectiveModel,
|
||||
getVendorType
|
||||
}
|
||||
Reference in New Issue
Block a user