fix: finalize safer MCP tool naming (#49505) (thanks @ziomancer)

This commit is contained in:
Peter Steinberger
2026-03-29 23:14:43 +01:00
parent 97bf38099a
commit 004bffa1c3
6 changed files with 34 additions and 8 deletions

View File

@@ -762,7 +762,7 @@ export async function compactEmbeddedPiSessionDirect(
// limitHistoryTurns can orphan tool_result blocks by removing the
// assistant message that contained the matching tool_use.
const limited = transcriptPolicy.repairToolUseResultPairing
? sanitizeToolUseResultPairing(truncated)
? sanitizeToolUseResultPairing(truncated, { dropErroredAssistantResults: true })
: truncated;
if (limited.length > 0) {
session.agent.replaceMessages(limited);

View File

@@ -617,7 +617,7 @@ export async function sanitizeSessionHistory(params: {
allowedToolNames: params.allowedToolNames,
});
const repairedTools = policy.repairToolUseResultPairing
? sanitizeToolUseResultPairing(sanitizedToolCalls)
? sanitizeToolUseResultPairing(sanitizedToolCalls, { dropErroredAssistantResults: true })
: sanitizedToolCalls;
const sanitizedToolResults = stripToolResultDetails(repairedTools);
const sanitizedCompactionUsage = ensureAssistantUsageSnapshots(

View File

@@ -565,9 +565,7 @@ export function wrapStreamFnSanitizeMalformedToolCalls(
if (sanitized.messages === messages) {
return baseFn(model, context, options);
}
let nextMessages = sanitizeToolUseResultPairing(sanitized.messages, {
preserveErroredAssistantResults: true,
});
let nextMessages = sanitizeToolUseResultPairing(sanitized.messages);
if (transcriptPolicy?.validateAnthropicTurns) {
nextMessages = sanitizeAnthropicReplayToolResults(nextMessages);
}

View File

@@ -1109,7 +1109,7 @@ export async function runEmbeddedAttempt(
// limitHistoryTurns can orphan tool_result blocks by removing the
// assistant message that contained the matching tool_use.
const limited = transcriptPolicy.repairToolUseResultPairing
? sanitizeToolUseResultPairing(truncated)
? sanitizeToolUseResultPairing(truncated, { dropErroredAssistantResults: true })
: truncated;
cacheTrace?.recordStage("session:limited", { messages: limited });
if (limited.length > 0) {

View File

@@ -235,6 +235,32 @@ describe("sanitizeToolUseResultPairing", () => {
expect(result.messages[2]?.role).toBe("user");
expect(result.added).toHaveLength(0);
});
it("drops matching tool results for aborted assistant messages when requested", () => {
const input = castAgentMessages([
{
role: "assistant",
content: [{ type: "toolCall", id: "call_aborted", name: "exec", arguments: {} }],
stopReason: "aborted",
},
{
role: "toolResult",
toolCallId: "call_aborted",
toolName: "exec",
content: [{ type: "text", text: "partial result" }],
isError: false,
},
{ role: "user", content: "retrying" },
]);
const result = repairToolUseResultPairing(input, { dropErroredAssistantResults: true });
expect(result.droppedOrphanCount).toBe(0);
expect(result.messages).toHaveLength(2);
expect(result.messages[0]?.role).toBe("assistant");
expect(result.messages[1]?.role).toBe("user");
expect(result.added).toHaveLength(0);
});
});
describe("sanitizeToolCallInputs", () => {

View File

@@ -196,7 +196,7 @@ export type ToolCallInputRepairOptions = {
};
export type ToolUseResultPairingOptions = {
preserveErroredAssistantResults?: boolean;
dropErroredAssistantResults?: boolean;
};
export function stripToolResultDetails(messages: AgentMessage[]): AgentMessage[] {
@@ -463,7 +463,7 @@ export function repairToolUseResultPairing(
const stopReason = (assistant as { stopReason?: string }).stopReason;
if (stopReason === "error" || stopReason === "aborted") {
out.push(msg);
if (options?.preserveErroredAssistantResults) {
if (!options?.dropErroredAssistantResults) {
for (const toolCall of toolCalls) {
const result = spanResultsById.get(toolCall.id);
if (!result) {
@@ -471,6 +471,8 @@ export function repairToolUseResultPairing(
}
pushToolResult(result);
}
} else if (spanResultsById.size > 0) {
changed = true;
}
for (const rem of remainder) {
out.push(rem);