mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: 新增Claude账号订阅类型设置
1. OAuth可自动判断订阅类型,Setup Token请自行选择。无论那种类型都可以自己改 2. 优化调度,Pro账号不再接受opus模型请求的调度
This commit is contained in:
@@ -6,11 +6,13 @@ const fs = require('fs')
|
||||
const os = require('os')
|
||||
|
||||
// 安全的 JSON 序列化函数,处理循环引用
|
||||
const safeStringify = (obj, maxDepth = 3) => {
|
||||
const safeStringify = (obj, maxDepth = 3, fullDepth = false) => {
|
||||
const seen = new WeakSet()
|
||||
// 如果是fullDepth模式,增加深度限制
|
||||
const actualMaxDepth = fullDepth ? 10 : maxDepth
|
||||
|
||||
const replacer = (key, value, depth = 0) => {
|
||||
if (depth > maxDepth) {
|
||||
if (depth > actualMaxDepth) {
|
||||
return '[Max Depth Reached]'
|
||||
}
|
||||
|
||||
@@ -152,6 +154,21 @@ const securityLogger = winston.createLogger({
|
||||
silent: false
|
||||
})
|
||||
|
||||
// 🔐 创建专门的认证详细日志记录器(记录完整的认证响应)
|
||||
const authDetailLogger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||
winston.format.printf(({ level, message, timestamp, data }) => {
|
||||
// 使用更深的深度和格式化的JSON输出
|
||||
const jsonData = data ? JSON.stringify(data, null, 2) : '{}'
|
||||
return `[${timestamp}] ${level.toUpperCase()}: ${message}\n${jsonData}\n${'='.repeat(80)}`
|
||||
})
|
||||
),
|
||||
transports: [createRotateTransport('claude-relay-auth-detail-%DATE%.log', 'info')],
|
||||
silent: false
|
||||
})
|
||||
|
||||
// 🌟 增强的 Winston logger
|
||||
const logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || config.logging.level,
|
||||
@@ -327,6 +344,28 @@ logger.healthCheck = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 🔐 记录认证详细信息的方法
|
||||
logger.authDetail = (message, data = {}) => {
|
||||
try {
|
||||
// 记录到主日志(简化版)
|
||||
logger.info(`🔐 ${message}`, {
|
||||
type: 'auth-detail',
|
||||
summary: {
|
||||
hasAccessToken: !!data.access_token,
|
||||
hasRefreshToken: !!data.refresh_token,
|
||||
scopes: data.scope || data.scopes,
|
||||
organization: data.organization?.name,
|
||||
account: data.account?.email_address
|
||||
}
|
||||
})
|
||||
|
||||
// 记录到专门的认证详细日志文件(完整数据)
|
||||
authDetailLogger.info(message, { data })
|
||||
} catch (error) {
|
||||
logger.error('Failed to log auth detail:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 🎬 启动日志记录系统
|
||||
logger.start('Logger initialized', {
|
||||
level: process.env.LOG_LEVEL || config.logging.level,
|
||||
|
||||
@@ -16,7 +16,7 @@ const OAUTH_CONFIG = {
|
||||
CLIENT_ID: '9d1c250a-e61b-44d9-88ed-5944d1962f5e',
|
||||
REDIRECT_URI: 'https://console.anthropic.com/oauth/code/callback',
|
||||
SCOPES: 'org:create_api_key user:profile user:inference',
|
||||
SCOPES_SETUP: 'user:inference'
|
||||
SCOPES_SETUP: 'user:inference' // Setup Token 只需要推理权限
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,23 +203,55 @@ async function exchangeCodeForTokens(authorizationCode, codeVerifier, state, pro
|
||||
timeout: 30000
|
||||
})
|
||||
|
||||
// 记录完整的响应数据到专门的认证详细日志
|
||||
logger.authDetail('OAuth token exchange response', response.data)
|
||||
|
||||
// 记录简化版本到主日志
|
||||
logger.info('📊 OAuth token exchange response (analyzing for subscription info):', {
|
||||
status: response.status,
|
||||
hasData: !!response.data,
|
||||
dataKeys: response.data ? Object.keys(response.data) : []
|
||||
})
|
||||
|
||||
logger.success('✅ OAuth token exchange successful', {
|
||||
status: response.status,
|
||||
hasAccessToken: !!response.data?.access_token,
|
||||
hasRefreshToken: !!response.data?.refresh_token,
|
||||
scopes: response.data?.scope
|
||||
scopes: response.data?.scope,
|
||||
// 尝试提取可能的套餐信息字段
|
||||
subscription: response.data?.subscription,
|
||||
plan: response.data?.plan,
|
||||
tier: response.data?.tier,
|
||||
accountType: response.data?.account_type,
|
||||
features: response.data?.features,
|
||||
limits: response.data?.limits
|
||||
})
|
||||
|
||||
const { data } = response
|
||||
|
||||
// 返回Claude格式的token数据
|
||||
return {
|
||||
// 返回Claude格式的token数据,包含可能的套餐信息
|
||||
const result = {
|
||||
accessToken: data.access_token,
|
||||
refreshToken: data.refresh_token,
|
||||
expiresAt: (Math.floor(Date.now() / 1000) + data.expires_in) * 1000,
|
||||
scopes: data.scope ? data.scope.split(' ') : ['user:inference', 'user:profile'],
|
||||
isMax: true
|
||||
}
|
||||
|
||||
// 如果响应中包含套餐信息,添加到返回结果中
|
||||
if (data.subscription || data.plan || data.tier || data.account_type) {
|
||||
result.subscriptionInfo = {
|
||||
subscription: data.subscription,
|
||||
plan: data.plan,
|
||||
tier: data.tier,
|
||||
accountType: data.account_type,
|
||||
features: data.features,
|
||||
limits: data.limits
|
||||
}
|
||||
logger.info('🎯 Found subscription info in OAuth response:', result.subscriptionInfo)
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
// 处理axios错误响应
|
||||
if (error.response) {
|
||||
@@ -340,7 +372,7 @@ async function exchangeSetupTokenCode(authorizationCode, codeVerifier, state, pr
|
||||
redirect_uri: OAUTH_CONFIG.REDIRECT_URI,
|
||||
code_verifier: codeVerifier,
|
||||
state,
|
||||
expires_in: 31536000
|
||||
expires_in: 31536000 // Setup Token 可以设置较长的过期时间
|
||||
}
|
||||
|
||||
// 创建代理agent
|
||||
@@ -368,16 +400,54 @@ async function exchangeSetupTokenCode(authorizationCode, codeVerifier, state, pr
|
||||
timeout: 30000
|
||||
})
|
||||
|
||||
// 记录完整的响应数据到专门的认证详细日志
|
||||
logger.authDetail('Setup Token exchange response', response.data)
|
||||
|
||||
// 记录简化版本到主日志
|
||||
logger.info('📊 Setup Token exchange response (analyzing for subscription info):', {
|
||||
status: response.status,
|
||||
hasData: !!response.data,
|
||||
dataKeys: response.data ? Object.keys(response.data) : []
|
||||
})
|
||||
|
||||
logger.success('✅ Setup Token exchange successful', {
|
||||
status: response.status,
|
||||
hasAccessToken: !!response.data?.access_token,
|
||||
scopes: response.data?.scope,
|
||||
// 尝试提取可能的套餐信息字段
|
||||
subscription: response.data?.subscription,
|
||||
plan: response.data?.plan,
|
||||
tier: response.data?.tier,
|
||||
accountType: response.data?.account_type,
|
||||
features: response.data?.features,
|
||||
limits: response.data?.limits
|
||||
})
|
||||
|
||||
const { data } = response
|
||||
|
||||
// 返回Claude格式的token数据
|
||||
return {
|
||||
// 返回Claude格式的token数据,包含可能的套餐信息
|
||||
const result = {
|
||||
accessToken: data.access_token,
|
||||
refreshToken: '',
|
||||
expiresAt: (Math.floor(Date.now() / 1000) + data.expires_in) * 1000,
|
||||
scopes: data.scope ? data.scope.split(' ') : ['user:inference', 'user:profile'],
|
||||
isMax: true
|
||||
}
|
||||
|
||||
// 如果响应中包含套餐信息,添加到返回结果中
|
||||
if (data.subscription || data.plan || data.tier || data.account_type) {
|
||||
result.subscriptionInfo = {
|
||||
subscription: data.subscription,
|
||||
plan: data.plan,
|
||||
tier: data.tier,
|
||||
accountType: data.account_type,
|
||||
features: data.features,
|
||||
limits: data.limits
|
||||
}
|
||||
logger.info('🎯 Found subscription info in Setup Token response:', result.subscriptionInfo)
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
// 使用与标准OAuth相同的错误处理逻辑
|
||||
if (error.response) {
|
||||
|
||||
Reference in New Issue
Block a user