mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-24 09:41:17 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ac31a5706 | ||
|
|
a3a922ac09 | ||
|
|
0073d40299 | ||
|
|
d812af9159 | ||
|
|
16e2bcfedb |
@@ -377,19 +377,13 @@ async function handleMessagesRequest(req, res) {
|
|||||||
accountId &&
|
accountId &&
|
||||||
accountType === 'claude-official'
|
accountType === 'claude-official'
|
||||||
) {
|
) {
|
||||||
// 🚫 检测旧会话(污染的会话)
|
// 🆕 允许新 session ID 创建绑定(支持 Claude Code /clear 等场景)
|
||||||
if (isOldSession(req.body)) {
|
// 信任客户端的 session ID 作为新会话的标识,不再检查请求内容
|
||||||
const cfg = await claudeRelayConfigService.getConfig()
|
logger.info(
|
||||||
logger.warn(
|
`🔗 Creating new session binding: sessionId=${originalSessionIdForBinding}, ` +
|
||||||
`🚫 Old session rejected: sessionId=${originalSessionIdForBinding}, messages.length=${req.body?.messages?.length}, tools.length=${req.body?.tools?.length || 0}, isOldSession=true`
|
`messages.length=${req.body?.messages?.length}, tools.length=${req.body?.tools?.length || 0}, ` +
|
||||||
)
|
`accountId=${accountId}, accountType=${accountType}`
|
||||||
return res.status(400).json({
|
)
|
||||||
error: {
|
|
||||||
type: 'session_binding_error',
|
|
||||||
message: cfg.sessionBindingErrorMessage || '你的本地session已污染,请清理后使用。'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建绑定
|
// 创建绑定
|
||||||
try {
|
try {
|
||||||
@@ -944,19 +938,13 @@ async function handleMessagesRequest(req, res) {
|
|||||||
accountId &&
|
accountId &&
|
||||||
accountType === 'claude-official'
|
accountType === 'claude-official'
|
||||||
) {
|
) {
|
||||||
// 🚫 检测旧会话(污染的会话)
|
// 🆕 允许新 session ID 创建绑定(支持 Claude Code /clear 等场景)
|
||||||
if (isOldSession(req.body)) {
|
// 信任客户端的 session ID 作为新会话的标识,不再检查请求内容
|
||||||
const cfg = await claudeRelayConfigService.getConfig()
|
logger.info(
|
||||||
logger.warn(
|
`🔗 Creating new session binding (non-stream): sessionId=${originalSessionIdForBindingNonStream}, ` +
|
||||||
`🚫 Old session rejected (non-stream): sessionId=${originalSessionIdForBindingNonStream}, messages.length=${req.body?.messages?.length}, tools.length=${req.body?.tools?.length || 0}, isOldSession=true`
|
`messages.length=${req.body?.messages?.length}, tools.length=${req.body?.tools?.length || 0}, ` +
|
||||||
)
|
`accountId=${accountId}, accountType=${accountType}`
|
||||||
return res.status(400).json({
|
)
|
||||||
error: {
|
|
||||||
type: 'session_binding_error',
|
|
||||||
message: cfg.sessionBindingErrorMessage || '你的本地session已污染,请清理后使用。'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建绑定
|
// 创建绑定
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const DEFAULT_CONFIG = {
|
|||||||
claudeCodeOnlyEnabled: false,
|
claudeCodeOnlyEnabled: false,
|
||||||
globalSessionBindingEnabled: false,
|
globalSessionBindingEnabled: false,
|
||||||
sessionBindingErrorMessage: '你的本地session已污染,请清理后使用。',
|
sessionBindingErrorMessage: '你的本地session已污染,请清理后使用。',
|
||||||
sessionBindingTtlDays: 30, // 会话绑定 TTL(天),默认30天
|
sessionBindingTtlDays: 1, // 会话绑定 TTL(天),默认1天(支持 /clear 场景,避免 Redis 累积)
|
||||||
// 用户消息队列配置
|
// 用户消息队列配置
|
||||||
userMessageQueueEnabled: false, // 是否启用用户消息队列(默认关闭)
|
userMessageQueueEnabled: false, // 是否启用用户消息队列(默认关闭)
|
||||||
userMessageQueueDelayMs: 200, // 请求间隔(毫秒)
|
userMessageQueueDelayMs: 200, // 请求间隔(毫秒)
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ class RateLimitCleanupService {
|
|||||||
openai: { checked: 0, cleared: 0, errors: [] },
|
openai: { checked: 0, cleared: 0, errors: [] },
|
||||||
claude: { checked: 0, cleared: 0, errors: [] },
|
claude: { checked: 0, cleared: 0, errors: [] },
|
||||||
claudeConsole: { checked: 0, cleared: 0, errors: [] },
|
claudeConsole: { checked: 0, cleared: 0, errors: [] },
|
||||||
|
quotaExceeded: { checked: 0, cleared: 0, errors: [] },
|
||||||
tokenRefresh: { checked: 0, refreshed: 0, errors: [] }
|
tokenRefresh: { checked: 0, refreshed: 0, errors: [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,13 +86,22 @@ class RateLimitCleanupService {
|
|||||||
// 清理 Claude Console 账号
|
// 清理 Claude Console 账号
|
||||||
await this.cleanupClaudeConsoleAccounts(results.claudeConsole)
|
await this.cleanupClaudeConsoleAccounts(results.claudeConsole)
|
||||||
|
|
||||||
|
// 清理 Claude Console 配额超限状态
|
||||||
|
await this.cleanupClaudeConsoleQuotaExceeded(results.quotaExceeded)
|
||||||
|
|
||||||
// 主动刷新等待重置的 Claude 账户 Token(防止 5小时/7天 等待期间 Token 过期)
|
// 主动刷新等待重置的 Claude 账户 Token(防止 5小时/7天 等待期间 Token 过期)
|
||||||
await this.proactiveRefreshClaudeTokens(results.tokenRefresh)
|
await this.proactiveRefreshClaudeTokens(results.tokenRefresh)
|
||||||
|
|
||||||
const totalChecked =
|
const totalChecked =
|
||||||
results.openai.checked + results.claude.checked + results.claudeConsole.checked
|
results.openai.checked +
|
||||||
|
results.claude.checked +
|
||||||
|
results.claudeConsole.checked +
|
||||||
|
results.quotaExceeded.checked
|
||||||
const totalCleared =
|
const totalCleared =
|
||||||
results.openai.cleared + results.claude.cleared + results.claudeConsole.cleared
|
results.openai.cleared +
|
||||||
|
results.claude.cleared +
|
||||||
|
results.claudeConsole.cleared +
|
||||||
|
results.quotaExceeded.cleared
|
||||||
const duration = Date.now() - startTime
|
const duration = Date.now() - startTime
|
||||||
|
|
||||||
if (totalCleared > 0 || results.tokenRefresh.refreshed > 0) {
|
if (totalCleared > 0 || results.tokenRefresh.refreshed > 0) {
|
||||||
@@ -103,6 +113,9 @@ class RateLimitCleanupService {
|
|||||||
logger.info(
|
logger.info(
|
||||||
` Claude Console: ${results.claudeConsole.cleared}/${results.claudeConsole.checked}`
|
` Claude Console: ${results.claudeConsole.cleared}/${results.claudeConsole.checked}`
|
||||||
)
|
)
|
||||||
|
logger.info(
|
||||||
|
` Quota Exceeded: ${results.quotaExceeded.cleared}/${results.quotaExceeded.checked}`
|
||||||
|
)
|
||||||
if (results.tokenRefresh.checked > 0 || results.tokenRefresh.refreshed > 0) {
|
if (results.tokenRefresh.checked > 0 || results.tokenRefresh.refreshed > 0) {
|
||||||
logger.info(
|
logger.info(
|
||||||
` Token Refresh: ${results.tokenRefresh.refreshed}/${results.tokenRefresh.checked} refreshed`
|
` Token Refresh: ${results.tokenRefresh.refreshed}/${results.tokenRefresh.checked} refreshed`
|
||||||
@@ -124,6 +137,7 @@ class RateLimitCleanupService {
|
|||||||
...results.openai.errors,
|
...results.openai.errors,
|
||||||
...results.claude.errors,
|
...results.claude.errors,
|
||||||
...results.claudeConsole.errors,
|
...results.claudeConsole.errors,
|
||||||
|
...results.quotaExceeded.errors,
|
||||||
...results.tokenRefresh.errors
|
...results.tokenRefresh.errors
|
||||||
]
|
]
|
||||||
if (allErrors.length > 0) {
|
if (allErrors.length > 0) {
|
||||||
@@ -358,6 +372,54 @@ class RateLimitCleanupService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查并恢复 Claude Console 账号的配额超限状态
|
||||||
|
*/
|
||||||
|
async cleanupClaudeConsoleQuotaExceeded(result) {
|
||||||
|
try {
|
||||||
|
const accounts = await claudeConsoleAccountService.getAllAccounts()
|
||||||
|
|
||||||
|
for (const account of accounts) {
|
||||||
|
// 检查是否处于配额超限状态
|
||||||
|
if (account.status === 'quota_exceeded' || account.quotaStoppedAt) {
|
||||||
|
result.checked++
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 使用 isAccountQuotaExceeded 方法,它会自动触发恢复
|
||||||
|
const isStillExceeded = await claudeConsoleAccountService.isAccountQuotaExceeded(
|
||||||
|
account.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!isStillExceeded) {
|
||||||
|
result.cleared++
|
||||||
|
logger.info(
|
||||||
|
`🧹 Auto-recovered quota exceeded for Claude Console account: ${account.name} (${account.id})`
|
||||||
|
)
|
||||||
|
|
||||||
|
// 记录已恢复的账户信息
|
||||||
|
this.clearedAccounts.push({
|
||||||
|
platform: 'Claude Console',
|
||||||
|
accountId: account.id,
|
||||||
|
accountName: account.name,
|
||||||
|
previousStatus: 'quota_exceeded',
|
||||||
|
currentStatus: 'active'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
result.errors.push({
|
||||||
|
accountId: account.id,
|
||||||
|
accountName: account.name,
|
||||||
|
error: error.message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to cleanup Claude Console quota exceeded accounts:', error)
|
||||||
|
result.errors.push({ error: error.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主动刷新 Claude 账户 Token(防止等待重置期间 Token 过期)
|
* 主动刷新 Claude 账户 Token(防止等待重置期间 Token 过期)
|
||||||
* 仅对因限流/配额限制而等待重置的账户执行刷新:
|
* 仅对因限流/配额限制而等待重置的账户执行刷新:
|
||||||
|
|||||||
@@ -673,6 +673,23 @@ class UnifiedClaudeScheduler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 主动检查配额超限状态并尝试恢复(在过滤之前执行,确保可以恢复配额超限的账户)
|
||||||
|
if (currentAccount.status === 'quota_exceeded') {
|
||||||
|
// 触发配额检查,如果已到重置时间会自动恢复账户
|
||||||
|
const isStillExceeded = await claudeConsoleAccountService.isAccountQuotaExceeded(
|
||||||
|
currentAccount.id
|
||||||
|
)
|
||||||
|
if (!isStillExceeded) {
|
||||||
|
// 重新获取账户最新状态
|
||||||
|
const refreshedAccount = await claudeConsoleAccountService.getAccount(currentAccount.id)
|
||||||
|
if (refreshedAccount) {
|
||||||
|
// 更新当前循环中的账户数据
|
||||||
|
currentAccount = refreshedAccount
|
||||||
|
logger.info(`✅ Account ${currentAccount.name} recovered from quota_exceeded status`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`🔍 Checking Claude Console account: ${currentAccount.name} - isActive: ${currentAccount.isActive}, status: ${currentAccount.status}, accountType: ${currentAccount.accountType}, schedulable: ${currentAccount.schedulable}`
|
`🔍 Checking Claude Console account: ${currentAccount.name} - isActive: ${currentAccount.isActive}, status: ${currentAccount.status}, accountType: ${currentAccount.accountType}, schedulable: ${currentAccount.schedulable}`
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4119,6 +4119,10 @@ const getSchedulableReason = (account) => {
|
|||||||
if (account.status === 'unauthorized') {
|
if (account.status === 'unauthorized') {
|
||||||
return 'API Key无效或已过期(401错误)'
|
return 'API Key无效或已过期(401错误)'
|
||||||
}
|
}
|
||||||
|
// 检查配额超限状态
|
||||||
|
if (account.status === 'quota_exceeded') {
|
||||||
|
return '余额不足'
|
||||||
|
}
|
||||||
if (account.overloadStatus === 'overloaded') {
|
if (account.overloadStatus === 'overloaded') {
|
||||||
return '服务过载(529错误)'
|
return '服务过载(529错误)'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1904,7 +1904,7 @@ const claudeConfig = ref({
|
|||||||
claudeCodeOnlyEnabled: false,
|
claudeCodeOnlyEnabled: false,
|
||||||
globalSessionBindingEnabled: false,
|
globalSessionBindingEnabled: false,
|
||||||
sessionBindingErrorMessage: '你的本地session已污染,请清理后使用。',
|
sessionBindingErrorMessage: '你的本地session已污染,请清理后使用。',
|
||||||
sessionBindingTtlDays: 30,
|
sessionBindingTtlDays: 1,
|
||||||
userMessageQueueEnabled: false, // 与后端默认值保持一致
|
userMessageQueueEnabled: false, // 与后端默认值保持一致
|
||||||
userMessageQueueDelayMs: 200,
|
userMessageQueueDelayMs: 200,
|
||||||
userMessageQueueTimeoutMs: 5000, // 与后端默认值保持一致(优化后锁持有时间短无需长等待)
|
userMessageQueueTimeoutMs: 5000, // 与后端默认值保持一致(优化后锁持有时间短无需长等待)
|
||||||
@@ -2203,7 +2203,7 @@ const loadClaudeConfig = async () => {
|
|||||||
globalSessionBindingEnabled: response.config?.globalSessionBindingEnabled ?? false,
|
globalSessionBindingEnabled: response.config?.globalSessionBindingEnabled ?? false,
|
||||||
sessionBindingErrorMessage:
|
sessionBindingErrorMessage:
|
||||||
response.config?.sessionBindingErrorMessage || '你的本地session已污染,请清理后使用。',
|
response.config?.sessionBindingErrorMessage || '你的本地session已污染,请清理后使用。',
|
||||||
sessionBindingTtlDays: response.config?.sessionBindingTtlDays ?? 30,
|
sessionBindingTtlDays: response.config?.sessionBindingTtlDays ?? 1,
|
||||||
userMessageQueueEnabled: response.config?.userMessageQueueEnabled ?? false, // 与后端默认值保持一致
|
userMessageQueueEnabled: response.config?.userMessageQueueEnabled ?? false, // 与后端默认值保持一致
|
||||||
userMessageQueueDelayMs: response.config?.userMessageQueueDelayMs ?? 200,
|
userMessageQueueDelayMs: response.config?.userMessageQueueDelayMs ?? 200,
|
||||||
userMessageQueueTimeoutMs: response.config?.userMessageQueueTimeoutMs ?? 5000, // 与后端默认值保持一致
|
userMessageQueueTimeoutMs: response.config?.userMessageQueueTimeoutMs ?? 5000, // 与后端默认值保持一致
|
||||||
|
|||||||
Reference in New Issue
Block a user