fix(gemini): backfill thoughtSignature and suppress stream done sentinel
Backfill DEFAULT_THINKING_AG_SIGNATURE onto functionCall parts missing it (client history replay) and on Claude tool_use blocks, fixing 400 INVALID_ARGUMENT from Gemini-family APIs. Suppress the OpenAI-style data: [DONE] sentinel for antigravity/gemini/vertex to avoid parser crashes. Fixes #2193. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { HTTP_STATUS } from "../config/runtimeConfig.js";
|
||||
import { resolveSessionId } from "../utils/sessionManager.js";
|
||||
import { proxyAwareFetch } from "../utils/proxyFetch.js";
|
||||
import { cleanJSONSchemaForAntigravity } from "../translator/formats/gemini.js";
|
||||
import { DEFAULT_THINKING_AG_SIGNATURE } from "../config/defaultThinkingSignature.js";
|
||||
|
||||
// Sanitize function name: Gemini requires [a-zA-Z_][a-zA-Z0-9_.:\-]{0,63}
|
||||
function sanitizeFunctionName(name) {
|
||||
@@ -177,8 +178,19 @@ export class AntigravityExecutor extends BaseExecutor {
|
||||
if (p.thoughtSignature && !p.functionCall && !p.text) return false;
|
||||
return true;
|
||||
});
|
||||
if (role !== c.role || parts?.length !== c.parts?.length) {
|
||||
return { ...c, role, parts };
|
||||
// Gemini 3+ rejects functionCall parts without thoughtSignature. Clients (Claude Code, IDE)
|
||||
// don't persist thoughtSignature in their history, so backfill the default signature on any
|
||||
// functionCall part that arrives without one.
|
||||
const needsBackfill = parts?.some(p => p.functionCall && !p.thoughtSignature) ?? false;
|
||||
if (role !== c.role || parts?.length !== c.parts?.length || needsBackfill) {
|
||||
return {
|
||||
...c, role,
|
||||
parts: needsBackfill
|
||||
? parts.map(p => (p.functionCall && !p.thoughtSignature)
|
||||
? { ...p, thoughtSignature: DEFAULT_THINKING_AG_SIGNATURE }
|
||||
: p)
|
||||
: parts,
|
||||
};
|
||||
}
|
||||
return c;
|
||||
});
|
||||
|
||||
@@ -299,7 +299,7 @@ function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigra
|
||||
}
|
||||
|
||||
// Wrap Claude format in Cloud Code envelope for Antigravity
|
||||
function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = null) {
|
||||
function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = null, signature = DEFAULT_THINKING_AG_SIGNATURE) {
|
||||
const projectId = credentials?.projectId || generateProjectId();
|
||||
|
||||
const envelope = {
|
||||
@@ -343,6 +343,7 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu
|
||||
parts.push({ text: block.text });
|
||||
} else if (block.type === CLAUDE_BLOCK.TOOL_USE) {
|
||||
parts.push({
|
||||
thoughtSignature: signature,
|
||||
functionCall: {
|
||||
id: block.id,
|
||||
name: sanitizeGeminiFunctionName(block.name),
|
||||
|
||||
@@ -350,7 +350,9 @@ export function createSSEStream(options = {}) {
|
||||
// Some clients (e.g. OpenClaw) expect the OpenAI-style sentinel:
|
||||
// data: [DONE]\n\n
|
||||
// Without it they can hang until timeout and trigger failover.
|
||||
if (!streamDoneSent) {
|
||||
// Gemini-family clients (Antigravity, Vertex, Gemini) reject this sentinel with 400 syntax errors.
|
||||
const isGeminiFamily = provider === "antigravity" || provider === "gemini" || provider === "vertex";
|
||||
if (!streamDoneSent && !isGeminiFamily) {
|
||||
const doneOutput = "data: [DONE]\n\n";
|
||||
reqLogger?.appendConvertedChunk?.(doneOutput);
|
||||
controller.enqueue(sharedEncoder.encode(doneOutput));
|
||||
|
||||
Reference in New Issue
Block a user