diff --git a/open-sse/executors/antigravity.js b/open-sse/executors/antigravity.js index f2ad630a..fb7beee0 100644 --- a/open-sse/executors/antigravity.js +++ b/open-sse/executors/antigravity.js @@ -5,7 +5,7 @@ import { OAUTH_ENDPOINTS, ANTIGRAVITY_HEADERS, AG_DEFAULT_TOOLS, AG_TOOL_SUFFIX, import { HTTP_STATUS } from "../config/runtimeConfig.js"; import { resolveSessionId, toNumericSessionId } from "../utils/sessionManager.js"; import { proxyAwareFetch } from "../utils/proxyFetch.js"; -import { cleanJSONSchemaForAntigravity } from "../translator/formats/gemini.js"; +import { cleanJSONSchemaForAntigravity, normalizeGeminiContents } from "../translator/formats/gemini.js"; import { DEFAULT_THINKING_AG_SIGNATURE } from "../config/defaultThinkingSignature.js"; import { getGeminiThoughtSignatureSync } from "../services/thoughtSignatureStore.js"; @@ -193,7 +193,7 @@ export class AntigravityExecutor extends BaseExecutor { // ─── Standard (non-image) request ─── // Fix contents for Claude models via Antigravity - const contents = body.request?.contents?.map(c => { + const rawContents = (body.request?.contents || []).map(c => { let role = c.role; // functionResponse must be role "user" for Claude models if (c.parts?.some(p => p.functionResponse)) { @@ -226,15 +226,13 @@ export class AntigravityExecutor extends BaseExecutor { return p; }); - const partsChanged = parts?.length !== c.parts?.length || modifiedParts?.some((p, idx) => p !== c.parts[idx]); - if (role !== c.role || partsChanged) { - return { - ...c, role, - parts: modifiedParts || parts, - }; - } - return c; + return { + ...c, + role, + parts: modifiedParts || parts || [], + }; }); + const contents = normalizeGeminiContents(rawContents); // Sanitize tool schemas and function names before sending to Antigravity. let tools = body.request?.tools; diff --git a/open-sse/translator/formats/gemini.js b/open-sse/translator/formats/gemini.js index 8a965fee..729e4c10 100644 --- a/open-sse/translator/formats/gemini.js +++ b/open-sse/translator/formats/gemini.js @@ -432,3 +432,21 @@ export function cleanJSONSchemaForAntigravity(schema) { return cleaned; } +// Merge adjacent same-role messages, strip empty parts, ensure initial user turn +export function normalizeGeminiContents(contents) { + const out = []; + for (const c of contents || []) { + if (!c?.role || !Array.isArray(c.parts)) continue; + const parts = c.parts.filter(p => p && Object.keys(p).length > 0); + if (parts.length === 0) continue; + const last = out.at(-1); + if (last?.role === c.role) last.parts.push(...parts); + else out.push({ ...c, parts: [...parts] }); + } + if (out.length > 0 && out[0].role !== "user") { + out.unshift({ role: "user", parts: [{ text: "..." }] }); + } + return out; +} + + diff --git a/open-sse/translator/request/openai-to-gemini.js b/open-sse/translator/request/openai-to-gemini.js index 2b05670d..24e7f262 100644 --- a/open-sse/translator/request/openai-to-gemini.js +++ b/open-sse/translator/request/openai-to-gemini.js @@ -15,7 +15,8 @@ import { generateRequestId, generateSessionId, generateProjectId, - cleanJSONSchemaForAntigravity + cleanJSONSchemaForAntigravity, + normalizeGeminiContents } from "../formats/gemini.js"; import { deriveSessionId, toNumericSessionId } from "../../utils/sessionManager.js"; import { ROLE, GEMINI_ROLE, OPENAI_BLOCK, CLAUDE_BLOCK } from "../schema/index.js"; @@ -35,17 +36,6 @@ function sanitizeGeminiFunctionName(name) { return sanitized.substring(0, 64); } -function normalizeGeminiContents(contents) { - const out = []; - for (const c of contents || []) { - if (!c?.role || !Array.isArray(c.parts) || c.parts.length === 0) continue; - const last = out.at(-1); - if (last?.role === c.role) last.parts.push(...c.parts); - else out.push({ ...c, parts: [...c.parts] }); - } - return out; -} - // Core: Convert OpenAI request to Gemini format (base for all variants) function openaiToGeminiBase(model, body, stream, signature = DEFAULT_THINKING_AG_SIGNATURE, sessionId = null) { const result = { @@ -163,12 +153,14 @@ function openaiToGeminiBase(model, body, stream, signature = DEFAULT_THINKING_AG } // Check if there are actual tool responses in the next messages - const hasActualResponses = toolCallIds.some(fid => toolResponses[fid]); + const isIntermediate = i < body.messages.length - 1; + const hasActualResponses = toolCallIds.some(fid => toolResponses[fid] !== undefined); - if (hasActualResponses) { + if (hasActualResponses || isIntermediate) { const toolParts = []; for (const fid of toolCallIds) { - if (!toolResponses[fid]) continue; + let resp = toolResponses[fid]; + if (resp === undefined) resp = ""; let name = tcID2Name[fid]; if (!name) { @@ -180,7 +172,6 @@ function openaiToGeminiBase(model, body, stream, signature = DEFAULT_THINKING_AG } } - let resp = toolResponses[fid]; let parsedResp = tryParseJSON(resp); if (parsedResp === null) { parsedResp = { result: resp };