mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 10:21:24 +00:00
fix (agents): harden transcript tool-call block sanitization
This commit is contained in:
@@ -223,6 +223,32 @@ describe("sanitizeToolCallInputs", () => {
|
|||||||
expect(out.map((m) => m.role)).toEqual(["user"]);
|
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", () => {
|
it("keeps valid tool calls and preserves text blocks", () => {
|
||||||
const input: AgentMessage[] = [
|
const input: AgentMessage[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -58,6 +58,18 @@ function hasToolCallInput(block: ToolCallBlock): boolean {
|
|||||||
return hasInput || hasArguments;
|
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 {
|
function extractToolResultId(msg: Extract<AgentMessage, { role: "toolResult" }>): string | null {
|
||||||
const toolCallId = (msg as { toolCallId?: unknown }).toolCallId;
|
const toolCallId = (msg as { toolCallId?: unknown }).toolCallId;
|
||||||
if (typeof toolCallId === "string" && toolCallId) {
|
if (typeof toolCallId === "string" && toolCallId) {
|
||||||
@@ -118,7 +130,10 @@ export function repairToolCallInputs(messages: AgentMessage[]): ToolCallInputRep
|
|||||||
let droppedInMessage = 0;
|
let droppedInMessage = 0;
|
||||||
|
|
||||||
for (const block of msg.content) {
|
for (const block of msg.content) {
|
||||||
if (isToolCallBlock(block) && !hasToolCallInput(block)) {
|
if (
|
||||||
|
isToolCallBlock(block) &&
|
||||||
|
(!hasToolCallInput(block) || !hasToolCallId(block) || !hasToolCallName(block))
|
||||||
|
) {
|
||||||
droppedToolCalls += 1;
|
droppedToolCalls += 1;
|
||||||
droppedInMessage += 1;
|
droppedInMessage += 1;
|
||||||
changed = true;
|
changed = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user