fix: sanitize antigravity thinking signatures

This commit is contained in:
Peter Steinberger
2026-01-22 08:17:40 +00:00
parent b748b86b23
commit ff69a9bd9c
4 changed files with 99 additions and 4 deletions

View File

@@ -86,7 +86,7 @@ describe("sanitizeSessionHistory (google thinking)", () => {
expect(assistant.content?.[0]?.thinking).toBe("reasoning");
});
it("keeps unsigned thinking blocks for Antigravity Claude", async () => {
it("drops unsigned thinking blocks for Antigravity Claude", async () => {
const sessionManager = SessionManager.inMemory();
const input = [
{
@@ -107,11 +107,37 @@ describe("sanitizeSessionHistory (google thinking)", () => {
sessionId: "session:antigravity-claude",
});
const assistant = out.find((msg) => (msg as { role?: string }).role === "assistant");
expect(assistant).toBeUndefined();
});
it("maps base64 signatures to thinkingSignature for Antigravity Claude", async () => {
const sessionManager = SessionManager.inMemory();
const input = [
{
role: "user",
content: "hi",
},
{
role: "assistant",
content: [{ type: "thinking", thinking: "reasoning", signature: "c2ln" }],
},
] satisfies AgentMessage[];
const out = await sanitizeSessionHistory({
messages: input,
modelApi: "google-antigravity",
modelId: "anthropic/claude-3.5-sonnet",
sessionManager,
sessionId: "session:antigravity-claude",
});
const assistant = out.find((msg) => (msg as { role?: string }).role === "assistant") as {
content?: Array<{ type?: string; thinking?: string }>;
content?: Array<{ type?: string; thinking?: string; thinkingSignature?: string }>;
};
expect(assistant.content?.map((block) => block.type)).toEqual(["thinking"]);
expect(assistant.content?.[0]?.thinking).toBe("reasoning");
expect(assistant.content?.[0]?.thinkingSignature).toBe("c2ln");
});
it("preserves order for mixed assistant content", async () => {

View File

@@ -55,6 +55,15 @@ const MISTRAL_MODEL_HINTS = [
"ministral",
"mistralai",
];
const ANTIGRAVITY_SIGNATURE_RE = /^[A-Za-z0-9+/]+={0,2}$/;
function isValidAntigravitySignature(value: unknown): value is string {
if (typeof value !== "string") return false;
const trimmed = value.trim();
if (!trimmed) return false;
if (trimmed.length % 4 !== 0) return false;
return ANTIGRAVITY_SIGNATURE_RE.test(trimmed);
}
function shouldSanitizeToolCallIds(modelApi?: string | null): boolean {
if (!modelApi) return false;
@@ -69,6 +78,61 @@ function isMistralModel(params: { provider?: string | null; modelId?: string | n
return MISTRAL_MODEL_HINTS.some((hint) => modelId.includes(hint));
}
function sanitizeAntigravityThinkingBlocks(messages: AgentMessage[]): AgentMessage[] {
let touched = false;
const out: AgentMessage[] = [];
for (const msg of messages) {
if (!msg || typeof msg !== "object" || msg.role !== "assistant") {
out.push(msg);
continue;
}
const assistant = msg as Extract<AgentMessage, { role: "assistant" }>;
if (!Array.isArray(assistant.content)) {
out.push(msg);
continue;
}
const nextContent = [];
let contentChanged = false;
for (const block of assistant.content) {
if (
!block ||
typeof block !== "object" ||
(block as { type?: unknown }).type !== "thinking"
) {
nextContent.push(block);
continue;
}
const rec = block as {
thinkingSignature?: unknown;
signature?: unknown;
thought_signature?: unknown;
thoughtSignature?: unknown;
};
const candidate =
rec.thinkingSignature ?? rec.signature ?? rec.thought_signature ?? rec.thoughtSignature;
if (!isValidAntigravitySignature(candidate)) {
contentChanged = true;
continue;
}
if (rec.thinkingSignature !== candidate) {
nextContent.push({ ...rec, thinkingSignature: candidate });
contentChanged = true;
} else {
nextContent.push(block);
}
}
if (contentChanged) {
touched = true;
}
if (nextContent.length === 0) {
touched = true;
continue;
}
out.push(contentChanged ? { ...assistant, content: nextContent } : msg);
}
return touched ? out : messages;
}
function findUnsupportedSchemaKeywords(schema: unknown, path: string): string[] {
if (!schema || typeof schema !== "object") return [];
if (Array.isArray(schema)) {
@@ -226,7 +290,11 @@ export async function sanitizeSessionHistory(params: {
? { allowBase64Only: true, includeCamelCase: true }
: undefined,
});
const repairedTools = sanitizeToolUseResultPairing(sanitizedImages);
const sanitizedThinking =
params.modelApi === "google-antigravity" && isAntigravityClaudeModel
? sanitizeAntigravityThinkingBlocks(sanitizedImages)
: sanitizedImages;
const repairedTools = sanitizeToolUseResultPairing(sanitizedThinking);
return applyGoogleTurnOrderingFix({
messages: repairedTools,