link0518
fix: 修复message.content.trim调用错误
3ed6209
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
}