fix: add missing arguments/input field to tool calls
When models call tools without parameters, they sometimes omit the
arguments/input field entirely. Anthropic/Cloud Code Assist APIs require
this field to be present (even if empty {}), causing the error:
messages.X.content.Y.tool_use.input: Field required
This corrupts the session history and prevents further messages.
Fix: Add sanitizeToolCallArguments() in images.ts that ensures all
toolCall, toolUse, and functionCall blocks have the required field.
Closes #issue-number
This commit is contained in:
parent
9688454a30
commit
bc96c27942
@ -117,4 +117,48 @@ describe("sanitizeSessionMessagesImages", () => {
|
|||||||
expect(out[0]?.role).toBe("user");
|
expect(out[0]?.role).toBe("user");
|
||||||
expect(out[1]?.role).toBe("toolResult");
|
expect(out[1]?.role).toBe("toolResult");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("adds missing arguments field to tool calls", async () => {
|
||||||
|
const input = [
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: [
|
||||||
|
{ type: "toolCall", id: "call_1", name: "session_status" }, // Missing arguments!
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] satisfies AgentMessage[];
|
||||||
|
|
||||||
|
const out = await sanitizeSessionMessagesImages(input, "test");
|
||||||
|
|
||||||
|
expect(out).toHaveLength(1);
|
||||||
|
const content = (out[0] as { content?: unknown }).content as Array<{
|
||||||
|
type?: string;
|
||||||
|
arguments?: unknown;
|
||||||
|
}>;
|
||||||
|
expect(content).toHaveLength(1);
|
||||||
|
expect(content[0]?.type).toBe("toolCall");
|
||||||
|
expect(content[0]?.arguments).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds missing input field to toolUse blocks", async () => {
|
||||||
|
const input = [
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: [
|
||||||
|
{ type: "toolUse", id: "call_1", name: "session_status" }, // Missing input!
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] satisfies AgentMessage[];
|
||||||
|
|
||||||
|
const out = await sanitizeSessionMessagesImages(input, "test");
|
||||||
|
|
||||||
|
expect(out).toHaveLength(1);
|
||||||
|
const content = (out[0] as { content?: unknown }).content as Array<{
|
||||||
|
type?: string;
|
||||||
|
input?: unknown;
|
||||||
|
}>;
|
||||||
|
expect(content).toHaveLength(1);
|
||||||
|
expect(content[0]?.type).toBe("toolUse");
|
||||||
|
expect(content[0]?.input).toEqual({});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -21,6 +21,25 @@ export function isEmptyAssistantMessageContent(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures tool call blocks have the required arguments/input field.
|
||||||
|
* Some models omit this field when calling tools with no parameters,
|
||||||
|
* but Anthropic/Cloud Code Assist APIs require it (even if empty `{}`).
|
||||||
|
*/
|
||||||
|
function sanitizeToolCallArguments(content: unknown[]): unknown[] {
|
||||||
|
return content.map((block) => {
|
||||||
|
if (!block || typeof block !== "object") return block;
|
||||||
|
const rec = block as { type?: unknown; arguments?: unknown; input?: unknown };
|
||||||
|
if (rec.type === "toolCall" && rec.arguments === undefined) {
|
||||||
|
return { ...rec, arguments: {} };
|
||||||
|
}
|
||||||
|
if ((rec.type === "toolUse" || rec.type === "functionCall") && rec.input === undefined) {
|
||||||
|
return { ...rec, input: {} };
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function sanitizeSessionMessagesImages(
|
export async function sanitizeSessionMessagesImages(
|
||||||
messages: AgentMessage[],
|
messages: AgentMessage[],
|
||||||
label: string,
|
label: string,
|
||||||
@ -97,17 +116,19 @@ export async function sanitizeSessionMessagesImages(
|
|||||||
}
|
}
|
||||||
const content = assistantMsg.content;
|
const content = assistantMsg.content;
|
||||||
if (Array.isArray(content)) {
|
if (Array.isArray(content)) {
|
||||||
|
// Ensure all tool calls have required arguments/input field
|
||||||
|
const sanitizedContent = sanitizeToolCallArguments(content);
|
||||||
if (!allowNonImageSanitization) {
|
if (!allowNonImageSanitization) {
|
||||||
const nextContent = (await sanitizeContentBlocksImages(
|
const nextContent = (await sanitizeContentBlocksImages(
|
||||||
content as unknown as ContentBlock[],
|
sanitizedContent as unknown as ContentBlock[],
|
||||||
label,
|
label,
|
||||||
)) as unknown as typeof assistantMsg.content;
|
)) as unknown as typeof assistantMsg.content;
|
||||||
out.push({ ...assistantMsg, content: nextContent });
|
out.push({ ...assistantMsg, content: nextContent });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const strippedContent = options?.preserveSignatures
|
const strippedContent = options?.preserveSignatures
|
||||||
? content // Keep signatures for Antigravity Claude
|
? sanitizedContent // Keep signatures for Antigravity Claude
|
||||||
: stripThoughtSignatures(content, options?.sanitizeThoughtSignatures); // Strip for Gemini
|
: stripThoughtSignatures(sanitizedContent, options?.sanitizeThoughtSignatures); // Strip for Gemini
|
||||||
|
|
||||||
const filteredContent = strippedContent.filter((block) => {
|
const filteredContent = strippedContent.filter((block) => {
|
||||||
if (!block || typeof block !== "object") return true;
|
if (!block || typeof block !== "object") return true;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user