fix: sanitize antigravity thinking signatures
This commit is contained in:
@@ -22,6 +22,7 @@ Docs: https://docs.clawd.bot
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Media: accept MEDIA paths with spaces/tilde and prefer the message tool hint for image replies.
|
- Media: accept MEDIA paths with spaces/tilde and prefer the message tool hint for image replies.
|
||||||
|
- Google Antigravity: drop unsigned thinking blocks for Claude models to avoid signature errors.
|
||||||
- Config: avoid stack traces for invalid configs and log the config path.
|
- Config: avoid stack traces for invalid configs and log the config path.
|
||||||
- CLI: read Codex CLI account_id for workspace billing. (#1422) Thanks @aj47.
|
- CLI: read Codex CLI account_id for workspace billing. (#1422) Thanks @aj47.
|
||||||
- Doctor: avoid recreating WhatsApp config when only legacy routing keys remain. (#900)
|
- Doctor: avoid recreating WhatsApp config when only legacy routing keys remain. (#900)
|
||||||
|
|||||||
@@ -1276,7 +1276,7 @@ Fix: either provide Google auth, or remove/avoid Google models in `agents.defaul
|
|||||||
Cause: the session history contains **thinking blocks without signatures** (often from
|
Cause: the session history contains **thinking blocks without signatures** (often from
|
||||||
an aborted/partial stream). Google Antigravity requires signatures for thinking blocks.
|
an aborted/partial stream). Google Antigravity requires signatures for thinking blocks.
|
||||||
|
|
||||||
Fix: start a **new session** or set `/thinking off` for that agent.
|
Fix: Clawdbot now strips unsigned thinking blocks for Google Antigravity Claude. If it still appears, start a **new session** or set `/thinking off` for that agent.
|
||||||
|
|
||||||
## Auth profiles: what they are and how to manage them
|
## Auth profiles: what they are and how to manage them
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ describe("sanitizeSessionHistory (google thinking)", () => {
|
|||||||
expect(assistant.content?.[0]?.thinking).toBe("reasoning");
|
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 sessionManager = SessionManager.inMemory();
|
||||||
const input = [
|
const input = [
|
||||||
{
|
{
|
||||||
@@ -107,11 +107,37 @@ describe("sanitizeSessionHistory (google thinking)", () => {
|
|||||||
sessionId: "session:antigravity-claude",
|
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 {
|
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?.map((block) => block.type)).toEqual(["thinking"]);
|
||||||
expect(assistant.content?.[0]?.thinking).toBe("reasoning");
|
expect(assistant.content?.[0]?.thinking).toBe("reasoning");
|
||||||
|
expect(assistant.content?.[0]?.thinkingSignature).toBe("c2ln");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("preserves order for mixed assistant content", async () => {
|
it("preserves order for mixed assistant content", async () => {
|
||||||
|
|||||||
@@ -55,6 +55,15 @@ const MISTRAL_MODEL_HINTS = [
|
|||||||
"ministral",
|
"ministral",
|
||||||
"mistralai",
|
"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 {
|
function shouldSanitizeToolCallIds(modelApi?: string | null): boolean {
|
||||||
if (!modelApi) return false;
|
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));
|
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[] {
|
function findUnsupportedSchemaKeywords(schema: unknown, path: string): string[] {
|
||||||
if (!schema || typeof schema !== "object") return [];
|
if (!schema || typeof schema !== "object") return [];
|
||||||
if (Array.isArray(schema)) {
|
if (Array.isArray(schema)) {
|
||||||
@@ -226,7 +290,11 @@ export async function sanitizeSessionHistory(params: {
|
|||||||
? { allowBase64Only: true, includeCamelCase: true }
|
? { allowBase64Only: true, includeCamelCase: true }
|
||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
const repairedTools = sanitizeToolUseResultPairing(sanitizedImages);
|
const sanitizedThinking =
|
||||||
|
params.modelApi === "google-antigravity" && isAntigravityClaudeModel
|
||||||
|
? sanitizeAntigravityThinkingBlocks(sanitizedImages)
|
||||||
|
: sanitizedImages;
|
||||||
|
const repairedTools = sanitizeToolUseResultPairing(sanitizedThinking);
|
||||||
|
|
||||||
return applyGoogleTurnOrderingFix({
|
return applyGoogleTurnOrderingFix({
|
||||||
messages: repairedTools,
|
messages: repairedTools,
|
||||||
|
|||||||
Reference in New Issue
Block a user