Qwen2API-A / src /controllers /cli.chat.js
github-actions[bot]
Sync from GitHub Viciy2023/Qwen2API-A@b372de2fdb435c7fa78fc69c146257a58c842fba
4289eb1
const axios = require('axios')
const { logger } = require('../utils/logger')
const { getProxyAgent, getCliBaseUrl, applyProxyToAxiosConfig } = require('../utils/proxy-helper')
const usageStats = require('../utils/usage-stats')
const MODEL_REDIRECT = {
'qwen3.5-plus': 'coder-model',
}
function preprocessCliRequestBody(rawBody) {
const body = rawBody && typeof rawBody === 'object' ? JSON.parse(JSON.stringify(rawBody)) : {}
if (body.model && MODEL_REDIRECT[body.model]) {
body.model = MODEL_REDIRECT[body.model]
}
const isStream = body.stream === true
if (isStream) {
const hasToolsArray = Array.isArray(body.tools)
if (!hasToolsArray || body.tools.length === 0) {
body.tools = [{
type: 'function',
function: {
name: 'do_not_call_me',
description: 'Do not call this tool.',
parameters: {
type: 'object',
properties: {
operation: { type: 'number', description: 'placeholder' }
},
required: ['operation']
}
}
}]
}
if (!body.stream_options || typeof body.stream_options !== 'object') {
body.stream_options = {}
}
body.stream_options.include_usage = true
}
return body
}
function formatCliJsonResponse(data, fallbackModel) {
if (!data || typeof data !== 'object') {
return data
}
if (!data.object) {
data.object = 'chat.completion'
}
if (!data.model && fallbackModel) {
data.model = fallbackModel
}
if (!Array.isArray(data.choices)) {
data.choices = []
}
return data
}
/**
* 处理CLI聊天完成请求(支持OpenAI格式的流式和JSON响应)
* @param {Object} req - Express请求对象
* @param {Object} res - Express响应对象
*/
const handleCliChatCompletion = async (req, res) => {
try {
const access_token = req.account.cli_info.access_token
const body = preprocessCliRequestBody(req.body)
const isStream = body.stream === true
// 打印当前使用的账号邮箱
logger.info(`CLI请求使用账号[${req.account.email}]开始处理`, 'CLI', '🚀')
// 无论成功失败都增加请求计数
req.account.cli_info.request_number++
const cliBaseUrl = getCliBaseUrl()
const proxyAgent = getProxyAgent()
// 设置请求配置
const axiosConfig = {
method: 'POST',
url: `${cliBaseUrl}/v1/chat/completions`,
headers: {
'Authorization': `Bearer ${access_token}`,
'Content-Type': 'application/json',
'Accept': isStream ? 'text/event-stream' : 'application/json',
'User-Agent': 'QwenCode/0.10.3 (darwin; arm64)',
'X-Dashscope-Useragent': 'QwenCode/0.10.3 (darwin; arm64)',
'X-Stainless-Runtime-Version': 'v22.17.0',
'Sec-Fetch-Mode': 'cors',
'X-Stainless-Lang': 'js',
'X-Stainless-Arch': 'arm64',
'X-Stainless-Package-Version': '5.11.0',
'X-Dashscope-Cachecontrol': 'enable',
'X-Stainless-Retry-Count': '0',
'X-Stainless-Os': 'MacOS',
'X-Dashscope-Authtype': 'qwen-oauth',
'X-Stainless-Runtime': 'node'
},
data: body,
timeout: 5 * 60 * 1000,
validateStatus: function () {
return true
}
}
// 添加代理配置
if (proxyAgent) {
axiosConfig.httpsAgent = proxyAgent
axiosConfig.proxy = false
}
// 如果是流式请求,设置响应类型为流
if (isStream) {
axiosConfig.responseType = 'stream'
// 设置响应头为流式
res.setHeader('Content-Type', 'text/event-stream')
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('Connection', 'keep-alive')
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Headers', '*')
}
const response = await axios(axiosConfig)
// 检查响应状态
if (response.status !== 200) {
logger.error(`CLI请求使用账号[${req.account.email}]转发失败 - 状态码: ${response.status} - 当前请求数: ${req.account.cli_info.request_number}`, 'CLI', '❌')
return res.status(response.status).json({
error: {
message: `api_error`,
type: 'api_error',
code: response.status,
details: response.data
}
})
}
// 处理流式响应
if (isStream) {
// 逐行转发,确保始终输出标准 SSE 片段
response.data.on('data', (chunk) => {
const text = chunk.toString('utf8')
const lines = text.split('\n')
for (const line of lines) {
if (!line || !line.startsWith('data:')) continue
res.write(`${line}\n\n`)
}
})
// 处理流错误
response.data.on('error', (streamError) => {
logger.error(`CLI请求使用账号[${req.account.email}]流式传输失败 - 当前请求数: ${req.account.cli_info.request_number}`, 'CLI', '❌')
if (!res.headersSent) {
res.status(500).json({
error: {
message: 'stream_error',
type: 'stream_error',
code: 500
}
})
}
})
// 处理流结束
response.data.on('end', () => {
logger.success(`CLI请求使用账号[${req.account.email}]转发成功 (流式) - 当前请求数: ${req.account.cli_info.request_number}`, 'CLI')
res.end()
usageStats.track({ model: body.model, success: true, usage: { total_tokens: 0 } })
})
} else {
// 处理JSON响应
res.json(formatCliJsonResponse(response.data, body.model))
logger.success(`CLI请求使用账号[${req.account.email}]转发成功 (JSON) - 当前请求数: ${req.account.cli_info.request_number}`, 'CLI')
await usageStats.track({ model: body.model, success: true, usage: response.data?.usage || { total_tokens: 0 } })
}
} catch (error) {
logger.error(`CLI请求使用账号[${req.account.email}]处理异常 - 当前请求数: ${req.account.cli_info.request_number}`, 'CLI', '💥', error.message)
await usageStats.track({ model: req.body?.model, success: false, usage: { total_tokens: 0 } })
// 如果是axios错误,提供更详细的错误信息
if (error.response) {
return res.status(error.response.status).json({
error: {
message: "api_error",
type: 'api_error',
code: error.response.status,
details: error.response.data
}
})
} else if (error.request) {
return res.status(503).json({
error: {
message: 'connection_error',
type: 'connection_error',
code: 503
}
})
} else {
return res.status(500).json({
error: {
message: 'internal_error',
type: 'internal_error',
code: 500
}
})
}
}
}
module.exports = {
handleCliChatCompletion
}