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.
- package/CHANGELOG.md +946 -0
- package/dist/core/Agent.js +32 -0
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/LLMCall.js +7 -10
- package/dist/core/LLMCall.js.map +1 -1
- package/dist/core/agent/buildAgentMessageApiChart.js +2 -4
- package/dist/core/agent/buildAgentMessageApiChart.js.map +1 -1
- package/dist/core/agent/buildDynamicAgentChart.js +10 -0
- package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/core/agent/buildMessageApiChart.js +2 -4
- package/dist/core/agent/buildMessageApiChart.js.map +1 -1
- package/dist/core/agent/composeRequest.js +134 -0
- package/dist/core/agent/composeRequest.js.map +1 -0
- package/dist/core/agent/stages/callLLM.js +62 -25
- package/dist/core/agent/stages/callLLM.js.map +1 -1
- package/dist/core/agent/stages/seed.js +12 -0
- package/dist/core/agent/stages/seed.js.map +1 -1
- package/dist/core/agent/stages/toolCalls.js +2 -4
- package/dist/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/core/flowchartAsTool.js.map +1 -1
- package/dist/esm/core/Agent.d.ts +3 -0
- package/dist/esm/core/Agent.js +32 -0
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/LLMCall.js +7 -10
- package/dist/esm/core/LLMCall.js.map +1 -1
- package/dist/esm/core/agent/buildAgentMessageApiChart.js +2 -4
- package/dist/esm/core/agent/buildAgentMessageApiChart.js.map +1 -1
- package/dist/esm/core/agent/buildDynamicAgentChart.js +10 -0
- package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/esm/core/agent/buildMessageApiChart.js +2 -4
- package/dist/esm/core/agent/buildMessageApiChart.js.map +1 -1
- package/dist/esm/core/agent/composeRequest.d.ts +121 -0
- package/dist/esm/core/agent/composeRequest.js +127 -0
- package/dist/esm/core/agent/composeRequest.js.map +1 -0
- package/dist/esm/core/agent/stages/callLLM.d.ts +28 -0
- package/dist/esm/core/agent/stages/callLLM.js +61 -24
- package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
- package/dist/esm/core/agent/stages/seed.d.ts +26 -0
- package/dist/esm/core/agent/stages/seed.js +12 -0
- package/dist/esm/core/agent/stages/seed.js.map +1 -1
- package/dist/esm/core/agent/stages/toolCalls.js +2 -4
- package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/esm/core/agent/types.d.ts +64 -0
- package/dist/esm/core/flowchartAsTool.d.ts +15 -7
- package/dist/esm/core/flowchartAsTool.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/context-bisect/llmEdgeWeigher.d.ts +8 -0
- package/dist/esm/lib/context-bisect/llmEdgeWeigher.js +0 -0
- package/dist/esm/lib/context-bisect/llmEdgeWeigher.js.map +1 -1
- package/dist/esm/lib/context-bisect/trajectory.js +3 -7
- package/dist/esm/lib/context-bisect/trajectory.js.map +1 -1
- package/dist/esm/lib/context-ledger/contextLedger.js +5 -12
- package/dist/esm/lib/context-ledger/contextLedger.js.map +1 -1
- package/dist/esm/lib/time-travel/epochs.d.ts +210 -0
- package/dist/esm/lib/time-travel/epochs.js +274 -0
- package/dist/esm/lib/time-travel/epochs.js.map +1 -0
- package/dist/esm/lib/time-travel/index.d.ts +10 -4
- package/dist/esm/lib/time-travel/index.js +10 -4
- package/dist/esm/lib/time-travel/index.js.map +1 -1
- package/dist/esm/lib/time-travel/keyedFold.d.ts +112 -0
- package/dist/esm/lib/time-travel/keyedFold.js +286 -0
- package/dist/esm/lib/time-travel/keyedFold.js.map +1 -0
- package/dist/esm/lib/time-travel/milestoneStops.d.ts +57 -36
- package/dist/esm/lib/time-travel/milestoneStops.js +75 -82
- package/dist/esm/lib/time-travel/milestoneStops.js.map +1 -1
- package/dist/esm/lib/time-travel/receipt.d.ts +496 -0
- package/dist/esm/lib/time-travel/receipt.js +434 -0
- package/dist/esm/lib/time-travel/receipt.js.map +1 -0
- package/dist/esm/lib/time-travel/servedView.d.ts +525 -0
- package/dist/esm/lib/time-travel/servedView.js +820 -0
- package/dist/esm/lib/time-travel/servedView.js.map +1 -0
- package/dist/esm/lib/time-travel/sha256.d.ts +24 -0
- package/dist/esm/lib/time-travel/sha256.js +103 -0
- package/dist/esm/lib/time-travel/sha256.js.map +1 -0
- package/dist/esm/recorders/observability/BoundaryRecorder.d.ts +21 -0
- package/dist/esm/recorders/observability/BoundaryRecorder.js +6 -0
- package/dist/esm/recorders/observability/BoundaryRecorder.js.map +1 -1
- package/dist/index.js +124 -103
- package/dist/index.js.map +1 -1
- package/dist/lib/context-bisect/llmEdgeWeigher.js +0 -0
- package/dist/lib/context-bisect/llmEdgeWeigher.js.map +1 -1
- package/dist/lib/context-bisect/trajectory.js +3 -7
- package/dist/lib/context-bisect/trajectory.js.map +1 -1
- package/dist/lib/context-ledger/contextLedger.js +5 -12
- package/dist/lib/context-ledger/contextLedger.js.map +1 -1
- package/dist/lib/time-travel/epochs.js +284 -0
- package/dist/lib/time-travel/epochs.js.map +1 -0
- package/dist/lib/time-travel/index.js +35 -5
- package/dist/lib/time-travel/index.js.map +1 -1
- package/dist/lib/time-travel/keyedFold.js +290 -0
- package/dist/lib/time-travel/keyedFold.js.map +1 -0
- package/dist/lib/time-travel/milestoneStops.js +74 -81
- package/dist/lib/time-travel/milestoneStops.js.map +1 -1
- package/dist/lib/time-travel/receipt.js +442 -0
- package/dist/lib/time-travel/receipt.js.map +1 -0
- package/dist/lib/time-travel/servedView.js +826 -0
- package/dist/lib/time-travel/servedView.js.map +1 -0
- package/dist/lib/time-travel/sha256.js +107 -0
- package/dist/lib/time-travel/sha256.js.map +1 -0
- package/dist/recorders/observability/BoundaryRecorder.js +6 -0
- package/dist/recorders/observability/BoundaryRecorder.js.map +1 -1
- package/dist/types/core/Agent.d.ts +3 -0
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/LLMCall.d.ts.map +1 -1
- package/dist/types/core/agent/buildAgentMessageApiChart.d.ts.map +1 -1
- package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/buildMessageApiChart.d.ts.map +1 -1
- package/dist/types/core/agent/composeRequest.d.ts +122 -0
- package/dist/types/core/agent/composeRequest.d.ts.map +1 -0
- package/dist/types/core/agent/stages/callLLM.d.ts +28 -0
- package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
- package/dist/types/core/agent/stages/seed.d.ts +26 -0
- package/dist/types/core/agent/stages/seed.d.ts.map +1 -1
- package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
- package/dist/types/core/agent/types.d.ts +64 -0
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/core/flowchartAsTool.d.ts +15 -7
- package/dist/types/core/flowchartAsTool.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/context-bisect/llmEdgeWeigher.d.ts +8 -0
- package/dist/types/lib/context-bisect/llmEdgeWeigher.d.ts.map +1 -1
- package/dist/types/lib/context-bisect/trajectory.d.ts.map +1 -1
- package/dist/types/lib/context-ledger/contextLedger.d.ts.map +1 -1
- package/dist/types/lib/time-travel/epochs.d.ts +211 -0
- package/dist/types/lib/time-travel/epochs.d.ts.map +1 -0
- package/dist/types/lib/time-travel/index.d.ts +10 -4
- package/dist/types/lib/time-travel/index.d.ts.map +1 -1
- package/dist/types/lib/time-travel/keyedFold.d.ts +113 -0
- package/dist/types/lib/time-travel/keyedFold.d.ts.map +1 -0
- package/dist/types/lib/time-travel/milestoneStops.d.ts +57 -36
- package/dist/types/lib/time-travel/milestoneStops.d.ts.map +1 -1
- package/dist/types/lib/time-travel/receipt.d.ts +497 -0
- package/dist/types/lib/time-travel/receipt.d.ts.map +1 -0
- package/dist/types/lib/time-travel/servedView.d.ts +526 -0
- package/dist/types/lib/time-travel/servedView.d.ts.map +1 -0
- package/dist/types/lib/time-travel/sha256.d.ts +25 -0
- package/dist/types/lib/time-travel/sha256.d.ts.map +1 -0
- package/dist/types/recorders/observability/BoundaryRecorder.d.ts +21 -0
- package/dist/types/recorders/observability/BoundaryRecorder.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,434 @@
|
|
|
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 { sha256Hex } from './sha256.js';
|
|
83
|
+
/**
|
|
84
|
+
* The separator between two fields inside one digest input — ASCII UNIT
|
|
85
|
+
* SEPARATOR, which no prompt, tool name or JSON payload can contain (`JSON.
|
|
86
|
+
* stringify` escapes it). Written as an escape rather than a literal control
|
|
87
|
+
* character so a reader can see it and a formatter cannot eat it.
|
|
88
|
+
*
|
|
89
|
+
* Without it a digest is a bare concatenation, and a bare concatenation is
|
|
90
|
+
* ambiguous: `role + content` cannot tell `'user' + 'x'` from `'use' + 'rx'`.
|
|
91
|
+
*/
|
|
92
|
+
const SEP = '\u001F';
|
|
93
|
+
/** The separator between two tool calls inside one message's digest. */
|
|
94
|
+
const CALL_SEP = '\u001C';
|
|
95
|
+
/** The separator between the fields of ONE tool call. */
|
|
96
|
+
const CALL_FIELD_SEP = '\u001D';
|
|
97
|
+
/** The separator between the top-level parts of a message's digest. */
|
|
98
|
+
const FIELD_SEP = '\u001E';
|
|
99
|
+
/**
|
|
100
|
+
* What a digest input records in place of a value JSON cannot express — a
|
|
101
|
+
* cycle, a `BigInt`, anything `stableJson` refuses. It is a MARK, not an empty
|
|
102
|
+
* string: two unserializable values are recorded as unserializable rather than
|
|
103
|
+
* as identical, which is what stops the cache comparison claiming "the strategy
|
|
104
|
+
* changed nothing" about two requests it could not read.
|
|
105
|
+
*/
|
|
106
|
+
export const UNSERIALIZABLE = '\u001F<unserializable>';
|
|
107
|
+
/**
|
|
108
|
+
* The boundary every receipt field is true at, in one sentence — exported so a
|
|
109
|
+
* renderer prints the library's own wording instead of inferring a stronger
|
|
110
|
+
* claim from a `null`.
|
|
111
|
+
*
|
|
112
|
+
* WHY IT IS A CONSTANT AND NOT A COMMENT. A reader who sees
|
|
113
|
+
* `cache.transform: 'unchanged'` will conclude "this request was not rewritten"
|
|
114
|
+
* unless something on the screen says otherwise, and the person who reads the
|
|
115
|
+
* screen is rarely the person who read the source. Print it beside the record.
|
|
116
|
+
*
|
|
117
|
+
* ── THE MECHANISM, WHICH THE PRINTED SENTENCE NO LONGER NAMES (9.88.0) ─────
|
|
118
|
+
* `buildReceipt` is called from `stages/callLLM.ts` with the request about to
|
|
119
|
+
* be passed to `LLMProvider.complete` — the PORT, this library's last sight of
|
|
120
|
+
* it. Three things sit downstream of that call and none of them is on the
|
|
121
|
+
* record: a provider decorated by the consumer (`complete()` wrapping
|
|
122
|
+
* `complete()`), a vendor adapter's own serializer, and the vendor's
|
|
123
|
+
* server-side defaults. `test/lib/time-travel/receipt-conformance.test.ts`
|
|
124
|
+
* reproduces the first of those — a decorator that appends a system suffix and
|
|
125
|
+
* a ghost tool after the mint, with `cache.transform` still reading
|
|
126
|
+
* `'unchanged'`, correctly.
|
|
127
|
+
*
|
|
128
|
+
* The sentence below used to say all of that, and it was PRINTED beside a
|
|
129
|
+
* trace by renderers that append it to a gap. A printed sentence names no
|
|
130
|
+
* module, function or call: it says which fields, what they mean here, and
|
|
131
|
+
* what to do — the rule in `test/helpers/gapProseClaims.ts`. So the mechanism
|
|
132
|
+
* lives in this comment, where a maintainer reads it and review catches its
|
|
133
|
+
* rot, and the constant says only the thing a reader must not get wrong.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* import { RECEIPT_BOUNDARY, receiptAt } from 'agentfootprint';
|
|
138
|
+
*
|
|
139
|
+
* const receipt = receiptAt(agent.getSnapshot()!, 1)!;
|
|
140
|
+
* if (receipt.cache.transform === 'unchanged') {
|
|
141
|
+
* console.log(`the cache strategy changed nothing. ${RECEIPT_BOUNDARY}`);
|
|
142
|
+
* }
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
export const RECEIPT_BOUNDARY = 'A receipt describes the request as this library last saw it. Whatever ' +
|
|
146
|
+
'handled it after that could have changed it, and nothing on the receipt ' +
|
|
147
|
+
'would show that.';
|
|
148
|
+
/** The scope key the receipt is committed under. */
|
|
149
|
+
export const RECEIPT_KEY = 'receipt';
|
|
150
|
+
/** The scope key holding the name of the tool a `'tool-forced'` output
|
|
151
|
+
* strategy put on the wire. `seed` commits it; `servedView.ts` · `servedAt`
|
|
152
|
+
* reads it — so a rebuild can NAME the forced tool without reading the
|
|
153
|
+
* receipt it is being checked against. */
|
|
154
|
+
export const FORCED_OUTPUT_TOOL_KEY = 'forcedOutputToolName';
|
|
155
|
+
/** How many hex characters of the digest a receipt keeps. 16 — 64 bits, which
|
|
156
|
+
* is far past collision range for the few dozen pieces of one run, and short
|
|
157
|
+
* enough to read in a terminal. */
|
|
158
|
+
export const RECEIPT_HASH_CHARS = 16;
|
|
159
|
+
/**
|
|
160
|
+
* The receipt's hash: run-salted SHA-256, first {@link RECEIPT_HASH_CHARS} hex
|
|
161
|
+
* characters. Exported because it is half of the conformance law — a reader
|
|
162
|
+
* that rebuilds a served view proves the rebuild by hashing it the same way.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* import { receiptAt, receiptHash, servedAt } from 'agentfootprint';
|
|
167
|
+
*
|
|
168
|
+
* const view = servedAt(agent.getSnapshot()!, 1)!;
|
|
169
|
+
* const receipt = receiptAt(agent.getSnapshot()!, 1)!;
|
|
170
|
+
* receiptHash(receipt.basis.runId, view.system.text) === receipt.system.hash; // true
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
export function receiptHash(runId, content) {
|
|
174
|
+
return sha256Hex(`${runId}${SEP}${content}`).slice(0, RECEIPT_HASH_CHARS);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* JSON with object keys in sorted order, at every depth — so two structurally
|
|
178
|
+
* equal values hash the same however they were built. Property order survives
|
|
179
|
+
* `structuredClone`, but it does NOT survive every path a value takes through
|
|
180
|
+
* a delta-encoded commit log and back, and a fingerprint that depends on
|
|
181
|
+
* insertion order is a fingerprint that reports a change nobody made.
|
|
182
|
+
*
|
|
183
|
+
* Returns `undefined` — never `''` — for anything JSON cannot express (a
|
|
184
|
+
* cycle, a `BigInt`, a value whose `toJSON` throws). It does not throw,
|
|
185
|
+
* because a receipt is bookkeeping and must never be able to stop a run; and
|
|
186
|
+
* it does not collapse, because `''` made every unreadable value equal to
|
|
187
|
+
* every other one. Two requests that both failed to serialize compared EQUAL,
|
|
188
|
+
* and the receipt wrote `transformHash: null` — "the cache strategy changed
|
|
189
|
+
* nothing" — about a pair it had not read. Callers substitute
|
|
190
|
+
* {@link UNSERIALIZABLE} where a digest needs bytes, and branch where a
|
|
191
|
+
* comparison needs truth.
|
|
192
|
+
*/
|
|
193
|
+
export function stableJson(value) {
|
|
194
|
+
const canonical = (input) => {
|
|
195
|
+
if (Array.isArray(input))
|
|
196
|
+
return input.map(canonical);
|
|
197
|
+
if (input !== null && typeof input === 'object') {
|
|
198
|
+
const source = input;
|
|
199
|
+
const out = {};
|
|
200
|
+
for (const key of Object.keys(source).sort()) {
|
|
201
|
+
const next = canonical(source[key]);
|
|
202
|
+
if (next === undefined)
|
|
203
|
+
continue;
|
|
204
|
+
out[key] = next;
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
return input;
|
|
209
|
+
};
|
|
210
|
+
try {
|
|
211
|
+
return JSON.stringify(canonical(value)) ?? undefined;
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/** `stableJson`, with the unreadable marked rather than dropped — the form a
|
|
218
|
+
* digest input takes. */
|
|
219
|
+
function digestJson(value) {
|
|
220
|
+
return stableJson(value) ?? UNSERIALIZABLE;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The bytes a tool schema's hash covers: the whole `LLMToolSchema` — `name`,
|
|
224
|
+
* `description`, `inputSchema` — as sorted-key JSON, so two schemas that are
|
|
225
|
+
* structurally the same hash the same however they were built, and with a
|
|
226
|
+
* schema JSON cannot express (a cycle, a `BigInt`) marked {@link UNSERIALIZABLE}
|
|
227
|
+
* rather than dropped or thrown on.
|
|
228
|
+
*
|
|
229
|
+
* WHAT TO PASS (9.89.0). The receipt hashes each tool AS HANDED TO THE PORT:
|
|
230
|
+
* `buildReceipt` is given the request's tool list, and that list is the
|
|
231
|
+
* `dynamicToolSchemas` the run committed, plus a forced answer tool when an
|
|
232
|
+
* output strategy adds one. `servedAt(k).tools.schemas[i]` is that committed
|
|
233
|
+
* list read back — the same shape, the same bytes — so a consumer holding a
|
|
234
|
+
* served view already holds the object this function takes. It does not take
|
|
235
|
+
* the served view, a `Tool`, or a `defineTool` definition: those carry an
|
|
236
|
+
* `execute`, `wants` and other fields the model never saw.
|
|
237
|
+
*
|
|
238
|
+
* WHY IT IS EXPORTED. A reader that rebuilds a served view proves the rebuild
|
|
239
|
+
* by hashing it the way the receipt did. `receiptHash` and `messageDigestInput`
|
|
240
|
+
* were exported in 9.88.0 for the system text, the pieces and the messages,
|
|
241
|
+
* and a consumer could verify all of them — and NOT the tools, because the
|
|
242
|
+
* serializer behind `schemaHashes` was internal. Its only options were to copy
|
|
243
|
+
* `stableJson` (a second owner of the rule, which drifts the day the digest
|
|
244
|
+
* gains a field, as the message digest did in 9.88.0) or to leave the schema
|
|
245
|
+
* rows unverified. This is the third digest half of the law, beside its two
|
|
246
|
+
* siblings, and the ONLY spelling of the schema rule: `buildReceipt` calls it
|
|
247
|
+
* too. `stableJson` stays off the root barrel for the same reason — a consumer
|
|
248
|
+
* composing `hash(stableJson(tool))` would be writing the rule a second time.
|
|
249
|
+
*
|
|
250
|
+
* @example verify every schema row of a receipt from outside
|
|
251
|
+
* ```ts
|
|
252
|
+
* import { receiptAt, receiptHash, servedAt, toolDigestInput } from 'agentfootprint';
|
|
253
|
+
*
|
|
254
|
+
* const snapshot = agent.getSnapshot()!;
|
|
255
|
+
* const view = servedAt(snapshot, 1)!;
|
|
256
|
+
* const receipt = receiptAt(snapshot, 1)!;
|
|
257
|
+
* for (const tool of view.tools.schemas) {
|
|
258
|
+
* receiptHash(receipt.basis.runId, toolDigestInput(tool)) ===
|
|
259
|
+
* receipt.tools.schemaHashes[tool.name]; // true, for every tool of every epoch
|
|
260
|
+
* }
|
|
261
|
+
* // A forced answer tool is in `schemaHashes` and NOT in `schemas` — its body
|
|
262
|
+
* // is a declared gap (`forced-tool-schema`), so there is no row to check.
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
export function toolDigestInput(tool) {
|
|
266
|
+
return digestJson(tool);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* The bytes a message's hash covers, each field behind a separator: its role,
|
|
270
|
+
* its text, its `toolCallId`, its `toolName`, the calls it asked for (id, name,
|
|
271
|
+
* arguments and `providerMeta`) and its `thinkingBlocks`.
|
|
272
|
+
*
|
|
273
|
+
* WHY `toolCallId` (9.88.0). It is the JOIN KEY — `tool_use_id` on Anthropic's
|
|
274
|
+
* wire, `tool_call_id` on OpenAI's — that pairs a tool result to the call that
|
|
275
|
+
* asked for it. Two parallel calls whose results happen to have identical text
|
|
276
|
+
* hashed the SAME without it, so a mis-pairing (the answer to `c1` filed under
|
|
277
|
+
* `c2`) was invisible to the law: exactly the defect a receipt exists to catch.
|
|
278
|
+
*
|
|
279
|
+
* WHY `toolName` TOO (9.88.0). On two shipped providers the id is not the join
|
|
280
|
+
* key at all. `adapters/llm/GeminiProvider.ts` · `toGeminiContents` pairs a
|
|
281
|
+
* `functionResponse` to its call BY NAME and says so in its own comment; the
|
|
282
|
+
* id is optional there and dropped when it is not a real one.
|
|
283
|
+
* `adapters/llm/OllamaProvider.ts` · `toOllamaMessages` puts `tool_name` on
|
|
284
|
+
* the wire and only falls back to the id. So on those providers a receipt that
|
|
285
|
+
* covered `toolCallId` alone still could not see the very mis-pairing the
|
|
286
|
+
* field was added for: two `role:'tool'` messages with identical text and
|
|
287
|
+
* SWAPPED names fingerprinted identically. It rides as its own field behind
|
|
288
|
+
* the separator, beside the id, so a name moving between two results changes
|
|
289
|
+
* both hashes and neither can absorb the other's bytes.
|
|
290
|
+
*
|
|
291
|
+
* WHY `thinkingBlocks` and `providerMeta` (9.88.0). `adapters/types.ts` is
|
|
292
|
+
* explicit that a signed thinking block MUST be echoed byte-exact or the API
|
|
293
|
+
* rejects the turn, and `providerMeta` round-trips vendor state (Gemini's
|
|
294
|
+
* `thoughtSignature`, without which the turn after a tool call is refused).
|
|
295
|
+
* Both are things the model receives; a fingerprint that omitted them said two
|
|
296
|
+
* requests were the same when one of them would be rejected. They enter as a
|
|
297
|
+
* `stableJson` FINGERPRINT, so nothing quotable is added — a signature is an
|
|
298
|
+
* opaque token, not content.
|
|
299
|
+
*
|
|
300
|
+
* Still deliberately NOT covered: `injectedBy` (stripped before the request
|
|
301
|
+
* exists) and `ephemeral` (a persistence flag, invisible to the model). Two
|
|
302
|
+
* messages that hash the same are the same thing said to the model.
|
|
303
|
+
*/
|
|
304
|
+
export function messageDigestInput(message) {
|
|
305
|
+
const calls = (message.toolCalls ?? []).map((call) => [
|
|
306
|
+
call.id,
|
|
307
|
+
call.name,
|
|
308
|
+
digestJson(call.args),
|
|
309
|
+
call.providerMeta === undefined ? '' : digestJson(call.providerMeta),
|
|
310
|
+
].join(CALL_FIELD_SEP));
|
|
311
|
+
return [
|
|
312
|
+
message.role,
|
|
313
|
+
message.content,
|
|
314
|
+
message.toolCallId ?? '',
|
|
315
|
+
message.toolName ?? '',
|
|
316
|
+
calls.join(CALL_SEP),
|
|
317
|
+
message.thinkingBlocks === undefined || message.thinkingBlocks.length === 0
|
|
318
|
+
? ''
|
|
319
|
+
: digestJson(message.thinkingBlocks),
|
|
320
|
+
].join(FIELD_SEP);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* The sampling knobs off the request the port was handed.
|
|
324
|
+
*
|
|
325
|
+
* Duck-typed and value-conditional: an agent that set no dial records no key,
|
|
326
|
+
* so `params` is `{}` rather than a row of `undefined`s claiming defaults the
|
|
327
|
+
* library never sent.
|
|
328
|
+
*/
|
|
329
|
+
function paramsOf(request) {
|
|
330
|
+
if (request === null || typeof request !== 'object')
|
|
331
|
+
return {};
|
|
332
|
+
const req = request;
|
|
333
|
+
const stop = Array.isArray(req.stop) ? req.stop.filter((s) => typeof s === 'string') : undefined;
|
|
334
|
+
return {
|
|
335
|
+
...(typeof req.temperature === 'number' && { temperature: req.temperature }),
|
|
336
|
+
...(typeof req.maxTokens === 'number' && { maxTokens: req.maxTokens }),
|
|
337
|
+
...(typeof req.thinking?.budget === 'number' && { thinkingBudget: req.thinking.budget }),
|
|
338
|
+
...(stop !== undefined && stop.length > 0 && { stop }),
|
|
339
|
+
...(typeof req.toolChoice?.type === 'string' && {
|
|
340
|
+
toolChoice: {
|
|
341
|
+
type: req.toolChoice.type,
|
|
342
|
+
...(typeof req.toolChoice.name === 'string' && { name: req.toolChoice.name }),
|
|
343
|
+
},
|
|
344
|
+
}),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Mint the receipt for one composed request.
|
|
349
|
+
*
|
|
350
|
+
* Pure and total: every branch here is arithmetic over what it was handed, so
|
|
351
|
+
* a receipt cannot fail a call. The one judgement it makes is the cache
|
|
352
|
+
* comparison, and it makes it in three values rather than two: `'unchanged'`
|
|
353
|
+
* exactly when the strategy handed back a request that serializes identically
|
|
354
|
+
* to the one it was given, `'rewritten'` when it did not, and `'unknown'` when
|
|
355
|
+
* either request could not be serialized at all — because "I could not read
|
|
356
|
+
* them" is not the same claim as "they were the same".
|
|
357
|
+
*
|
|
358
|
+
* Every value it produces is true AT THE PROVIDER PORT and nowhere past it —
|
|
359
|
+
* see {@link RECEIPT_BOUNDARY}.
|
|
360
|
+
*/
|
|
361
|
+
export function buildReceipt(input) {
|
|
362
|
+
const hash = (content) => receiptHash(input.runId, content);
|
|
363
|
+
const schemaHashes = {};
|
|
364
|
+
for (const tool of input.tools)
|
|
365
|
+
schemaHashes[tool.name] = hash(toolDigestInput(tool));
|
|
366
|
+
// The cache verdict, three-valued. A pair the receipt could not read is
|
|
367
|
+
// 'unknown' — never 'unchanged', which is what `''` used to make it.
|
|
368
|
+
const base = stableJson(input.baseRequest);
|
|
369
|
+
const prepared = stableJson(input.preparedRequest);
|
|
370
|
+
const comparable = base !== undefined && prepared !== undefined;
|
|
371
|
+
const transform = !comparable
|
|
372
|
+
? 'unknown'
|
|
373
|
+
: base === prepared
|
|
374
|
+
? 'unchanged'
|
|
375
|
+
: 'rewritten';
|
|
376
|
+
return {
|
|
377
|
+
system: {
|
|
378
|
+
hash: hash(input.systemText),
|
|
379
|
+
chars: input.systemText.length,
|
|
380
|
+
pieces: input.systemPieces.map((piece) => ({
|
|
381
|
+
hash: hash(piece.text),
|
|
382
|
+
slot: piece.slot,
|
|
383
|
+
source: piece.source,
|
|
384
|
+
})),
|
|
385
|
+
},
|
|
386
|
+
messages: {
|
|
387
|
+
count: input.messages.length + input.requestOnly.length,
|
|
388
|
+
entries: input.messages.map((message) => ({
|
|
389
|
+
role: message.role,
|
|
390
|
+
hash: hash(messageDigestInput(message)),
|
|
391
|
+
...(message.toolCallId !== undefined && { key: message.toolCallId }),
|
|
392
|
+
})),
|
|
393
|
+
requestOnly: input.requestOnly.map((line) => ({
|
|
394
|
+
role: line.message.role,
|
|
395
|
+
hash: hash(messageDigestInput(line.message)),
|
|
396
|
+
reason: line.reason,
|
|
397
|
+
})),
|
|
398
|
+
},
|
|
399
|
+
tools: {
|
|
400
|
+
names: input.tools.map((tool) => tool.name),
|
|
401
|
+
schemaHashes,
|
|
402
|
+
forced: input.forced,
|
|
403
|
+
withheld: input.withheld,
|
|
404
|
+
},
|
|
405
|
+
cache: {
|
|
406
|
+
transform,
|
|
407
|
+
transformHash: transform === 'rewritten' ? hash(prepared) : null,
|
|
408
|
+
markersApplied: (input.markersApplied ?? []).map((marker) => ({
|
|
409
|
+
field: marker.field,
|
|
410
|
+
boundaryIndex: marker.boundaryIndex,
|
|
411
|
+
ttl: marker.ttl,
|
|
412
|
+
})),
|
|
413
|
+
},
|
|
414
|
+
// The PREPARED request, not the base one: the port is handed what the cache
|
|
415
|
+
// strategy returned, and a strategy that rewrote maxTokens must not leave
|
|
416
|
+
// five receipt fields describing a request nobody sent. See
|
|
417
|
+
// {@link RECEIPT_BOUNDARY} — this is the boundary those fields are true at.
|
|
418
|
+
params: paramsOf(input.preparedRequest),
|
|
419
|
+
...(input.omittedForAttention !== undefined &&
|
|
420
|
+
input.omittedForAttention.count > 0 && {
|
|
421
|
+
omittedForAttention: {
|
|
422
|
+
count: input.omittedForAttention.count,
|
|
423
|
+
hashes: input.omittedForAttention.summaries.map((summary) => hash(summary)),
|
|
424
|
+
},
|
|
425
|
+
}),
|
|
426
|
+
basis: {
|
|
427
|
+
epoch: input.epoch,
|
|
428
|
+
runId: input.runId,
|
|
429
|
+
model: input.model,
|
|
430
|
+
provider: input.provider,
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
//# sourceMappingURL=receipt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"receipt.js","sourceRoot":"","sources":["../../../../src/lib/time-travel/receipt.ts"],"names":[],"mappings":"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"}
|