agentfootprint 9.87.1 → 9.89.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/CHANGELOG.md +946 -0
  2. package/dist/core/Agent.js +32 -0
  3. package/dist/core/Agent.js.map +1 -1
  4. package/dist/core/LLMCall.js +7 -10
  5. package/dist/core/LLMCall.js.map +1 -1
  6. package/dist/core/agent/buildAgentMessageApiChart.js +2 -4
  7. package/dist/core/agent/buildAgentMessageApiChart.js.map +1 -1
  8. package/dist/core/agent/buildDynamicAgentChart.js +10 -0
  9. package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
  10. package/dist/core/agent/buildMessageApiChart.js +2 -4
  11. package/dist/core/agent/buildMessageApiChart.js.map +1 -1
  12. package/dist/core/agent/composeRequest.js +134 -0
  13. package/dist/core/agent/composeRequest.js.map +1 -0
  14. package/dist/core/agent/stages/callLLM.js +62 -25
  15. package/dist/core/agent/stages/callLLM.js.map +1 -1
  16. package/dist/core/agent/stages/seed.js +12 -0
  17. package/dist/core/agent/stages/seed.js.map +1 -1
  18. package/dist/core/agent/stages/toolCalls.js +2 -4
  19. package/dist/core/agent/stages/toolCalls.js.map +1 -1
  20. package/dist/core/flowchartAsTool.js.map +1 -1
  21. package/dist/esm/core/Agent.d.ts +3 -0
  22. package/dist/esm/core/Agent.js +32 -0
  23. package/dist/esm/core/Agent.js.map +1 -1
  24. package/dist/esm/core/LLMCall.js +7 -10
  25. package/dist/esm/core/LLMCall.js.map +1 -1
  26. package/dist/esm/core/agent/buildAgentMessageApiChart.js +2 -4
  27. package/dist/esm/core/agent/buildAgentMessageApiChart.js.map +1 -1
  28. package/dist/esm/core/agent/buildDynamicAgentChart.js +10 -0
  29. package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
  30. package/dist/esm/core/agent/buildMessageApiChart.js +2 -4
  31. package/dist/esm/core/agent/buildMessageApiChart.js.map +1 -1
  32. package/dist/esm/core/agent/composeRequest.d.ts +121 -0
  33. package/dist/esm/core/agent/composeRequest.js +127 -0
  34. package/dist/esm/core/agent/composeRequest.js.map +1 -0
  35. package/dist/esm/core/agent/stages/callLLM.d.ts +28 -0
  36. package/dist/esm/core/agent/stages/callLLM.js +61 -24
  37. package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
  38. package/dist/esm/core/agent/stages/seed.d.ts +26 -0
  39. package/dist/esm/core/agent/stages/seed.js +12 -0
  40. package/dist/esm/core/agent/stages/seed.js.map +1 -1
  41. package/dist/esm/core/agent/stages/toolCalls.js +2 -4
  42. package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
  43. package/dist/esm/core/agent/types.d.ts +64 -0
  44. package/dist/esm/core/flowchartAsTool.d.ts +15 -7
  45. package/dist/esm/core/flowchartAsTool.js.map +1 -1
  46. package/dist/esm/index.d.ts +1 -0
  47. package/dist/esm/index.js +9 -0
  48. package/dist/esm/index.js.map +1 -1
  49. package/dist/esm/lib/context-bisect/llmEdgeWeigher.d.ts +8 -0
  50. package/dist/esm/lib/context-bisect/llmEdgeWeigher.js +0 -0
  51. package/dist/esm/lib/context-bisect/llmEdgeWeigher.js.map +1 -1
  52. package/dist/esm/lib/context-bisect/trajectory.js +3 -7
  53. package/dist/esm/lib/context-bisect/trajectory.js.map +1 -1
  54. package/dist/esm/lib/context-ledger/contextLedger.js +5 -12
  55. package/dist/esm/lib/context-ledger/contextLedger.js.map +1 -1
  56. package/dist/esm/lib/time-travel/epochs.d.ts +210 -0
  57. package/dist/esm/lib/time-travel/epochs.js +274 -0
  58. package/dist/esm/lib/time-travel/epochs.js.map +1 -0
  59. package/dist/esm/lib/time-travel/index.d.ts +10 -4
  60. package/dist/esm/lib/time-travel/index.js +10 -4
  61. package/dist/esm/lib/time-travel/index.js.map +1 -1
  62. package/dist/esm/lib/time-travel/keyedFold.d.ts +112 -0
  63. package/dist/esm/lib/time-travel/keyedFold.js +286 -0
  64. package/dist/esm/lib/time-travel/keyedFold.js.map +1 -0
  65. package/dist/esm/lib/time-travel/milestoneStops.d.ts +57 -36
  66. package/dist/esm/lib/time-travel/milestoneStops.js +75 -82
  67. package/dist/esm/lib/time-travel/milestoneStops.js.map +1 -1
  68. package/dist/esm/lib/time-travel/receipt.d.ts +496 -0
  69. package/dist/esm/lib/time-travel/receipt.js +434 -0
  70. package/dist/esm/lib/time-travel/receipt.js.map +1 -0
  71. package/dist/esm/lib/time-travel/servedView.d.ts +525 -0
  72. package/dist/esm/lib/time-travel/servedView.js +820 -0
  73. package/dist/esm/lib/time-travel/servedView.js.map +1 -0
  74. package/dist/esm/lib/time-travel/sha256.d.ts +24 -0
  75. package/dist/esm/lib/time-travel/sha256.js +103 -0
  76. package/dist/esm/lib/time-travel/sha256.js.map +1 -0
  77. package/dist/esm/recorders/observability/BoundaryRecorder.d.ts +21 -0
  78. package/dist/esm/recorders/observability/BoundaryRecorder.js +6 -0
  79. package/dist/esm/recorders/observability/BoundaryRecorder.js.map +1 -1
  80. package/dist/index.js +124 -103
  81. package/dist/index.js.map +1 -1
  82. package/dist/lib/context-bisect/llmEdgeWeigher.js +0 -0
  83. package/dist/lib/context-bisect/llmEdgeWeigher.js.map +1 -1
  84. package/dist/lib/context-bisect/trajectory.js +3 -7
  85. package/dist/lib/context-bisect/trajectory.js.map +1 -1
  86. package/dist/lib/context-ledger/contextLedger.js +5 -12
  87. package/dist/lib/context-ledger/contextLedger.js.map +1 -1
  88. package/dist/lib/time-travel/epochs.js +284 -0
  89. package/dist/lib/time-travel/epochs.js.map +1 -0
  90. package/dist/lib/time-travel/index.js +35 -5
  91. package/dist/lib/time-travel/index.js.map +1 -1
  92. package/dist/lib/time-travel/keyedFold.js +290 -0
  93. package/dist/lib/time-travel/keyedFold.js.map +1 -0
  94. package/dist/lib/time-travel/milestoneStops.js +74 -81
  95. package/dist/lib/time-travel/milestoneStops.js.map +1 -1
  96. package/dist/lib/time-travel/receipt.js +442 -0
  97. package/dist/lib/time-travel/receipt.js.map +1 -0
  98. package/dist/lib/time-travel/servedView.js +826 -0
  99. package/dist/lib/time-travel/servedView.js.map +1 -0
  100. package/dist/lib/time-travel/sha256.js +107 -0
  101. package/dist/lib/time-travel/sha256.js.map +1 -0
  102. package/dist/recorders/observability/BoundaryRecorder.js +6 -0
  103. package/dist/recorders/observability/BoundaryRecorder.js.map +1 -1
  104. package/dist/types/core/Agent.d.ts +3 -0
  105. package/dist/types/core/Agent.d.ts.map +1 -1
  106. package/dist/types/core/LLMCall.d.ts.map +1 -1
  107. package/dist/types/core/agent/buildAgentMessageApiChart.d.ts.map +1 -1
  108. package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
  109. package/dist/types/core/agent/buildMessageApiChart.d.ts.map +1 -1
  110. package/dist/types/core/agent/composeRequest.d.ts +122 -0
  111. package/dist/types/core/agent/composeRequest.d.ts.map +1 -0
  112. package/dist/types/core/agent/stages/callLLM.d.ts +28 -0
  113. package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
  114. package/dist/types/core/agent/stages/seed.d.ts +26 -0
  115. package/dist/types/core/agent/stages/seed.d.ts.map +1 -1
  116. package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
  117. package/dist/types/core/agent/types.d.ts +64 -0
  118. package/dist/types/core/agent/types.d.ts.map +1 -1
  119. package/dist/types/core/flowchartAsTool.d.ts +15 -7
  120. package/dist/types/core/flowchartAsTool.d.ts.map +1 -1
  121. package/dist/types/index.d.ts +1 -0
  122. package/dist/types/index.d.ts.map +1 -1
  123. package/dist/types/lib/context-bisect/llmEdgeWeigher.d.ts +8 -0
  124. package/dist/types/lib/context-bisect/llmEdgeWeigher.d.ts.map +1 -1
  125. package/dist/types/lib/context-bisect/trajectory.d.ts.map +1 -1
  126. package/dist/types/lib/context-ledger/contextLedger.d.ts.map +1 -1
  127. package/dist/types/lib/time-travel/epochs.d.ts +211 -0
  128. package/dist/types/lib/time-travel/epochs.d.ts.map +1 -0
  129. package/dist/types/lib/time-travel/index.d.ts +10 -4
  130. package/dist/types/lib/time-travel/index.d.ts.map +1 -1
  131. package/dist/types/lib/time-travel/keyedFold.d.ts +113 -0
  132. package/dist/types/lib/time-travel/keyedFold.d.ts.map +1 -0
  133. package/dist/types/lib/time-travel/milestoneStops.d.ts +57 -36
  134. package/dist/types/lib/time-travel/milestoneStops.d.ts.map +1 -1
  135. package/dist/types/lib/time-travel/receipt.d.ts +497 -0
  136. package/dist/types/lib/time-travel/receipt.d.ts.map +1 -0
  137. package/dist/types/lib/time-travel/servedView.d.ts +526 -0
  138. package/dist/types/lib/time-travel/servedView.d.ts.map +1 -0
  139. package/dist/types/lib/time-travel/sha256.d.ts +25 -0
  140. package/dist/types/lib/time-travel/sha256.d.ts.map +1 -0
  141. package/dist/types/recorders/observability/BoundaryRecorder.d.ts +21 -0
  142. package/dist/types/recorders/observability/BoundaryRecorder.d.ts.map +1 -1
  143. package/package.json +3 -3
@@ -0,0 +1,496 @@
1
+ /**
2
+ * receipt — what the model was actually handed, recorded at the stop where it
3
+ * was handed it.
4
+ *
5
+ * Role: Fold. This module owns the receipt's SHAPE and the one function that
6
+ * mints one (`buildReceipt`); the agent's request assembly calls it and
7
+ * commits the result under the scope key `receipt`, so the record lands
8
+ * in the call-llm bundle that already exists. Nothing here executes,
9
+ * reads scope, or emits.
10
+ * Reads: its arguments.
11
+ * Emits: N/A.
12
+ *
13
+ * ── THE FIRST LAW: NO AUTHORITY OMISSIONS ──────────────────────────────────
14
+ * A receipt never names — and never counts — what a caller's ROLE was not
15
+ * allowed to see. Committed state is readable by the trace toolpack's
16
+ * debugging tools, so a receipt carrying `hiddenSkillIds`, or even "3 skills
17
+ * withheld", would turn a permission decision into a leak path: the thing the
18
+ * check exists to hide, restated one layer down where nobody is checking. A
19
+ * reader that needs those ids reads them from the fold (`hiddenSkillIds` is
20
+ * committed state, and reading it is governed where it is read), not from
21
+ * here.
22
+ *
23
+ * ATTENTION omissions are a different fact and MAY ride the receipt:
24
+ * `omittedForAttention` says a slot's budget dropped content. Nobody was
25
+ * refused anything — the request simply did not fit — and a reader chasing "why
26
+ * did it not know that?" needs to see it. Even there the summaries are hashed,
27
+ * never quoted, for the same reason everything else on a receipt is.
28
+ *
29
+ * Measured on 9.88.0, NO chart shape supplies it: a slot writes its drops to
30
+ * `slotCompositions` inside its own subflow and no boundary bubbles that record
31
+ * out, so the agent's request assembly has nothing to pass. The field is on the
32
+ * shape because `buildReceipt` is a pure exported mint a caller can hand the
33
+ * fact to, and because the law it obeys is worth stating once rather than the
34
+ * day the record starts crossing. What is NOT done is go looking for it: a
35
+ * tracked read of an always-absent key is a read edge the trace then has to
36
+ * explain.
37
+ *
38
+ * ── THE SECOND LAW: HASHES AND REFERENCES, NEVER BYTES ─────────────────────
39
+ * A receipt is a fingerprint, not a copy. It records that a piece of a given
40
+ * shape and size was in a given position, and nothing about what it said. The
41
+ * bytes are already governed — `recordSystemPrompt` is opt-in for exactly this
42
+ * reason, redaction patterns scrub the committed mirror, and a window strategy
43
+ * decides what survives. A receipt that carried content would quietly reopen
44
+ * all three.
45
+ *
46
+ * ── THE THIRD LAW: HASHES ARE SALTED WITH THE RUN ID ───────────────────────
47
+ * `hash = sha256(runId + '\u001f' + content)`, first 16 hex characters. The
48
+ * salt is what stops a short piece from being fingerprinted ACROSS runs: an
49
+ * unsalted hash of a one-line system prompt, a tool schema, or a two-word user
50
+ * turn is a dictionary lookup away from being read back, and the receipt is
51
+ * committed state that travels in recordings. Salted per run, the same
52
+ * sentence in two runs has two hashes, so the fingerprint answers "is this the
53
+ * same as THAT piece of THIS run?" — which is the only question the
54
+ * conformance law asks — and answers nothing else.
55
+ *
56
+ * Hashes are NOT redacted, and the salt is what makes shipping THEM safe — not
57
+ * an assumption that something scrubbed them. It says nothing about the rest of
58
+ * the recording, and this law must not be read as if it did.
59
+ *
60
+ * WHAT A RECORDING ACTUALLY CONTAINS. An agent run is not redacted:
61
+ * `Agent.create(...)` has no redaction door, so a secret in a system prompt is
62
+ * in the commit log and in `servedAt(k).system.text`, verbatim. Redaction in
63
+ * this library is EXECUTOR-level — `flowchartAsTool({ redact })` /
64
+ * `runbookAsTool({ redact })` set a policy on an inner run, and footprintjs
65
+ * scrubs at COMMIT time, so a redacted key never enters that inner commit log
66
+ * (the same snapshot's live `sharedState` is a different view and is not
67
+ * scrubbed). A snapshot taken with `redact: true` also omits `initialState`,
68
+ * so folding it reports `basis: 'log-only'` and `servedView.ts` raises
69
+ * `no-fold-base` rather than rebuilding a short view in silence. Treat a
70
+ * recording as the plaintext it is; the salt protects the fingerprints, and
71
+ * only the fingerprints.
72
+ *
73
+ * ── THE FOURTH LAW: THE RECEIPT IS MINTED AT THE PROVIDER PORT ─────────────
74
+ * Everything on a receipt describes the request AS HANDED TO
75
+ * `LLMProvider.complete` — the port, not the wire. Anything downstream of that
76
+ * boundary is outside what this record can witness: a decorated provider, a
77
+ * vendor adapter's own serializer, a consumer's hand-written `complete`, or the
78
+ * vendor's server-side defaults. `RECEIPT_BOUNDARY` states it in one sentence
79
+ * for a renderer to print, and `servedView.ts` · `SERVED_GAPS` names the two
80
+ * places a reader is most likely to mistake it for wire-level proof.
81
+ */
82
+ import type { LLMMessage, LLMToolSchema } from '../../adapters/types.js';
83
+ import type { ContextRole, ContextSlot, ContextSource } from '../../events/types.js';
84
+ /**
85
+ * What a digest input records in place of a value JSON cannot express — a
86
+ * cycle, a `BigInt`, anything `stableJson` refuses. It is a MARK, not an empty
87
+ * string: two unserializable values are recorded as unserializable rather than
88
+ * as identical, which is what stops the cache comparison claiming "the strategy
89
+ * changed nothing" about two requests it could not read.
90
+ */
91
+ export declare const UNSERIALIZABLE = "\u001F<unserializable>";
92
+ /**
93
+ * The boundary every receipt field is true at, in one sentence — exported so a
94
+ * renderer prints the library's own wording instead of inferring a stronger
95
+ * claim from a `null`.
96
+ *
97
+ * WHY IT IS A CONSTANT AND NOT A COMMENT. A reader who sees
98
+ * `cache.transform: 'unchanged'` will conclude "this request was not rewritten"
99
+ * unless something on the screen says otherwise, and the person who reads the
100
+ * screen is rarely the person who read the source. Print it beside the record.
101
+ *
102
+ * ── THE MECHANISM, WHICH THE PRINTED SENTENCE NO LONGER NAMES (9.88.0) ─────
103
+ * `buildReceipt` is called from `stages/callLLM.ts` with the request about to
104
+ * be passed to `LLMProvider.complete` — the PORT, this library's last sight of
105
+ * it. Three things sit downstream of that call and none of them is on the
106
+ * record: a provider decorated by the consumer (`complete()` wrapping
107
+ * `complete()`), a vendor adapter's own serializer, and the vendor's
108
+ * server-side defaults. `test/lib/time-travel/receipt-conformance.test.ts`
109
+ * reproduces the first of those — a decorator that appends a system suffix and
110
+ * a ghost tool after the mint, with `cache.transform` still reading
111
+ * `'unchanged'`, correctly.
112
+ *
113
+ * The sentence below used to say all of that, and it was PRINTED beside a
114
+ * trace by renderers that append it to a gap. A printed sentence names no
115
+ * module, function or call: it says which fields, what they mean here, and
116
+ * what to do — the rule in `test/helpers/gapProseClaims.ts`. So the mechanism
117
+ * lives in this comment, where a maintainer reads it and review catches its
118
+ * rot, and the constant says only the thing a reader must not get wrong.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * import { RECEIPT_BOUNDARY, receiptAt } from 'agentfootprint';
123
+ *
124
+ * const receipt = receiptAt(agent.getSnapshot()!, 1)!;
125
+ * if (receipt.cache.transform === 'unchanged') {
126
+ * console.log(`the cache strategy changed nothing. ${RECEIPT_BOUNDARY}`);
127
+ * }
128
+ * ```
129
+ */
130
+ export declare const RECEIPT_BOUNDARY: string;
131
+ /** The scope key the receipt is committed under. */
132
+ export declare const RECEIPT_KEY = "receipt";
133
+ /** The scope key holding the name of the tool a `'tool-forced'` output
134
+ * strategy put on the wire. `seed` commits it; `servedView.ts` · `servedAt`
135
+ * reads it — so a rebuild can NAME the forced tool without reading the
136
+ * receipt it is being checked against. */
137
+ export declare const FORCED_OUTPUT_TOOL_KEY = "forcedOutputToolName";
138
+ /** How many hex characters of the digest a receipt keeps. 16 — 64 bits, which
139
+ * is far past collision range for the few dozen pieces of one run, and short
140
+ * enough to read in a terminal. */
141
+ export declare const RECEIPT_HASH_CHARS = 16;
142
+ /** One piece of the composed system string. */
143
+ export interface ReceiptPiece {
144
+ readonly hash: string;
145
+ readonly slot: ContextSlot;
146
+ readonly source: ContextSource;
147
+ }
148
+ /** One message as it went out. */
149
+ export interface ReceiptMessage {
150
+ readonly role: ContextRole;
151
+ readonly hash: string;
152
+ /** The message's own join key when it has one — a tool result's
153
+ * `toolCallId`. Absent for every other message. */
154
+ readonly key?: string;
155
+ }
156
+ /** A line that existed on the request only and was never written to history. */
157
+ export interface ReceiptRequestOnlyMessage {
158
+ readonly role: ContextRole;
159
+ readonly hash: string;
160
+ /** Which library mechanism composed it. `'staged-refs-nudge'` today. */
161
+ readonly reason: string;
162
+ }
163
+ /**
164
+ * One `cache_control` breakpoint the cache strategy actually APPLIED — three
165
+ * scalars, no bytes.
166
+ *
167
+ * `cache.transformHash` collapses a whole rewrite into one bit ("something
168
+ * changed"), and a digest over the whole prepared request is not comparable
169
+ * across epochs. The question that decides an Anthropic bill is narrower and
170
+ * concrete: *did the breakpoints move between call 3 and call 4?* Two receipts'
171
+ * `markersApplied` answer it by inspection.
172
+ *
173
+ * APPLIED, not offered: `scope.cacheMarkers` holds the CANDIDATES the strategy
174
+ * was given, and a strategy clamps them to what the provider allows. The
175
+ * candidates are on the record; which survived is not, which is why they ride
176
+ * here.
177
+ *
178
+ * @example did the breakpoints move between two turns?
179
+ * ```ts
180
+ * import { receiptAt } from 'agentfootprint';
181
+ *
182
+ * const at = (k: number) =>
183
+ * JSON.stringify(receiptAt(agent.getSnapshot()!, k)?.cache.markersApplied);
184
+ * at(3) === at(4); // false ⇒ the cached prefix moved, and the bill with it
185
+ * ```
186
+ */
187
+ export interface ReceiptCacheMarker {
188
+ readonly field: 'system' | 'tools' | 'messages';
189
+ readonly boundaryIndex: number;
190
+ readonly ttl: 'short' | 'long';
191
+ }
192
+ /**
193
+ * The sampling knobs the call went out with — scalars and short strings, no
194
+ * bytes, no privacy change.
195
+ *
196
+ * They decide the answer as surely as the prompt does: the same context at
197
+ * `temperature: 0` and at `1.2` is a different call, and "why did this turn
198
+ * ramble?" is unanswerable from a record that kept the prompt and dropped the
199
+ * dial.
200
+ *
201
+ * READ OFF THE PREPARED REQUEST — the object the port is handed, after the
202
+ * cache strategy has had it. This is the one part of a receipt that describes
203
+ * the post-strategy request rather than the pre-strategy one, and deliberately
204
+ * so: a strategy holds the whole composed request and can move a dial, and
205
+ * `params` claims in this very docstring to be what the PORT got.
206
+ * `servedView.ts` · `SERVED_GAPS` says the same from the other side —
207
+ * `cache-transform` names every field the log rebuilds pre-strategy, and
208
+ * `params` is not among them.
209
+ *
210
+ * PORT VALUES ONLY — see {@link RECEIPT_BOUNDARY}. An unset `maxTokens` may
211
+ * still become the model's own maximum inside a vendor SDK, and the receipt
212
+ * records that the library sent nothing, never what the vendor decided.
213
+ *
214
+ * @example compare the dial two turns went out on
215
+ * ```ts
216
+ * import { receiptAt } from 'agentfootprint';
217
+ *
218
+ * const snapshot = agent.getSnapshot()!;
219
+ * receiptAt(snapshot, 1)?.params.temperature; // 0.25
220
+ * receiptAt(snapshot, 2)?.params.temperature; // 0.25 — the dial did not move
221
+ * receiptAt(snapshot, 1)?.params.stop; // undefined: none was sent
222
+ * ```
223
+ */
224
+ export interface ReceiptParams {
225
+ readonly temperature?: number;
226
+ readonly maxTokens?: number;
227
+ /** `LLMRequest.thinking.budget` — the reasoning-token ceiling asked for. */
228
+ readonly thinkingBudget?: number;
229
+ readonly stop?: readonly string[];
230
+ /** The forced tool choice, as the port carried it. */
231
+ readonly toolChoice?: {
232
+ readonly type: string;
233
+ readonly name?: string;
234
+ };
235
+ }
236
+ /** What a slot's budget dropped before the request was composed. */
237
+ export interface ReceiptAttentionOmission {
238
+ readonly count: number;
239
+ /** One hash per dropped summary — see the first law for why not the text. */
240
+ readonly hashes: readonly string[];
241
+ }
242
+ /**
243
+ * THE RECEIPT. One per composed request, committed at the call-llm stop.
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * import { receiptAt } from 'agentfootprint';
248
+ *
249
+ * // Epochs are the run's OWN iteration numbers and start at 1.
250
+ * const receipt = receiptAt(agent.getSnapshot()!, 1);
251
+ * receipt?.tools.withheld; // 'wrap-up' on the out-of-budget call
252
+ * receipt?.messages.count; // how many turns went out
253
+ * receipt?.params.temperature; // the dial this turn went out on
254
+ * ```
255
+ */
256
+ export interface Receipt {
257
+ readonly system: {
258
+ readonly hash: string;
259
+ readonly chars: number;
260
+ readonly pieces: readonly ReceiptPiece[];
261
+ };
262
+ readonly messages: {
263
+ readonly count: number;
264
+ readonly entries: readonly ReceiptMessage[];
265
+ readonly requestOnly: readonly ReceiptRequestOnlyMessage[];
266
+ };
267
+ readonly tools: {
268
+ readonly names: readonly string[];
269
+ readonly schemaHashes: Readonly<Record<string, string>>;
270
+ /** The tool the model was forced to answer through, or `null`. */
271
+ readonly forced: string | null;
272
+ /** Why the tool list is empty when it would not otherwise be. */
273
+ readonly withheld: 'wrap-up' | null;
274
+ };
275
+ readonly cache: {
276
+ /**
277
+ * What the comparison between the request handed TO the cache strategy and
278
+ * the one it handed back could establish. BRANCH ON THIS, never on
279
+ * `transformHash === null`:
280
+ *
281
+ * - `'unchanged'` — the two serialize identically.
282
+ * - `'rewritten'` — they do not, and `transformHash` fingerprints the
283
+ * result.
284
+ * - `'unknown'` — one of them could not be serialized at all, so the
285
+ * receipt refuses to claim either.
286
+ *
287
+ * It scopes to the CACHE STRATEGY and to nothing else — see
288
+ * {@link RECEIPT_BOUNDARY}.
289
+ */
290
+ readonly transform: 'unchanged' | 'rewritten' | 'unknown';
291
+ /** Hash of the request the cache strategy handed back, when it differed
292
+ * from the one it was given; `null` otherwise. */
293
+ readonly transformHash: string | null;
294
+ /** The breakpoints the strategy actually applied, in the order it applied
295
+ * them — see {@link ReceiptCacheMarker}. Empty when it applied none. */
296
+ readonly markersApplied: readonly ReceiptCacheMarker[];
297
+ };
298
+ /** The sampling knobs the call went out with — see {@link ReceiptParams}. */
299
+ readonly params: ReceiptParams;
300
+ /**
301
+ * Absent when no slot reported a drop — which, measured on 9.88.0, is EVERY
302
+ * run in both chart shapes: no boundary bubbles `slotCompositions` out of the
303
+ * slot subflow that writes it, so request assembly has nothing to pass. It is
304
+ * a key of `servedView.ts` · `UNGAPPED_FIELDS` for that reason: its absence
305
+ * is universal and says nothing about any particular recording. Absent means
306
+ * nobody recorded a drop, never that nothing was dropped.
307
+ */
308
+ readonly omittedForAttention?: ReceiptAttentionOmission;
309
+ readonly basis: {
310
+ readonly epoch: number;
311
+ readonly runId: string;
312
+ readonly model: string;
313
+ readonly provider: string;
314
+ };
315
+ }
316
+ /**
317
+ * The receipt's hash: run-salted SHA-256, first {@link RECEIPT_HASH_CHARS} hex
318
+ * characters. Exported because it is half of the conformance law — a reader
319
+ * that rebuilds a served view proves the rebuild by hashing it the same way.
320
+ *
321
+ * @example
322
+ * ```ts
323
+ * import { receiptAt, receiptHash, servedAt } from 'agentfootprint';
324
+ *
325
+ * const view = servedAt(agent.getSnapshot()!, 1)!;
326
+ * const receipt = receiptAt(agent.getSnapshot()!, 1)!;
327
+ * receiptHash(receipt.basis.runId, view.system.text) === receipt.system.hash; // true
328
+ * ```
329
+ */
330
+ export declare function receiptHash(runId: string, content: string): string;
331
+ /**
332
+ * JSON with object keys in sorted order, at every depth — so two structurally
333
+ * equal values hash the same however they were built. Property order survives
334
+ * `structuredClone`, but it does NOT survive every path a value takes through
335
+ * a delta-encoded commit log and back, and a fingerprint that depends on
336
+ * insertion order is a fingerprint that reports a change nobody made.
337
+ *
338
+ * Returns `undefined` — never `''` — for anything JSON cannot express (a
339
+ * cycle, a `BigInt`, a value whose `toJSON` throws). It does not throw,
340
+ * because a receipt is bookkeeping and must never be able to stop a run; and
341
+ * it does not collapse, because `''` made every unreadable value equal to
342
+ * every other one. Two requests that both failed to serialize compared EQUAL,
343
+ * and the receipt wrote `transformHash: null` — "the cache strategy changed
344
+ * nothing" — about a pair it had not read. Callers substitute
345
+ * {@link UNSERIALIZABLE} where a digest needs bytes, and branch where a
346
+ * comparison needs truth.
347
+ */
348
+ export declare function stableJson(value: unknown): string | undefined;
349
+ /**
350
+ * The bytes a tool schema's hash covers: the whole `LLMToolSchema` — `name`,
351
+ * `description`, `inputSchema` — as sorted-key JSON, so two schemas that are
352
+ * structurally the same hash the same however they were built, and with a
353
+ * schema JSON cannot express (a cycle, a `BigInt`) marked {@link UNSERIALIZABLE}
354
+ * rather than dropped or thrown on.
355
+ *
356
+ * WHAT TO PASS (9.89.0). The receipt hashes each tool AS HANDED TO THE PORT:
357
+ * `buildReceipt` is given the request's tool list, and that list is the
358
+ * `dynamicToolSchemas` the run committed, plus a forced answer tool when an
359
+ * output strategy adds one. `servedAt(k).tools.schemas[i]` is that committed
360
+ * list read back — the same shape, the same bytes — so a consumer holding a
361
+ * served view already holds the object this function takes. It does not take
362
+ * the served view, a `Tool`, or a `defineTool` definition: those carry an
363
+ * `execute`, `wants` and other fields the model never saw.
364
+ *
365
+ * WHY IT IS EXPORTED. A reader that rebuilds a served view proves the rebuild
366
+ * by hashing it the way the receipt did. `receiptHash` and `messageDigestInput`
367
+ * were exported in 9.88.0 for the system text, the pieces and the messages,
368
+ * and a consumer could verify all of them — and NOT the tools, because the
369
+ * serializer behind `schemaHashes` was internal. Its only options were to copy
370
+ * `stableJson` (a second owner of the rule, which drifts the day the digest
371
+ * gains a field, as the message digest did in 9.88.0) or to leave the schema
372
+ * rows unverified. This is the third digest half of the law, beside its two
373
+ * siblings, and the ONLY spelling of the schema rule: `buildReceipt` calls it
374
+ * too. `stableJson` stays off the root barrel for the same reason — a consumer
375
+ * composing `hash(stableJson(tool))` would be writing the rule a second time.
376
+ *
377
+ * @example verify every schema row of a receipt from outside
378
+ * ```ts
379
+ * import { receiptAt, receiptHash, servedAt, toolDigestInput } from 'agentfootprint';
380
+ *
381
+ * const snapshot = agent.getSnapshot()!;
382
+ * const view = servedAt(snapshot, 1)!;
383
+ * const receipt = receiptAt(snapshot, 1)!;
384
+ * for (const tool of view.tools.schemas) {
385
+ * receiptHash(receipt.basis.runId, toolDigestInput(tool)) ===
386
+ * receipt.tools.schemaHashes[tool.name]; // true, for every tool of every epoch
387
+ * }
388
+ * // A forced answer tool is in `schemaHashes` and NOT in `schemas` — its body
389
+ * // is a declared gap (`forced-tool-schema`), so there is no row to check.
390
+ * ```
391
+ */
392
+ export declare function toolDigestInput(tool: LLMToolSchema): string;
393
+ /**
394
+ * The bytes a message's hash covers, each field behind a separator: its role,
395
+ * its text, its `toolCallId`, its `toolName`, the calls it asked for (id, name,
396
+ * arguments and `providerMeta`) and its `thinkingBlocks`.
397
+ *
398
+ * WHY `toolCallId` (9.88.0). It is the JOIN KEY — `tool_use_id` on Anthropic's
399
+ * wire, `tool_call_id` on OpenAI's — that pairs a tool result to the call that
400
+ * asked for it. Two parallel calls whose results happen to have identical text
401
+ * hashed the SAME without it, so a mis-pairing (the answer to `c1` filed under
402
+ * `c2`) was invisible to the law: exactly the defect a receipt exists to catch.
403
+ *
404
+ * WHY `toolName` TOO (9.88.0). On two shipped providers the id is not the join
405
+ * key at all. `adapters/llm/GeminiProvider.ts` · `toGeminiContents` pairs a
406
+ * `functionResponse` to its call BY NAME and says so in its own comment; the
407
+ * id is optional there and dropped when it is not a real one.
408
+ * `adapters/llm/OllamaProvider.ts` · `toOllamaMessages` puts `tool_name` on
409
+ * the wire and only falls back to the id. So on those providers a receipt that
410
+ * covered `toolCallId` alone still could not see the very mis-pairing the
411
+ * field was added for: two `role:'tool'` messages with identical text and
412
+ * SWAPPED names fingerprinted identically. It rides as its own field behind
413
+ * the separator, beside the id, so a name moving between two results changes
414
+ * both hashes and neither can absorb the other's bytes.
415
+ *
416
+ * WHY `thinkingBlocks` and `providerMeta` (9.88.0). `adapters/types.ts` is
417
+ * explicit that a signed thinking block MUST be echoed byte-exact or the API
418
+ * rejects the turn, and `providerMeta` round-trips vendor state (Gemini's
419
+ * `thoughtSignature`, without which the turn after a tool call is refused).
420
+ * Both are things the model receives; a fingerprint that omitted them said two
421
+ * requests were the same when one of them would be rejected. They enter as a
422
+ * `stableJson` FINGERPRINT, so nothing quotable is added — a signature is an
423
+ * opaque token, not content.
424
+ *
425
+ * Still deliberately NOT covered: `injectedBy` (stripped before the request
426
+ * exists) and `ephemeral` (a persistence flag, invisible to the model). Two
427
+ * messages that hash the same are the same thing said to the model.
428
+ */
429
+ export declare function messageDigestInput(message: LLMMessage): string;
430
+ /** What `buildReceipt` needs from the stage that composed the request. */
431
+ export interface BuildReceiptInput {
432
+ readonly runId: string;
433
+ readonly epoch: number;
434
+ readonly model: string;
435
+ readonly provider: string;
436
+ /** The joined system string, exactly as sent. */
437
+ readonly systemText: string;
438
+ /** The pieces it was joined from, in order. */
439
+ readonly systemPieces: readonly {
440
+ readonly text: string;
441
+ readonly slot: ContextSlot;
442
+ readonly source: ContextSource;
443
+ }[];
444
+ /** The messages that came from history, post-strip, in wire order. */
445
+ readonly messages: readonly LLMMessage[];
446
+ /** Lines appended to this request only, each with the mechanism that
447
+ * composed it. */
448
+ readonly requestOnly: readonly {
449
+ readonly message: LLMMessage;
450
+ readonly reason: string;
451
+ }[];
452
+ /** The tool list as sent — including a forced output tool, if there is one. */
453
+ readonly tools: readonly LLMToolSchema[];
454
+ readonly forced: string | null;
455
+ readonly withheld: 'wrap-up' | null;
456
+ /** The request handed TO the cache strategy — one half of the cache
457
+ * comparison, and nothing else is read off it. */
458
+ readonly baseRequest: unknown;
459
+ /**
460
+ * The request it handed back: the object the provider port is given.
461
+ *
462
+ * The other half of the cache comparison, AND where {@link ReceiptParams} is
463
+ * read from. A strategy is handed the whole composed request, so it can move
464
+ * a dial as easily as a marker; reading the knobs off `baseRequest` recorded
465
+ * what assembly PROPOSED and called it what the port was handed, which is
466
+ * the one thing `RECEIPT_BOUNDARY` promises a receipt never does.
467
+ */
468
+ readonly preparedRequest: unknown;
469
+ /** The breakpoints the strategy reported applying (`prepareRequest`'s
470
+ * `markersApplied`). Absent ⇒ recorded as none. */
471
+ readonly markersApplied?: readonly {
472
+ readonly field: 'system' | 'tools' | 'messages';
473
+ readonly boundaryIndex: number;
474
+ readonly ttl: 'short' | 'long';
475
+ }[];
476
+ /** What a slot's budget dropped, when a slot reported any. */
477
+ readonly omittedForAttention?: {
478
+ readonly count: number;
479
+ readonly summaries: readonly string[];
480
+ };
481
+ }
482
+ /**
483
+ * Mint the receipt for one composed request.
484
+ *
485
+ * Pure and total: every branch here is arithmetic over what it was handed, so
486
+ * a receipt cannot fail a call. The one judgement it makes is the cache
487
+ * comparison, and it makes it in three values rather than two: `'unchanged'`
488
+ * exactly when the strategy handed back a request that serializes identically
489
+ * to the one it was given, `'rewritten'` when it did not, and `'unknown'` when
490
+ * either request could not be serialized at all — because "I could not read
491
+ * them" is not the same claim as "they were the same".
492
+ *
493
+ * Every value it produces is true AT THE PROVIDER PORT and nowhere past it —
494
+ * see {@link RECEIPT_BOUNDARY}.
495
+ */
496
+ export declare function buildReceipt(input: BuildReceiptInput): Receipt;