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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//! Serving recorded answers, so an evaluation case runs identically twice.
//!
//! [`Replay`] is a [`Provider`] with no provider behind it. It loads a file
//! written by [`Record`](crate::provider::Record) and answers from it, and it has
//! no endpoint, no client and no key — [`Provider::endpoint`] is `None`, so a run
//! driven by a replay opens no socket and needs no egress grant to do so.
//!
//! ## How a request finds its answer
//!
//! By its *content* — `system`, `user` and `tools`, which is the whole of a
//! [`CompletionRequest`] — and never by a call counter.
//!
//! Every scripted mock in `tests/` keys on an `AtomicUsize` index, and every one
//! of them is resume-unsafe for the same reason: 0.7.0's resume re-runs the step
//! that was in flight when the process died, that step calls `complete` again, and
//! a counter-keyed script hands it the response meant for the *next* step. The run
//! then continues from a script one place ahead of itself, silently. The stateless
//! prompt-driven fixture in `tests/checkpoint.rs` exists because of exactly this.
//!
//! Content-keying makes the re-ask return what the first ask returned, because it
//! is the same question.
//!
//! ## What that guarantees, precisely
//!
//! - The same request asked any number of times gets the same response, unless a
//! *different* request is asked in between and the recording holds more than one
//! answer for it (see below). A re-run step is therefore reproducible.
//! - A request the recording never saw is [`Error::Provider`] of kind
//! [`Request`](crate::ProviderErrorKind::Request) — loud and non-retryable —
//! rather than a default response. `CompletionResponse::default()` reads exactly
//! like "the model chose not to call a tool", so a silent default would make a
//! diverged replay look like a successful one.
//!
//! ## What it does not guarantee
//!
//! - **Anything about a diverged prompt.** The key is the exact request text, so a
//! replay must be driven against the same contract and the same workspace state
//! as the recording. A goal reworded, a fixture file edited, or a step that reads
//! a file the previous run's replay left different produces a request that was
//! never recorded — reported as a missing recording, not silently absorbed.
//! - **Duplicate requests across a process restart.** A recording that answered
//! one identical request differently twice is served in recorded order, tracked
//! in memory. A resume in a *new* process starts that tracking over, so a re-run
//! step whose request duplicates an earlier step's gets the earlier answer. Only
//! identical requests are affected: a run's prompt carries its observations, so
//! consecutive steps normally differ.
//! - **Ordering beyond what was recorded.** Once a key's recorded answers are
//! exhausted, the last one is served again for every further ask. The stated
//! guarantee is same-request-same-answer, and a request recorded once must still
//! be answerable twice; erroring instead would break the common recording where
//! each request appears exactly once.
use HashMap;
use Path;
use Mutex;
use crate;
use crateRecording;
use crate;
/// The lookup key for a request: its content, canonically.
///
/// JSON of the whole request rather than a hash, so a mismatch can be read by a
/// human debugging a divergence, and rather than a hand-rolled concatenation, so
/// no separator can be forged by a prompt containing it. Serialising a
/// `CompletionRequest` cannot fail: every field is a `String`, a `Vec`, or a
/// `serde_json::Value`, none of which has a failing `Serialize`.
/// Which answer each key is currently on, and which key was asked last.
/// Answer from a recording instead of from a provider.
///
/// ```no_run
/// use io_harness::provider::Replay;
///
/// # fn main() -> io_harness::Result<()> {
/// let provider = Replay::load("recording.json")?;
/// // ... run(&contract, &provider, &store).await? — no network, no key.
/// # Ok(())
/// # }
/// ```