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