fix(agent): sanitize invalid tool call ids in context
This commit is contained in:
parent
9d719c66af
commit
e2d4803f8b
3 changed files with 51 additions and 1 deletions
|
|
@ -38,6 +38,10 @@ import {
|
|||
type SystemPromptMode,
|
||||
} from "./system-prompt/index.js";
|
||||
import type { AuthProfileFailureReason } from "./auth-profiles/index.js";
|
||||
import {
|
||||
sanitizeToolCallInputs,
|
||||
sanitizeToolUseResultPairing,
|
||||
} from "./session/session-transcript-repair.js";
|
||||
|
||||
// ============================================================
|
||||
// Error classification for auth profile rotation
|
||||
|
|
@ -181,6 +185,10 @@ export class Agent {
|
|||
}
|
||||
return this.currentApiKey;
|
||||
},
|
||||
transformContext: async (messages) => {
|
||||
const sanitizedInputs = sanitizeToolCallInputs(messages);
|
||||
return sanitizeToolUseResultPairing(sanitizedInputs);
|
||||
},
|
||||
});
|
||||
|
||||
// Load Agent Profile (if profileId is specified)
|
||||
|
|
|
|||
|
|
@ -112,6 +112,28 @@ describe("sanitizeToolUseResultPairing", () => {
|
|||
expect(out.some((m) => m.role === "toolResult")).toBe(false);
|
||||
expect(out.map((m) => m.role)).toEqual(["user", "assistant"]);
|
||||
});
|
||||
|
||||
it("drops tool results with empty tool call id", () => {
|
||||
const input = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "toolCall", id: "call_1", name: "read", arguments: {} }],
|
||||
},
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "",
|
||||
toolName: "read",
|
||||
content: [{ type: "text", text: "invalid id" }],
|
||||
isError: true,
|
||||
},
|
||||
{ role: "user", content: "next" },
|
||||
] as AgentMessage[];
|
||||
|
||||
const out = sanitizeToolUseResultPairing(input);
|
||||
const toolResults = out.filter((m) => m.role === "toolResult") as Array<{ toolCallId?: string }>;
|
||||
expect(toolResults).toHaveLength(1);
|
||||
expect(toolResults[0]?.toolCallId).toBe("call_1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("sanitizeToolCallInputs", () => {
|
||||
|
|
@ -147,4 +169,20 @@ describe("sanitizeToolCallInputs", () => {
|
|||
: [];
|
||||
expect(types).toEqual(["text", "toolUse"]);
|
||||
});
|
||||
|
||||
it("drops tool calls with empty id even when input exists", () => {
|
||||
const input = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "toolCall", id: "", name: "read", arguments: { path: "a" } },
|
||||
{ type: "toolUse", id: " ", name: "exec", input: { cmd: "pwd" } },
|
||||
],
|
||||
},
|
||||
{ role: "user", content: "hello" },
|
||||
] as AgentMessage[];
|
||||
|
||||
const out = sanitizeToolCallInputs(input);
|
||||
expect(out.map((m) => m.role)).toEqual(["user"]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@ function hasToolCallInput(block: ToolCallBlock): boolean {
|
|||
return hasInput || hasArguments;
|
||||
}
|
||||
|
||||
function hasValidToolCallId(block: ToolCallBlock): boolean {
|
||||
return typeof block.id === "string" && block.id.trim().length > 0;
|
||||
}
|
||||
|
||||
function extractToolResultId(msg: Extract<AgentMessage, { role: "toolResult" }>): string | null {
|
||||
const toolCallId = (msg as { toolCallId?: unknown }).toolCallId;
|
||||
if (typeof toolCallId === "string" && toolCallId) {
|
||||
|
|
@ -118,7 +122,7 @@ 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) || !hasValidToolCallId(block))) {
|
||||
droppedToolCalls += 1;
|
||||
droppedInMessage += 1;
|
||||
changed = true;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue