Merge pull request #880 from DaydreamCoding/feature/fix-claude-openai-endpoint

fix: 修正Claude通过openaiClaudeRoutes访问失败问题
This commit is contained in:
Wesley Liddick
2026-01-09 15:55:01 +08:00
committed by GitHub
4 changed files with 100 additions and 66 deletions

View File

@@ -862,7 +862,7 @@ async function handleKeyInfo(req, res) {
res.json({ res.json({
id: keyData.id, id: keyData.id,
name: keyData.name, name: keyData.name,
permissions: keyData.permissions || 'all', permissions: keyData.permissions,
token_limit: keyData.tokenLimit, token_limit: keyData.tokenLimit,
tokens_used: keyData.usage.total.tokens, tokens_used: keyData.usage.total.tokens,
tokens_remaining: tokens_remaining:

View File

@@ -155,7 +155,7 @@ router.post('/api/user-stats', async (req, res) => {
restrictedModels, restrictedModels,
enableClientRestriction: keyData.enableClientRestriction === 'true', enableClientRestriction: keyData.enableClientRestriction === 'true',
allowedClients, allowedClients,
permissions: keyData.permissions || 'all', permissions: keyData.permissions,
// 添加激活相关字段 // 添加激活相关字段
expirationMode: keyData.expirationMode || 'fixed', expirationMode: keyData.expirationMode || 'fixed',
isActivated: keyData.isActivated === 'true', isActivated: keyData.isActivated === 'true',

View File

@@ -8,6 +8,7 @@ const router = express.Router()
const logger = require('../utils/logger') const logger = require('../utils/logger')
const { authenticateApiKey } = require('../middleware/auth') const { authenticateApiKey } = require('../middleware/auth')
const claudeRelayService = require('../services/claudeRelayService') const claudeRelayService = require('../services/claudeRelayService')
const claudeConsoleRelayService = require('../services/claudeConsoleRelayService')
const openaiToClaude = require('../services/openaiToClaude') const openaiToClaude = require('../services/openaiToClaude')
const apiKeyService = require('../services/apiKeyService') const apiKeyService = require('../services/apiKeyService')
const unifiedClaudeScheduler = require('../services/unifiedClaudeScheduler') const unifiedClaudeScheduler = require('../services/unifiedClaudeScheduler')
@@ -19,8 +20,7 @@ const { getEffectiveModel } = require('../utils/modelHelper')
// 🔧 辅助函数:检查 API Key 权限 // 🔧 辅助函数:检查 API Key 权限
function checkPermissions(apiKeyData, requiredPermission = 'claude') { function checkPermissions(apiKeyData, requiredPermission = 'claude') {
const permissions = apiKeyData.permissions || 'all' return apiKeyService.hasPermission(apiKeyData?.permissions, requiredPermission)
return permissions === 'all' || permissions === requiredPermission
} }
function queueRateLimitUpdate(rateLimitInfo, usageSummary, model, context = '') { function queueRateLimitUpdate(rateLimitInfo, usageSummary, model, context = '') {
@@ -235,7 +235,7 @@ async function handleChatCompletion(req, res, apiKeyData) {
} }
throw error throw error
} }
const { accountId } = accountSelection const { accountId, accountType } = accountSelection
// 获取该账号存储的 Claude Code headers // 获取该账号存储的 Claude Code headers
const claudeCodeHeaders = await claudeCodeHeadersService.getAccountHeaders(accountId) const claudeCodeHeaders = await claudeCodeHeadersService.getAccountHeaders(accountId)
@@ -265,72 +265,105 @@ async function handleChatCompletion(req, res, apiKeyData) {
} }
}) })
// 使用转换后的响应流 (使用 OAuth-only beta header添加 Claude Code 必需的 headers) // 使用转换后的响应流 (根据账户类型选择转发服务)
await claudeRelayService.relayStreamRequestWithUsageCapture( // 创建 usage 回调函数
claudeRequest, const usageCallback = (usage) => {
apiKeyData, // 记录使用统计
res, if (usage && usage.input_tokens !== undefined && usage.output_tokens !== undefined) {
claudeCodeHeaders, const model = usage.model || claudeRequest.model
(usage) => { const cacheCreateTokens =
// 记录使用统计 (usage.cache_creation && typeof usage.cache_creation === 'object'
if (usage && usage.input_tokens !== undefined && usage.output_tokens !== undefined) { ? (usage.cache_creation.ephemeral_5m_input_tokens || 0) +
const model = usage.model || claudeRequest.model (usage.cache_creation.ephemeral_1h_input_tokens || 0)
const cacheCreateTokens = : usage.cache_creation_input_tokens || 0) || 0
(usage.cache_creation && typeof usage.cache_creation === 'object' const cacheReadTokens = usage.cache_read_input_tokens || 0
? (usage.cache_creation.ephemeral_5m_input_tokens || 0) +
(usage.cache_creation.ephemeral_1h_input_tokens || 0)
: usage.cache_creation_input_tokens || 0) || 0
const cacheReadTokens = usage.cache_read_input_tokens || 0
// 使用新的 recordUsageWithDetails 方法来支持详细的缓存数据 // 使用新的 recordUsageWithDetails 方法来支持详细的缓存数据
apiKeyService apiKeyService
.recordUsageWithDetails( .recordUsageWithDetails(
apiKeyData.id, apiKeyData.id,
usage, // 直接传递整个 usage 对象,包含可能的 cache_creation 详细数据 usage, // 直接传递整个 usage 对象,包含可能的 cache_creation 详细数据
model,
accountId
)
.catch((error) => {
logger.error('❌ Failed to record usage:', error)
})
queueRateLimitUpdate(
req.rateLimitInfo,
{
inputTokens: usage.input_tokens || 0,
outputTokens: usage.output_tokens || 0,
cacheCreateTokens,
cacheReadTokens
},
model, model,
'openai-claude-stream' accountId,
accountType
) )
} .catch((error) => {
}, logger.error('❌ Failed to record usage:', error)
// 流转换器 })
(() => {
// 为每个请求创建独立的会话ID queueRateLimitUpdate(
const sessionId = `chatcmpl-${Math.random().toString(36).substring(2, 15)}${Math.random().toString(36).substring(2, 15)}` req.rateLimitInfo,
return (chunk) => openaiToClaude.convertStreamChunk(chunk, req.body.model, sessionId) {
})(), inputTokens: usage.input_tokens || 0,
{ outputTokens: usage.output_tokens || 0,
betaHeader: cacheCreateTokens,
'oauth-2025-04-20,claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14' cacheReadTokens
},
model,
`openai-${accountType}-stream`
)
} }
) }
// 创建流转换器
const sessionId = `chatcmpl-${Math.random().toString(36).substring(2, 15)}${Math.random().toString(36).substring(2, 15)}`
const streamTransformer = (chunk) =>
openaiToClaude.convertStreamChunk(chunk, req.body.model, sessionId)
// 根据账户类型选择转发服务
if (accountType === 'claude-console') {
// Claude Console 账户使用 Console 转发服务
await claudeConsoleRelayService.relayStreamRequestWithUsageCapture(
claudeRequest,
apiKeyData,
res,
claudeCodeHeaders,
usageCallback,
accountId,
streamTransformer
)
} else {
// Claude Official 账户使用标准转发服务
await claudeRelayService.relayStreamRequestWithUsageCapture(
claudeRequest,
apiKeyData,
res,
claudeCodeHeaders,
usageCallback,
streamTransformer,
{
betaHeader:
'oauth-2025-04-20,claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14'
}
)
}
} else { } else {
// 非流式请求 // 非流式请求
logger.info(`📄 Processing OpenAI non-stream request for model: ${req.body.model}`) logger.info(`📄 Processing OpenAI non-stream request for model: ${req.body.model}`)
// 发送请求到 Claude (使用 OAuth-only beta header添加 Claude Code 必需的 headers) // 根据账户类型选择转发服务
const claudeResponse = await claudeRelayService.relayRequest( let claudeResponse
claudeRequest, if (accountType === 'claude-console') {
apiKeyData, // Claude Console 账户使用 Console 转发服务
req, claudeResponse = await claudeConsoleRelayService.relayRequest(
res, claudeRequest,
claudeCodeHeaders, apiKeyData,
{ betaHeader: 'oauth-2025-04-20' } req,
) res,
claudeCodeHeaders,
accountId
)
} else {
// Claude Official 账户使用标准转发服务
claudeResponse = await claudeRelayService.relayRequest(
claudeRequest,
apiKeyData,
req,
res,
claudeCodeHeaders,
{ betaHeader: 'oauth-2025-04-20' }
)
}
// 解析 Claude 响应 // 解析 Claude 响应
let claudeData let claudeData
@@ -376,7 +409,8 @@ async function handleChatCompletion(req, res, apiKeyData) {
apiKeyData.id, apiKeyData.id,
usage, // 直接传递整个 usage 对象,包含可能的 cache_creation 详细数据 usage, // 直接传递整个 usage 对象,包含可能的 cache_creation 详细数据
claudeRequest.model, claudeRequest.model,
accountId accountId,
accountType
) )
.catch((error) => { .catch((error) => {
logger.error('❌ Failed to record usage:', error) logger.error('❌ Failed to record usage:', error)
@@ -391,7 +425,7 @@ async function handleChatCompletion(req, res, apiKeyData) {
cacheReadTokens cacheReadTokens
}, },
claudeRequest.model, claudeRequest.model,
'openai-claude-non-stream' `openai-${accountType}-non-stream`
) )
} }

View File

@@ -904,7 +904,7 @@ router.get('/key-info', authenticateApiKey, async (req, res) => {
id: keyData.id, id: keyData.id,
name: keyData.name, name: keyData.name,
description: keyData.description, description: keyData.description,
permissions: keyData.permissions || 'all', permissions: keyData.permissions,
token_limit: keyData.tokenLimit, token_limit: keyData.tokenLimit,
tokens_used: keyData.usage.total.tokens, tokens_used: keyData.usage.total.tokens,
tokens_remaining: tokens_remaining: