1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! Fix the message sequence before sending it to the provider.
//!
//! ## Why this is needed
//!
//! The turn main loop appends the assistant message (which may contain `tool_use` blocks)
//! and the subsequent `tool_result` messages in two separate steps (see `turn.rs`: first
//! append the assistant message, then append tool_results after the tool finishes). Any
//! interruption between these two steps — a permanent LLM call failure, `session/cancel`,
//! a SIGKILL, a power outage — will leave **orphan tool_use** blocks in the persisted
//! history: a `tool_use` without a corresponding `tool_result`.
//!
//! Anthropic / Bedrock enforces that "every tool_use must be immediately followed by its
//! tool_result". Once an orphan reaches the request, it is permanently rejected
//! (`tool_use ids were found without tool_result blocks`). Every subsequent request for
//! that session will fail — the session is dead. Such interruptions are **unavoidable**
//! (the process can be killed at any time), so fault tolerance must be on the **read side
//! (before sending the request)**, not just on the write side.
//!
//! ## Placement: at request construction, not in snapshot
//!
//! The fix is applied **only at the point where the request to the provider is
//! constructed** (`build_request` in `turn.rs`, and summary sub-requests in
//! `compact.rs`). It is **not** placed in `History::snapshot()`:
//!
//! - The semantics of `snapshot` are "faithfully read the current real state of the
//! history" — injecting synthetic results there would make "what is read" ≠ "what is
//! stored", breaking the read-only contract.
//! - More critically: micro/full compaction goes through `snapshot() → process →
//! replace()`, which **writes** the result back into the history. If `snapshot` already
//! injected synthetic results, those blocks — which should only be visible to the
//! provider — would be persisted by `replace`, leaking temporary modifications into the
//! source of truth (a second source of truth).
//!
//! Therefore: `snapshot` / `replace` / storage records / UI replay all read the **real**
//! sequence (orphans exist as-is, the UI shows them as unclosed tool_calls — a faithful
//! representation of the real state). Only the copy sent to the provider is patched to be
//! wire-legal.
use crate;
/// Synthetic tool_result text inserted for an interrupted tool_use.
const INTERRUPTED_RESULT_TEXT: &str = "tool call interrupted; no result was recorded";
/// For each `ToolUse` that lacks a following `ToolResult`, inject a synthetic error
/// `ToolResult` so that the sequence satisfies the provider constraint that every
/// `ToolUse` is immediately followed by its corresponding `ToolResult`.
///
/// Pairing rule: the result for each `ToolUse{id}` in an assistant message should appear
/// in the **immediately following message** (in the Anthropic format, the `ToolResult` is
/// in the next user message). This function collects the set of `tool_use_id`s that are
/// already satisfied by some message, and for any unsatisfied ones, inserts a synthetic
/// `ToolResult` message with `Role::User` **after** the assistant message.
///
/// If there are no orphans, the input is returned unchanged (a single O(n) scan with zero
/// allocation changes).
pub