mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-21 19:00:44 +00:00
fix(ai): avoid unsigned Gemini 3 tool calls (#741)
This commit is contained in:
parent
2c10cc6da9
commit
b18f401d9e
2 changed files with 90 additions and 10 deletions
|
|
@ -131,18 +131,29 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (block.type === "toolCall") {
|
} else if (block.type === "toolCall") {
|
||||||
const part: Part = {
|
|
||||||
functionCall: {
|
|
||||||
name: block.name,
|
|
||||||
args: block.arguments,
|
|
||||||
...(requiresToolCallId(model.id) ? { id: block.id } : {}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.thoughtSignature);
|
const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.thoughtSignature);
|
||||||
if (thoughtSignature) {
|
// Gemini 3 requires thoughtSignature on all function calls when thinking mode is enabled.
|
||||||
part.thoughtSignature = thoughtSignature;
|
// When replaying history from providers without thought signatures (e.g. Claude via Antigravity),
|
||||||
|
// convert unsigned function calls to text to avoid API validation errors.
|
||||||
|
const isGemini3 = model.id.toLowerCase().includes("gemini-3");
|
||||||
|
if (isGemini3 && !thoughtSignature) {
|
||||||
|
const argsStr = JSON.stringify(block.arguments, null, 2);
|
||||||
|
parts.push({
|
||||||
|
text: `[Tool Call: ${block.name}]\nArguments: ${argsStr}`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const part: Part = {
|
||||||
|
functionCall: {
|
||||||
|
name: block.name,
|
||||||
|
args: block.arguments,
|
||||||
|
...(requiresToolCallId(model.id) ? { id: block.id } : {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (thoughtSignature) {
|
||||||
|
part.thoughtSignature = thoughtSignature;
|
||||||
|
}
|
||||||
|
parts.push(part);
|
||||||
}
|
}
|
||||||
parts.push(part);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { convertMessages } from "../src/providers/google-shared.js";
|
||||||
|
import type { Context, Model } from "../src/types.js";
|
||||||
|
|
||||||
|
describe("google-shared convertMessages", () => {
|
||||||
|
it("converts unsigned tool calls to text for Gemini 3", () => {
|
||||||
|
const model: Model<"google-generative-ai"> = {
|
||||||
|
id: "gemini-3-pro-preview",
|
||||||
|
name: "Gemini 3 Pro Preview",
|
||||||
|
api: "google-generative-ai",
|
||||||
|
provider: "google",
|
||||||
|
baseUrl: "https://generativelanguage.googleapis.com",
|
||||||
|
reasoning: true,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: 128000,
|
||||||
|
maxTokens: 8192,
|
||||||
|
};
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const context: Context = {
|
||||||
|
messages: [
|
||||||
|
{ role: "user", content: "Hi", timestamp: now },
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "toolCall",
|
||||||
|
id: "call_1",
|
||||||
|
name: "bash",
|
||||||
|
arguments: { command: "ls -la" },
|
||||||
|
// No thoughtSignature: simulates Claude via Antigravity.
|
||||||
|
},
|
||||||
|
],
|
||||||
|
api: "google-gemini-cli",
|
||||||
|
provider: "google-antigravity",
|
||||||
|
model: "claude-sonnet-4-20250514",
|
||||||
|
usage: {
|
||||||
|
input: 0,
|
||||||
|
output: 0,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
totalTokens: 0,
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
||||||
|
},
|
||||||
|
stopReason: "stop",
|
||||||
|
timestamp: now,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const contents = convertMessages(model, context);
|
||||||
|
|
||||||
|
let toolTurn: (typeof contents)[number] | undefined;
|
||||||
|
for (let i = contents.length - 1; i >= 0; i -= 1) {
|
||||||
|
if (contents[i]?.role === "model") {
|
||||||
|
toolTurn = contents[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(toolTurn).toBeTruthy();
|
||||||
|
expect(toolTurn?.parts?.some((p) => p.functionCall !== undefined)).toBe(false);
|
||||||
|
|
||||||
|
const text = toolTurn?.parts?.map((p) => p.text ?? "").join("\n");
|
||||||
|
expect(text).toContain("[Tool Call: bash]");
|
||||||
|
expect(text).toContain("ls -la");
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue