fix (agents): harden transcript tool-call block sanitization

This commit is contained in:
Vignesh Natarajan
2026-02-14 20:09:04 -08:00
parent cbf712b7be
commit aa56045b49
2 changed files with 42 additions and 1 deletions

View File

@@ -223,6 +223,32 @@ describe("sanitizeToolCallInputs", () => {
expect(out.map((m) => m.role)).toEqual(["user"]);
});
it("drops tool calls with missing or blank name/id", () => {
const input: AgentMessage[] = [
{
role: "assistant",
content: [
{ type: "toolCall", id: "call_ok", name: "read", arguments: {} },
{ type: "toolCall", id: "call_empty_name", name: "", arguments: {} },
{ type: "toolUse", id: "call_blank_name", name: " ", input: {} },
{ type: "functionCall", id: "", name: "exec", arguments: {} },
],
},
];
const out = sanitizeToolCallInputs(input);
const assistant = out[0] as Extract<AgentMessage, { role: "assistant" }>;
const toolCalls = Array.isArray(assistant.content)
? assistant.content.filter((block) => {
const type = (block as { type?: unknown }).type;
return typeof type === "string" && ["toolCall", "toolUse", "functionCall"].includes(type);
})
: [];
expect(toolCalls).toHaveLength(1);
expect((toolCalls[0] as { id?: unknown }).id).toBe("call_ok");
});
it("keeps valid tool calls and preserves text blocks", () => {
const input: AgentMessage[] = [
{

View File

@@ -58,6 +58,18 @@ function hasToolCallInput(block: ToolCallBlock): boolean {
return hasInput || hasArguments;
}
function hasNonEmptyStringField(value: unknown): boolean {
return typeof value === "string" && value.trim().length > 0;
}
function hasToolCallId(block: ToolCallBlock): boolean {
return hasNonEmptyStringField(block.id);
}
function hasToolCallName(block: ToolCallBlock): boolean {
return hasNonEmptyStringField(block.name);
}
function extractToolResultId(msg: Extract<AgentMessage, { role: "toolResult" }>): string | null {
const toolCallId = (msg as { toolCallId?: unknown }).toolCallId;
if (typeof toolCallId === "string" && toolCallId) {
@@ -118,7 +130,10 @@ export function repairToolCallInputs(messages: AgentMessage[]): ToolCallInputRep
let droppedInMessage = 0;
for (const block of msg.content) {
if (isToolCallBlock(block) && !hasToolCallInput(block)) {
if (
isToolCallBlock(block) &&
(!hasToolCallInput(block) || !hasToolCallId(block) || !hasToolCallName(block))
) {
droppedToolCalls += 1;
droppedInMessage += 1;
changed = true;