mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
Merge pull request #880 from DaydreamCoding/feature/fix-claude-openai-endpoint
fix: 修正Claude通过openaiClaudeRoutes访问失败问题
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user