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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
//! T30/TR-4 — terminal-noise normalization (`ReductionKind::OutputNormalized`):
//! a small, deterministic line-buffer terminal simulator that collapses ANSI
//! color/style codes and carriage-return/erase-line/cursor-up redraws down to
//! the FINAL rendered content of each line — the same content a human
//! watching the build would actually see, without the hundreds of
//! intermediate redraws a captured progress bar otherwise leaves in the
//! transcript.
//!
//! **Not a full `vte` emulation** (per SPEC.md TR-4's approach sketch): this
//! supports exactly the sequences that dominate real `cargo`/`npm`/`pip`/
//! `docker` output —
//!
//! - SGR (`ESC[...m`, colors/styles) — stripped; it never prints or moves.
//! - CR (`\r`) — cursor to column 0 of the current row.
//! - LF (`\n`) — cursor to column 0 of the NEXT row (a deliberate
//! simplification: real LF preserves column, but every real capture in
//! this codebase's fixtures pairs LF with either a preceding CR or content
//! that starts a fresh line anyway, so this never diverges from the
//! fixtures' actual rendering and keeps the model trivial to reason about).
//! - EL (`ESC[K`, `ESC[0K`, `ESC[1K`, `ESC[2K`) — erase to end / to start /
//! whole line.
//! - CUU / CUD (`ESC[<n>A` / `ESC[<n>B`) — cursor up/down `n` rows (the
//! multi-line redraw idiom `docker pull` uses for concurrent layers).
//! - CHA (`ESC[<n>G`) — cursor to absolute column `n` (1-based); the idiom
//! modern `npm`'s spinner uses instead of `\r`.
//! - DEC private mode set/reset (`ESC[?...h` / `ESC[?...l`) — e.g. `?25l`/
//! `?25h` (cursor hide/show around a spinner): dropped silently. This is a
//! deliberately narrow carve-out (real DEC private modes can do much more,
//! e.g. the alternate screen buffer) but build-tool output never uses those
//! — see the module-level safety note below.
//!
//! Everything else — including a CSI sequence with a final byte this module
//! doesn't recognize (device status report, cursor-position, scroll, etc.),
//! an OSC sequence, or any escape truncated by an upstream byte cap before
//! its terminator — is passed through **verbatim, as literal printable
//! text**, landing in the rendered output unchanged. "Never guess": an
//! unrecognized sequence is never assumed to be a no-op, so its bytes are
//! never silently dropped, and the reduction is content-preserving even for
//! escape vocabulary this module has never seen.
//!
//! # Safety / no-panic guarantee
//!
//! [`normalize`] never panics on any input, including a `&str` truncated
//! mid-escape-sequence (the TR-1 gotcha this module was warned about:
//! `Agent::cap_tool_output`'s 100 KB history cap can slice a raw tool output
//! anywhere, including through the middle of a CSI/OSC sequence, before this
//! module ever sees it). A truncated sequence at the end of the input is
//! detected (no terminator found before the string ends) and copied through
//! as literal text, same as any other unrecognized sequence — never a panic,
//! never an out-of-bounds slice. See `tests::never_panics_on_malformed_input`
//! for a sweep over adversarial byte patterns (including sequences chopped at
//! every possible byte boundary).
//!
//! All scanning here is on `&str` byte offsets, but every control byte this
//! module inspects (`ESC` 0x1B, `CR` 0x0D, `LF` 0x0A, CSI param/final bytes
//! 0x20-0x7E) is ASCII — and ASCII bytes are never a continuation byte
//! (0x80-0xBF) or a lead byte (0xC0-0xFF) of a multi-byte UTF-8 sequence, so
//! every position this module treats as a slice boundary is guaranteed to
//! already be a valid `char` boundary in a well-formed `&str`. Regular
//! (non-control) runs between control bytes are therefore always safe to
//! slice directly.
use Write as _;
/// Minimum byte savings (`original.len() - normalized.len()`) for
/// `project_messages` to accept a normalization candidate —
/// SPEC.md TR-4's "savings floor" knob, mirrored as
/// `ReductionPolicy::terminal_output_min_savings`. Exposed
/// here as the documented default; the policy field is what callers actually
/// tune.
pub const DEFAULT_MIN_SAVINGS: usize = 128;
/// Tool names T30/TR-4's candidate rule treats as "terminal/exec" — a result
/// from one of these is eligible for [`super::ReductionKind::OutputNormalized`].
/// Compared against the INVOKING tool call's function name (see
/// `detect_normalize_candidates`), the same pattern the A8 read-tool list
/// uses for read-type tools:
///
/// - `"bash"` — this SDK's own built-in (`tools/builtins.rs`'s
/// `BashTool::name`); `B6` must keep this in sync with any built-in tool
/// rename.
/// - `"shell"` — the other shell-tool name this SDK already anticipates for
/// embedder-registered tools (see `tools/mod.rs`'s
/// `shell_sandbox_unenforceable`, which checks the identical pair).
/// - `"exec_command"` — Codex's own native exec tool name, so a Codex log
/// loaded via `Session::from_codex` (whose `function_call`/
/// `function_call_output` records never carry a `ChatMessage::name` at
/// all — see `detect_normalize_candidates`'s doc comment) is covered too.
///
pub const NORMALIZE_TOOLS: & = &;
/// One simulated terminal row: a flat char buffer supporting index-based
/// overwrite (what CR/EL/cursor-up redraws need) without tracking style —
/// SGR is stripped at parse time, never simulated as row state.
type Row = ;
/// The minimal line-buffer terminal state [`normalize`] drives.
/// Is `b` a CSI parameter byte (ECMA-48: `0x30..=0x3F`, i.e. digits, `;`,
/// `:`, `<`, `=`, `>`, `?`)?
/// Is `b` a CSI final byte (ECMA-48: `0x40..=0x7E`)?
/// Parse the (at most one) leading numeric parameter of a CSI param string,
/// ignoring everything after the first `;` (none of the sequences this
/// module simulates take more than one meaningful parameter) and any leading
/// `?` (DEC private-mode prefix, stripped by the caller's own dispatch, but
/// tolerated here too so a stray `?` never breaks the digit parse).
/// Normalize `input`: strip ANSI SGR, simulate CR/EL/CUU/CUD/CHA redraws, and
/// return the final rendered text. Deterministic and pure — same bytes in,
/// byte-identical text out, every call (SPEC.md TR-4's determinism
/// requirement; see `tests::deterministic_across_repeated_runs`).
///
/// Never panics (see the module doc comment's safety note): a malformed or
/// truncated escape sequence is copied through as literal text rather than
/// ever indexing out of bounds or asserting on unexpected structure.
/// Consume one CSI sequence (`ESC [ params final`) starting at `esc_pos`
/// (the index of the `ESC` byte, with `bytes[esc_pos + 1] == b'['` already
/// verified by the caller). Dispatches recognized final bytes to `screen`;
/// anything else — including a sequence with no final byte before the input
/// ends (truncated) — is written through as literal text. Returns the index
/// to resume scanning from.
/// Consume one OSC sequence (`ESC ] ... (BEL | ESC \\)`) starting at
/// `esc_pos` (with `bytes[esc_pos + 1] == b']'` already verified). OSC
/// payloads (window title, etc.) never move the cursor or print visible
/// content themselves, but this module still does not special-case them —
/// "never guess" applies to properties like OSC-8 hyperlinks wrapping
/// visible text, which real build tools do not use but this module has no
/// way to rule out categorically. So: pass the whole sequence through
/// verbatim, same as any other unrecognized escape. An OSC with no
/// terminator before the input ends is likewise passed through verbatim to
/// the end (the truncated-sequence case). Returns the index to resume
/// scanning from.
/// Format the honesty trailer's summary text (SPEC.md TR-4: "normalized text
/// must remain honest"): plain ASCII, one line, no `]` — same constraints
/// the reduction-stub formatter already enforces on every summary, so
/// this is folded into the shared `[sc-reduced output-normalized <id>: ...]`
/// grammar (see `reduce.rs`'s `OutputNormalized` candidate pass) rather than
/// a bespoke sentinel — that keeps the existing leak-guard (A11),
/// `stub::parse` (`sessions show-reductions`), and `Kind::from` dispatch all
/// working for this kind with no special-casing.