@statelyai/agent 2.0.0-alpha.24 → 2.0.0-alpha.26

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 (53) hide show
  1. package/dist/ai-sdk.cjs +30 -34
  2. package/dist/ai-sdk.d.cts +27 -31
  3. package/dist/ai-sdk.d.mts +27 -31
  4. package/dist/ai-sdk.mjs +27 -31
  5. package/dist/decision-CM9CYZ6t.cjs +519 -0
  6. package/dist/decision-DzSv1NpD.mjs +453 -0
  7. package/dist/event-log-store-Ba2fsc4E.mjs +576 -0
  8. package/dist/event-log-store-DQmImLYk.cjs +702 -0
  9. package/dist/event-log-store-DvOcs10J.d.cts +338 -0
  10. package/dist/event-log-store-DxyVgnas.d.mts +338 -0
  11. package/dist/index.cjs +429 -4272
  12. package/dist/index.d.cts +104 -1005
  13. package/dist/index.d.mts +104 -1005
  14. package/dist/index.mjs +389 -4202
  15. package/dist/log.cjs +392 -0
  16. package/dist/log.d.cts +32 -0
  17. package/dist/log.d.mts +32 -0
  18. package/dist/log.mjs +371 -0
  19. package/dist/machines.cjs +27 -16
  20. package/dist/machines.d.cts +1 -1
  21. package/dist/machines.d.mts +1 -1
  22. package/dist/machines.mjs +27 -16
  23. package/dist/openai.cjs +16 -15
  24. package/dist/openai.d.cts +9 -9
  25. package/dist/openai.d.mts +9 -9
  26. package/dist/openai.mjs +10 -9
  27. package/dist/otel.d.cts +1 -1
  28. package/dist/otel.d.mts +1 -1
  29. package/dist/{run-agent-Ck7Sa3Tb.d.mts → run-agent-BOfoJg5B.d.cts} +86 -387
  30. package/dist/{run-agent-DWqA8KiI.d.cts → run-agent-CG343kFs.d.mts} +86 -387
  31. package/dist/run-agent-Cb1-LS5P.cjs +1783 -0
  32. package/dist/run-agent-DvDQd4If.mjs +1669 -0
  33. package/dist/{setup-agent-CeO2fARv.mjs → setup-agent-CFW-jPI8.mjs} +9 -20
  34. package/dist/{setup-agent-Dl2VELLi.cjs → setup-agent-CPf7MxnG.cjs} +20 -37
  35. package/dist/testing.cjs +1088 -0
  36. package/dist/testing.d.cts +617 -0
  37. package/dist/testing.d.mts +617 -0
  38. package/dist/testing.mjs +1079 -0
  39. package/dist/{text-logic-DJBK3LbG.d.mts → text-logic-BFEOxyxv.d.cts} +71 -147
  40. package/dist/text-logic-Bl_ZRxDi.mjs +833 -0
  41. package/dist/text-logic-C8xyr6Ag.cjs +1091 -0
  42. package/dist/{text-logic-C7bSnH_B.d.cts → text-logic-hZSiRUo1.d.mts} +71 -147
  43. package/dist/{types-CjHk5OdH.d.cts → types-B1Qs2q3q.d.cts} +2 -1
  44. package/dist/{types-DWA6X9R-.d.mts → types-tA3SdczG.d.mts} +2 -1
  45. package/dist/usage-BdEjFuIi.mjs +5 -0
  46. package/dist/usage-efOQPdf_.cjs +10 -0
  47. package/dist/utils-C2DPqomn.d.mts +109 -0
  48. package/dist/utils-ceStNCOh.d.cts +109 -0
  49. package/package.json +26 -5
  50. package/readme.md +5 -3
  51. package/skills/generate-machine/SKILL.md +4 -6
  52. package/dist/decision-BBZ2jMO1.mjs +0 -1490
  53. package/dist/decision-Cv6SfNWl.cjs +0 -1856
@@ -0,0 +1,576 @@
1
+ import { H as AgentError, L as resolveMachineVersion, O as djb2Hex, t as AGENT_USAGE_TOKEN_FIELDS } from "./text-logic-Bl_ZRxDi.mjs";
2
+ import "./usage-BdEjFuIi.mjs";
3
+ import { initialTransition, transition } from "xstate";
4
+ //#region src/event-log.ts
5
+ /**
6
+ * The event log: an append-only journal of the EXTERNAL inputs a machine
7
+ * consumed, and the pure fold ({@link replay}) that turns that journal back
8
+ * into a snapshot without executing anything.
9
+ *
10
+ * A journaled entry is always an external input — an invoke completion
11
+ * (`xstate.done.actor`/`xstate.error.actor` carrying its output inline), a
12
+ * user-sent event, a timer firing, a reserved `@agent.usage` record. Never a
13
+ * raised/internal event: replay re-derives those from the machine's own logic,
14
+ * so journaling them would double-apply them.
15
+ *
16
+ * A log is self-contained: its reserved first entry ({@link initEntry}) carries
17
+ * either the machine `input` (a fresh start) or a persisted snapshot (a
18
+ * continuation across a machine version, or from a log-less legacy snapshot),
19
+ * so {@link replay} needs no side channel.
20
+ * @module
21
+ */
22
+ /** The durable entry envelope version. */
23
+ const AGENT_EVENT_SCHEMA_VERSION = 1;
24
+ /**
25
+ * The reserved event type of the first entry of every log. It carries the
26
+ * machine `input` (fresh start) or a persisted `snapshot` (continuation), so a
27
+ * log replays with no side channel. Lives in the reserved `@agent.*` namespace
28
+ * so it can never collide with a machine's own vocabulary; it is consumed by
29
+ * {@link replay} and never fed to `transition`.
30
+ */
31
+ const AGENT_INIT_EVENT_TYPE = "@agent.init";
32
+ /** A malformed or internally inconsistent log. */
33
+ var AgentEventLogError = class extends AgentError {
34
+ constructor(message) {
35
+ super("invalid-event-log", message);
36
+ this.name = "AgentEventLogError";
37
+ }
38
+ };
39
+ /** A precise failure when an entry would not survive a JSON round-trip. */
40
+ var NonSerializableAgentEventError = class extends AgentError {
41
+ path;
42
+ valueType;
43
+ constructor(path, valueType) {
44
+ super("non-serializable-event", `Agent event field '${path}' is not JSON-serializable (${valueType}).`);
45
+ this.name = "NonSerializableAgentEventError";
46
+ this.path = path;
47
+ this.valueType = valueType;
48
+ }
49
+ };
50
+ /** Replay reached a state that does not match the entry's recorded hash. */
51
+ var AgentReplayDivergenceError = class extends AgentError {
52
+ constructor(eventId, index, kind, expected, actual) {
53
+ super("replay-diverged", kind === "missing-verification" ? `Replay entry '${eventId}' at index ${index} has no recorded verification hash.` : `Replay diverged after '${eventId}' at index ${index}: expected state hash '${expected}', got '${actual}'.`);
54
+ this.eventId = eventId;
55
+ this.index = index;
56
+ this.kind = kind;
57
+ this.expected = expected;
58
+ this.actual = actual;
59
+ this.name = "AgentReplayDivergenceError";
60
+ }
61
+ };
62
+ /**
63
+ * An entry was recorded against a different machine (id) or a different
64
+ * machine version than the one replaying it. An init-with-snapshot entry is
65
+ * exempt: that entry IS the bridge from the old version to the new one.
66
+ */
67
+ var AgentMachineVersionMismatchError = class extends AgentError {
68
+ constructor(eventId, index, expected, actual) {
69
+ super("machine-version-mismatch", `Event log entry '${eventId}' at index ${index} targets machine '${actual.machineId}'@'${actual.machineVersion}', expected '${expected.machineId}'@'${expected.machineVersion}'.`);
70
+ this.eventId = eventId;
71
+ this.index = index;
72
+ this.expected = expected;
73
+ this.actual = actual;
74
+ this.name = "AgentMachineVersionMismatchError";
75
+ }
76
+ };
77
+ function assertNoHiddenKeys(value, path, isOwnKey) {
78
+ const symbols = Object.getOwnPropertySymbols(value);
79
+ if (symbols.length > 0) throw new NonSerializableAgentEventError(`${path}.[${String(symbols[0])}]`, "symbol key");
80
+ const hiddenKey = Object.getOwnPropertyNames(value).find((key) => !isOwnKey?.(key) && !Object.getOwnPropertyDescriptor(value, key)?.enumerable);
81
+ if (hiddenKey !== void 0) throw new NonSerializableAgentEventError(`${path}.${hiddenKey}`, "non-enumerable property");
82
+ }
83
+ const arrayIndexPattern = /^(?:0|[1-9]\d*)$/;
84
+ /**
85
+ * Rejects values JSON would drop or coerce. Unlike `JSON.stringify`, this does
86
+ * not silently erase `undefined`/functions or turn non-finite numbers into
87
+ * `null`; durable entries contain only plain JSON values.
88
+ */
89
+ function assertJsonSerializable(value, path = "entry", ancestors = /* @__PURE__ */ new WeakSet()) {
90
+ if (value === null || typeof value === "string" || typeof value === "boolean") return;
91
+ if (typeof value === "number") {
92
+ if (!Number.isFinite(value) || Object.is(value, -0)) throw new NonSerializableAgentEventError(path, Object.is(value, -0) ? "-0" : String(value));
93
+ return;
94
+ }
95
+ if (typeof value !== "object") throw new NonSerializableAgentEventError(path, typeof value);
96
+ if (ancestors.has(value)) throw new NonSerializableAgentEventError(path, "circular reference");
97
+ ancestors.add(value);
98
+ try {
99
+ if (Array.isArray(value)) {
100
+ for (let index = 0; index < value.length; index++) {
101
+ if (!Object.hasOwn(value, index)) throw new NonSerializableAgentEventError(`${path}[${index}]`, "array hole");
102
+ assertJsonSerializable(value[index], `${path}[${index}]`, ancestors);
103
+ }
104
+ const extraKey = Object.keys(value).find((key) => !arrayIndexPattern.test(key) || Number(key) >= value.length);
105
+ if (extraKey !== void 0) throw new NonSerializableAgentEventError(`${path}.${extraKey}`, "array property");
106
+ assertNoHiddenKeys(value, path, (key) => key === "length" || arrayIndexPattern.test(key));
107
+ return;
108
+ }
109
+ const prototype = Object.getPrototypeOf(value);
110
+ if (prototype !== Object.prototype && prototype !== null) throw new NonSerializableAgentEventError(path, value.constructor?.name ?? "object");
111
+ assertNoHiddenKeys(value, path);
112
+ for (const [key, child] of Object.entries(value)) assertJsonSerializable(child, `${path}.${key}`, ancestors);
113
+ } finally {
114
+ ancestors.delete(value);
115
+ }
116
+ }
117
+ function requireNonEmptyString(value, label, qualifier = "") {
118
+ if (typeof value !== "string" || !value) throw new AgentEventLogError(`Agent event ${label} must be a non-empty string${qualifier}.`);
119
+ }
120
+ /** Validates the complete durable envelope before append/export/replay. */
121
+ function assertAgentLogEntry(entry) {
122
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) throw new AgentEventLogError("Agent event entry must be an object.");
123
+ assertJsonSerializable(entry);
124
+ const candidate = entry;
125
+ if (candidate.schemaVersion !== 1) throw new AgentEventLogError(`Unsupported agent event schema version '${String(candidate.schemaVersion)}'; expected '1'.`);
126
+ if (!Number.isInteger(candidate.index) || candidate.index < 0) throw new AgentEventLogError(`Agent event entry.index must be a non-negative integer; got ${String(candidate.index)}.`);
127
+ for (const field of [
128
+ "id",
129
+ "machineId",
130
+ "machineVersion"
131
+ ]) requireNonEmptyString(candidate[field], `entry.${field}`);
132
+ if (typeof candidate.recordedAt !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(candidate.recordedAt) || Number.isNaN(Date.parse(candidate.recordedAt))) throw new AgentEventLogError(`Agent event entry.recordedAt is not RFC 3339: '${String(candidate.recordedAt)}'.`);
133
+ if (candidate.event === null || typeof candidate.event !== "object" || Array.isArray(candidate.event) || typeof candidate.event.type !== "string" || !candidate.event.type) throw new AgentEventLogError("Agent event entry.event requires a non-empty string type.");
134
+ if (candidate.causationId !== void 0) requireNonEmptyString(candidate.causationId, "entry.causationId", " when present");
135
+ if (candidate.metadata !== void 0 && (candidate.metadata === null || typeof candidate.metadata !== "object" || Array.isArray(candidate.metadata))) throw new AgentEventLogError("Agent event entry.metadata must be an object when present.");
136
+ if (candidate.verification !== void 0 && (candidate.verification === null || typeof candidate.verification !== "object" || typeof candidate.verification.stateHash !== "string" || !candidate.verification.stateHash)) throw new AgentEventLogError("Agent event entry.verification requires a non-empty stateHash string.");
137
+ }
138
+ /**
139
+ * Replaces `Error` instances with plain `{ name, message, cause? }` objects so
140
+ * an `xstate.error.actor` payload survives the log's JSON round-trip. Cycles
141
+ * are preserved by identity; non-plain objects are left alone (and are then
142
+ * rejected by {@link assertJsonSerializable}).
143
+ */
144
+ function normalizeEventErrors(value, seen = /* @__PURE__ */ new WeakMap()) {
145
+ if (value instanceof Error) {
146
+ const normalized = {
147
+ name: value.name,
148
+ message: value.message
149
+ };
150
+ seen.set(value, normalized);
151
+ if (value.cause !== void 0) normalized.cause = normalizeEventErrors(value.cause, seen);
152
+ return normalized;
153
+ }
154
+ if (value === null || typeof value !== "object") return value;
155
+ const existing = seen.get(value);
156
+ if (existing !== void 0) return existing;
157
+ if (Array.isArray(value)) {
158
+ const result = [];
159
+ seen.set(value, result);
160
+ for (const item of value) result.push(normalizeEventErrors(item, seen));
161
+ return result;
162
+ }
163
+ const prototype = Object.getPrototypeOf(value);
164
+ if (prototype !== Object.prototype && prototype !== null) return value;
165
+ const result = {};
166
+ seen.set(value, result);
167
+ for (const [key, item] of Object.entries(value)) result[key] = normalizeEventErrors(item, seen);
168
+ return result;
169
+ }
170
+ const SNAPSHOT_VOLATILE_KEYS = new Set([
171
+ "sessionId",
172
+ "_nextTimerId",
173
+ "_nextActorIds"
174
+ ]);
175
+ const CHILD_VOLATILE_KEYS = new Set(["sessionId"]);
176
+ function positionOf(parent, key) {
177
+ if (parent === "snapshot") return key === "children" ? "children" : "data";
178
+ if (parent === "children") return "child";
179
+ if (parent === "child") return key === "snapshot" ? "snapshot" : "data";
180
+ return "data";
181
+ }
182
+ function canonicalizeForHash(value, seen = /* @__PURE__ */ new WeakSet(), position = "snapshot") {
183
+ if (value === void 0) return "[undefined]";
184
+ if (typeof value === "function") return "[function]";
185
+ if (typeof value === "bigint") return `[bigint:${String(value)}]`;
186
+ if (typeof value === "symbol") return `[symbol:${String(value)}]`;
187
+ if (value === null || typeof value !== "object") return value;
188
+ if (value instanceof Date) return `[Date:${value.toISOString()}]`;
189
+ if (value instanceof Error) {
190
+ if (seen.has(value)) return "[circular]";
191
+ seen.add(value);
192
+ try {
193
+ return {
194
+ errorName: value.name,
195
+ message: value.message,
196
+ ...value.cause !== void 0 ? { cause: canonicalizeForHash(value.cause, seen, "data") } : {}
197
+ };
198
+ } finally {
199
+ seen.delete(value);
200
+ }
201
+ }
202
+ if (seen.has(value)) return "[circular]";
203
+ seen.add(value);
204
+ try {
205
+ if (value instanceof Set) return [...value].map((item) => canonicalizeForHash(item, seen, "data")).sort((a, b) => stableJson(a).localeCompare(stableJson(b)));
206
+ if (value instanceof Map) return [...value.entries()].map(([key, item]) => [canonicalizeForHash(key, seen, "data"), canonicalizeForHash(item, seen, "data")]).sort(([a], [b]) => stableJson(a).localeCompare(stableJson(b)));
207
+ if (Array.isArray(value)) return value.map((item) => canonicalizeForHash(item, seen, "data"));
208
+ const volatileKeys = position === "snapshot" ? SNAPSHOT_VOLATILE_KEYS : position === "child" ? CHILD_VOLATILE_KEYS : void 0;
209
+ const result = {};
210
+ for (const key of Object.keys(value).sort()) {
211
+ if (volatileKeys?.has(key)) continue;
212
+ const child = value[key];
213
+ if (child === void 0) continue;
214
+ result[key] = canonicalizeForHash(child, seen, positionOf(position, key));
215
+ }
216
+ return result;
217
+ } finally {
218
+ seen.delete(value);
219
+ }
220
+ }
221
+ /**
222
+ * Key-sorted JSON for comparing two events by content: key insertion order
223
+ * does not count, so an entry read back from storage equals the one that was
224
+ * appended.
225
+ * @internal
226
+ */
227
+ function canonicalEventJson(event) {
228
+ return stableJson(canonicalizeForHash(event, /* @__PURE__ */ new Set(), "data"));
229
+ }
230
+ function stableJson(value) {
231
+ return JSON.stringify(sortJson(value));
232
+ }
233
+ function sortJson(value) {
234
+ if (Array.isArray(value)) return value.map(sortJson);
235
+ if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortJson(value[key])]));
236
+ return value;
237
+ }
238
+ /**
239
+ * The canonical hash of a PERSISTED snapshot (`machine.getPersistedSnapshot()`,
240
+ * or {@link replay}'s `persistedSnapshot`) — the value recorded in an entry's
241
+ * `verification.stateHash`.
242
+ *
243
+ * Stripped before hashing, because they are re-minted on every fold and would
244
+ * otherwise make two identical runs hash differently: `sessionId`,
245
+ * `_nextTimerId` and `_nextActorIds` at the snapshot root, `sessionId` on each
246
+ * `children[*]` entry (recursively, through nested child snapshots), and
247
+ * `undefined`-valued properties (so a snapshot hashes the same before and
248
+ * after a JSON round-trip). Stripping is confined to those structural
249
+ * positions: a `sessionId` inside `context`, `output`, `error` or an event
250
+ * payload is application data and is hashed. Object keys are sorted, so key
251
+ * order is irrelevant.
252
+ *
253
+ * A change detector, not a cryptographic digest.
254
+ */
255
+ function getSnapshotStateHash(snapshot) {
256
+ return djb2Hex(stableJson(canonicalizeForHash(snapshot)));
257
+ }
258
+ function machineIdOf(machine) {
259
+ return machine.config.id ?? machine.id ?? "(machine)";
260
+ }
261
+ function toPersisted(machine, snapshot) {
262
+ return typeof snapshot.matches === "function" ? machine.getPersistedSnapshot(snapshot) : snapshot;
263
+ }
264
+ /**
265
+ * Creates a JSON-safe, self-describing entry for `event` appended after
266
+ * `entries` (which must be the complete prefix, so `index` and the recorded
267
+ * state hash line up).
268
+ */
269
+ function createReplayEntry(machine, entries, event, options = {}) {
270
+ const index = entries.length;
271
+ const machineVersion = options.machineVersion ?? resolveMachineVersion(machine);
272
+ const entry = {
273
+ schemaVersion: 1,
274
+ id: options.id ?? `evt_${String(index).padStart(8, "0")}`,
275
+ index,
276
+ recordedAt: options.recordedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
277
+ machineId: machineIdOf(machine),
278
+ machineVersion,
279
+ event: normalizeEventErrors(event),
280
+ ...options.causationId !== void 0 ? { causationId: options.causationId } : {},
281
+ ...options.metadata !== void 0 ? { metadata: options.metadata } : {}
282
+ };
283
+ assertAgentLogEntry(entry);
284
+ if (options.verification !== false) entry.verification = { stateHash: getSnapshotStateHash(options.snapshot ? toPersisted(machine, options.snapshot) : replay(machine, [...entries, entry], {
285
+ machineVersion,
286
+ verify: false
287
+ }).persistedSnapshot) };
288
+ return entry;
289
+ }
290
+ function pruneUndefined(value) {
291
+ if (Array.isArray(value)) return value.map(pruneUndefined);
292
+ if (value === null || typeof value !== "object") return value;
293
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return value;
294
+ const result = {};
295
+ for (const [key, child] of Object.entries(value)) {
296
+ if (child === void 0) continue;
297
+ result[key] = pruneUndefined(child);
298
+ }
299
+ return result;
300
+ }
301
+ /**
302
+ * The reserved first entry of a log. Pass EXACTLY one of:
303
+ *
304
+ * - `{ input }` — a fresh start; replay folds from `initialTransition`.
305
+ * - `{ snapshot }` — a continuation from a persisted snapshot: a version
306
+ * bridge, or a legacy snapshot that predates the log. Replay restores it
307
+ * purely (no actors started) and folds the rest of the log onto it.
308
+ *
309
+ * `options.metadata.executionId` names the lineage; this library always sets
310
+ * one, and readers must tolerate its absence in logs written elsewhere.
311
+ */
312
+ function initEntry(machine, init = {}, options = {}) {
313
+ const hasSnapshot = init.snapshot !== void 0;
314
+ if (hasSnapshot && init.input !== void 0) throw new AgentEventLogError("initEntry accepts exactly one of `input` (fresh start) or `snapshot` (continuation).");
315
+ return createReplayEntry(machine, [], {
316
+ type: AGENT_INIT_EVENT_TYPE,
317
+ ...hasSnapshot ? { snapshot: pruneUndefined(init.snapshot) } : init.input !== void 0 ? { input: init.input } : {}
318
+ }, options);
319
+ }
320
+ /** Whether `entry` is the reserved init entry. */
321
+ function isInitEntry(entry) {
322
+ return entry?.event?.type === AGENT_INIT_EVENT_TYPE;
323
+ }
324
+ /** Whether `entry` is the version-bridging init-with-snapshot entry. */
325
+ function isSnapshotInitEntry(entry) {
326
+ return isInitEntry(entry) && entry.event.snapshot !== void 0;
327
+ }
328
+ /**
329
+ * The log's execution id: the lineage identity pinned in the init entry's
330
+ * `metadata.executionId`.
331
+ *
332
+ * Minted once per run and inherited verbatim by every resume of that log, so
333
+ * it names one lineage rather than one process. Entry ids cannot serve this
334
+ * role — the built-in ids are index-derived, so every log's init entry is
335
+ * `evt_00000000`. A fork copies the init entry, so it shares its parent's
336
+ * execution id by construction.
337
+ *
338
+ * Returns `undefined` for a log with no init entry and for one written without
339
+ * the field — callers must treat the id as optional.
340
+ */
341
+ function getLogExecutionId(entries) {
342
+ const executionId = (entries.find((entry) => isInitEntry(entry))?.metadata)?.executionId;
343
+ return typeof executionId === "string" ? executionId : void 0;
344
+ }
345
+ /**
346
+ * Validates a log before replay or append: every envelope well-formed, indices
347
+ * contiguous from 0, ids unique, the first entry the reserved init entry, and
348
+ * one consistent machine identity throughout.
349
+ *
350
+ * With `machine`, entries are also checked against that machine's id and
351
+ * resolved version — a mismatch throws {@link AgentMachineVersionMismatchError}.
352
+ * An init-with-snapshot entry is exempt from the version check: that entry is
353
+ * itself the bridge from whatever version produced the snapshot.
354
+ */
355
+ function validateReplayEntries(entries, options = {}) {
356
+ if (entries.length === 0) return;
357
+ const reference = entries.find((entry) => !isSnapshotInitEntry(entry)) ?? entries[0];
358
+ const expected = options.machine ? {
359
+ machineId: machineIdOf(options.machine),
360
+ machineVersion: options.machineVersion ?? resolveMachineVersion(options.machine)
361
+ } : {
362
+ machineId: reference.machineId,
363
+ machineVersion: options.machineVersion ?? reference.machineVersion
364
+ };
365
+ const ids = /* @__PURE__ */ new Set();
366
+ for (let index = 0; index < entries.length; index++) {
367
+ const entry = entries[index];
368
+ assertAgentLogEntry(entry);
369
+ if (entry.index !== index) throw new AgentEventLogError(`Event log must be contiguous from index 0; found entry.index ${entry.index} at position ${index}.`);
370
+ if (ids.has(entry.id)) throw new AgentEventLogError(`Event log contains duplicate entry id '${entry.id}'.`);
371
+ ids.add(entry.id);
372
+ if (index === 0 && !isInitEntry(entry)) throw new AgentEventLogError(`Event log must start with the reserved '${AGENT_INIT_EVENT_TYPE}' entry; found '${entry.event.type}'.`);
373
+ if (index > 0 && isInitEntry(entry)) throw new AgentEventLogError(`Event log has a second '${AGENT_INIT_EVENT_TYPE}' entry at index ${index}.`);
374
+ const versionMatches = entry.machineVersion === expected.machineVersion;
375
+ if (entry.machineId !== expected.machineId || !versionMatches && !isSnapshotInitEntry(entry)) throw new AgentMachineVersionMismatchError(entry.id, entry.index, expected, {
376
+ machineId: entry.machineId,
377
+ machineVersion: entry.machineVersion
378
+ });
379
+ }
380
+ }
381
+ /**
382
+ * The prefix `[0, upToIndex)` of a log, as a new log. `upToIndex` is exclusive
383
+ * and must leave the init entry in place, so the fork is still self-contained.
384
+ */
385
+ function forkEventLog(entries, upToIndex) {
386
+ if (!Number.isInteger(upToIndex) || upToIndex < 1 || upToIndex > entries.length) throw new AgentEventLogError(`forkEventLog: upToIndex must be an integer in [1, ${entries.length}]; got ${String(upToIndex)}.`);
387
+ const forked = entries.slice(0, upToIndex);
388
+ if (!isInitEntry(forked[0])) throw new AgentEventLogError(`forkEventLog: the forked prefix must start with the reserved '${AGENT_INIT_EVENT_TYPE}' entry.`);
389
+ return forked;
390
+ }
391
+ const DONE_ACTOR_EVENT_TYPE = "xstate.done.actor";
392
+ const ERROR_ACTOR_EVENT_TYPE = "xstate.error.actor";
393
+ /** Normalizes a mixed `EventObject | AgentLogEntry` history into bare events. */
394
+ function toEvents(history) {
395
+ if (!history) return [];
396
+ return history.map((item) => {
397
+ const candidate = item;
398
+ return candidate?.schemaVersion === 1 && typeof candidate.index === "number" && candidate.event !== null && typeof candidate.event === "object" ? candidate.event : item;
399
+ });
400
+ }
401
+ /**
402
+ * The 1-based occurrence `n` for the NEXT call at invoke site `siteId`:
403
+ * `1 + completions`, where a completion is a journaled `xstate.done.actor` OR
404
+ * `xstate.error.actor` for that `actorId` (an error is a semantic completion).
405
+ * The one counting rule behind every `${siteId}#${n}` call key, so a live run
406
+ * and a replay derive the same `n` by construction.
407
+ */
408
+ function agentCallOccurrence(history, siteId) {
409
+ let count = 0;
410
+ for (const event of toEvents(history)) if ((event.type === DONE_ACTOR_EVENT_TYPE || event.type === ERROR_ACTOR_EVENT_TYPE) && event.actorId === siteId) count++;
411
+ return count + 1;
412
+ }
413
+ /**
414
+ * Folds the reserved `@agent.usage` entries of a log into one cumulative usage
415
+ * total. The log is the source of truth, so the totals are a pure projection of
416
+ * it: replay a log, fold it, and you get the numbers the run reported with no
417
+ * host-side accumulator in between.
418
+ *
419
+ * Accepts entries or bare events. Token fields are partial sums: a field stays
420
+ * `undefined` until some entry reports it, and non-finite values are ignored.
421
+ */
422
+ function getUsageFromEvents(entries) {
423
+ const totals = {};
424
+ for (const event of toEvents(entries)) {
425
+ if (event.type !== "@agent.usage") continue;
426
+ const usage = event.usage;
427
+ if (!usage || typeof usage !== "object") continue;
428
+ for (const field of AGENT_USAGE_TOKEN_FIELDS) {
429
+ const value = usage[field];
430
+ if (typeof value === "number" && Number.isFinite(value)) totals[field] = (totals[field] ?? 0) + value;
431
+ }
432
+ }
433
+ return totals;
434
+ }
435
+ /**
436
+ * Rewrites a journaled `xstate.done.actor`/`xstate.error.actor` event's
437
+ * `sessionId` to the session the CURRENT fold minted for that `actorId`.
438
+ *
439
+ * Session ids are re-minted on every fold, and XState drops a completion event
440
+ * whose `sessionId` does not match the live child — silently, as a no-op
441
+ * transition. Rebinding is what makes a journaled completion replayable at all.
442
+ * `sessions` caches the mapping per fold, so a later event referring to the
443
+ * recorded session of an already-stopped child still resolves.
444
+ */
445
+ function rebindActorSession(event, snapshot, sessions) {
446
+ const actorEvent = event;
447
+ if (typeof actorEvent.actorId !== "string" || typeof actorEvent.sessionId !== "string") return event;
448
+ const key = `${actorEvent.actorId}\0${actorEvent.sessionId}`;
449
+ let sessionId = sessions.get(key);
450
+ if (!sessionId) {
451
+ const child = snapshot.children[actorEvent.actorId];
452
+ if (typeof child?.sessionId !== "string") return event;
453
+ sessionId = child.sessionId;
454
+ sessions.set(key, sessionId);
455
+ }
456
+ return {
457
+ ...event,
458
+ sessionId
459
+ };
460
+ }
461
+ function verifyEntry(entry, persisted, mode) {
462
+ if (mode === false) return;
463
+ if (!entry.verification) {
464
+ if (mode === "strict") throw new AgentReplayDivergenceError(entry.id, entry.index, "missing-verification");
465
+ return;
466
+ }
467
+ const actual = getSnapshotStateHash(persisted);
468
+ if (actual !== entry.verification.stateHash) throw new AgentReplayDivergenceError(entry.id, entry.index, "state", entry.verification.stateHash, actual);
469
+ }
470
+ /**
471
+ * Folds a log through XState's pure `initialTransition`/`transition` WITHOUT
472
+ * executing anything: no actor is started, no action runs, no model is called.
473
+ * A journaled `xstate.done.actor` entry carries the completed actor's output
474
+ * inline, so the invoke it belongs to is never re-run. Crash recovery, fork
475
+ * resume, and time travel in one call.
476
+ *
477
+ * The first entry is the reserved {@link initEntry}: `input` folds from
478
+ * `initialTransition`, `snapshot` restores the persisted snapshot purely
479
+ * (`machine.restoreSnapshot`) and folds the rest of the log onto it.
480
+ */
481
+ function replay(machine, entries, options = {}) {
482
+ validateReplayEntries(entries, {
483
+ machine,
484
+ machineVersion: options.machineVersion ?? resolveMachineVersion(machine)
485
+ });
486
+ if (entries.length === 0) throw new AgentEventLogError(`Cannot replay an empty event log; it must start with a '${AGENT_INIT_EVENT_TYPE}' entry.`);
487
+ const initEvent = entries[0].event;
488
+ let snapshot = initEvent.snapshot !== void 0 ? machine.restoreSnapshot(initEvent.snapshot) : initialTransition(machine, initEvent.input)[0];
489
+ let persisted = machine.getPersistedSnapshot(snapshot);
490
+ verifyEntry(entries[0], persisted, options.verify);
491
+ const sessions = /* @__PURE__ */ new Map();
492
+ for (let index = 1; index < entries.length; index++) {
493
+ const entry = entries[index];
494
+ const event = rebindActorSession(entry.event, snapshot, sessions);
495
+ [snapshot] = transition(machine, snapshot, event);
496
+ persisted = machine.getPersistedSnapshot(snapshot);
497
+ verifyEntry(entry, persisted, options.verify);
498
+ }
499
+ return {
500
+ snapshot,
501
+ persistedSnapshot: persisted
502
+ };
503
+ }
504
+ //#endregion
505
+ //#region src/event-log-store.ts
506
+ /**
507
+ * Durable storage for {@link AgentLogEntry} logs: the one interface a host
508
+ * implements, plus the in-memory reference implementation the conformance
509
+ * suite is written against.
510
+ * @module
511
+ */
512
+ /**
513
+ * Rejection from {@link AgentEventLogStore.append} when the thread's stored
514
+ * length is not the `expectedIndex` the writer held — a concurrent writer
515
+ * appended first. `actualIndex` is the next index the store would accept.
516
+ */
517
+ var AgentEventLogConflictError = class extends AgentError {
518
+ threadId;
519
+ expectedIndex;
520
+ actualIndex;
521
+ constructor(threadId, expectedIndex, actualIndex) {
522
+ super("event-log-conflict", `AgentEventLogStore.append: index conflict on thread "${threadId}": expected index ${expectedIndex} but found ${actualIndex} — a concurrent writer won.`);
523
+ this.name = "AgentEventLogConflictError";
524
+ this.threadId = threadId;
525
+ this.expectedIndex = expectedIndex;
526
+ this.actualIndex = actualIndex;
527
+ }
528
+ };
529
+ /**
530
+ * In-memory reference store, and the baseline the conformance suite runs
531
+ * against. Per-thread entries are held in a contiguous, index-ordered list;
532
+ * append's check-and-push runs synchronously (no `await` between reading the
533
+ * length and writing), so two appends racing on the same `expectedIndex`
534
+ * resolve to exactly one winner. Every stored and returned entry is
535
+ * `structuredClone`d, so a caller can neither mutate stored state through a
536
+ * value it appended nor through a value it read.
537
+ */
538
+ function createInMemoryEventLogStore() {
539
+ const threads = /* @__PURE__ */ new Map();
540
+ const clone = (value) => structuredClone(value);
541
+ return {
542
+ async append({ threadId, expectedIndex, entries }) {
543
+ for (const entry of entries) assertAgentLogEntry(entry);
544
+ for (let i = 0; i < entries.length; i++) if (entries[i].index !== expectedIndex + i) throw new Error(`AgentEventLogStore.append: entry.index (${entries[i].index}) must be contiguous from expectedIndex (${expectedIndex}); expected ${expectedIndex + i} at position ${i} on thread "${threadId}".`);
545
+ const list = threads.get(threadId);
546
+ const length = list ? list.length : 0;
547
+ if (length !== expectedIndex) throw new AgentEventLogConflictError(threadId, expectedIndex, length);
548
+ const ids = new Set((list ?? []).map((entry) => entry.id));
549
+ for (const entry of entries) {
550
+ if (ids.has(entry.id)) throw new Error(`AgentEventLogStore.append: duplicate event id "${entry.id}" in thread "${threadId}".`);
551
+ ids.add(entry.id);
552
+ }
553
+ const cloned = entries.map((entry) => clone(entry));
554
+ if (list) for (const entry of cloned) list.push(entry);
555
+ else threads.set(threadId, cloned);
556
+ },
557
+ async read(threadId, options) {
558
+ const list = threads.get(threadId) ?? [];
559
+ const from = options?.from ?? 0;
560
+ return list.slice(from).map((entry) => clone(entry));
561
+ },
562
+ async length(threadId) {
563
+ return threads.get(threadId)?.length ?? 0;
564
+ },
565
+ async fork({ threadId, newThreadId, upToIndex }) {
566
+ if ((threads.get(newThreadId)?.length ?? 0) > 0) throw new Error(`AgentEventLogStore.fork: newThreadId "${newThreadId}" already has entries.`);
567
+ const source = threads.get(threadId);
568
+ if (!source) throw new Error(`AgentEventLogStore.fork: unknown source thread "${threadId}".`);
569
+ const upTo = upToIndex ?? source.length;
570
+ if (!Number.isInteger(upTo) || upTo < 1 || upTo > source.length) throw new Error(`AgentEventLogStore.fork: upToIndex must be an integer in [1, ${source.length}] on thread "${threadId}"; got ${String(upTo)}.`);
571
+ threads.set(newThreadId, source.slice(0, upTo).map((entry) => clone(entry)));
572
+ }
573
+ };
574
+ }
575
+ //#endregion
576
+ export { getUsageFromEvents as _, AgentEventLogError as a, replay as b, NonSerializableAgentEventError as c, assertJsonSerializable as d, canonicalEventJson as f, getSnapshotStateHash as g, getLogExecutionId as h, AGENT_INIT_EVENT_TYPE as i, agentCallOccurrence as l, forkEventLog as m, createInMemoryEventLogStore as n, AgentMachineVersionMismatchError as o, createReplayEntry as p, AGENT_EVENT_SCHEMA_VERSION as r, AgentReplayDivergenceError as s, AgentEventLogConflictError as t, assertAgentLogEntry as u, initEntry as v, validateReplayEntries as x, rebindActorSession as y };