Spaces:
Running
Running
| 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(/<!-- thought_signature: (.+?) -->/); | |
| 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 | |
| } | |