fix: 修复claude console账号Test未响应的的bug

This commit is contained in:
shaw
2025-11-29 14:13:28 +08:00
parent 326adaaeca
commit 6ec4f4bf5b

View File

@@ -12,7 +12,7 @@ const {
class ClaudeConsoleRelayService {
constructor() {
this.defaultUserAgent = 'claude-cli/1.0.69 (external, cli)'
this.defaultUserAgent = 'claude-cli/2.0.52 (external, cli)'
}
// 🚀 转发请求到Claude Console API
@@ -1113,20 +1113,53 @@ class ClaudeConsoleRelayService {
}
}
// 🧪 测试账号连接供Admin API使用直接复用 _makeClaudeConsoleStreamRequest
// 🧪 测试账号连接供Admin API使用独立处理以确保错误时也返回SSE格式
async testAccountConnection(accountId, responseStream) {
const testRequestBody = {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 100,
max_tokens: 32000,
stream: true,
messages: [
{
role: 'user',
content: 'hi'
}
],
system: [
{
type: 'text',
text: "You are Claude Code, Anthropic's official CLI for Claude."
}
]
}
// 辅助函数:发送 SSE 事件
const sendSSEEvent = (type, data) => {
if (!responseStream.destroyed && !responseStream.writableEnded) {
try {
responseStream.write(`data: ${JSON.stringify({ type, ...data })}\n\n`)
} catch {
// 忽略写入错误
}
}
}
// 辅助函数:结束测试并关闭流
const endTest = (success, error = null) => {
if (!responseStream.destroyed && !responseStream.writableEnded) {
try {
if (success) {
sendSSEEvent('test_complete', { success: true })
} else {
sendSSEEvent('test_complete', { success: false, error: error || '测试失败' })
}
responseStream.end()
} catch {
// 忽略写入错误
}
}
}
try {
// 获取账户信息
const account = await claudeConsoleAccountService.getAccount(accountId)
@@ -1149,35 +1182,165 @@ class ClaudeConsoleRelayService {
})
}
// 创建流转换器,将 Claude API 格式转换为前端测试页面期望的格式
const streamTransformer = this._createTestStreamTransformer()
// 发送测试开始事件
sendSSEEvent('test_start', {})
// 直接复用现有的流式请求方法
await this._makeClaudeConsoleStreamRequest(
testRequestBody,
account,
proxyAgent,
{}, // clientHeaders - 测试不需要客户端headers
responseStream,
accountId,
null, // usageCallback - 测试不需要统计
streamTransformer, // 使用转换器将 Claude API 格式转为前端期望格式
{} // requestOptions
)
// 构建完整的API URL
const cleanUrl = account.apiUrl.replace(/\/$/, '')
const apiEndpoint = cleanUrl.endsWith('/v1/messages') ? cleanUrl : `${cleanUrl}/v1/messages`
logger.info(`✅ Test request completed for account: ${account.name}`)
// 决定使用的 User-Agent
const userAgent = account.userAgent || this.defaultUserAgent
// 准备请求配置
const requestConfig = {
method: 'POST',
url: apiEndpoint,
data: testRequestBody,
headers: {
'Content-Type': 'application/json',
'anthropic-version': '2023-06-01',
'User-Agent': userAgent
},
timeout: 30000, // 测试请求使用较短超时
responseType: 'stream',
validateStatus: () => true
}
if (proxyAgent) {
requestConfig.httpAgent = proxyAgent
requestConfig.httpsAgent = proxyAgent
requestConfig.proxy = false
}
// 设置认证方式
if (account.apiKey && account.apiKey.startsWith('sk-ant-')) {
requestConfig.headers['x-api-key'] = account.apiKey
} else {
requestConfig.headers['Authorization'] = `Bearer ${account.apiKey}`
}
// 发送请求
const response = await axios(requestConfig)
logger.debug(`🌊 Claude Console test response status: ${response.status}`)
// 处理非200响应
if (response.status !== 200) {
logger.error(
`❌ Claude Console API returned error status: ${response.status} | Account: ${account?.name || accountId}`
)
// 收集错误响应数据
return new Promise((resolve) => {
const errorChunks = []
response.data.on('data', (chunk) => {
errorChunks.push(chunk)
})
response.data.on('end', () => {
try {
const fullErrorData = Buffer.concat(errorChunks).toString()
logger.error(
`📝 [Test] Upstream error response from ${account?.name || accountId}: ${fullErrorData.substring(0, 500)}`
)
// 尝试解析错误信息
let errorMessage = `API Error: ${response.status}`
try {
const errorJson = JSON.parse(fullErrorData)
// 直接提取所有可能的错误信息字段
errorMessage =
errorJson.message ||
errorJson.error?.message ||
errorJson.statusMessage ||
errorJson.error ||
(typeof errorJson === 'string' ? errorJson : JSON.stringify(errorJson))
} catch {
errorMessage = fullErrorData.substring(0, 200) || `API Error: ${response.status}`
}
endTest(false, errorMessage)
resolve()
} catch {
endTest(false, `API Error: ${response.status}`)
resolve()
}
})
response.data.on('error', (err) => {
endTest(false, err.message || '流读取错误')
resolve()
})
})
}
// 处理成功的流式响应
return new Promise((resolve) => {
let buffer = ''
response.data.on('data', (chunk) => {
try {
buffer += chunk.toString()
const lines = buffer.split('\n')
buffer = lines.pop() || ''
for (const line of lines) {
if (!line.startsWith('data: ')) {
continue
}
const jsonStr = line.substring(6).trim()
if (!jsonStr || jsonStr === '[DONE]') {
continue
}
try {
const data = JSON.parse(jsonStr)
// 转换 content_block_delta 为 content
if (data.type === 'content_block_delta' && data.delta && data.delta.text) {
sendSSEEvent('content', { text: data.delta.text })
}
// 处理消息完成
if (data.type === 'message_stop') {
endTest(true)
}
// 处理错误事件
if (data.type === 'error') {
const errorMsg = data.error?.message || data.message || '未知错误'
endTest(false, errorMsg)
}
} catch {
// 忽略解析错误
}
}
} catch {
// 忽略处理错误
}
})
response.data.on('end', () => {
logger.info(`✅ Test request completed for account: ${account.name}`)
// 如果还没结束,发送完成事件
if (!responseStream.destroyed && !responseStream.writableEnded) {
endTest(true)
}
resolve()
})
response.data.on('error', (err) => {
logger.error(`❌ Test stream error:`, err)
endTest(false, err.message || '流处理错误')
resolve()
})
})
} catch (error) {
logger.error(`❌ Test account connection failed:`, error)
// 发送错误事件给前端
if (!responseStream.destroyed && !responseStream.writableEnded) {
try {
const errorMsg = error.message || '测试失败'
responseStream.write(`data: ${JSON.stringify({ type: 'error', error: errorMsg })}\n\n`)
} catch {
// 忽略写入错误
}
}
throw error
endTest(false, error.message || '测试失败')
}
}