fix: 增强console账号test端点

This commit is contained in:
shaw
2025-12-01 15:08:40 +08:00
parent d3155b82ea
commit e8e6f972b4
4 changed files with 279 additions and 461 deletions

View File

@@ -0,0 +1,242 @@
const crypto = require('crypto')
/**
* 生成随机十六进制字符串
* @param {number} bytes - 字节数
* @returns {string} 十六进制字符串
*/
function randomHex(bytes = 32) {
return crypto.randomBytes(bytes).toString('hex')
}
/**
* 生成 Claude Code 风格的会话字符串
* @returns {string} 会话字符串,格式: user_{64位hex}_account__session_{uuid}
*/
function generateSessionString() {
const hex64 = randomHex(32) // 32 bytes => 64 hex characters
const uuid = crypto.randomUUID()
return `user_${hex64}_account__session_${uuid}`
}
/**
* 生成 Claude 测试请求体
* @param {string} model - 模型名称
* @param {object} options - 可选配置
* @param {boolean} options.stream - 是否流式默认false
* @returns {object} 测试请求体
*/
function createClaudeTestPayload(model = 'claude-sonnet-4-5-20250929', options = {}) {
const payload = {
model,
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: 'hi',
cache_control: {
type: 'ephemeral'
}
}
]
}
],
system: [
{
type: 'text',
text: "You are Claude Code, Anthropic's official CLI for Claude.",
cache_control: {
type: 'ephemeral'
}
}
],
metadata: {
user_id: generateSessionString()
},
max_tokens: 21333,
temperature: 1
}
if (options.stream) {
payload.stream = true
}
return payload
}
/**
* 发送流式测试请求并处理SSE响应
* @param {object} options - 配置选项
* @param {string} options.apiUrl - API URL
* @param {string} options.authorization - Authorization header值
* @param {object} options.responseStream - Express响应流
* @param {object} [options.payload] - 请求体默认使用createClaudeTestPayload
* @param {object} [options.proxyAgent] - 代理agent
* @param {number} [options.timeout] - 超时时间默认30000
* @param {object} [options.extraHeaders] - 额外的请求头
* @returns {Promise<void>}
*/
async function sendStreamTestRequest(options) {
const axios = require('axios')
const logger = require('./logger')
const {
apiUrl,
authorization,
responseStream,
payload = createClaudeTestPayload('claude-sonnet-4-5-20250929', { stream: true }),
proxyAgent = null,
timeout = 30000,
extraHeaders = {}
} = options
const sendSSE = (type, data = {}) => {
if (!responseStream.destroyed && !responseStream.writableEnded) {
try {
responseStream.write(`data: ${JSON.stringify({ type, ...data })}\n\n`)
} catch {
// ignore
}
}
}
const endTest = (success, error = null) => {
if (!responseStream.destroyed && !responseStream.writableEnded) {
try {
responseStream.write(
`data: ${JSON.stringify({ type: 'test_complete', success, error: error || undefined })}\n\n`
)
responseStream.end()
} catch {
// ignore
}
}
}
// 设置响应头
if (!responseStream.headersSent) {
responseStream.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
'X-Accel-Buffering': 'no'
})
}
sendSSE('test_start', { message: 'Test started' })
const requestConfig = {
method: 'POST',
url: apiUrl,
data: payload,
headers: {
'Content-Type': 'application/json',
'anthropic-version': '2023-06-01',
'User-Agent': 'claude-cli/2.0.52 (external, cli)',
authorization,
...extraHeaders
},
timeout,
responseType: 'stream',
validateStatus: () => true
}
if (proxyAgent) {
requestConfig.httpAgent = proxyAgent
requestConfig.httpsAgent = proxyAgent
requestConfig.proxy = false
}
try {
const response = await axios(requestConfig)
logger.debug(`🌊 Test response status: ${response.status}`)
// 处理非200响应
if (response.status !== 200) {
return new Promise((resolve) => {
const chunks = []
response.data.on('data', (chunk) => chunks.push(chunk))
response.data.on('end', () => {
const errorData = Buffer.concat(chunks).toString()
let errorMsg = `API Error: ${response.status}`
try {
const json = JSON.parse(errorData)
errorMsg = json.message || json.error?.message || json.error || errorMsg
} catch {
if (errorData.length < 200) {
errorMsg = errorData || errorMsg
}
}
endTest(false, errorMsg)
resolve()
})
response.data.on('error', (err) => {
endTest(false, err.message)
resolve()
})
})
}
// 处理成功的流式响应
return new Promise((resolve) => {
let buffer = ''
response.data.on('data', (chunk) => {
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(5).trim()
if (!jsonStr || jsonStr === '[DONE]') {
continue
}
try {
const data = JSON.parse(jsonStr)
if (data.type === 'content_block_delta' && data.delta?.text) {
sendSSE('content', { text: data.delta.text })
}
if (data.type === 'message_stop') {
sendSSE('message_stop')
}
if (data.type === 'error' || data.error) {
const errMsg = data.error?.message || data.message || data.error || 'Unknown error'
sendSSE('error', { error: errMsg })
}
} catch {
// ignore parse errors
}
}
})
response.data.on('end', () => {
if (!responseStream.destroyed && !responseStream.writableEnded) {
endTest(true)
}
resolve()
})
response.data.on('error', (err) => {
endTest(false, err.message)
resolve()
})
})
} catch (error) {
logger.error('❌ Stream test request failed:', error.message)
endTest(false, error.message)
}
}
module.exports = {
randomHex,
generateSessionString,
createClaudeTestPayload,
sendStreamTestRequest
}