fix(gemini): normalize contents and handle intermediate tool responses in Antigravity
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user