@esankhan3/anvil-agent-core 0.3.0 → 0.5.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/README.md +1 -1
- package/dist/__tests__/gemini-adapter-recording.test.d.ts +11 -0
- package/dist/__tests__/gemini-adapter-recording.test.d.ts.map +1 -0
- package/dist/__tests__/gemini-adapter-recording.test.js +108 -0
- package/dist/__tests__/gemini-adapter-recording.test.js.map +1 -0
- package/dist/__tests__/legacy-adapter-language-model.test.d.ts +11 -0
- package/dist/__tests__/legacy-adapter-language-model.test.d.ts.map +1 -0
- package/dist/__tests__/legacy-adapter-language-model.test.js +102 -0
- package/dist/__tests__/legacy-adapter-language-model.test.js.map +1 -0
- package/dist/__tests__/mcp-merged-executor.test.js +4 -6
- package/dist/__tests__/mcp-merged-executor.test.js.map +1 -1
- package/dist/__tests__/ollama-adapter-recording.test.d.ts +15 -0
- package/dist/__tests__/ollama-adapter-recording.test.d.ts.map +1 -0
- package/dist/__tests__/ollama-adapter-recording.test.js +128 -0
- package/dist/__tests__/ollama-adapter-recording.test.js.map +1 -0
- package/dist/__tests__/openrouter-prefill.integration.test.d.ts +19 -0
- package/dist/__tests__/openrouter-prefill.integration.test.d.ts.map +1 -0
- package/dist/__tests__/openrouter-prefill.integration.test.js +410 -0
- package/dist/__tests__/openrouter-prefill.integration.test.js.map +1 -0
- package/dist/__tests__/prefill-truncate.test.d.ts +8 -0
- package/dist/__tests__/prefill-truncate.test.d.ts.map +1 -0
- package/dist/__tests__/prefill-truncate.test.js +75 -0
- package/dist/__tests__/prefill-truncate.test.js.map +1 -0
- package/dist/__tests__/router-circuit-breaker.test.js +1 -1
- package/dist/__tests__/router-circuit-breaker.test.js.map +1 -1
- package/dist/__tests__/router-classify-unified.test.d.ts +12 -0
- package/dist/__tests__/router-classify-unified.test.d.ts.map +1 -0
- package/dist/__tests__/router-classify-unified.test.js +108 -0
- package/dist/__tests__/router-classify-unified.test.js.map +1 -0
- package/dist/__tests__/router-fallback.test.js +1 -1
- package/dist/__tests__/router-fallback.test.js.map +1 -1
- package/dist/__tests__/router-model-registry.test.js +24 -0
- package/dist/__tests__/router-model-registry.test.js.map +1 -1
- package/dist/__tests__/router-retry.test.js +3 -5
- package/dist/__tests__/router-retry.test.js.map +1 -1
- package/dist/__tests__/router-runagent.test.d.ts +14 -0
- package/dist/__tests__/router-runagent.test.d.ts.map +1 -0
- package/dist/__tests__/router-runagent.test.js +95 -0
- package/dist/__tests__/router-runagent.test.js.map +1 -0
- package/dist/__tests__/router-scaffold.test.js +2 -1
- package/dist/__tests__/router-scaffold.test.js.map +1 -1
- package/dist/__tests__/router-spend-ledger.test.js +1 -1
- package/dist/__tests__/router-spend-ledger.test.js.map +1 -1
- package/dist/__tests__/router-stream-fallback.test.d.ts +13 -0
- package/dist/__tests__/router-stream-fallback.test.d.ts.map +1 -0
- package/dist/__tests__/router-stream-fallback.test.js +129 -0
- package/dist/__tests__/router-stream-fallback.test.js.map +1 -0
- package/dist/__tests__/router-telemetry.test.js +3 -1
- package/dist/__tests__/router-telemetry.test.js.map +1 -1
- package/dist/__tests__/util/mock-upstream.d.ts +77 -0
- package/dist/__tests__/util/mock-upstream.d.ts.map +1 -0
- package/dist/__tests__/util/mock-upstream.js +219 -0
- package/dist/__tests__/util/mock-upstream.js.map +1 -0
- package/dist/adk-adapter.d.ts.map +1 -1
- package/dist/adk-adapter.js +51 -0
- package/dist/adk-adapter.js.map +1 -1
- package/dist/agent/session/adapter.d.ts +15 -0
- package/dist/agent/session/adapter.d.ts.map +1 -1
- package/dist/agent/session/adapter.js +3 -0
- package/dist/agent/session/adapter.js.map +1 -1
- package/dist/agent/session/index.d.ts +1 -0
- package/dist/agent/session/index.d.ts.map +1 -1
- package/dist/agent/session/index.js +1 -0
- package/dist/agent/session/index.js.map +1 -1
- package/dist/agent/session/language-model-bridge.d.ts.map +1 -1
- package/dist/agent/session/language-model-bridge.js +22 -0
- package/dist/agent/session/language-model-bridge.js.map +1 -1
- package/dist/agent/session/legacy-adapter-language-model.d.ts +27 -0
- package/dist/agent/session/legacy-adapter-language-model.d.ts.map +1 -0
- package/dist/agent/session/legacy-adapter-language-model.js +221 -0
- package/dist/agent/session/legacy-adapter-language-model.js.map +1 -0
- package/dist/agent/session/types.d.ts +20 -0
- package/dist/agent/session/types.d.ts.map +1 -1
- package/dist/agent/session/types.js.map +1 -1
- package/dist/claude-adapter.d.ts.map +1 -1
- package/dist/claude-adapter.js +27 -0
- package/dist/claude-adapter.js.map +1 -1
- package/dist/gemini-adapter.d.ts.map +1 -1
- package/dist/gemini-adapter.js +46 -0
- package/dist/gemini-adapter.js.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +17 -21
- package/dist/mcp/client.js.map +1 -1
- package/dist/ollama-adapter.d.ts.map +1 -1
- package/dist/ollama-adapter.js +118 -8
- package/dist/ollama-adapter.js.map +1 -1
- package/dist/openrouter-adapter.d.ts.map +1 -1
- package/dist/openrouter-adapter.js +289 -15
- package/dist/openrouter-adapter.js.map +1 -1
- package/dist/prefill/strip.d.ts +49 -0
- package/dist/prefill/strip.d.ts.map +1 -0
- package/dist/prefill/strip.js +93 -0
- package/dist/prefill/strip.js.map +1 -0
- package/dist/prefill/translate.d.ts +55 -0
- package/dist/prefill/translate.d.ts.map +1 -0
- package/dist/prefill/translate.js +162 -0
- package/dist/prefill/translate.js.map +1 -0
- package/dist/prefill/truncate.d.ts +62 -0
- package/dist/prefill/truncate.d.ts.map +1 -0
- package/dist/prefill/truncate.js +77 -0
- package/dist/prefill/truncate.js.map +1 -0
- package/dist/router/errors.d.ts +24 -7
- package/dist/router/errors.d.ts.map +1 -1
- package/dist/router/errors.js +71 -14
- package/dist/router/errors.js.map +1 -1
- package/dist/router/index.d.ts +3 -2
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +2 -1
- package/dist/router/index.js.map +1 -1
- package/dist/router/model-registry.d.ts +22 -0
- package/dist/router/model-registry.d.ts.map +1 -1
- package/dist/router/model-registry.js +64 -1
- package/dist/router/model-registry.js.map +1 -1
- package/dist/router/provider-registry-resolver.d.ts +25 -0
- package/dist/router/provider-registry-resolver.d.ts.map +1 -0
- package/dist/router/provider-registry-resolver.js +84 -0
- package/dist/router/provider-registry-resolver.js.map +1 -0
- package/dist/router/retry.d.ts +3 -0
- package/dist/router/retry.d.ts.map +1 -1
- package/dist/router/retry.js +4 -1
- package/dist/router/retry.js.map +1 -1
- package/dist/router/router.d.ts +79 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +256 -2
- package/dist/router/router.js.map +1 -1
- package/dist/router/types.d.ts +1 -1
- package/dist/router/types.d.ts.map +1 -1
- package/dist/router/types.js +1 -0
- package/dist/router/types.js.map +1 -1
- package/dist/turn-recorder/__tests__/turn-recorder.test.d.ts +20 -0
- package/dist/turn-recorder/__tests__/turn-recorder.test.d.ts.map +1 -0
- package/dist/turn-recorder/__tests__/turn-recorder.test.js +282 -0
- package/dist/turn-recorder/__tests__/turn-recorder.test.js.map +1 -0
- package/dist/turn-recorder/hash.d.ts +10 -0
- package/dist/turn-recorder/hash.d.ts.map +1 -0
- package/dist/turn-recorder/hash.js +35 -0
- package/dist/turn-recorder/hash.js.map +1 -0
- package/dist/turn-recorder/index.d.ts +119 -0
- package/dist/turn-recorder/index.d.ts.map +1 -0
- package/dist/turn-recorder/index.js +326 -0
- package/dist/turn-recorder/index.js.map +1 -0
- package/dist/turn-recorder/types.d.ts +227 -0
- package/dist/turn-recorder/types.d.ts.map +1 -0
- package/dist/turn-recorder/types.js +20 -0
- package/dist/turn-recorder/types.js.map +1 -0
- package/dist/types.d.ts +35 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TurnRecorder — splits one LLM invocation into per-turn sub-effects so
|
|
3
|
+
* the durable engine can replay tool results and resume mid-stream after
|
|
4
|
+
* a chain-fallback model swap.
|
|
5
|
+
*
|
|
6
|
+
* Effect-name vocabulary (per the v2 ADR §2.1):
|
|
7
|
+
* - `turn:<N>:assistant-start` — request payload (recorded, NOT input-hash-guarded: see startTurn)
|
|
8
|
+
* - `turn:<N>:tool_use:<M>` — intent (name + parsed args)
|
|
9
|
+
* - `turn:<N>:tool_result:<M>` — neutral result; replay short-circuits the executor
|
|
10
|
+
* - `turn:<N>:assistant-end` — completed assistant text + provenance
|
|
11
|
+
*
|
|
12
|
+
* Surface note vs ADR §2.5: the ADR sketches recordToolUse / recordToolResult
|
|
13
|
+
* as two separate calls. That split has no clean replay-vs-execute boundary
|
|
14
|
+
* through the `effect(name, fn)` interface — once the runtime accepts a fn
|
|
15
|
+
* for `tool_result:N`, fn already either ran (live) or didn't (replay), and
|
|
16
|
+
* the caller can't tell from outside. So the implementation collapses to a
|
|
17
|
+
* single `runTool(turn, name, args, key, exec)` that owns the
|
|
18
|
+
* intent-record → execute-or-replay → result-record sequence atomically.
|
|
19
|
+
* Same on-disk shape; cleaner adapter code; ADR §2.5 to be amended in
|
|
20
|
+
* follow-up.
|
|
21
|
+
*
|
|
22
|
+
* NullTurnRecorder factory: pass a `createNullRuntime()`-built runtime to
|
|
23
|
+
* make every call a no-op pass-through with live execution. Adapters that
|
|
24
|
+
* haven't been ported through their own H-phase use this default, so the
|
|
25
|
+
* recorder calls compile and run today without behavior change.
|
|
26
|
+
*/
|
|
27
|
+
const DEFAULT_TOOL_RESULT_CAP_BYTES = 65_536;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the persisted-tool-result cap. `ANVIL_DURABLE_MAX_TOOL_RESULT_BYTES`
|
|
30
|
+
* overrides the 64 KiB default; `0` disables the cap (persist the full result).
|
|
31
|
+
*/
|
|
32
|
+
function toolResultPersistCapBytes() {
|
|
33
|
+
const raw = process.env.ANVIL_DURABLE_MAX_TOOL_RESULT_BYTES;
|
|
34
|
+
if (raw === undefined)
|
|
35
|
+
return DEFAULT_TOOL_RESULT_CAP_BYTES;
|
|
36
|
+
const n = Number(raw);
|
|
37
|
+
if (n === 0)
|
|
38
|
+
return Infinity; // explicit opt-out — persist full payloads
|
|
39
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_TOOL_RESULT_CAP_BYTES;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Cap the `content` of a NeutralToolResult for durable storage. Only string
|
|
43
|
+
* content past the cap is truncated (with a marker); the result keeps a valid
|
|
44
|
+
* NeutralToolResult shape so resume's message reconstruction still works. The
|
|
45
|
+
* live caller is unaffected — this runs only on the persisted copy via
|
|
46
|
+
* `EffectOptions.persistTransform`.
|
|
47
|
+
*/
|
|
48
|
+
function capToolResultForPersist(result) {
|
|
49
|
+
const cap = toolResultPersistCapBytes();
|
|
50
|
+
if (cap === Infinity || !result || typeof result !== 'object')
|
|
51
|
+
return result;
|
|
52
|
+
const r = result;
|
|
53
|
+
if (typeof r.content === 'string' && r.content.length > cap) {
|
|
54
|
+
const omitted = r.content.length - cap;
|
|
55
|
+
return {
|
|
56
|
+
...r,
|
|
57
|
+
content: r.content.slice(0, cap) +
|
|
58
|
+
`\n…[${omitted} bytes truncated for durable storage; full output was sent to the model live]`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
const DEFAULT_UUID = () => (globalThis.crypto?.randomUUID?.() ?? fallbackUuid());
|
|
64
|
+
function fallbackUuid() {
|
|
65
|
+
// Minimal v4 fallback for runtimes without globalThis.crypto.
|
|
66
|
+
const hex = (n) => n.toString(16).padStart(2, '0');
|
|
67
|
+
const bytes = new Uint8Array(16);
|
|
68
|
+
for (let i = 0; i < 16; i++)
|
|
69
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
70
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
71
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
72
|
+
const h = Array.from(bytes, hex);
|
|
73
|
+
return `${h.slice(0, 4).join('')}-${h.slice(4, 6).join('')}-${h.slice(6, 8).join('')}-${h.slice(8, 10).join('')}-${h.slice(10, 16).join('')}`;
|
|
74
|
+
}
|
|
75
|
+
export class TurnRecorder {
|
|
76
|
+
deps;
|
|
77
|
+
/** Monotonic turn counter, scoped to this recorder (one per adapter.run).
|
|
78
|
+
* Seeded from deps.initialTurn so a sendInput-resume recorder continues
|
|
79
|
+
* past the prior adapter's turns (§2.5.1). */
|
|
80
|
+
turnCounter;
|
|
81
|
+
/** Per-turn tool_use index counter — bumped serially per call. */
|
|
82
|
+
toolIdxByTurn = new Map();
|
|
83
|
+
/** Per-turn uuid stash so flushPartial can echo the right one. */
|
|
84
|
+
turnUuids = new Map();
|
|
85
|
+
/** Per-turn (model, provider) stash from startTurn so endTurn can
|
|
86
|
+
* stamp the assistant-end payload — the H3 cost rollup prices each
|
|
87
|
+
* turn by its authoring model. */
|
|
88
|
+
turnMeta = new Map();
|
|
89
|
+
/** Per-repo effect-name scope (e.g. `service-a:`); '' for single-stage. */
|
|
90
|
+
prefix;
|
|
91
|
+
constructor(deps) {
|
|
92
|
+
this.deps = deps;
|
|
93
|
+
this.turnCounter = deps.initialTurn ?? 0;
|
|
94
|
+
this.prefix = deps.effectPrefix ?? '';
|
|
95
|
+
}
|
|
96
|
+
/** Scoped effect name for a turn sub-effect. */
|
|
97
|
+
key(name) {
|
|
98
|
+
return `${this.prefix}${name}`;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Open a new assistant turn. Records the request payload as an
|
|
102
|
+
* `assistant-start` effect; on replay, returns the recorded turn's
|
|
103
|
+
* full transcript so the adapter can skip the upstream call entirely.
|
|
104
|
+
*/
|
|
105
|
+
async startTurn(req) {
|
|
106
|
+
const turn = this.turnCounter++;
|
|
107
|
+
const turnUuid = this.uuid();
|
|
108
|
+
this.turnUuids.set(turn, turnUuid);
|
|
109
|
+
this.toolIdxByTurn.set(turn, 0);
|
|
110
|
+
this.turnMeta.set(turn, { model: req.model, provider: req.provider });
|
|
111
|
+
// Record the start. On replay this is a no-op that returns the
|
|
112
|
+
// recorded payload — we don't use the return value here because
|
|
113
|
+
// the meaningful replay signal is whether `assistant-end` exists.
|
|
114
|
+
//
|
|
115
|
+
// Deliberately NO `idempotencyKey` (input-hash) guard on this effect.
|
|
116
|
+
// The turn's input (system prompt + messages) embeds live-queried,
|
|
117
|
+
// process-local context: the Knowledge Base graph and memory block are
|
|
118
|
+
// re-queried in a fresh process on resume (the "stable" prompt getters
|
|
119
|
+
// only memoise WITHIN one run, not across a restart), and a cross-vendor
|
|
120
|
+
// continuation legitimately swaps the model/prefill. So the input is NOT
|
|
121
|
+
// byte-stable across resume — and it doesn't need to be: when
|
|
122
|
+
// `assistant-end` exists the recorded transcript is replayed verbatim
|
|
123
|
+
// (the upstream call is skipped, so the rebuilt prompt is never sent);
|
|
124
|
+
// when it doesn't, the turn re-runs live (the whole point of resume).
|
|
125
|
+
// Hashing the input into a FATAL determinism guard turned that benign
|
|
126
|
+
// drift into a `DeterminismViolationError` and broke resume entirely.
|
|
127
|
+
// The effect-SEQUENCE guard (name + idx) still catches genuine step-logic
|
|
128
|
+
// divergence; `runTool` still pins tool args via its own idempotencyKey.
|
|
129
|
+
await this.deps.runtime.effect(this.key(`turn:${turn}:assistant-start`), async () => ({
|
|
130
|
+
turnUuid,
|
|
131
|
+
model: req.model,
|
|
132
|
+
provider: req.provider,
|
|
133
|
+
prefillTurnUuid: req.prefill?.turnUuid ?? null,
|
|
134
|
+
// §Tier 2: persist the phase's user prompt so a stateful resume can
|
|
135
|
+
// reconstruct prior turns WITH their prompts. Additive only — never
|
|
136
|
+
// re-read by replay-skip (which reads assistant-end).
|
|
137
|
+
userPrompt: req.userPrompt ?? null,
|
|
138
|
+
}), { smallResult: true });
|
|
139
|
+
// H3 replay-skip: if this turn's `assistant-end` is already in the
|
|
140
|
+
// durable log, the whole turn ran on a prior process. Reconstruct
|
|
141
|
+
// it so the adapter skips the upstream call and re-appends the
|
|
142
|
+
// recorded native history verbatim. `peekRecorded` is a pure read
|
|
143
|
+
// (no cursor/idx mutation) — the adapter still calls runTool/endTurn
|
|
144
|
+
// in order to advance the replay cursor over the recorded sub-effects.
|
|
145
|
+
const replayed = this.tryReconstructTurn(turn, turnUuid);
|
|
146
|
+
if (replayed)
|
|
147
|
+
return { turn, turnUuid, replayed };
|
|
148
|
+
return { turn, turnUuid };
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Peek the durable log for a fully-recorded turn N. Returns the
|
|
152
|
+
* reconstructed `AssistantTurn` iff `turn:N:assistant-end` exists;
|
|
153
|
+
* undefined on the live path (peek unsupported, or end not yet
|
|
154
|
+
* recorded). Pure reads only — no effect cursor is advanced here.
|
|
155
|
+
*/
|
|
156
|
+
tryReconstructTurn(turn, turnUuid) {
|
|
157
|
+
const peek = this.deps.runtime.peekRecorded?.bind(this.deps.runtime);
|
|
158
|
+
if (!peek)
|
|
159
|
+
return undefined;
|
|
160
|
+
const end = peek(this.key(`turn:${turn}:assistant-end`));
|
|
161
|
+
if (!end)
|
|
162
|
+
return undefined;
|
|
163
|
+
const toolUses = [];
|
|
164
|
+
const toolResults = [];
|
|
165
|
+
for (let m = 0;; m += 1) {
|
|
166
|
+
const tu = peek(this.key(`turn:${turn}:tool_use:${m}`));
|
|
167
|
+
if (!tu)
|
|
168
|
+
break;
|
|
169
|
+
const tr = peek(this.key(`turn:${turn}:tool_result:${m}`));
|
|
170
|
+
toolUses.push({
|
|
171
|
+
// `id` isn't in the tool_use payload; the matching tool_result
|
|
172
|
+
// carries `toolUseId` (the provider id). Used only for cosmetic
|
|
173
|
+
// re-emit on replay — runTool keys off (name, idx, idempotencyKey).
|
|
174
|
+
id: tr?.toolUseId ?? '',
|
|
175
|
+
name: tu.name ?? '',
|
|
176
|
+
arguments: tu.arguments ?? {},
|
|
177
|
+
idempotencyKey: tu.idempotencyKey ?? '',
|
|
178
|
+
});
|
|
179
|
+
if (tr)
|
|
180
|
+
toolResults.push(tr);
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
turnUuid,
|
|
184
|
+
turn,
|
|
185
|
+
text: end.text ?? '',
|
|
186
|
+
stopReason: end.stopReason ?? 'end_turn',
|
|
187
|
+
usage: end.usage ?? { inputTokens: 0, outputTokens: 0 },
|
|
188
|
+
provenance: end.provenance ?? { segments: [] },
|
|
189
|
+
toolUses,
|
|
190
|
+
toolResults,
|
|
191
|
+
historyDelta: end.historyDelta ?? [],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Record a tool intent + execute (or replay) the tool. Combines what
|
|
196
|
+
* the ADR drafted as two calls into one atomic sequence. The
|
|
197
|
+
* `idempotencyKey` is hashed by the caller (or fed in pre-computed)
|
|
198
|
+
* and guards against args drift on replay.
|
|
199
|
+
*
|
|
200
|
+
* Replay behavior: the inner `tool_result:M` effect is replayed
|
|
201
|
+
* verbatim — `exec()` is NOT invoked. Side effects (file writes, bash
|
|
202
|
+
* runs) stay as the prior model left them.
|
|
203
|
+
*/
|
|
204
|
+
async runTool(turn, name, args, idempotencyKey, exec) {
|
|
205
|
+
const idx = this.bumpToolIdx(turn);
|
|
206
|
+
await this.deps.runtime.effect(this.key(`turn:${turn}:tool_use:${idx}`), async () => ({ name, arguments: args, idempotencyKey }), { idempotencyKey, smallResult: true });
|
|
207
|
+
const result = await this.deps.runtime.effect(this.key(`turn:${turn}:tool_result:${idx}`), exec, {
|
|
208
|
+
// Cap the PERSISTED copy of large tool results (file dumps, bash
|
|
209
|
+
// stdout). A single agentic turn can fire many tool calls; persisting
|
|
210
|
+
// multi-MB payloads synchronously into SQLite blocks the event loop
|
|
211
|
+
// and is the dominant "LLM feels slow" cost. The live model still
|
|
212
|
+
// receives the full result — only the durable copy (replayed on
|
|
213
|
+
// resume) is truncated. Default cap 64 KiB; set
|
|
214
|
+
// ANVIL_DURABLE_MAX_TOOL_RESULT_BYTES=0 to disable.
|
|
215
|
+
persistTransform: (r) => capToolResultForPersist(r),
|
|
216
|
+
});
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Close an assistant turn with the completed text, stop reason, usage,
|
|
221
|
+
* and provenance. Records `assistant-end`; on replay this is a no-op.
|
|
222
|
+
*/
|
|
223
|
+
async endTurn(turn, text, stopReason, usage, provenance,
|
|
224
|
+
/**
|
|
225
|
+
* Provider-native messages appended to history this turn (assistant
|
|
226
|
+
* + tool-result messages). Recorded so crash-resume re-appends them
|
|
227
|
+
* byte-for-byte (§ AssistantTurn.historyDelta). Default [] for the
|
|
228
|
+
* terminal no-tool turn (nothing appended). Opaque to the recorder.
|
|
229
|
+
*/
|
|
230
|
+
historyDelta = []) {
|
|
231
|
+
const meta = this.turnMeta.get(turn);
|
|
232
|
+
await this.deps.runtime.effect(this.key(`turn:${turn}:assistant-end`), async () => ({
|
|
233
|
+
text,
|
|
234
|
+
stopReason,
|
|
235
|
+
usage,
|
|
236
|
+
provenance,
|
|
237
|
+
historyDelta,
|
|
238
|
+
// model + provider stamp lets the H3 per-model cost rollup price
|
|
239
|
+
// this turn's tokens by its authoring model.
|
|
240
|
+
model: meta?.model,
|
|
241
|
+
provider: meta?.provider,
|
|
242
|
+
}), { smallResult: text.length < 4_000 });
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Flush an in-progress assistant text to the partial sink. Called from
|
|
246
|
+
* an adapter's catch block BEFORE re-throwing UpstreamError. Does NOT
|
|
247
|
+
* go through `effect()` — partial events are not subject to the
|
|
248
|
+
* (name, idx) counter; they're durable signals scoped by turnUuid.
|
|
249
|
+
*
|
|
250
|
+
* Safe to call when text is empty (sink decides whether to bother).
|
|
251
|
+
* Safe to call multiple times for the same turn — the sink handles
|
|
252
|
+
* dedup via turnUuid + recordedAt.
|
|
253
|
+
*/
|
|
254
|
+
flushPartial(turn, text, toolUsesEmitted, reason) {
|
|
255
|
+
const turnUuid = this.turnUuids.get(turn);
|
|
256
|
+
if (!turnUuid)
|
|
257
|
+
return; // startTurn was never called — nothing to flush.
|
|
258
|
+
const partial = {
|
|
259
|
+
runId: this.deps.runId,
|
|
260
|
+
stepId: this.deps.stepId,
|
|
261
|
+
turnUuid,
|
|
262
|
+
turn,
|
|
263
|
+
text,
|
|
264
|
+
toolUsesEmitted,
|
|
265
|
+
reason,
|
|
266
|
+
recordedAt: this.nowIso(),
|
|
267
|
+
};
|
|
268
|
+
try {
|
|
269
|
+
this.deps.partialSink(partial);
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
// Sink should be a fire-and-forget; if it throws we still re-throw
|
|
273
|
+
// the upstream error — log to stderr so the failure is visible.
|
|
274
|
+
process.stderr.write(`[turn-recorder] partialSink threw: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// ─────────────────────────────────────────────────────────────────
|
|
278
|
+
// Internals
|
|
279
|
+
// ─────────────────────────────────────────────────────────────────
|
|
280
|
+
bumpToolIdx(turn) {
|
|
281
|
+
const next = this.toolIdxByTurn.get(turn) ?? 0;
|
|
282
|
+
this.toolIdxByTurn.set(turn, next + 1);
|
|
283
|
+
return next;
|
|
284
|
+
}
|
|
285
|
+
uuid() {
|
|
286
|
+
return this.deps.uuid?.() ?? DEFAULT_UUID();
|
|
287
|
+
}
|
|
288
|
+
nowIso() {
|
|
289
|
+
return this.deps.nowIso?.() ?? new Date().toISOString();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
293
|
+
// Null runtime — for adapters that haven't yet been wired to a real
|
|
294
|
+
// EffectRuntime. Calls execute live; nothing is recorded; replay is a
|
|
295
|
+
// no-op (since nothing is on disk). Lets us land the H1 recorder calls
|
|
296
|
+
// without forcing every adapter or every call-site to thread a real
|
|
297
|
+
// EffectRuntime through agent-core's bridge today.
|
|
298
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
299
|
+
export function createNullEffectRuntime() {
|
|
300
|
+
return {
|
|
301
|
+
async effect(_name, fn) {
|
|
302
|
+
return await fn();
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
export function createNullPartialSink() {
|
|
307
|
+
return () => { };
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Convenience factory: a TurnRecorder that does the right structural
|
|
311
|
+
* thing (records → executes → emits) but persists nothing. Adapters use
|
|
312
|
+
* this when their caller hasn't supplied a real runtime + sink. Identical
|
|
313
|
+
* observable behavior to today's adapters; opens the door for the H2+
|
|
314
|
+
* wiring to inject the real instances without touching adapter code
|
|
315
|
+
* again.
|
|
316
|
+
*/
|
|
317
|
+
export function createNullTurnRecorder(opts) {
|
|
318
|
+
return new TurnRecorder({
|
|
319
|
+
runtime: createNullEffectRuntime(),
|
|
320
|
+
partialSink: createNullPartialSink(),
|
|
321
|
+
runId: opts?.runId ?? 'null-run',
|
|
322
|
+
stepId: opts?.stepId ?? 'null-step',
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
export * from './types.js';
|
|
326
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/turn-recorder/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TurnRecorder types — public shapes consumed by adapters that drive a
|
|
3
|
+
* multi-turn LLM tool loop and want their turns split into sub-effects
|
|
4
|
+
* so chain-fallback can resume mid-stream.
|
|
5
|
+
*
|
|
6
|
+
* Design notes:
|
|
7
|
+
* - agent-core does NOT depend on `@anvil/core-pipeline`. The
|
|
8
|
+
* `EffectRuntimeLike` interface here is the structural shape an
|
|
9
|
+
* adapter needs — core-pipeline's concrete `EffectRuntime` satisfies
|
|
10
|
+
* it, but agent-core never imports the class. This avoids a
|
|
11
|
+
* circular dep direction.
|
|
12
|
+
* - The recorder is constructed per `adapter.run()` call. It holds a
|
|
13
|
+
* monotonic `turn` counter scoped to the (stepId, sessionId) pair.
|
|
14
|
+
* - `flushPartial` is intentionally non-async and not routed through
|
|
15
|
+
* `effect()` — it MUST be safe to fire from a `catch` block that is
|
|
16
|
+
* about to re-throw an `UpstreamError`. The sink may queue the
|
|
17
|
+
* write durably under the hood; the recorder doesn't wait.
|
|
18
|
+
*/
|
|
19
|
+
import type { ProviderName } from '../types.js';
|
|
20
|
+
/**
|
|
21
|
+
* Minimal effect-runtime shape the recorder relies on. Mirrors the
|
|
22
|
+
* `effect(name, fn, opts?)` method on core-pipeline's `EffectRuntime`.
|
|
23
|
+
* Anything else on the real class (now/uuid/random/sleep/waitForSignal)
|
|
24
|
+
* is irrelevant to the recorder.
|
|
25
|
+
*
|
|
26
|
+
* Replay semantics: when an effect with `(name, idx)` is already in the
|
|
27
|
+
* durable log, `fn` is NOT invoked — the recorded payload is returned.
|
|
28
|
+
* Recording side effects (`record*` methods) thus become no-ops on
|
|
29
|
+
* replay, which is exactly what we want.
|
|
30
|
+
*/
|
|
31
|
+
export interface EffectRuntimeLike {
|
|
32
|
+
effect<T>(name: string, fn: () => Promise<T>, opts?: EffectInvokeOptions): Promise<T>;
|
|
33
|
+
/**
|
|
34
|
+
* H3 replay-skip seam: peek the recorded payload of a COMPLETED effect
|
|
35
|
+
* by name WITHOUT advancing the replay cursor or the per-step idx
|
|
36
|
+
* counter. Returns the recorded `effect:completed` payload if one
|
|
37
|
+
* exists in this step's log, else undefined. Lets `startTurn` detect
|
|
38
|
+
* that a turn was already fully recorded (its `assistant-end` exists)
|
|
39
|
+
* so the adapter can skip the upstream call entirely on crash-resume.
|
|
40
|
+
*
|
|
41
|
+
* Optional: the NullEffectRuntime omits it, so non-durable callers
|
|
42
|
+
* (single-shot / no DurableStore) always run live (peek → undefined →
|
|
43
|
+
* no replay). The concrete core-pipeline `EffectRuntime` implements it
|
|
44
|
+
* as a pure read over the pre-loaded recorded-effect set.
|
|
45
|
+
*/
|
|
46
|
+
peekRecorded?<T = unknown>(name: string): T | undefined;
|
|
47
|
+
}
|
|
48
|
+
export interface EffectInvokeOptions {
|
|
49
|
+
/** Stable input hash; mismatches on replay throw a determinism error. */
|
|
50
|
+
idempotencyKey?: string;
|
|
51
|
+
/** Hint that the payload is small enough to inline; runtime may skip blobs. */
|
|
52
|
+
smallResult?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Transform applied to the result before it is persisted to the durable
|
|
55
|
+
* log; the live caller still gets the full result. Used to cap large
|
|
56
|
+
* tool-result payloads so a turn doesn't block the loop serialising them.
|
|
57
|
+
* Mirrors `EffectOptions.persistTransform` in core-pipeline.
|
|
58
|
+
*/
|
|
59
|
+
persistTransform?: (result: unknown) => unknown;
|
|
60
|
+
}
|
|
61
|
+
export interface RecordedToolUse {
|
|
62
|
+
/** Provider-supplied tool_use id (e.g. OpenAI's `tool_call_id`). */
|
|
63
|
+
id: string;
|
|
64
|
+
name: string;
|
|
65
|
+
/** Parsed JSON arguments. UNPARSED args MUST NOT be recorded (§2.1.1). */
|
|
66
|
+
arguments: Record<string, unknown>;
|
|
67
|
+
/** sha256(name || JSON.stringify(arguments)). */
|
|
68
|
+
idempotencyKey: string;
|
|
69
|
+
}
|
|
70
|
+
export interface NeutralToolResult {
|
|
71
|
+
toolUseId: string;
|
|
72
|
+
toolName: string;
|
|
73
|
+
ok: boolean;
|
|
74
|
+
content: string | unknown;
|
|
75
|
+
durationMs?: number;
|
|
76
|
+
}
|
|
77
|
+
export interface ProvenanceSegment {
|
|
78
|
+
model: string;
|
|
79
|
+
provider: ProviderName;
|
|
80
|
+
range: [number, number];
|
|
81
|
+
source: 'live' | 'prefill';
|
|
82
|
+
}
|
|
83
|
+
export interface Provenance {
|
|
84
|
+
segments: ProvenanceSegment[];
|
|
85
|
+
}
|
|
86
|
+
export interface TurnTokenUsage {
|
|
87
|
+
inputTokens: number;
|
|
88
|
+
outputTokens: number;
|
|
89
|
+
cacheReadTokens?: number;
|
|
90
|
+
cacheWriteTokens?: number;
|
|
91
|
+
/** Subset of inputTokens carved out for §2.6 prefill-reinjection. */
|
|
92
|
+
prefilledInputTokens?: number;
|
|
93
|
+
}
|
|
94
|
+
export interface PrefillToolUse {
|
|
95
|
+
id: string;
|
|
96
|
+
name: string;
|
|
97
|
+
input: unknown;
|
|
98
|
+
result: NeutralToolResult;
|
|
99
|
+
producedBy: ProviderName;
|
|
100
|
+
}
|
|
101
|
+
export interface Prefill {
|
|
102
|
+
turnUuid: string;
|
|
103
|
+
text: string;
|
|
104
|
+
toolUses: PrefillToolUse[];
|
|
105
|
+
sourceProvider: ProviderName;
|
|
106
|
+
/** Model id that authored the partial — drives §2.7 provenance
|
|
107
|
+
* segment[0].model on the resuming turn. Optional: omitted by
|
|
108
|
+
* pre-H3 callers / when the source model is unknown. */
|
|
109
|
+
sourceModel?: string;
|
|
110
|
+
/** Tokens (as billed by sourceProvider) for `text`; H3 cost accounting. */
|
|
111
|
+
sourceTokens: number;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* §Tier 2 — one COMPLETED prior turn of a stateful session, reconstructed
|
|
115
|
+
* from the durable log so a cross-model (or merely non-claude) resume can
|
|
116
|
+
* re-present the full conversation in the target provider's wire format.
|
|
117
|
+
* Unlike `Prefill` (the in-progress burned turn), these are finished turns.
|
|
118
|
+
*
|
|
119
|
+
* `userPrompt` is present on the FIRST turn of each phase (the new user
|
|
120
|
+
* message that opened that phase); it's undefined on tool-loop continuation
|
|
121
|
+
* turns within a phase, and on pre-Tier-2 logs that never recorded it.
|
|
122
|
+
*/
|
|
123
|
+
export interface PrefillTurn {
|
|
124
|
+
userPrompt?: string;
|
|
125
|
+
text: string;
|
|
126
|
+
toolUses: PrefillToolUse[];
|
|
127
|
+
/**
|
|
128
|
+
* Authoring provider of this completed turn — informational / provenance.
|
|
129
|
+
* `materializePriorTurns` flattens all turns to the resuming target's wire
|
|
130
|
+
* format and does NOT run per-turn `stripForTarget`: completed turns are
|
|
131
|
+
* rebuilt from neutral fields (text + `NeutralToolResult`) that carry no
|
|
132
|
+
* vendor keys (no `reasoning_details` / `cache_control` / `prompt_cache_key`),
|
|
133
|
+
* so there is nothing to strip. (Contrast the `prefill` path, which does
|
|
134
|
+
* `stripForTarget` because it can re-present source-shaped messages.)
|
|
135
|
+
*/
|
|
136
|
+
producedBy: ProviderName;
|
|
137
|
+
}
|
|
138
|
+
export type PartialReason = 'upstream' | 'abort' | 'timeout';
|
|
139
|
+
export interface AssistantPartial {
|
|
140
|
+
runId: string;
|
|
141
|
+
stepId: string;
|
|
142
|
+
turnUuid: string;
|
|
143
|
+
turn: number;
|
|
144
|
+
text: string;
|
|
145
|
+
/** Count of FULLY-PARSED tool_use blocks emitted before the throw. */
|
|
146
|
+
toolUsesEmitted: number;
|
|
147
|
+
reason: PartialReason;
|
|
148
|
+
recordedAt: string;
|
|
149
|
+
}
|
|
150
|
+
export type PartialSink = (partial: AssistantPartial) => void;
|
|
151
|
+
export interface AssistantStartRequest {
|
|
152
|
+
model: string;
|
|
153
|
+
provider: ProviderName;
|
|
154
|
+
system?: string;
|
|
155
|
+
/** Raw conversation messages sent to the upstream. Recorded for provenance. */
|
|
156
|
+
messages: unknown;
|
|
157
|
+
prefill?: Prefill;
|
|
158
|
+
/**
|
|
159
|
+
* §Tier 2 — the new user message that opened this phase. Recorded in the
|
|
160
|
+
* assistant-start payload so `reconstructSessionHistory` can re-present
|
|
161
|
+
* prior turns with their prompts on a stateful (non-claude) session resume.
|
|
162
|
+
*/
|
|
163
|
+
userPrompt?: string;
|
|
164
|
+
}
|
|
165
|
+
export interface AssistantTurn {
|
|
166
|
+
turnUuid: string;
|
|
167
|
+
turn: number;
|
|
168
|
+
text: string;
|
|
169
|
+
stopReason: string;
|
|
170
|
+
usage: TurnTokenUsage;
|
|
171
|
+
provenance: Provenance;
|
|
172
|
+
toolUses: RecordedToolUse[];
|
|
173
|
+
toolResults: NeutralToolResult[];
|
|
174
|
+
/**
|
|
175
|
+
* H3 replay determinism: the EXACT provider-native messages the
|
|
176
|
+
* adapter appended to its conversation history during this turn
|
|
177
|
+
* (assistant message + tool-result messages), as opaque objects. On
|
|
178
|
+
* crash-resume the adapter re-appends these verbatim instead of
|
|
179
|
+
* reconstructing them from the neutral `toolUses`/`text` — raw vs
|
|
180
|
+
* canonical `tool_calls` arguments and vendor `reasoning_details` echoes
|
|
181
|
+
* would otherwise drift, leaving the FIRST live turn past the replay
|
|
182
|
+
* frontier with a malformed upstream request (e.g. unpaired tool_calls,
|
|
183
|
+
* or "reasoning_content is missing"). Empty for a terminal (no-tool)
|
|
184
|
+
* turn that appended nothing before breaking.
|
|
185
|
+
*/
|
|
186
|
+
historyDelta: unknown[];
|
|
187
|
+
}
|
|
188
|
+
export interface TurnRecorderDeps {
|
|
189
|
+
/** EffectRuntime to record sub-effects against. */
|
|
190
|
+
runtime: EffectRuntimeLike;
|
|
191
|
+
/** Fire-and-forget partial sink; called from adapter catch blocks. */
|
|
192
|
+
partialSink: PartialSink;
|
|
193
|
+
/** Stable runId — propagated into AssistantPartial rows. */
|
|
194
|
+
runId: string;
|
|
195
|
+
/** Stable stepId — scopes the recorder to one pipeline Step. */
|
|
196
|
+
stepId: string;
|
|
197
|
+
/** v4 uuid factory — overridable for tests. Defaults to crypto.randomUUID. */
|
|
198
|
+
uuid?: () => string;
|
|
199
|
+
/** Wall-clock helper — overridable for tests. Defaults to () => new Date().toISOString(). */
|
|
200
|
+
nowIso?: () => string;
|
|
201
|
+
/**
|
|
202
|
+
* Effect-name prefix (v2 ADR §2.4 per-repo scoping). When a step fans
|
|
203
|
+
* out across repos in parallel, every repo's recorder writes effects
|
|
204
|
+
* under the SAME durable step id, so a bare `turn:0:assistant-start`
|
|
205
|
+
* from two repos would collide. The caller passes a per-repo prefix
|
|
206
|
+
* (e.g. `service-a:`) so the recorded keys become
|
|
207
|
+
* `service-a:turn:0:assistant-start` — unique per repo AND matched by
|
|
208
|
+
* the durable engine's per-repo effect filter (which keys on delimited
|
|
209
|
+
* scope tokens). Empty ('') for single-stage paths. Applied uniformly
|
|
210
|
+
* to every effect name AND to the `peekRecorded` replay lookup.
|
|
211
|
+
*/
|
|
212
|
+
effectPrefix?: string;
|
|
213
|
+
/**
|
|
214
|
+
* Seed for the per-recorder turn counter (v2 ADR §2.5.1). When an
|
|
215
|
+
* `AgentProcess.sendInput()` resume spawns a NEW adapter (hence a new
|
|
216
|
+
* recorder) for a session whose prior adapter already recorded turns
|
|
217
|
+
* 0..N, the new recorder MUST continue at N+1 so `(stepId, turnUuid)`
|
|
218
|
+
* stays globally unique and replay's effect-idx matching doesn't
|
|
219
|
+
* collide. The recorder itself cannot read the durable log (it only
|
|
220
|
+
* holds a structural `EffectRuntimeLike`), so the CALLER computes the
|
|
221
|
+
* highest completed turn for (runId, stepId) and passes it here; the
|
|
222
|
+
* recorder seeds `turnCounter = initialTurn`. Defaults to 0 (fresh
|
|
223
|
+
* session / no prior turns).
|
|
224
|
+
*/
|
|
225
|
+
initialTurn?: number;
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/turn-recorder/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TurnRecorder types — public shapes consumed by adapters that drive a
|
|
3
|
+
* multi-turn LLM tool loop and want their turns split into sub-effects
|
|
4
|
+
* so chain-fallback can resume mid-stream.
|
|
5
|
+
*
|
|
6
|
+
* Design notes:
|
|
7
|
+
* - agent-core does NOT depend on `@anvil/core-pipeline`. The
|
|
8
|
+
* `EffectRuntimeLike` interface here is the structural shape an
|
|
9
|
+
* adapter needs — core-pipeline's concrete `EffectRuntime` satisfies
|
|
10
|
+
* it, but agent-core never imports the class. This avoids a
|
|
11
|
+
* circular dep direction.
|
|
12
|
+
* - The recorder is constructed per `adapter.run()` call. It holds a
|
|
13
|
+
* monotonic `turn` counter scoped to the (stepId, sessionId) pair.
|
|
14
|
+
* - `flushPartial` is intentionally non-async and not routed through
|
|
15
|
+
* `effect()` — it MUST be safe to fire from a `catch` block that is
|
|
16
|
+
* about to re-throw an `UpstreamError`. The sink may queue the
|
|
17
|
+
* write durably under the hood; the recorder doesn't wait.
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/turn-recorder/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}
|
package/dist/types.d.ts
CHANGED
|
@@ -129,8 +129,13 @@ export interface LanguageModel {
|
|
|
129
129
|
version?: string;
|
|
130
130
|
error?: string;
|
|
131
131
|
}>;
|
|
132
|
-
/**
|
|
133
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Streaming surface — yields events as the provider produces them and
|
|
134
|
+
* RETURNS the assembled `InvokeResult` (cost/usage/finish) on completion,
|
|
135
|
+
* so a streaming consumer (the router's chain walk) gets the same outcome
|
|
136
|
+
* data a single-shot `invoke()` would, without re-running the call.
|
|
137
|
+
*/
|
|
138
|
+
invokeStream(opts: LanguageModelInvokeOptions): AsyncGenerator<StreamEvent, InvokeResult>;
|
|
134
139
|
/** Single-shot surface — drains the stream and returns the final block. */
|
|
135
140
|
invoke(opts: LanguageModelInvokeOptions): Promise<InvokeResult>;
|
|
136
141
|
}
|
|
@@ -189,6 +194,34 @@ export interface ModelAdapterConfig {
|
|
|
189
194
|
* ADR §C5.
|
|
190
195
|
*/
|
|
191
196
|
mcpConfigPath?: string;
|
|
197
|
+
/**
|
|
198
|
+
* Turn-level durable recorder (v2 ADR §2.5). When supplied by the
|
|
199
|
+
* caller, the adapter splits its LLM invocation into per-turn
|
|
200
|
+
* sub-effects (`assistant-start` / `tool_use:N` / `tool_result:N` /
|
|
201
|
+
* `assistant-end`) recorded against the caller's `EffectRuntime`.
|
|
202
|
+
* When absent, adapters construct a `NullTurnRecorder` internally —
|
|
203
|
+
* structural calls still happen so the code path is the same, but
|
|
204
|
+
* nothing is persisted. Recording is implemented for every adapter
|
|
205
|
+
* except `gemini-cli` (a CLI subprocess with no token-level stream),
|
|
206
|
+
* which always no-ops the recorder.
|
|
207
|
+
*/
|
|
208
|
+
turnRecorder?: import('./turn-recorder/index.js').TurnRecorder;
|
|
209
|
+
/**
|
|
210
|
+
* Prefill from a prior model that burned mid-stream (v2 ADR §2.3).
|
|
211
|
+
* When present, the adapter materializes the trailing-assistant +
|
|
212
|
+
* recorded tool-result history into its provider's wire format and
|
|
213
|
+
* continues from the exact character where the prior model stopped.
|
|
214
|
+
*/
|
|
215
|
+
prefill?: import('./turn-recorder/index.js').Prefill;
|
|
216
|
+
/**
|
|
217
|
+
* §Tier 2 — COMPLETED prior turns of a stateful session, reconstructed
|
|
218
|
+
* from the durable log. Spliced (materialized into this provider's wire
|
|
219
|
+
* format) BEFORE the new user message so a non-claude session resume
|
|
220
|
+
* re-presents the full conversation instead of starting fresh. Claude
|
|
221
|
+
* uses native `--resume` and is gated out at the bridge. Empty/undefined
|
|
222
|
+
* → unchanged single-turn behavior.
|
|
223
|
+
*/
|
|
224
|
+
priorMessages?: import('./turn-recorder/index.js').PrefillTurn[];
|
|
192
225
|
}
|
|
193
226
|
export interface ToolExecutorLike {
|
|
194
227
|
listSchemas(): ToolSchema[];
|