Spaces:
Running
Running
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 | |
| } | |