import { randomUUID } from 'crypto'; import config from '../config/config.js'; function generateRequestId() { return `agent-${randomUUID()}`; } function generateSessionId() { return String(-Math.floor(Math.random() * 9e18)); } function generateProjectId() { const adjectives = ['useful', 'bright', 'swift', 'calm', 'bold']; const nouns = ['fuze', 'wave', 'spark', 'flow', 'core']; const randomAdj = adjectives[Math.floor(Math.random() * adjectives.length)]; const randomNoun = nouns[Math.floor(Math.random() * nouns.length)]; const randomNum = Math.random().toString(36).substring(2, 7); return `${randomAdj}-${randomNoun}-${randomNum}`; } function extractImagesFromContent(content) { const result = { text: '', images: [] }; // 如果content是字符串,直接返回 if (typeof content === 'string') { result.text = content; return result; } // 如果content是数组(multimodal格式) if (Array.isArray(content)) { for (const item of content) { if (item.type === 'text') { result.text += item.text; } else if (item.type === 'image_url') { // 提取base64图片数据 const imageUrl = item.image_url?.url || ''; // 匹配 data:image/{format};base64,{data} 格式 const match = imageUrl.match(/^data:image\/(\w+);base64,(.+)$/); if (match) { const format = match[1]; // 例如 png, jpeg, jpg const base64Data = match[2]; result.images.push({ inlineData: { mimeType: `image/${format}`, data: base64Data } }) } } } } return result; } function handleUserMessage(extracted, antigravityMessages) { antigravityMessages.push({ role: "user", parts: [ { text: extracted.text }, ...extracted.images ] }) } function handleAssistantMessage(message, antigravityMessages) { const lastMessage = antigravityMessages[antigravityMessages.length - 1]; const hasToolCalls = message.tool_calls && message.tool_calls.length > 0; let contentText = ''; if (typeof message.content === 'string') { contentText = message.content; } else if (Array.isArray(message.content)) { for (const item of message.content) { if (item.type === 'text') { contentText += item.text; } } } const hasContent = contentText && contentText.trim() !== ''; const antigravityTools = hasToolCalls ? message.tool_calls.map(toolCall => ({ functionCall: { id: toolCall.id, name: toolCall.function.name, args: { query: toolCall.function.arguments } } })) : []; if (lastMessage?.role === "model" && hasToolCalls && !hasContent) { lastMessage.parts.push(...antigravityTools) } else { const parts = []; if (hasContent) { let text = contentText; let thoughtSignature = null; const signatureMatch = text.match(//); if (signatureMatch) { thoughtSignature = signatureMatch[1]; text = text.replace(signatureMatch[0], '').trim(); } const part = { text }; if (thoughtSignature) { part.thought_signature = thoughtSignature; } parts.push(part); } parts.push(...antigravityTools); antigravityMessages.push({ role: "model", parts }) } } function handleToolCall(message, antigravityMessages) { // 从之前的 model 消息中找到对应的 functionCall name let functionName = ''; for (let i = antigravityMessages.length - 1; i >= 0; i--) { if (antigravityMessages[i].role === 'model') { const parts = antigravityMessages[i].parts; for (const part of parts) { if (part.functionCall && part.functionCall.id === message.tool_call_id) { functionName = part.functionCall.name; break; } } if (functionName) break; } } const lastMessage = antigravityMessages[antigravityMessages.length - 1]; const functionResponse = { functionResponse: { id: message.tool_call_id, name: functionName, response: { output: message.content } } }; // 如果上一条消息是 user 且包含 functionResponse,则合并 if (lastMessage?.role === "user" && lastMessage.parts.some(p => p.functionResponse)) { lastMessage.parts.push(functionResponse); } else { antigravityMessages.push({ role: "user", parts: [functionResponse] }); } } function openaiMessageToAntigravity(openaiMessages) { const antigravityMessages = []; for (const message of openaiMessages) { if (message.role === "user" || message.role === "system") { const extracted = extractImagesFromContent(message.content); handleUserMessage(extracted, antigravityMessages); } else if (message.role === "assistant") { handleAssistantMessage(message, antigravityMessages); } else if (message.role === "tool") { handleToolCall(message, antigravityMessages); } } return antigravityMessages; } function generateGenerationConfig(parameters, enableThinking, actualModelName) { const generationConfig = { topP: parameters.top_p ?? config.defaults.top_p, topK: parameters.top_k ?? config.defaults.top_k, temperature: parameters.temperature ?? config.defaults.temperature, candidateCount: 1, maxOutputTokens: parameters.max_tokens ?? config.defaults.max_tokens, stopSequences: [ "<|user|>", "<|bot|>", "<|context_request|>", "<|endoftext|>", "<|end_of_turn|>" ] } if (enableThinking) { generationConfig.thinkingConfig = { includeThoughts: true, thinkingBudget: 1024 }; } if (enableThinking && actualModelName.includes("claude")) { delete generationConfig.topP; } return generationConfig } function convertOpenAIToolsToAntigravity(openaiTools) { if (!openaiTools || openaiTools.length === 0) return []; return openaiTools.map((tool) => { delete tool.function.parameters.$schema; return { functionDeclarations: [ { name: tool.function.name, description: tool.function.description, parameters: tool.function.parameters } ] } }) } const idCache = new Map(); const SESSION_ID_DURATION = 60 * 60 * 1000; // 1 hour const PROJECT_ID_DURATION = 12 * 60 * 60 * 1000; // 12 hours function getCachedIds(apiKey) { const now = Date.now(); let cache = idCache.get(apiKey); if (!cache) { cache = { projectId: generateProjectId(), projectExpiry: now + PROJECT_ID_DURATION, sessionId: generateSessionId(), sessionExpiry: now + SESSION_ID_DURATION }; idCache.set(apiKey, cache); return cache; } if (now > cache.projectExpiry) { cache.projectId = generateProjectId(); cache.projectExpiry = now + PROJECT_ID_DURATION; } if (now > cache.sessionExpiry) { cache.sessionId = generateSessionId(); cache.sessionExpiry = now + SESSION_ID_DURATION; } return cache; } function generateRequestBody(openaiMessages, modelName, parameters, openaiTools, apiKey) { const enableThinking = modelName.endsWith('-thinking') || modelName === 'gemini-2.5-pro' || modelName === 'gemini-2.5-pro-image' || modelName.startsWith('gemini-3-pro-') || modelName === "rev19-uic3-1p" || modelName === "gpt-oss-120b-medium" const actualModelName = modelName.endsWith('-thinking') ? modelName.slice(0, -9) : modelName; // Use a default key if none provided (though it should be provided by the server) const cacheKey = apiKey || 'default'; const { projectId, sessionId } = getCachedIds(cacheKey); return { project: projectId, requestId: generateRequestId(), request: { contents: openaiMessageToAntigravity(openaiMessages), systemInstruction: { role: "user", parts: [{ text: config.systemInstruction }] }, tools: convertOpenAIToolsToAntigravity(openaiTools), toolConfig: { functionCallingConfig: { mode: "VALIDATED" } }, generationConfig: generateGenerationConfig(parameters, enableThinking, actualModelName), sessionId: sessionId }, model: actualModelName, userAgent: "antigravity" } } export { generateRequestId, generateSessionId, generateProjectId, generateRequestBody }