mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
fix: 修复claude console账号Test未响应的的bug
This commit is contained in:
@@ -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 || '测试失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user