mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 06:12:10 +03:00
fix(claude): drop orphan tool_result after fixToolAdjacency strip (discussion #2410)
Discussion #2410 reports Claude returning 400 for sequences like:
assistant: tool_use(id=X)
user: <plain text> ← breaks adjacency
user: tool_result(id=X)
The previous round added `fixToolAdjacency` (commit 44d9abac9) which
correctly strips the orphan tool_use from the assistant message. But
that left the now-unmatched tool_result intact, so the upstream
rejected the request with:
messages.N.content.M: unexpected `tool_use_id` found in `tool_result`
blocks: X. Each tool_result block must have a corresponding tool_use
block in the previous message.
Fix: after running `fixToolAdjacency`, re-run `fixToolPairs` to drop
the orphaned tool_result blocks. All three call sites updated:
- contextManager.purifyHistory (both inside the binary-search loop
and the final pass)
- BaseExecutor message-prep (Claude path)
- claudeCodeCompatible request signer
Also tightens an unrelated dynamic-key access in
readNestedString (claudeCodeCompatible) to satisfy the prototype-
pollution scanner triggered by the post-tool semgrep hook.
This commit is contained in:
@@ -894,7 +894,10 @@ export class BaseExecutor {
|
||||
// Only apply for Claude/Claude-compatible — OpenAI allows results
|
||||
// spread across multiple subsequent messages.
|
||||
const isClaude = this.provider === "claude" || isClaudeCodeCompatible(this.provider);
|
||||
const adjacent = isClaude ? fixToolAdjacency(fixed) : fixed;
|
||||
// For Claude, fixToolAdjacency may strip tool_use blocks whose
|
||||
// tool_result isn't in the next message; re-run fixToolPairs to
|
||||
// drop any tool_result orphaned by that strip (discussion #2410).
|
||||
const adjacent = isClaude ? fixToolPairs(fixToolAdjacency(fixed)) : fixed;
|
||||
tb.messages = stripTrailingAssistantOrphanToolUse(adjacent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,7 +378,11 @@ export async function buildAndSignClaudeCodeRequest(
|
||||
if (Array.isArray(b.messages)) {
|
||||
const fixed = fixToolPairs(b.messages as Record<string, unknown>[]);
|
||||
const adjacent = fixToolAdjacency(fixed);
|
||||
b.messages = stripTrailingAssistantOrphanToolUse(adjacent);
|
||||
// fixToolAdjacency can leave orphan tool_result blocks behind when it
|
||||
// strips a tool_use whose tool_result wasn't in the next message.
|
||||
// Re-pair to drop those orphans (discussion #2410).
|
||||
const cleaned = fixToolPairs(adjacent);
|
||||
b.messages = stripTrailingAssistantOrphanToolUse(cleaned);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1158,7 +1162,9 @@ function readNestedString(
|
||||
if (!current || typeof current !== "object" || Array.isArray(current)) {
|
||||
return null;
|
||||
}
|
||||
current = (current as Record<string, unknown>)[key];
|
||||
if (key === "__proto__" || key === "constructor" || key === "prototype") return null;
|
||||
if (!Object.prototype.hasOwnProperty.call(current, key)) return null;
|
||||
current = Reflect.get(current as object, key);
|
||||
}
|
||||
return toNonEmptyString(current);
|
||||
}
|
||||
|
||||
@@ -267,6 +267,9 @@ function purifyHistory(messages: Record<string, unknown>[], targetTokens: number
|
||||
let candidate = [...system, ...nonSystem.slice(-keep)];
|
||||
candidate = fixToolPairs(candidate);
|
||||
candidate = fixToolAdjacency(candidate);
|
||||
// Re-run pair fix: fixToolAdjacency may have stripped tool_use blocks, leaving
|
||||
// orphan tool_results that Claude rejects ("tool_result without preceding tool_use").
|
||||
candidate = fixToolPairs(candidate);
|
||||
candidate = stripTrailingAssistantOrphanToolUse(candidate);
|
||||
const tokens = estimateTokens(JSON.stringify(candidate));
|
||||
if (tokens <= targetTokens) break;
|
||||
@@ -276,6 +279,9 @@ function purifyHistory(messages: Record<string, unknown>[], targetTokens: number
|
||||
let result = [...system, ...nonSystem.slice(-keep)];
|
||||
result = fixToolPairs(result);
|
||||
result = fixToolAdjacency(result);
|
||||
// Re-run pair fix to drop any tool_result whose matching tool_use was removed by
|
||||
// fixToolAdjacency (discussion #2410 — orphan tool_result -> upstream 400).
|
||||
result = fixToolPairs(result);
|
||||
result = stripTrailingAssistantOrphanToolUse(result);
|
||||
|
||||
// Add summary of dropped messages
|
||||
|
||||
Reference in New Issue
Block a user