From 530dac0e7fa4dc41e3570aecb3287315499be83c Mon Sep 17 00:00:00 2001
From: jft0m <377632523@qq.com>
Date: Sat, 4 Oct 2025 14:11:13 +0800
Subject: [PATCH 1/5] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=20handleChat?=
=?UTF-8?q?Completions=20=E5=87=BD=E6=95=B0=E6=A8=A1=E5=9D=97=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 使用策略模式处理不同后端(Claude/OpenAI/Gemini)
- 添加 OpenAI chat/completions 兼容支持
---
src/routes/api.js | 500 ++++++++++++++++++++++++++++++++-
src/services/openaiToClaude.js | 19 +-
2 files changed, 507 insertions(+), 12 deletions(-)
diff --git a/src/routes/api.js b/src/routes/api.js
index d4b572d4..825aeff1 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -12,9 +12,28 @@ const logger = require('../utils/logger')
const redis = require('../models/redis')
const { getEffectiveModel, parseVendorPrefixedModel } = require('../utils/modelHelper')
const sessionHelper = require('../utils/sessionHelper')
+const openaiToClaude = require('../services/openaiToClaude')
+const claudeCodeHeadersService = require('../services/claudeCodeHeadersService')
const router = express.Router()
+// 🔍 检测模型对应的后端服务
+function detectBackendFromModel(modelName) {
+ if (!modelName) {
+ return 'claude'
+ }
+ if (modelName.startsWith('claude-')) {
+ return 'claude'
+ }
+ if (modelName.startsWith('gpt-')) {
+ return 'openai'
+ }
+ if (modelName.startsWith('gemini-')) {
+ return 'gemini'
+ }
+ return 'claude' // 默认使用 Claude
+}
+
// 🔧 共享的消息处理函数
async function handleMessagesRequest(req, res) {
try {
@@ -778,25 +797,20 @@ router.post('/v1/messages', authenticateApiKey, handleMessagesRequest)
// 🚀 Claude API messages 端点 - /claude/v1/messages (别名)
router.post('/claude/v1/messages', authenticateApiKey, handleMessagesRequest)
-// 📋 模型列表端点 - Claude Code 客户端需要
+// 📋 模型列表端点 - OpenAI 兼容,返回所有支持的模型
router.get('/v1/models', authenticateApiKey, async (req, res) => {
try {
- // 返回支持的模型列表
+ // 返回支持的模型列表(Claude + OpenAI + Gemini)
const models = [
+ // Claude 模型
{
- id: 'claude-3-5-sonnet-20241022',
+ id: 'claude-sonnet-4-5-20250929',
object: 'model',
created: 1669599635,
owned_by: 'anthropic'
},
{
- id: 'claude-3-5-haiku-20241022',
- object: 'model',
- created: 1669599635,
- owned_by: 'anthropic'
- },
- {
- id: 'claude-3-opus-20240229',
+ id: 'claude-opus-4-1-20250805',
object: 'model',
created: 1669599635,
owned_by: 'anthropic'
@@ -806,6 +820,92 @@ router.get('/v1/models', authenticateApiKey, async (req, res) => {
object: 'model',
created: 1669599635,
owned_by: 'anthropic'
+ },
+ {
+ id: 'claude-opus-4-20250514',
+ object: 'model',
+ created: 1669599635,
+ owned_by: 'anthropic'
+ },
+ {
+ id: 'claude-3-7-sonnet-20250219',
+ object: 'model',
+ created: 1669599635,
+ owned_by: 'anthropic'
+ },
+ {
+ id: 'claude-3-5-sonnet-20241022',
+ object: 'model',
+ created: 1729036800,
+ owned_by: 'anthropic'
+ },
+ {
+ id: 'claude-3-5-haiku-20241022',
+ object: 'model',
+ created: 1729036800,
+ owned_by: 'anthropic'
+ },
+ {
+ id: 'claude-3-haiku-20240307',
+ object: 'model',
+ created: 1709251200,
+ owned_by: 'anthropic'
+ },
+ {
+ id: 'claude-3-opus-20240229',
+ object: 'model',
+ created: 1736726400,
+ owned_by: 'anthropic'
+ },
+ // OpenAI 模型
+ {
+ id: 'gpt-4o',
+ object: 'model',
+ created: 1715367600,
+ owned_by: 'openai'
+ },
+ {
+ id: 'gpt-4o-mini',
+ object: 'model',
+ created: 1721088000,
+ owned_by: 'openai'
+ },
+ {
+ id: 'gpt-4-turbo',
+ object: 'model',
+ created: 1712102400,
+ owned_by: 'openai'
+ },
+ {
+ id: 'gpt-4',
+ object: 'model',
+ created: 1687132800,
+ owned_by: 'openai'
+ },
+ {
+ id: 'gpt-3.5-turbo',
+ object: 'model',
+ created: 1677649200,
+ owned_by: 'openai'
+ },
+ // Gemini 模型
+ {
+ id: 'gemini-1.5-pro',
+ object: 'model',
+ created: 1707868800,
+ owned_by: 'google'
+ },
+ {
+ id: 'gemini-1.5-flash',
+ object: 'model',
+ created: 1715990400,
+ owned_by: 'google'
+ },
+ {
+ id: 'gemini-2.0-flash-exp',
+ object: 'model',
+ created: 1733011200,
+ owned_by: 'google'
}
]
@@ -1038,5 +1138,385 @@ router.post('/v1/messages/count_tokens', authenticateApiKey, async (req, res) =>
}
})
+// 🔍 验证 OpenAI chat/completions 请求参数
+function validateChatCompletionRequest(body) {
+ if (!body || !body.messages || !Array.isArray(body.messages)) {
+ return {
+ valid: false,
+ error: {
+ message: 'Missing or invalid field: messages (must be an array)',
+ type: 'invalid_request_error',
+ code: 'invalid_request'
+ }
+ }
+ }
+
+ if (body.messages.length === 0) {
+ return {
+ valid: false,
+ error: {
+ message: 'Messages array cannot be empty',
+ type: 'invalid_request_error',
+ code: 'invalid_request'
+ }
+ }
+ }
+
+ return { valid: true }
+}
+
+// 🌊 处理 Claude 流式请求
+async function handleClaudeStreamRequest(
+ claudeRequest,
+ apiKeyData,
+ req,
+ res,
+ accountId,
+ requestedModel
+) {
+ logger.info(`🌊 Processing OpenAI stream request for model: ${requestedModel}`)
+
+ // 设置 SSE 响应头
+ res.setHeader('Content-Type', 'text/event-stream')
+ res.setHeader('Cache-Control', 'no-cache')
+ res.setHeader('Connection', 'keep-alive')
+ res.setHeader('X-Accel-Buffering', 'no')
+
+ // 创建中止控制器
+ const abortController = new AbortController()
+
+ // 处理客户端断开
+ req.on('close', () => {
+ if (abortController && !abortController.signal.aborted) {
+ logger.info('🔌 Client disconnected, aborting Claude request')
+ abortController.abort()
+ }
+ })
+
+ // 获取该账号存储的 Claude Code headers
+ const claudeCodeHeaders = await claudeCodeHeadersService.getAccountHeaders(accountId)
+
+ // 使用转换后的响应流
+ await claudeRelayService.relayStreamRequestWithUsageCapture(
+ claudeRequest,
+ apiKeyData,
+ res,
+ claudeCodeHeaders,
+ (usage) => {
+ // 记录使用统计
+ if (usage && usage.input_tokens !== undefined && usage.output_tokens !== undefined) {
+ const model = usage.model || claudeRequest.model
+
+ apiKeyService
+ .recordUsageWithDetails(apiKeyData.id, usage, model, accountId)
+ .catch((error) => {
+ logger.error('❌ Failed to record usage:', error)
+ })
+ }
+ },
+ // 流转换器:将 Claude SSE 转换为 OpenAI SSE
+ (() => {
+ const sessionId = `chatcmpl-${Math.random().toString(36).substring(2, 15)}${Math.random().toString(36).substring(2, 15)}`
+ return (chunk) => openaiToClaude.convertStreamChunk(chunk, requestedModel, sessionId)
+ })(),
+ {
+ betaHeader:
+ 'oauth-2025-04-20,claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14'
+ }
+ )
+
+ return { abortController }
+}
+
+// 📄 处理 Claude 非流式请求
+async function handleClaudeNonStreamRequest(
+ claudeRequest,
+ apiKeyData,
+ req,
+ res,
+ accountId,
+ requestedModel
+) {
+ logger.info(`📄 Processing OpenAI non-stream request for model: ${requestedModel}`)
+
+ // 获取该账号存储的 Claude Code headers
+ const claudeCodeHeaders = await claudeCodeHeadersService.getAccountHeaders(accountId)
+
+ // 发送请求到 Claude
+ const claudeResponse = await claudeRelayService.relayRequest(
+ claudeRequest,
+ apiKeyData,
+ req,
+ res,
+ claudeCodeHeaders,
+ { betaHeader: 'oauth-2025-04-20' }
+ )
+
+ // 解析 Claude 响应
+ let claudeData
+ try {
+ claudeData = JSON.parse(claudeResponse.body)
+ } catch (error) {
+ logger.error('❌ Failed to parse Claude response:', error)
+ return {
+ error: {
+ status: 502,
+ data: {
+ error: {
+ message: 'Invalid response from Claude API',
+ type: 'api_error',
+ code: 'invalid_response'
+ }
+ }
+ }
+ }
+ }
+
+ // 处理错误响应
+ if (claudeResponse.statusCode >= 400) {
+ return {
+ error: {
+ status: claudeResponse.statusCode,
+ data: {
+ error: {
+ message: claudeData.error?.message || 'Claude API error',
+ type: claudeData.error?.type || 'api_error',
+ code: claudeData.error?.code || 'unknown_error'
+ }
+ }
+ }
+ }
+ }
+
+ // 转换为 OpenAI 格式
+ const openaiResponse = openaiToClaude.convertResponse(claudeData, requestedModel)
+
+ // 记录使用统计
+ if (claudeData.usage) {
+ const { usage } = claudeData
+ apiKeyService
+ .recordUsageWithDetails(apiKeyData.id, usage, claudeRequest.model, accountId)
+ .catch((error) => {
+ logger.error('❌ Failed to record usage:', error)
+ })
+ }
+
+ return { success: true, data: openaiResponse }
+}
+
+// 🤖 处理 Claude 后端
+async function handleClaudeBackend(req, res, apiKeyData, requestedModel) {
+ // 转换 OpenAI 请求为 Claude 格式
+ const claudeRequest = openaiToClaude.convertRequest(req.body)
+
+ // 检查模型限制
+ if (apiKeyData.enableModelRestriction && apiKeyData.restrictedModels?.length > 0) {
+ if (!apiKeyData.restrictedModels.includes(claudeRequest.model)) {
+ return res.status(403).json({
+ error: {
+ message: `Model ${requestedModel} is not allowed for this API key`,
+ type: 'invalid_request_error',
+ code: 'model_not_allowed'
+ }
+ })
+ }
+ }
+
+ // 生成会话哈希用于 sticky 会话
+ const sessionHash = sessionHelper.generateSessionHash(claudeRequest)
+
+ // 选择可用的 Claude 账户
+ const accountSelection = await unifiedClaudeScheduler.selectAccountForApiKey(
+ apiKeyData,
+ sessionHash,
+ claudeRequest.model
+ )
+ const { accountId } = accountSelection
+
+ // 处理流式或非流式请求
+ if (claudeRequest.stream) {
+ const { abortController } = await handleClaudeStreamRequest(
+ claudeRequest,
+ apiKeyData,
+ req,
+ res,
+ accountId,
+ requestedModel
+ )
+ return { abortController }
+ } else {
+ const result = await handleClaudeNonStreamRequest(
+ claudeRequest,
+ apiKeyData,
+ req,
+ res,
+ accountId,
+ requestedModel
+ )
+
+ if (result.error) {
+ return res.status(result.error.status).json(result.error.data)
+ }
+
+ return res.json(result.data)
+ }
+}
+
+// 🔧 处理 OpenAI 后端(未实现)
+async function handleOpenAIBackend(req, res, _apiKeyData, _requestedModel) {
+ return res.status(501).json({
+ error: {
+ message: 'OpenAI backend not yet implemented for this endpoint',
+ type: 'not_implemented',
+ code: 'not_implemented'
+ }
+ })
+}
+
+// 💎 处理 Gemini 后端(未实现)
+async function handleGeminiBackend(req, res, _apiKeyData, _requestedModel) {
+ return res.status(501).json({
+ error: {
+ message: 'Gemini backend not yet implemented for this endpoint',
+ type: 'not_implemented',
+ code: 'not_implemented'
+ }
+ })
+}
+
+// 🗺️ 后端处理策略映射
+const backendHandlers = {
+ claude: handleClaudeBackend,
+ openai: handleOpenAIBackend,
+ gemini: handleGeminiBackend
+}
+
+// 🚀 OpenAI 兼容的 chat/completions 处理器(智能路由)
+async function handleChatCompletions(req, res) {
+ const startTime = Date.now()
+ let abortController = null
+
+ try {
+ const apiKeyData = req.apiKey
+
+ // 验证必需参数
+ const validation = validateChatCompletionRequest(req.body)
+ if (!validation.valid) {
+ return res.status(400).json({ error: validation.error })
+ }
+
+ // 检测模型对应的后端
+ const requestedModel = req.body.model || 'claude-3-5-sonnet-20241022'
+ const backend = detectBackendFromModel(requestedModel)
+
+ logger.debug(
+ `📥 Received OpenAI format request for model: ${requestedModel}, backend: ${backend}`
+ )
+
+ // 使用策略模式处理不同后端
+ const handler = backendHandlers[backend]
+ if (!handler) {
+ return res.status(500).json({
+ error: {
+ message: `Unsupported backend: ${backend}`,
+ type: 'server_error',
+ code: 'unsupported_backend'
+ }
+ })
+ }
+
+ // 调用对应的后端处理器
+ const result = await handler(req, res, apiKeyData, requestedModel)
+
+ // 保存 abort controller(用于清理)
+ if (result && result.abortController) {
+ ;({ abortController } = result)
+ }
+
+ const duration = Date.now() - startTime
+ logger.info(`✅ OpenAI chat/completions request completed in ${duration}ms`)
+ return undefined
+ } catch (error) {
+ logger.error('❌ OpenAI chat/completions error:', error)
+
+ const status = error.status || 500
+ if (!res.headersSent) {
+ res.status(status).json({
+ error: {
+ message: error.message || 'Internal server error',
+ type: 'server_error',
+ code: 'internal_error'
+ }
+ })
+ }
+ return undefined
+ } finally {
+ // 清理资源
+ if (abortController) {
+ abortController = null
+ }
+ }
+}
+
+// 🔧 OpenAI 兼容的 completions 处理器(传统格式,转换为 chat 格式)
+async function handleCompletions(req, res) {
+ try {
+ // 验证必需参数
+ if (!req.body.prompt) {
+ return res.status(400).json({
+ error: {
+ message: 'Prompt is required',
+ type: 'invalid_request_error',
+ code: 'invalid_request'
+ }
+ })
+ }
+
+ // 将传统 completions 格式转换为 chat 格式
+ const chatRequest = {
+ model: req.body.model || 'claude-3-5-sonnet-20241022',
+ messages: [
+ {
+ role: 'user',
+ content: req.body.prompt
+ }
+ ],
+ max_tokens: req.body.max_tokens,
+ temperature: req.body.temperature,
+ top_p: req.body.top_p,
+ stream: req.body.stream,
+ stop: req.body.stop,
+ n: req.body.n || 1,
+ presence_penalty: req.body.presence_penalty,
+ frequency_penalty: req.body.frequency_penalty,
+ logit_bias: req.body.logit_bias,
+ user: req.body.user
+ }
+
+ // 使用 chat/completions 处理器
+ req.body = chatRequest
+ await handleChatCompletions(req, res)
+ return undefined
+ } catch (error) {
+ logger.error('❌ OpenAI completions error:', error)
+ if (!res.headersSent) {
+ res.status(500).json({
+ error: {
+ message: 'Failed to process completion request',
+ type: 'server_error',
+ code: 'internal_error'
+ }
+ })
+ }
+ return undefined
+ }
+}
+
+// 📋 OpenAI 兼容的 chat/completions 端点
+router.post('/v1/chat/completions', authenticateApiKey, handleChatCompletions)
+
+// 🔧 OpenAI 兼容的 completions 端点(传统格式)
+router.post('/v1/completions', authenticateApiKey, handleCompletions)
+
module.exports = router
module.exports.handleMessagesRequest = handleMessagesRequest
diff --git a/src/services/openaiToClaude.js b/src/services/openaiToClaude.js
index 10c8ae24..1f335f0e 100644
--- a/src/services/openaiToClaude.js
+++ b/src/services/openaiToClaude.js
@@ -31,10 +31,25 @@ class OpenAIToClaudeConverter {
stream: openaiRequest.stream || false
}
- // Claude Code 必需的系统消息
+ // 定义 Claude Code 的默认系统提示词
const claudeCodeSystemMessage = "You are Claude Code, Anthropic's official CLI for Claude."
- claudeRequest.system = claudeCodeSystemMessage
+ // 如果 OpenAI 请求中包含系统消息,提取并检查
+ const systemMessage = this._extractSystemMessage(openaiRequest.messages)
+ if (systemMessage && systemMessage.includes('You are currently in Xcode')) {
+ // Xcode 系统提示词
+ claudeRequest.system = systemMessage
+ logger.info(
+ `🔍 Xcode request detected, using Xcode system prompt (${systemMessage.length} chars)`
+ )
+ logger.debug(`📋 System prompt preview: ${systemMessage.substring(0, 150)}...`)
+ } else {
+ // 使用 Claude Code 默认系统提示词
+ claudeRequest.system = claudeCodeSystemMessage
+ logger.debug(
+ `📋 Using Claude Code default system prompt${systemMessage ? ' (ignored custom prompt)' : ''}`
+ )
+ }
// 处理停止序列
if (openaiRequest.stop) {
From 61e5cb4584404c1181122df0c711f6a599240267 Mon Sep 17 00:00:00 2001
From: jft0m <377632523@qq.com>
Date: Sat, 4 Oct 2025 13:48:07 +0800
Subject: [PATCH 2/5] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=20handleChat?=
=?UTF-8?q?Completions=20=E5=87=BD=E6=95=B0=E6=A8=A1=E5=9D=97=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 使用策略模式处理不同后端(Claude/OpenAI/Gemini)
- 添加 OpenAI chat/completions 兼容支持
- 修复代码缩进符合 ESLint 规范
---
src/validators/clients/codexCliValidator.js | 3 ++-
src/validators/clients/geminiCliValidator.js | 4 +++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/validators/clients/codexCliValidator.js b/src/validators/clients/codexCliValidator.js
index aff09fbf..78c2a1aa 100644
--- a/src/validators/clients/codexCliValidator.js
+++ b/src/validators/clients/codexCliValidator.js
@@ -53,7 +53,8 @@ class CodexCliValidator {
// 2. 对于特定路径,进行额外的严格验证
// 对于 /openai 和 /azure 路径需要完整验证
const strictValidationPaths = ['/openai', '/azure']
- const needsStrictValidation = req.path && strictValidationPaths.some(path => req.path.startsWith(path))
+ const needsStrictValidation =
+ req.path && strictValidationPaths.some((path) => req.path.startsWith(path))
if (!needsStrictValidation) {
// 其他路径,只要 User-Agent 匹配就认为是 Codex CLI
diff --git a/src/validators/clients/geminiCliValidator.js b/src/validators/clients/geminiCliValidator.js
index ea8e60e7..8e9ed0de 100644
--- a/src/validators/clients/geminiCliValidator.js
+++ b/src/validators/clients/geminiCliValidator.js
@@ -55,7 +55,9 @@ class GeminiCliValidator {
// 包含 generateContent 的路径需要验证 User-Agent
const geminiCliPattern = /^GeminiCLI\/v?[\d\.]+/i
if (!geminiCliPattern.test(userAgent)) {
- logger.debug(`Gemini CLI validation failed - UA mismatch for generateContent: ${userAgent}`)
+ logger.debug(
+ `Gemini CLI validation failed - UA mismatch for generateContent: ${userAgent}`
+ )
return false
}
}
From 5d7225b2eb8475489213868fc22470e3d1bed2f8 Mon Sep 17 00:00:00 2001
From: jft0m <377632523@qq.com>
Date: Mon, 6 Oct 2025 15:34:13 +0000
Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20Opus=20?=
=?UTF-8?q?=E9=99=90=E6=B5=81=E7=8A=B6=E6=80=81=E6=98=BE=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在账户列表中显示 Opus 限流状态徽章
- 显示限流剩余时间(天/小时)
- 后端 API 添加 opusRateLimitedAt 和 opusRateLimitEndAt 字段
- 优化徽章样式,防止文字溢出
---
src/services/claudeAccountService.js | 5 +-
web/admin-spa/src/views/AccountsView.vue | 60 +++++++++++++++++++++++-
2 files changed, 63 insertions(+), 2 deletions(-)
diff --git a/src/services/claudeAccountService.js b/src/services/claudeAccountService.js
index ec9e5b11..f931345e 100644
--- a/src/services/claudeAccountService.js
+++ b/src/services/claudeAccountService.js
@@ -528,7 +528,10 @@ class ClaudeAccountService {
useUnifiedClientId: account.useUnifiedClientId === 'true', // 默认为false
unifiedClientId: account.unifiedClientId || '', // 统一的客户端标识
// 添加停止原因
- stoppedReason: account.stoppedReason || null
+ stoppedReason: account.stoppedReason || null,
+ // 添加 Opus 限流信息
+ opusRateLimitedAt: account.opusRateLimitedAt || null,
+ opusRateLimitEndAt: account.opusRateLimitEndAt || null
}
})
)
diff --git a/web/admin-spa/src/views/AccountsView.vue b/web/admin-spa/src/views/AccountsView.vue
index f804f639..1a9469ee 100644
--- a/web/admin-spa/src/views/AccountsView.vue
+++ b/web/admin-spa/src/views/AccountsView.vue
@@ -606,9 +606,19 @@
>({{ formatRateLimitTime(account.rateLimitStatus.minutesRemaining) }})
+
+
+ Opus限流
+
+ ({{ formatOpusLimitEndTime(account.opusRateLimitEndAt) }})
+
+
不可调度
@@ -2491,6 +2501,54 @@ const formatRateLimitTime = (minutes) => {
}
}
+// 检查账户是否处于 Opus 限流状态
+const isOpusRateLimited = (account) => {
+ if (!account.opusRateLimitEndAt) {
+ return false
+ }
+ const endTime = new Date(account.opusRateLimitEndAt)
+ const now = new Date()
+ return endTime > now
+}
+
+// 格式化 Opus 限流结束时间
+const formatOpusLimitEndTime = (endTimeStr) => {
+ if (!endTimeStr) return ''
+
+ const endTime = new Date(endTimeStr)
+ const now = new Date()
+
+ // 如果已经过期,返回"已解除"
+ if (endTime <= now) {
+ return '已解除'
+ }
+
+ // 计算剩余时间(毫秒)
+ const remainingMs = endTime - now
+ const remainingMinutes = Math.floor(remainingMs / (1000 * 60))
+
+ // 计算天数、小时和分钟
+ const days = Math.floor(remainingMinutes / 1440)
+ const remainingAfterDays = remainingMinutes % 1440
+ const hours = Math.floor(remainingAfterDays / 60)
+ const mins = remainingAfterDays % 60
+
+ // 格式化显示
+ const parts = []
+ if (days > 0) {
+ parts.push(`${days}天`)
+ }
+ if (hours > 0) {
+ parts.push(`${hours}小时`)
+ }
+ if (mins > 0 && days === 0) {
+ // 只有在天数为0时才显示分钟
+ parts.push(`${mins}分钟`)
+ }
+
+ return parts.join('')
+}
+
// 打开创建账户模态框
const openCreateAccountModal = () => {
newAccountPlatform.value = null // 重置选择的平台
From bb95c0b7d566fed33cf02f286f647bcaf564755a Mon Sep 17 00:00:00 2001
From: jft0m <377632523@qq.com>
Date: Mon, 6 Oct 2025 15:57:09 +0000
Subject: [PATCH 4/5] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20ESLint=20?=
=?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=A7=84=E8=8C=83=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 移除正则表达式中不必要的转义字符
- 添加 if 语句的花括号
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
src/validators/clients/codexCliValidator.js | 10 +++++++---
src/validators/clients/geminiCliValidator.js | 10 +++++++---
2 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/src/validators/clients/codexCliValidator.js b/src/validators/clients/codexCliValidator.js
index 78c2a1aa..d8922bd2 100644
--- a/src/validators/clients/codexCliValidator.js
+++ b/src/validators/clients/codexCliValidator.js
@@ -42,7 +42,7 @@ class CodexCliValidator {
// Codex CLI 的 UA 格式:
// - codex_vscode/0.35.0 (Windows 10.0.26100; x86_64) unknown (Cursor; 0.4.10)
// - codex_cli_rs/0.38.0 (Ubuntu 22.4.0; x86_64) WindowsTerminal
- const codexCliPattern = /^(codex_vscode|codex_cli_rs)\/[\d\.]+/i
+ const codexCliPattern = /^(codex_vscode|codex_cli_rs)\/[\d.]+/i
const uaMatch = userAgent.match(codexCliPattern)
if (!uaMatch) {
@@ -125,8 +125,12 @@ class CodexCliValidator {
const part1 = parts1[i] || 0
const part2 = parts2[i] || 0
- if (part1 < part2) return -1
- if (part1 > part2) return 1
+ if (part1 < part2) {
+ return -1
+ }
+ if (part1 > part2) {
+ return 1
+ }
}
return 0
diff --git a/src/validators/clients/geminiCliValidator.js b/src/validators/clients/geminiCliValidator.js
index 8e9ed0de..0d438384 100644
--- a/src/validators/clients/geminiCliValidator.js
+++ b/src/validators/clients/geminiCliValidator.js
@@ -53,7 +53,7 @@ class GeminiCliValidator {
// 2. 对于 /gemini 路径,检查是否包含 generateContent
if (path.includes('generateContent')) {
// 包含 generateContent 的路径需要验证 User-Agent
- const geminiCliPattern = /^GeminiCLI\/v?[\d\.]+/i
+ const geminiCliPattern = /^GeminiCLI\/v?[\d.]+/i
if (!geminiCliPattern.test(userAgent)) {
logger.debug(
`Gemini CLI validation failed - UA mismatch for generateContent: ${userAgent}`
@@ -84,8 +84,12 @@ class GeminiCliValidator {
const part1 = parts1[i] || 0
const part2 = parts2[i] || 0
- if (part1 < part2) return -1
- if (part1 > part2) return 1
+ if (part1 < part2) {
+ return -1
+ }
+ if (part1 > part2) {
+ return 1
+ }
}
return 0
From 8f9286c30eb5957f336c0afc89a522bbac486aeb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=96=BC=E6=9E=97=E6=B6=9B?=
Date: Wed, 8 Oct 2025 19:49:36 +0800
Subject: [PATCH 5/5] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20ESLint=20?=
=?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=A7=84=E8=8C=83=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/routes/api.js | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/routes/api.js b/src/routes/api.js
index 42858c52..7d700c9d 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -34,8 +34,6 @@ function detectBackendFromModel(modelName) {
const { updateRateLimitCounters } = require('../utils/rateLimitHelper')
-const router = express.Router()
-
function queueRateLimitUpdate(rateLimitInfo, usageSummary, model, context = '') {
if (!rateLimitInfo) {
return Promise.resolve({ totalTokens: 0, totalCost: 0 })