| import axios from 'axios'; |
| import tokenManager from '../auth/token_manager.js'; |
| import config from '../config/config.js'; |
| import { generateToolCallId } from '../utils/idGenerator.js'; |
| import AntigravityRequester from '../AntigravityRequester.js'; |
|
|
| |
| let requester = null; |
| let useAxios = false; |
|
|
| if (config.useNativeAxios === true) { |
| useAxios = true; |
| } else { |
| try { |
| requester = new AntigravityRequester(); |
| } catch (error) { |
| console.warn('AntigravityRequester 初始化失败,降级使用 axios:', error.message); |
| useAxios = true; |
| } |
| } |
|
|
| |
|
|
| function buildHeaders(token) { |
| return { |
| 'Host': config.api.host, |
| 'User-Agent': config.api.userAgent, |
| 'Authorization': `Bearer ${token.access_token}`, |
| 'Content-Type': 'application/json', |
| 'Accept-Encoding': 'gzip' |
| }; |
| } |
|
|
| function buildAxiosConfig(url, headers, body = null) { |
| const axiosConfig = { |
| method: 'POST', |
| url, |
| headers, |
| timeout: config.timeout, |
| proxy: config.proxy ? (() => { |
| const proxyUrl = new URL(config.proxy); |
| return { protocol: proxyUrl.protocol.replace(':', ''), host: proxyUrl.hostname, port: parseInt(proxyUrl.port) }; |
| })() : false |
| }; |
| if (body !== null) axiosConfig.data = body; |
| return axiosConfig; |
| } |
|
|
| function buildRequesterConfig(headers, body = null) { |
| const reqConfig = { |
| method: 'POST', |
| headers, |
| timeout_ms: config.timeout, |
| proxy: config.proxy |
| }; |
| if (body !== null) reqConfig.body = JSON.stringify(body); |
| return reqConfig; |
| } |
|
|
| |
| async function handleApiError(error, token) { |
| const status = error.response?.status || error.status || 'Unknown'; |
| let errorBody = error.message; |
| |
| if (error.response?.data?.readable) { |
| const chunks = []; |
| for await (const chunk of error.response.data) { |
| chunks.push(chunk); |
| } |
| errorBody = Buffer.concat(chunks).toString(); |
| } else if (typeof error.response?.data === 'object') { |
| errorBody = JSON.stringify(error.response.data, null, 2); |
| } else if (error.response?.data) { |
| errorBody = error.response.data; |
| } |
| |
| if (status === 403) { |
| tokenManager.disableCurrentToken(token); |
| throw new Error(`该账号没有使用权限,已自动禁用。错误详情: ${errorBody}`); |
| } |
| |
| throw new Error(`API请求失败 (${status}): ${errorBody}`); |
| } |
|
|
| |
| function convertToToolCall(functionCall) { |
| return { |
| id: functionCall.id || generateToolCallId(), |
| type: 'function', |
| function: { |
| name: functionCall.name, |
| arguments: JSON.stringify(functionCall.args) |
| } |
| }; |
| } |
|
|
| |
| function parseAndEmitStreamChunk(line, state, callback) { |
| if (!line.startsWith('data: ')) return; |
| |
| try { |
| const data = JSON.parse(line.slice(6)); |
| const parts = data.response?.candidates?.[0]?.content?.parts; |
| |
| if (parts) { |
| for (const part of parts) { |
| if (part.thought === true) { |
| |
| if (!state.thinkingStarted) { |
| callback({ type: 'thinking', content: '<think>\n' }); |
| state.thinkingStarted = true; |
| } |
| callback({ type: 'thinking', content: part.text || '' }); |
| } else if (part.text !== undefined) { |
| |
| if (state.thinkingStarted) { |
| callback({ type: 'thinking', content: '\n</think>\n' }); |
| state.thinkingStarted = false; |
| } |
| callback({ type: 'text', content: part.text }); |
| } else if (part.functionCall) { |
| |
| state.toolCalls.push(convertToToolCall(part.functionCall)); |
| } |
| } |
| } |
| |
| |
| if (data.response?.candidates?.[0]?.finishReason && state.toolCalls.length > 0) { |
| if (state.thinkingStarted) { |
| callback({ type: 'thinking', content: '\n</think>\n' }); |
| state.thinkingStarted = false; |
| } |
| callback({ type: 'tool_calls', tool_calls: state.toolCalls }); |
| state.toolCalls = []; |
| } |
| } catch (e) { |
| |
| } |
| } |
|
|
| |
|
|
| export async function generateAssistantResponse(requestBody, callback) { |
| const token = await tokenManager.getToken(); |
| if (!token) throw new Error('没有可用的token,请运行 npm run login 获取token'); |
| |
| const headers = buildHeaders(token); |
| const state = { thinkingStarted: false, toolCalls: [] }; |
| let buffer = ''; |
| |
| const processChunk = (chunk) => { |
| buffer += chunk; |
| const lines = buffer.split('\n'); |
| buffer = lines.pop(); |
| lines.forEach(line => parseAndEmitStreamChunk(line, state, callback)); |
| }; |
| |
| if (useAxios) { |
| try { |
| const axiosConfig = { ...buildAxiosConfig(config.api.url, headers, requestBody), responseType: 'stream' }; |
| const response = await axios(axiosConfig); |
| |
| response.data.on('data', chunk => processChunk(chunk.toString())); |
| await new Promise((resolve, reject) => { |
| response.data.on('end', resolve); |
| response.data.on('error', reject); |
| }); |
| } catch (error) { |
| await handleApiError(error, token); |
| } |
| } else { |
| try { |
| const streamResponse = requester.antigravity_fetchStream(config.api.url, buildRequesterConfig(headers, requestBody)); |
| let errorBody = ''; |
| let statusCode = null; |
|
|
| await new Promise((resolve, reject) => { |
| streamResponse |
| .onStart(({ status }) => { statusCode = status; }) |
| .onData((chunk) => statusCode !== 200 ? errorBody += chunk : processChunk(chunk)) |
| .onEnd(() => statusCode !== 200 ? reject({ status: statusCode, message: errorBody }) : resolve()) |
| .onError(reject); |
| }); |
| } catch (error) { |
| await handleApiError(error, token); |
| } |
| } |
| } |
|
|
| export async function getAvailableModels() { |
| const token = await tokenManager.getToken(); |
| if (!token) throw new Error('没有可用的token,请运行 npm run login 获取token'); |
| |
| const headers = buildHeaders(token); |
| |
| try { |
| let data; |
| if (useAxios) { |
| data = (await axios(buildAxiosConfig(config.api.modelsUrl, headers, {}))).data; |
| } else { |
| const response = await requester.antigravity_fetch(config.api.modelsUrl, buildRequesterConfig(headers, {})); |
| if (response.status !== 200) { |
| const errorBody = await response.text(); |
| throw { status: response.status, message: errorBody }; |
| } |
| data = await response.json(); |
| } |
| |
| return { |
| object: 'list', |
| data: Object.keys(data.models).map(id => ({ |
| id, |
| object: 'model', |
| created: Math.floor(Date.now() / 1000), |
| owned_by: 'google' |
| })) |
| }; |
| } catch (error) { |
| await handleApiError(error, token); |
| } |
| } |
|
|
| export async function generateAssistantResponseNoStream(requestBody) { |
| const token = await tokenManager.getToken(); |
| if (!token) throw new Error('没有可用的token,请运行 npm run login 获取token'); |
| |
| const headers = buildHeaders(token); |
| let data; |
| |
| try { |
| if (useAxios) { |
| data = (await axios(buildAxiosConfig(config.api.noStreamUrl, headers, requestBody))).data; |
| } else { |
| const response = await requester.antigravity_fetch(config.api.noStreamUrl, buildRequesterConfig(headers, requestBody)); |
| if (response.status !== 200) { |
| const errorBody = await response.text(); |
| throw { status: response.status, message: errorBody }; |
| } |
| data = await response.json(); |
| } |
| } catch (error) { |
| await handleApiError(error, token); |
| } |
| |
| |
| const parts = data.response?.candidates?.[0]?.content?.parts || []; |
| let content = ''; |
| let thinkingContent = ''; |
| const toolCalls = []; |
| const imageContents = []; |
| |
| for (const part of parts) { |
| if (part.thought === true) { |
| thinkingContent += part.text || ''; |
| } else if (part.text !== undefined) { |
| content += part.text; |
| } else if (part.functionCall) { |
| toolCalls.push(convertToToolCall(part.functionCall)); |
| } else if (part.inlineData) { |
| imageContents.push({ |
| type: 'image_url', |
| image_url: { url: `data:${part.inlineData.mimeType};base64,${part.inlineData.data}` } |
| }); |
| } |
| } |
| |
| |
| if (thinkingContent) { |
| content = `<think>\n${thinkingContent}\n</think>\n${content}`; |
| } |
| |
| |
| if (imageContents.length > 0) { |
| let markdown = content ? content + '\n\n' : ''; |
| markdown += imageContents.map(img => ``).join('\n\n'); |
| return { content: markdown, toolCalls }; |
| } |
| |
| return { content, toolCalls }; |
| } |
|
|
| export function closeRequester() { |
| if (requester) requester.close(); |
| } |
|
|