@statelyai/agent 2.0.0-alpha.28 → 2.0.0-alpha.30
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/dist/ai-sdk.cjs +150 -27
- package/dist/ai-sdk.d.cts +59 -36
- package/dist/ai-sdk.d.mts +59 -36
- package/dist/ai-sdk.mjs +148 -24
- package/dist/{decision-RQ0iv0oZ.mjs → decision-BHkvuImD.mjs} +74 -8
- package/dist/{decision-CmM8-HHz.cjs → decision-CIb7ddIv.cjs} +113 -30
- package/dist/{text-logic-f5F5sCdc.cjs → event-log-BZRfBRd7.cjs} +814 -7
- package/dist/{text-logic-CIqHAmBi.mjs → event-log-DJCMHek_.mjs} +680 -4
- package/dist/{event-log-store-BIPOz-di.d.mts → event-log-store-CIIJohjj.d.mts} +90 -5
- package/dist/{event-log-store-2xzFmgr2.d.cts → event-log-store-CLAC9q2Y.d.cts} +90 -5
- package/dist/event-log-store-DZR4IIWe.cjs +95 -0
- package/dist/event-log-store-DsP6GCRh.mjs +84 -0
- package/dist/index.cjs +576 -58
- package/dist/index.d.cts +104 -510
- package/dist/index.d.mts +104 -510
- package/dist/index.mjs +547 -32
- package/dist/log.cjs +77 -20
- package/dist/log.d.cts +5 -5
- package/dist/log.d.mts +5 -5
- package/dist/log.mjs +58 -3
- package/dist/machines.cjs +1 -1
- package/dist/machines.d.cts +15 -15
- package/dist/machines.d.mts +15 -15
- package/dist/machines.mjs +1 -1
- package/dist/model-source-BCKg4qJ1.cjs +44 -0
- package/dist/model-source-BaHwJ1wK.mjs +39 -0
- package/dist/openai.cjs +11 -11
- package/dist/openai.d.cts +28 -26
- package/dist/openai.d.mts +28 -26
- package/dist/openai.mjs +3 -3
- package/dist/otel.cjs +5 -4
- package/dist/otel.d.cts +5 -7
- package/dist/otel.d.mts +5 -7
- package/dist/otel.mjs +5 -3
- package/dist/{run-agent-xNTxpt82.mjs → run-agent-BpHqkaZw.mjs} +181 -44
- package/dist/{run-agent-COXXc0RW.d.mts → run-agent-CAVt5R-r.d.mts} +95 -30
- package/dist/{run-agent-ByO-Bxyd.d.cts → run-agent-CTlMc3Jb.d.cts} +95 -30
- package/dist/{run-agent-Dmf8GV2d.cjs → run-agent-ctLCvNgL.cjs} +230 -88
- package/dist/{setup-agent-BuanDXeh.mjs → setup-agent-7rV_cujV.mjs} +15 -9
- package/dist/setup-agent-CGELn1qZ.d.mts +577 -0
- package/dist/setup-agent-CLPCkXsu.d.cts +577 -0
- package/dist/{setup-agent-DV4cTou-.cjs → setup-agent-DoUk7EdC.cjs} +31 -20
- package/dist/tanstack-ai.cjs +627 -0
- package/dist/tanstack-ai.d.cts +204 -0
- package/dist/tanstack-ai.d.mts +204 -0
- package/dist/tanstack-ai.mjs +618 -0
- package/dist/testing.cjs +204 -985
- package/dist/testing.d.cts +132 -347
- package/dist/testing.d.mts +132 -347
- package/dist/testing.mjs +191 -973
- package/dist/{text-logic-IjfYy9DP.d.cts → text-logic-BRYOPWvY.d.mts} +52 -12
- package/dist/{text-logic-D-czVPYp.d.mts → text-logic-DmciuVUQ.d.cts} +52 -12
- package/dist/{types-BgNbNKnJ.d.mts → types-Dp5_Ru4R.d.cts} +2 -2
- package/dist/{types-hR5f5YXa.d.cts → types-Dp5_Ru4R.d.mts} +2 -2
- package/dist/usage-BkM3z02Y.d.mts +17 -0
- package/dist/usage-Cm_ovz9V.d.cts +17 -0
- package/dist/{utils-_q7F-AZu.d.mts → utils-BnXdWih3.d.mts} +1 -2
- package/dist/{utils-o4YRdHJX.d.cts → utils-ebVpCYoF.d.cts} +1 -2
- package/dist/validate.cjs +239 -8
- package/dist/validate.d.cts +110 -3
- package/dist/validate.d.mts +110 -3
- package/dist/validate.mjs +238 -7
- package/dist/verify-COFWyprJ.mjs +1181 -0
- package/dist/verify-CVlsTZ-V.cjs +1222 -0
- package/dist/verify-Cn86rT8l.d.mts +414 -0
- package/dist/verify-Dzwd4Mvk.d.cts +414 -0
- package/package.json +44 -31
- package/readme.md +40 -28
- package/skills/add-evals/SKILL.md +129 -0
- package/skills/add-traces/SKILL.md +135 -0
- package/skills/generate-machine/SKILL.md +143 -69
- package/skills/graph-engineering/SKILL.md +33 -0
- package/skills/improve-machine/SKILL.md +241 -0
- package/skills/improve-machine/reference.md +101 -0
- package/skills/loop-to-machine/SKILL.md +183 -0
- package/skills/loop-to-machine/policy.md +158 -0
- package/dist/event-log-store-2pAFiWuc.mjs +0 -576
- package/dist/event-log-store-C-COhIXP.cjs +0 -702
- package/dist/usage-BdEjFuIi.mjs +0 -5
- package/dist/usage-efOQPdf_.cjs +0 -10
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
require("./validate.cjs");
|
|
2
1
|
let xstate = require("xstate");
|
|
3
2
|
//#region src/errors.ts
|
|
4
3
|
/**
|
|
@@ -384,7 +383,7 @@ function getRegisteredAgentExecutionOptions(machine, options) {
|
|
|
384
383
|
const GENERATE_TEXT_ACTOR = "agent.generateText";
|
|
385
384
|
const STREAM_TEXT_ACTOR = "agent.streamText";
|
|
386
385
|
const DECIDE_ACTOR = "agent.decide";
|
|
387
|
-
const AGENT_FINISH_REASONS = new Set([
|
|
386
|
+
const AGENT_FINISH_REASONS = /* @__PURE__ */ new Set([
|
|
388
387
|
"stop",
|
|
389
388
|
"length",
|
|
390
389
|
"tool-calls",
|
|
@@ -578,7 +577,7 @@ function resolveTextLogicValue(value, args) {
|
|
|
578
577
|
* export const tellJoke = createTextLogic({
|
|
579
578
|
* mode: 'stream',
|
|
580
579
|
* schemas: { input: z.object({ topic: z.string() }), output: z.string() },
|
|
581
|
-
* model: 'openai/gpt-
|
|
580
|
+
* model: 'openai/gpt-6-luna',
|
|
582
581
|
* system: 'You tell short, punchy jokes.',
|
|
583
582
|
* prompt: ({ input }) => `Tell a joke about ${input.topic}.`,
|
|
584
583
|
* });
|
|
@@ -815,6 +814,700 @@ function invalidGeneratorResult(id) {
|
|
|
815
814
|
return /* @__PURE__ */ new Error(`Executor for '${id}' returned an invalid result: executors must return { result } (the text string or structured object as \`result\`, plus optional \`messages\`, \`usage\` and other passthrough fields). Wrap a raw SDK result with an adapter such as createAiSdkExecutors.`);
|
|
816
815
|
}
|
|
817
816
|
//#endregion
|
|
817
|
+
//#region src/usage.ts
|
|
818
|
+
/** Reserved event delivered after a model call reports token usage. */
|
|
819
|
+
const AGENT_USAGE_EVENT_TYPE = "@agent.usage";
|
|
820
|
+
//#endregion
|
|
821
|
+
//#region src/event-log.ts
|
|
822
|
+
/**
|
|
823
|
+
* The event log: an append-only journal of the EXTERNAL inputs a machine
|
|
824
|
+
* consumed, and the pure fold ({@link replay}) that turns that journal back
|
|
825
|
+
* into a snapshot without executing anything.
|
|
826
|
+
*
|
|
827
|
+
* A journaled entry is always an external input — an invoke completion
|
|
828
|
+
* (`xstate.done.actor`/`xstate.error.actor` carrying its output inline), a
|
|
829
|
+
* user-sent event, a timer firing, a reserved `@agent.usage` record. Never a
|
|
830
|
+
* raised/internal event: replay re-derives those from the machine's own logic,
|
|
831
|
+
* so journaling them would double-apply them.
|
|
832
|
+
*
|
|
833
|
+
* A log is self-contained: its reserved first entry ({@link initEntry}) carries
|
|
834
|
+
* either the machine `input` (a fresh start) or a persisted snapshot (a
|
|
835
|
+
* continuation across a machine version, or from a log-less legacy snapshot),
|
|
836
|
+
* so {@link replay} needs no side channel.
|
|
837
|
+
* @module
|
|
838
|
+
*/
|
|
839
|
+
/** The durable entry envelope version. */
|
|
840
|
+
const AGENT_EVENT_SCHEMA_VERSION = 1;
|
|
841
|
+
/**
|
|
842
|
+
* The reserved event type of the first entry of every log. It carries the
|
|
843
|
+
* machine `input` (fresh start) or a persisted `snapshot` (continuation), so a
|
|
844
|
+
* log replays with no side channel. Lives in the reserved `@agent.*` namespace
|
|
845
|
+
* so it can never collide with a machine's own vocabulary; it is consumed by
|
|
846
|
+
* {@link replay} and never fed to `transition`.
|
|
847
|
+
*/
|
|
848
|
+
const AGENT_INIT_EVENT_TYPE = "@agent.init";
|
|
849
|
+
/** A malformed or internally inconsistent log. */
|
|
850
|
+
var AgentEventLogError = class extends AgentError {
|
|
851
|
+
constructor(message) {
|
|
852
|
+
super("invalid-event-log", message);
|
|
853
|
+
this.name = "AgentEventLogError";
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
/** A precise failure when an entry would not survive a JSON round-trip. */
|
|
857
|
+
var NonSerializableAgentEventError = class extends AgentError {
|
|
858
|
+
path;
|
|
859
|
+
valueType;
|
|
860
|
+
constructor(path, valueType) {
|
|
861
|
+
super("non-serializable-event", `Agent event field '${path}' is not JSON-serializable (${valueType}).`);
|
|
862
|
+
this.name = "NonSerializableAgentEventError";
|
|
863
|
+
this.path = path;
|
|
864
|
+
this.valueType = valueType;
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
/** Replay reached a state that does not match the entry's recorded hash. */
|
|
868
|
+
var AgentReplayDivergenceError = class extends AgentError {
|
|
869
|
+
eventId;
|
|
870
|
+
index;
|
|
871
|
+
kind;
|
|
872
|
+
expected;
|
|
873
|
+
actual;
|
|
874
|
+
constructor(eventId, index, kind, expected, actual) {
|
|
875
|
+
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}'.`);
|
|
876
|
+
this.eventId = eventId;
|
|
877
|
+
this.index = index;
|
|
878
|
+
this.kind = kind;
|
|
879
|
+
this.expected = expected;
|
|
880
|
+
this.actual = actual;
|
|
881
|
+
this.name = "AgentReplayDivergenceError";
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
/**
|
|
885
|
+
* An entry was recorded against a different machine (id) or a different
|
|
886
|
+
* machine version than the one replaying it. An init-with-snapshot entry is
|
|
887
|
+
* exempt: that entry IS the bridge from the old version to the new one.
|
|
888
|
+
*/
|
|
889
|
+
var AgentMachineVersionMismatchError = class extends AgentError {
|
|
890
|
+
eventId;
|
|
891
|
+
index;
|
|
892
|
+
expected;
|
|
893
|
+
actual;
|
|
894
|
+
constructor(eventId, index, expected, actual) {
|
|
895
|
+
super("machine-version-mismatch", `Event log entry '${eventId}' at index ${index} targets machine '${actual.machineId}'@'${actual.machineVersion}', expected '${expected.machineId}'@'${expected.machineVersion}'.`);
|
|
896
|
+
this.eventId = eventId;
|
|
897
|
+
this.index = index;
|
|
898
|
+
this.expected = expected;
|
|
899
|
+
this.actual = actual;
|
|
900
|
+
this.name = "AgentMachineVersionMismatchError";
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
function assertNoHiddenKeys(value, path, isOwnKey) {
|
|
904
|
+
const symbols = Object.getOwnPropertySymbols(value);
|
|
905
|
+
if (symbols.length > 0) throw new NonSerializableAgentEventError(`${path}.[${String(symbols[0])}]`, "symbol key");
|
|
906
|
+
const hiddenKey = Object.getOwnPropertyNames(value).find((key) => !isOwnKey?.(key) && !Object.getOwnPropertyDescriptor(value, key)?.enumerable);
|
|
907
|
+
if (hiddenKey !== void 0) throw new NonSerializableAgentEventError(`${path}.${hiddenKey}`, "non-enumerable property");
|
|
908
|
+
}
|
|
909
|
+
const arrayIndexPattern = /^(?:0|[1-9]\d*)$/;
|
|
910
|
+
/**
|
|
911
|
+
* Rejects values JSON would drop or coerce. Unlike `JSON.stringify`, this does
|
|
912
|
+
* not silently erase `undefined`/functions or turn non-finite numbers into
|
|
913
|
+
* `null`; durable entries contain only plain JSON values.
|
|
914
|
+
*/
|
|
915
|
+
function assertJsonSerializable(value, path = "entry", ancestors = /* @__PURE__ */ new WeakSet()) {
|
|
916
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return;
|
|
917
|
+
if (typeof value === "number") {
|
|
918
|
+
if (!Number.isFinite(value) || Object.is(value, -0)) throw new NonSerializableAgentEventError(path, Object.is(value, -0) ? "-0" : String(value));
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
if (typeof value !== "object") throw new NonSerializableAgentEventError(path, typeof value);
|
|
922
|
+
if (ancestors.has(value)) throw new NonSerializableAgentEventError(path, "circular reference");
|
|
923
|
+
ancestors.add(value);
|
|
924
|
+
try {
|
|
925
|
+
if (Array.isArray(value)) {
|
|
926
|
+
for (let index = 0; index < value.length; index++) {
|
|
927
|
+
if (!Object.hasOwn(value, index)) throw new NonSerializableAgentEventError(`${path}[${index}]`, "array hole");
|
|
928
|
+
assertJsonSerializable(value[index], `${path}[${index}]`, ancestors);
|
|
929
|
+
}
|
|
930
|
+
const extraKey = Object.keys(value).find((key) => !arrayIndexPattern.test(key) || Number(key) >= value.length);
|
|
931
|
+
if (extraKey !== void 0) throw new NonSerializableAgentEventError(`${path}.${extraKey}`, "array property");
|
|
932
|
+
assertNoHiddenKeys(value, path, (key) => key === "length" || arrayIndexPattern.test(key));
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
const prototype = Object.getPrototypeOf(value);
|
|
936
|
+
if (prototype !== Object.prototype && prototype !== null) throw new NonSerializableAgentEventError(path, value.constructor?.name ?? "object");
|
|
937
|
+
assertNoHiddenKeys(value, path);
|
|
938
|
+
for (const [key, child] of Object.entries(value)) assertJsonSerializable(child, `${path}.${key}`, ancestors);
|
|
939
|
+
} finally {
|
|
940
|
+
ancestors.delete(value);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function requireNonEmptyString(value, label, qualifier = "") {
|
|
944
|
+
if (typeof value !== "string" || !value) throw new AgentEventLogError(`Agent event ${label} must be a non-empty string${qualifier}.`);
|
|
945
|
+
}
|
|
946
|
+
/** Validates the complete durable envelope before append/export/replay. */
|
|
947
|
+
function assertAgentLogEntry(entry) {
|
|
948
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) throw new AgentEventLogError("Agent event entry must be an object.");
|
|
949
|
+
assertJsonSerializable(entry);
|
|
950
|
+
const candidate = entry;
|
|
951
|
+
if (candidate.schemaVersion !== 1) throw new AgentEventLogError(`Unsupported agent event schema version '${String(candidate.schemaVersion)}'; expected '1'.`);
|
|
952
|
+
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)}.`);
|
|
953
|
+
for (const field of [
|
|
954
|
+
"id",
|
|
955
|
+
"machineId",
|
|
956
|
+
"machineVersion"
|
|
957
|
+
]) requireNonEmptyString(candidate[field], `entry.${field}`);
|
|
958
|
+
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)}'.`);
|
|
959
|
+
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.");
|
|
960
|
+
if (candidate.causationId !== void 0) requireNonEmptyString(candidate.causationId, "entry.causationId", " when present");
|
|
961
|
+
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.");
|
|
962
|
+
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.");
|
|
963
|
+
if (candidate.attempts !== void 0) assertLogAttempts(candidate.attempts);
|
|
964
|
+
}
|
|
965
|
+
function assertLogAttempts(attempts) {
|
|
966
|
+
if (!Array.isArray(attempts) || !attempts.every((attempt) => attempt !== null && typeof attempt === "object" && typeof attempt.failure === "string" && typeof attempt.reason === "string")) throw new AgentEventLogError("Agent event entry.attempts requires an array of { failure, reason } objects.");
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Whether `value` survives the log's JSON rules. Diagnostic fields the runtime
|
|
970
|
+
* records on its own (decision attempts) are dropped rather than failing the
|
|
971
|
+
* entry they explain.
|
|
972
|
+
*/
|
|
973
|
+
function isJsonSerializable(value) {
|
|
974
|
+
try {
|
|
975
|
+
assertJsonSerializable(value);
|
|
976
|
+
return true;
|
|
977
|
+
} catch {
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* The decision-exhausted error's `attempts`, when JSON-safe. Duck-typed on the
|
|
983
|
+
* error code: `decision.ts` imports this module, so importing the class here
|
|
984
|
+
* would be a cycle.
|
|
985
|
+
*/
|
|
986
|
+
const DECISION_EXHAUSTED_CODE = "decision-exhausted";
|
|
987
|
+
function exhaustedAttemptsOf(error) {
|
|
988
|
+
if (error.code !== DECISION_EXHAUSTED_CODE) return void 0;
|
|
989
|
+
const attempts = error.attempts;
|
|
990
|
+
return Array.isArray(attempts) && isJsonSerializable(attempts) ? attempts : void 0;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Replaces `Error` instances with plain `{ name, message, cause? }` objects so
|
|
994
|
+
* an `xstate.error.actor` payload survives the log's JSON round-trip. A
|
|
995
|
+
* decision-exhausted error also keeps its `code` and `attempts`. Cycles
|
|
996
|
+
* are preserved by identity; non-plain objects are left alone (and are then
|
|
997
|
+
* rejected by {@link assertJsonSerializable}).
|
|
998
|
+
*/
|
|
999
|
+
function normalizeEventErrors(value, seen = /* @__PURE__ */ new WeakMap()) {
|
|
1000
|
+
if (value instanceof Error) {
|
|
1001
|
+
const normalized = {
|
|
1002
|
+
name: value.name,
|
|
1003
|
+
message: value.message
|
|
1004
|
+
};
|
|
1005
|
+
seen.set(value, normalized);
|
|
1006
|
+
if (value.cause !== void 0) normalized.cause = normalizeEventErrors(value.cause, seen);
|
|
1007
|
+
if (value.code === DECISION_EXHAUSTED_CODE) {
|
|
1008
|
+
normalized.code = DECISION_EXHAUSTED_CODE;
|
|
1009
|
+
const attempts = exhaustedAttemptsOf(value);
|
|
1010
|
+
if (attempts !== void 0) normalized.attempts = structuredClone(attempts);
|
|
1011
|
+
}
|
|
1012
|
+
return normalized;
|
|
1013
|
+
}
|
|
1014
|
+
if (value === null || typeof value !== "object") return value;
|
|
1015
|
+
const existing = seen.get(value);
|
|
1016
|
+
if (existing !== void 0) return existing;
|
|
1017
|
+
if (Array.isArray(value)) {
|
|
1018
|
+
const result = [];
|
|
1019
|
+
seen.set(value, result);
|
|
1020
|
+
for (const item of value) result.push(normalizeEventErrors(item, seen));
|
|
1021
|
+
return result;
|
|
1022
|
+
}
|
|
1023
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1024
|
+
if (prototype !== Object.prototype && prototype !== null) return value;
|
|
1025
|
+
const result = {};
|
|
1026
|
+
seen.set(value, result);
|
|
1027
|
+
for (const [key, item] of Object.entries(value)) result[key] = normalizeEventErrors(item, seen);
|
|
1028
|
+
return result;
|
|
1029
|
+
}
|
|
1030
|
+
const SNAPSHOT_VOLATILE_KEYS = /* @__PURE__ */ new Set([
|
|
1031
|
+
"sessionId",
|
|
1032
|
+
"_nextTimerId",
|
|
1033
|
+
"_nextActorIds"
|
|
1034
|
+
]);
|
|
1035
|
+
const CHILD_VOLATILE_KEYS = /* @__PURE__ */ new Set(["sessionId"]);
|
|
1036
|
+
function positionOf(parent, key) {
|
|
1037
|
+
if (parent === "snapshot") return key === "children" ? "children" : "data";
|
|
1038
|
+
if (parent === "children") return "child";
|
|
1039
|
+
if (parent === "child") return key === "snapshot" ? "snapshot" : "data";
|
|
1040
|
+
return "data";
|
|
1041
|
+
}
|
|
1042
|
+
function canonicalizeForHash(value, seen = /* @__PURE__ */ new WeakSet(), position = "snapshot") {
|
|
1043
|
+
if (value === void 0) return "[undefined]";
|
|
1044
|
+
if (typeof value === "function") return "[function]";
|
|
1045
|
+
if (typeof value === "bigint") return `[bigint:${String(value)}]`;
|
|
1046
|
+
if (typeof value === "symbol") return `[symbol:${String(value)}]`;
|
|
1047
|
+
if (value === null || typeof value !== "object") return value;
|
|
1048
|
+
if (value instanceof Date) return `[Date:${value.toISOString()}]`;
|
|
1049
|
+
if (value instanceof Error) {
|
|
1050
|
+
if (seen.has(value)) return "[circular]";
|
|
1051
|
+
seen.add(value);
|
|
1052
|
+
try {
|
|
1053
|
+
return {
|
|
1054
|
+
errorName: value.name,
|
|
1055
|
+
message: value.message,
|
|
1056
|
+
...value.cause !== void 0 ? { cause: canonicalizeForHash(value.cause, seen, "data") } : {}
|
|
1057
|
+
};
|
|
1058
|
+
} finally {
|
|
1059
|
+
seen.delete(value);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
if (seen.has(value)) return "[circular]";
|
|
1063
|
+
seen.add(value);
|
|
1064
|
+
try {
|
|
1065
|
+
if (value instanceof Set) return [...value].map((item) => canonicalizeForHash(item, seen, "data")).sort((a, b) => stableJson(a).localeCompare(stableJson(b)));
|
|
1066
|
+
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)));
|
|
1067
|
+
if (Array.isArray(value)) return value.map((item) => canonicalizeForHash(item, seen, "data"));
|
|
1068
|
+
const volatileKeys = position === "snapshot" ? SNAPSHOT_VOLATILE_KEYS : position === "child" ? CHILD_VOLATILE_KEYS : void 0;
|
|
1069
|
+
const result = {};
|
|
1070
|
+
for (const key of Object.keys(value).sort()) {
|
|
1071
|
+
if (volatileKeys?.has(key)) continue;
|
|
1072
|
+
const child = value[key];
|
|
1073
|
+
if (child === void 0) continue;
|
|
1074
|
+
result[key] = canonicalizeForHash(child, seen, positionOf(position, key));
|
|
1075
|
+
}
|
|
1076
|
+
return result;
|
|
1077
|
+
} finally {
|
|
1078
|
+
seen.delete(value);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Key-sorted JSON for comparing two events by content: key insertion order
|
|
1083
|
+
* does not count, so an entry read back from storage equals the one that was
|
|
1084
|
+
* appended.
|
|
1085
|
+
* @internal
|
|
1086
|
+
*/
|
|
1087
|
+
function canonicalEventJson(event) {
|
|
1088
|
+
return stableJson(canonicalizeForHash(event, /* @__PURE__ */ new Set(), "data"));
|
|
1089
|
+
}
|
|
1090
|
+
function stableJson(value) {
|
|
1091
|
+
return JSON.stringify(sortJson(value));
|
|
1092
|
+
}
|
|
1093
|
+
function sortJson(value) {
|
|
1094
|
+
if (Array.isArray(value)) return value.map(sortJson);
|
|
1095
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortJson(value[key])]));
|
|
1096
|
+
return value;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* The canonical hash of a PERSISTED snapshot (`machine.getPersistedSnapshot()`,
|
|
1100
|
+
* or {@link replay}'s `persistedSnapshot`) — the value recorded in an entry's
|
|
1101
|
+
* `verification.stateHash`.
|
|
1102
|
+
*
|
|
1103
|
+
* Stripped before hashing, because they are re-minted on every fold and would
|
|
1104
|
+
* otherwise make two identical runs hash differently: `sessionId`,
|
|
1105
|
+
* `_nextTimerId` and `_nextActorIds` at the snapshot root, `sessionId` on each
|
|
1106
|
+
* `children[*]` entry (recursively, through nested child snapshots), and
|
|
1107
|
+
* `undefined`-valued properties (so a snapshot hashes the same before and
|
|
1108
|
+
* after a JSON round-trip). Stripping is confined to those structural
|
|
1109
|
+
* positions: a `sessionId` inside `context`, `output`, `error` or an event
|
|
1110
|
+
* payload is application data and is hashed. Object keys are sorted, so key
|
|
1111
|
+
* order is irrelevant.
|
|
1112
|
+
*
|
|
1113
|
+
* A change detector, not a cryptographic digest.
|
|
1114
|
+
*/
|
|
1115
|
+
function getSnapshotStateHash(snapshot) {
|
|
1116
|
+
return djb2Hex(stableJson(canonicalizeForHash(snapshot)));
|
|
1117
|
+
}
|
|
1118
|
+
function machineIdOf(machine) {
|
|
1119
|
+
return machine.config.id ?? machine.id ?? "(machine)";
|
|
1120
|
+
}
|
|
1121
|
+
function toPersisted(machine, snapshot) {
|
|
1122
|
+
return typeof snapshot.matches === "function" ? machine.getPersistedSnapshot(snapshot) : snapshot;
|
|
1123
|
+
}
|
|
1124
|
+
function normalizeDoneActorEvent(event) {
|
|
1125
|
+
if (event.type !== DONE_ACTOR_EVENT_TYPE) return event;
|
|
1126
|
+
const { output, ...rest } = event;
|
|
1127
|
+
return output === void 0 ? rest : event;
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Creates a JSON-safe, self-describing entry for `event` appended after
|
|
1131
|
+
* `entries` (which must be the complete prefix, so `index` and the recorded
|
|
1132
|
+
* state hash line up).
|
|
1133
|
+
*/
|
|
1134
|
+
function createReplayEntry(machine, entries, event, options = {}) {
|
|
1135
|
+
const index = entries.length;
|
|
1136
|
+
const machineVersion = options.machineVersion ?? resolveMachineVersion(machine);
|
|
1137
|
+
const entry = {
|
|
1138
|
+
schemaVersion: 1,
|
|
1139
|
+
id: options.id ?? `evt_${String(index).padStart(8, "0")}`,
|
|
1140
|
+
index,
|
|
1141
|
+
recordedAt: options.recordedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1142
|
+
machineId: machineIdOf(machine),
|
|
1143
|
+
machineVersion,
|
|
1144
|
+
event: normalizeDoneActorEvent(normalizeEventErrors(event)),
|
|
1145
|
+
...options.causationId !== void 0 ? { causationId: options.causationId } : {},
|
|
1146
|
+
...options.metadata !== void 0 ? { metadata: options.metadata } : {},
|
|
1147
|
+
...options.attempts !== void 0 && options.attempts.length > 0 ? { attempts: options.attempts } : {}
|
|
1148
|
+
};
|
|
1149
|
+
assertAgentLogEntry(entry);
|
|
1150
|
+
if (entry.attempts !== void 0) entry.attempts = structuredClone(entry.attempts);
|
|
1151
|
+
if (options.verification !== false) entry.verification = { stateHash: getSnapshotStateHash(options.snapshot ? toPersisted(machine, options.snapshot) : replay(machine, [...entries, entry], {
|
|
1152
|
+
machineVersion,
|
|
1153
|
+
verify: false
|
|
1154
|
+
}).persistedSnapshot) };
|
|
1155
|
+
return entry;
|
|
1156
|
+
}
|
|
1157
|
+
function pruneUndefined(value) {
|
|
1158
|
+
if (Array.isArray(value)) return value.map(pruneUndefined);
|
|
1159
|
+
if (value === null || typeof value !== "object") return value;
|
|
1160
|
+
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return value;
|
|
1161
|
+
const result = {};
|
|
1162
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1163
|
+
if (child === void 0) continue;
|
|
1164
|
+
result[key] = pruneUndefined(child);
|
|
1165
|
+
}
|
|
1166
|
+
return result;
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* The reserved first entry of a log. Pass EXACTLY one of:
|
|
1170
|
+
*
|
|
1171
|
+
* - `{ input }` — a fresh start; replay folds from `initialTransition`.
|
|
1172
|
+
* - `{ snapshot }` — a continuation from a persisted snapshot: a version
|
|
1173
|
+
* bridge, or a legacy snapshot that predates the log. Replay restores it
|
|
1174
|
+
* purely (no actors started) and folds the rest of the log onto it.
|
|
1175
|
+
*
|
|
1176
|
+
* `options.metadata.executionId` names the lineage; this library always sets
|
|
1177
|
+
* one, and readers must tolerate its absence in logs written elsewhere.
|
|
1178
|
+
*/
|
|
1179
|
+
function initEntry(machine, init = {}, options = {}) {
|
|
1180
|
+
const hasSnapshot = init.snapshot !== void 0;
|
|
1181
|
+
if (hasSnapshot && init.input !== void 0) throw new AgentEventLogError("initEntry accepts exactly one of `input` (fresh start) or `snapshot` (continuation).");
|
|
1182
|
+
return createReplayEntry(machine, [], {
|
|
1183
|
+
type: AGENT_INIT_EVENT_TYPE,
|
|
1184
|
+
...hasSnapshot ? { snapshot: pruneUndefined(init.snapshot) } : init.input !== void 0 ? { input: init.input } : {}
|
|
1185
|
+
}, options);
|
|
1186
|
+
}
|
|
1187
|
+
/** Whether `entry` is the reserved init entry. */
|
|
1188
|
+
function isInitEntry(entry) {
|
|
1189
|
+
return entry?.event?.type === AGENT_INIT_EVENT_TYPE;
|
|
1190
|
+
}
|
|
1191
|
+
/** Whether `entry` is the version-bridging init-with-snapshot entry. */
|
|
1192
|
+
function isSnapshotInitEntry(entry) {
|
|
1193
|
+
return isInitEntry(entry) && entry.event.snapshot !== void 0;
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* The log's execution id: the lineage identity pinned in the init entry's
|
|
1197
|
+
* `metadata.executionId`.
|
|
1198
|
+
*
|
|
1199
|
+
* Minted once per run and inherited verbatim by every resume of that log, so
|
|
1200
|
+
* it names one lineage rather than one process. Entry ids cannot serve this
|
|
1201
|
+
* role — the built-in ids are index-derived, so every log's init entry is
|
|
1202
|
+
* `evt_00000000`. A fork copies the init entry, so it shares its parent's
|
|
1203
|
+
* execution id by construction.
|
|
1204
|
+
*
|
|
1205
|
+
* Returns `undefined` for a log with no init entry and for one written without
|
|
1206
|
+
* the field — callers must treat the id as optional.
|
|
1207
|
+
*/
|
|
1208
|
+
function getLogExecutionId(entries) {
|
|
1209
|
+
const executionId = (entries.find((entry) => isInitEntry(entry))?.metadata)?.executionId;
|
|
1210
|
+
return typeof executionId === "string" ? executionId : void 0;
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Validates a log before replay or append: every envelope well-formed, indices
|
|
1214
|
+
* contiguous from 0, ids unique, the first entry the reserved init entry, and
|
|
1215
|
+
* one consistent machine identity throughout.
|
|
1216
|
+
*
|
|
1217
|
+
* With `machine`, entries are also checked against that machine's id and
|
|
1218
|
+
* resolved version — a mismatch throws {@link AgentMachineVersionMismatchError}.
|
|
1219
|
+
* An init-with-snapshot entry is exempt from the version check: that entry is
|
|
1220
|
+
* itself the bridge from whatever version produced the snapshot.
|
|
1221
|
+
*/
|
|
1222
|
+
function validateReplayEntries(entries, options = {}) {
|
|
1223
|
+
if (entries.length === 0) return;
|
|
1224
|
+
const reference = entries.find((entry) => !isSnapshotInitEntry(entry)) ?? entries[0];
|
|
1225
|
+
const expected = options.machine ? {
|
|
1226
|
+
machineId: machineIdOf(options.machine),
|
|
1227
|
+
machineVersion: options.machineVersion ?? resolveMachineVersion(options.machine)
|
|
1228
|
+
} : {
|
|
1229
|
+
machineId: reference.machineId,
|
|
1230
|
+
machineVersion: options.machineVersion ?? reference.machineVersion
|
|
1231
|
+
};
|
|
1232
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1233
|
+
for (let index = 0; index < entries.length; index++) {
|
|
1234
|
+
const entry = entries[index];
|
|
1235
|
+
assertAgentLogEntry(entry);
|
|
1236
|
+
if (entry.index !== index) throw new AgentEventLogError(`Event log must be contiguous from index 0; found entry.index ${entry.index} at position ${index}.`);
|
|
1237
|
+
if (ids.has(entry.id)) throw new AgentEventLogError(`Event log contains duplicate entry id '${entry.id}'.`);
|
|
1238
|
+
ids.add(entry.id);
|
|
1239
|
+
if (index === 0 && !isInitEntry(entry)) throw new AgentEventLogError(`Event log must start with the reserved '${AGENT_INIT_EVENT_TYPE}' entry; found '${entry.event.type}'.`);
|
|
1240
|
+
if (index > 0 && isInitEntry(entry)) throw new AgentEventLogError(`Event log has a second '${AGENT_INIT_EVENT_TYPE}' entry at index ${index}.`);
|
|
1241
|
+
const versionMatches = entry.machineVersion === expected.machineVersion;
|
|
1242
|
+
if (entry.machineId !== expected.machineId || !versionMatches && !isSnapshotInitEntry(entry)) throw new AgentMachineVersionMismatchError(entry.id, entry.index, expected, {
|
|
1243
|
+
machineId: entry.machineId,
|
|
1244
|
+
machineVersion: entry.machineVersion
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* The prefix `[0, upToIndex)` of a log, as a new log. `upToIndex` is exclusive
|
|
1250
|
+
* and must leave the init entry in place, so the fork is still self-contained.
|
|
1251
|
+
*/
|
|
1252
|
+
function forkEventLog(entries, upToIndex) {
|
|
1253
|
+
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)}.`);
|
|
1254
|
+
const forked = entries.slice(0, upToIndex);
|
|
1255
|
+
if (!isInitEntry(forked[0])) throw new AgentEventLogError(`forkEventLog: the forked prefix must start with the reserved '${AGENT_INIT_EVENT_TYPE}' entry.`);
|
|
1256
|
+
return forked;
|
|
1257
|
+
}
|
|
1258
|
+
const DONE_ACTOR_EVENT_TYPE = "xstate.done.actor";
|
|
1259
|
+
const ERROR_ACTOR_EVENT_TYPE = "xstate.error.actor";
|
|
1260
|
+
/** Normalizes a mixed `EventObject | AgentLogEntry` history into bare events. */
|
|
1261
|
+
function toEvents(history) {
|
|
1262
|
+
if (!history) return [];
|
|
1263
|
+
return history.map((item) => {
|
|
1264
|
+
const candidate = item;
|
|
1265
|
+
return candidate?.schemaVersion === 1 && typeof candidate.index === "number" && candidate.event !== null && typeof candidate.event === "object" ? candidate.event : item;
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* The 1-based occurrence `n` for the NEXT call at invoke site `siteId`:
|
|
1270
|
+
* `1 + completions`, where a completion is a journaled `xstate.done.actor` OR
|
|
1271
|
+
* `xstate.error.actor` for that `actorId` (an error is a semantic completion).
|
|
1272
|
+
* The one counting rule behind every `${siteId}#${n}` call key, so a live run
|
|
1273
|
+
* and a replay derive the same `n` by construction.
|
|
1274
|
+
*/
|
|
1275
|
+
function agentCallOccurrence(history, siteId) {
|
|
1276
|
+
let count = 0;
|
|
1277
|
+
for (const event of toEvents(history)) if ((event.type === DONE_ACTOR_EVENT_TYPE || event.type === ERROR_ACTOR_EVENT_TYPE) && event.actorId === siteId) count++;
|
|
1278
|
+
return count + 1;
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Folds the reserved `@agent.usage` entries of a log into one cumulative usage
|
|
1282
|
+
* total. The log is the source of truth, so the totals are a pure projection of
|
|
1283
|
+
* it: replay a log, fold it, and you get the numbers the run reported with no
|
|
1284
|
+
* host-side accumulator in between.
|
|
1285
|
+
*
|
|
1286
|
+
* Accepts entries or bare events. Token fields are partial sums: a field stays
|
|
1287
|
+
* `undefined` until some entry reports it, and non-finite values are ignored.
|
|
1288
|
+
*/
|
|
1289
|
+
function getUsageFromEvents(entries) {
|
|
1290
|
+
const totals = {};
|
|
1291
|
+
for (const event of toEvents(entries)) {
|
|
1292
|
+
if (event.type !== "@agent.usage") continue;
|
|
1293
|
+
const usage = event.usage;
|
|
1294
|
+
if (!usage || typeof usage !== "object") continue;
|
|
1295
|
+
for (const field of AGENT_USAGE_TOKEN_FIELDS) {
|
|
1296
|
+
const value = usage[field];
|
|
1297
|
+
if (typeof value === "number" && Number.isFinite(value)) totals[field] = (totals[field] ?? 0) + value;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
return totals;
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* The decision attempts a log recorded but the machine never accepted, in log
|
|
1304
|
+
* order — the input for improving a decider from its traces. Two sources:
|
|
1305
|
+
*
|
|
1306
|
+
* - `entry.attempts`: the failed tries before the entry's event landed.
|
|
1307
|
+
* - An `xstate.error.actor.*` entry whose `error` is a decision-exhausted
|
|
1308
|
+
* error (`code: "decision-exhausted"`) that kept its `attempts`: every try
|
|
1309
|
+
* failed. Another actor error's `attempts` field is not read.
|
|
1310
|
+
*
|
|
1311
|
+
* Entries with neither are skipped.
|
|
1312
|
+
*/
|
|
1313
|
+
function getDecisionAttempts(entries) {
|
|
1314
|
+
const found = [];
|
|
1315
|
+
for (const entry of entries) {
|
|
1316
|
+
const { type } = entry.event;
|
|
1317
|
+
if (entry.attempts !== void 0 && entry.attempts.length > 0) {
|
|
1318
|
+
found.push({
|
|
1319
|
+
index: entry.index,
|
|
1320
|
+
eventType: type,
|
|
1321
|
+
attempts: entry.attempts
|
|
1322
|
+
});
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
if (type.startsWith(ERROR_ACTOR_EVENT_TYPE)) {
|
|
1326
|
+
const error = entry.event.error;
|
|
1327
|
+
if (error?.code === DECISION_EXHAUSTED_CODE && Array.isArray(error.attempts) && error.attempts.length > 0) found.push({
|
|
1328
|
+
index: entry.index,
|
|
1329
|
+
eventType: type,
|
|
1330
|
+
attempts: error.attempts
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
return found;
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Rewrites a journaled `xstate.done.actor`/`xstate.error.actor` event's
|
|
1338
|
+
* `sessionId` to the session the CURRENT fold minted for that `actorId`.
|
|
1339
|
+
*
|
|
1340
|
+
* Session ids are re-minted on every fold, and XState drops a completion event
|
|
1341
|
+
* whose `sessionId` does not match the live child — silently, as a no-op
|
|
1342
|
+
* transition. Rebinding is what makes a journaled completion replayable at all.
|
|
1343
|
+
* `sessions` caches the mapping per fold, so a later event referring to the
|
|
1344
|
+
* recorded session of an already-stopped child still resolves.
|
|
1345
|
+
*/
|
|
1346
|
+
function rebindActorSession(event, snapshot, sessions) {
|
|
1347
|
+
const actorEvent = event;
|
|
1348
|
+
if (typeof actorEvent.actorId !== "string" || typeof actorEvent.sessionId !== "string") return event;
|
|
1349
|
+
const key = `${actorEvent.actorId}\0${actorEvent.sessionId}`;
|
|
1350
|
+
let sessionId = sessions.get(key);
|
|
1351
|
+
if (!sessionId) {
|
|
1352
|
+
const child = snapshot.children[actorEvent.actorId];
|
|
1353
|
+
if (typeof child?.sessionId !== "string") return event;
|
|
1354
|
+
sessionId = child.sessionId;
|
|
1355
|
+
sessions.set(key, sessionId);
|
|
1356
|
+
}
|
|
1357
|
+
return {
|
|
1358
|
+
...event,
|
|
1359
|
+
sessionId
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
function restoreDoneActorOutput(event) {
|
|
1363
|
+
if (event.type !== DONE_ACTOR_EVENT_TYPE || Object.hasOwn(event, "output")) return event;
|
|
1364
|
+
return {
|
|
1365
|
+
...event,
|
|
1366
|
+
output: void 0
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
function verifyEntry(entry, persisted, mode) {
|
|
1370
|
+
if (mode === false) return;
|
|
1371
|
+
if (!entry.verification) {
|
|
1372
|
+
if (mode === "strict") throw new AgentReplayDivergenceError(entry.id, entry.index, "missing-verification");
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
const actual = getSnapshotStateHash(persisted);
|
|
1376
|
+
if (actual !== entry.verification.stateHash) throw new AgentReplayDivergenceError(entry.id, entry.index, "state", entry.verification.stateHash, actual);
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Folds a log through XState's pure `initialTransition`/`transition` WITHOUT
|
|
1380
|
+
* executing anything: no actor is started, no action runs, no model is called.
|
|
1381
|
+
* A journaled `xstate.done.actor` entry carries the completed actor's output
|
|
1382
|
+
* inline, so the invoke it belongs to is never re-run. Crash recovery, fork
|
|
1383
|
+
* resume, and time travel in one call.
|
|
1384
|
+
*
|
|
1385
|
+
* The first entry is the reserved {@link initEntry}: `input` folds from
|
|
1386
|
+
* `initialTransition`, `snapshot` restores the persisted snapshot purely
|
|
1387
|
+
* (`machine.restoreSnapshot`) and folds the rest of the log onto it.
|
|
1388
|
+
*/
|
|
1389
|
+
function replay(machine, entries, options = {}) {
|
|
1390
|
+
const fold = foldLog(machine, entries, options);
|
|
1391
|
+
return {
|
|
1392
|
+
snapshot: fold.snapshot,
|
|
1393
|
+
persistedSnapshot: fold.persistedSnapshot
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* {@link replay}, returning a {@link LogFold} that further entries can be
|
|
1398
|
+
* folded onto in exactly the way `replay` would fold them. A live run records
|
|
1399
|
+
* each entry's hash from this fold, never from its own snapshot: a live
|
|
1400
|
+
* snapshot also holds what the log cannot reproduce (a sibling child that
|
|
1401
|
+
* already settled while its completion waits in the mailbox). @internal
|
|
1402
|
+
*/
|
|
1403
|
+
function foldLog(machine, entries, options = {}) {
|
|
1404
|
+
validateReplayEntries(entries, {
|
|
1405
|
+
machine,
|
|
1406
|
+
machineVersion: options.machineVersion ?? resolveMachineVersion(machine)
|
|
1407
|
+
});
|
|
1408
|
+
if (entries.length === 0) throw new AgentEventLogError(`Cannot replay an empty event log; it must start with a '${AGENT_INIT_EVENT_TYPE}' entry.`);
|
|
1409
|
+
const initEvent = entries[0].event;
|
|
1410
|
+
let snapshot = initEvent.snapshot !== void 0 ? machine.restoreSnapshot(initEvent.snapshot) : (0, xstate.initialTransition)(machine, initEvent.input)[0];
|
|
1411
|
+
let persisted = machine.getPersistedSnapshot(snapshot);
|
|
1412
|
+
verifyEntry(entries[0], persisted, options.verify);
|
|
1413
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
1414
|
+
const apply = (entry) => {
|
|
1415
|
+
const event = restoreDoneActorOutput(rebindActorSession(entry.event, snapshot, sessions));
|
|
1416
|
+
[snapshot] = (0, xstate.transition)(machine, snapshot, event);
|
|
1417
|
+
persisted = machine.getPersistedSnapshot(snapshot);
|
|
1418
|
+
return persisted;
|
|
1419
|
+
};
|
|
1420
|
+
for (let index = 1; index < entries.length; index++) {
|
|
1421
|
+
const entry = entries[index];
|
|
1422
|
+
verifyEntry(entry, apply(entry), options.verify);
|
|
1423
|
+
}
|
|
1424
|
+
return {
|
|
1425
|
+
get snapshot() {
|
|
1426
|
+
return snapshot;
|
|
1427
|
+
},
|
|
1428
|
+
get persistedSnapshot() {
|
|
1429
|
+
return persisted;
|
|
1430
|
+
},
|
|
1431
|
+
apply
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Records a plain `createActor` run as an event log, with no
|
|
1436
|
+
* `createAgentRuntime`: the init entry (the actor's `input`, or the snapshot
|
|
1437
|
+
* it was created from), then every event the root machine processed, invoke
|
|
1438
|
+
* completions with their outputs inline. Attach it before `actor.start()`.
|
|
1439
|
+
*
|
|
1440
|
+
* @example
|
|
1441
|
+
* ```ts no-check
|
|
1442
|
+
* const actor = createActor(machine, { input });
|
|
1443
|
+
* const log = recordLog(actor);
|
|
1444
|
+
* actor.start();
|
|
1445
|
+
* // ...later
|
|
1446
|
+
* const { snapshot } = replay(machine, log.entries); // no actor runs
|
|
1447
|
+
* ```
|
|
1448
|
+
*/
|
|
1449
|
+
function recordLog(actor) {
|
|
1450
|
+
if (actor.getSnapshot().status !== "active") throw new AgentEventLogError("recordLog must be attached before actor.start(): the actor has already finished.");
|
|
1451
|
+
const machine = actor.logic;
|
|
1452
|
+
const machineVersion = resolveMachineVersion(machine);
|
|
1453
|
+
const entries = [];
|
|
1454
|
+
let fold;
|
|
1455
|
+
let failure;
|
|
1456
|
+
const record = (event) => {
|
|
1457
|
+
if (fold === void 0) {
|
|
1458
|
+
if (event.type !== "xstate.init") throw new AgentEventLogError("recordLog must be attached before actor.start(): it missed the init event.");
|
|
1459
|
+
const restored = actor.options.snapshot;
|
|
1460
|
+
const init = restored ? { snapshot: restored } : { input: event.input };
|
|
1461
|
+
entries.push(initEntry(machine, init, { machineVersion }));
|
|
1462
|
+
fold = foldLog(machine, entries, {
|
|
1463
|
+
machineVersion,
|
|
1464
|
+
verify: false
|
|
1465
|
+
});
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
const entry = createReplayEntry(machine, entries, event, {
|
|
1469
|
+
machineVersion,
|
|
1470
|
+
verification: false
|
|
1471
|
+
});
|
|
1472
|
+
entry.verification = { stateHash: getSnapshotStateHash(fold.apply(entry)) };
|
|
1473
|
+
entries.push(entry);
|
|
1474
|
+
};
|
|
1475
|
+
const subscription = actor.system.inspect((inspectionEvent) => {
|
|
1476
|
+
if (failure !== void 0 || inspectionEvent.type !== "@xstate.transition" || inspectionEvent.actorRef !== actor) return;
|
|
1477
|
+
try {
|
|
1478
|
+
record(inspectionEvent.event);
|
|
1479
|
+
} catch (error) {
|
|
1480
|
+
failure = error;
|
|
1481
|
+
}
|
|
1482
|
+
});
|
|
1483
|
+
return {
|
|
1484
|
+
get entries() {
|
|
1485
|
+
if (failure !== void 0) throw failure;
|
|
1486
|
+
if (entries.length === 0) throw new AgentEventLogError("recordLog has no init entry: the actor has not started, or recordLog was attached after actor.start().");
|
|
1487
|
+
return [...entries];
|
|
1488
|
+
},
|
|
1489
|
+
stop: () => subscription.unsubscribe()
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
//#endregion
|
|
1493
|
+
Object.defineProperty(exports, "AGENT_EVENT_SCHEMA_VERSION", {
|
|
1494
|
+
enumerable: true,
|
|
1495
|
+
get: function() {
|
|
1496
|
+
return AGENT_EVENT_SCHEMA_VERSION;
|
|
1497
|
+
}
|
|
1498
|
+
});
|
|
1499
|
+
Object.defineProperty(exports, "AGENT_INIT_EVENT_TYPE", {
|
|
1500
|
+
enumerable: true,
|
|
1501
|
+
get: function() {
|
|
1502
|
+
return AGENT_INIT_EVENT_TYPE;
|
|
1503
|
+
}
|
|
1504
|
+
});
|
|
1505
|
+
Object.defineProperty(exports, "AGENT_USAGE_EVENT_TYPE", {
|
|
1506
|
+
enumerable: true,
|
|
1507
|
+
get: function() {
|
|
1508
|
+
return AGENT_USAGE_EVENT_TYPE;
|
|
1509
|
+
}
|
|
1510
|
+
});
|
|
818
1511
|
Object.defineProperty(exports, "AGENT_USAGE_TOKEN_FIELDS", {
|
|
819
1512
|
enumerable: true,
|
|
820
1513
|
get: function() {
|
|
@@ -827,6 +1520,24 @@ Object.defineProperty(exports, "AgentError", {
|
|
|
827
1520
|
return AgentError;
|
|
828
1521
|
}
|
|
829
1522
|
});
|
|
1523
|
+
Object.defineProperty(exports, "AgentEventLogError", {
|
|
1524
|
+
enumerable: true,
|
|
1525
|
+
get: function() {
|
|
1526
|
+
return AgentEventLogError;
|
|
1527
|
+
}
|
|
1528
|
+
});
|
|
1529
|
+
Object.defineProperty(exports, "AgentMachineVersionMismatchError", {
|
|
1530
|
+
enumerable: true,
|
|
1531
|
+
get: function() {
|
|
1532
|
+
return AgentMachineVersionMismatchError;
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
Object.defineProperty(exports, "AgentReplayDivergenceError", {
|
|
1536
|
+
enumerable: true,
|
|
1537
|
+
get: function() {
|
|
1538
|
+
return AgentReplayDivergenceError;
|
|
1539
|
+
}
|
|
1540
|
+
});
|
|
830
1541
|
Object.defineProperty(exports, "AgentTruncatedError", {
|
|
831
1542
|
enumerable: true,
|
|
832
1543
|
get: function() {
|
|
@@ -845,12 +1556,36 @@ Object.defineProperty(exports, "GENERATE_TEXT_ACTOR", {
|
|
|
845
1556
|
return GENERATE_TEXT_ACTOR;
|
|
846
1557
|
}
|
|
847
1558
|
});
|
|
1559
|
+
Object.defineProperty(exports, "NonSerializableAgentEventError", {
|
|
1560
|
+
enumerable: true,
|
|
1561
|
+
get: function() {
|
|
1562
|
+
return NonSerializableAgentEventError;
|
|
1563
|
+
}
|
|
1564
|
+
});
|
|
848
1565
|
Object.defineProperty(exports, "STREAM_TEXT_ACTOR", {
|
|
849
1566
|
enumerable: true,
|
|
850
1567
|
get: function() {
|
|
851
1568
|
return STREAM_TEXT_ACTOR;
|
|
852
1569
|
}
|
|
853
1570
|
});
|
|
1571
|
+
Object.defineProperty(exports, "agentCallOccurrence", {
|
|
1572
|
+
enumerable: true,
|
|
1573
|
+
get: function() {
|
|
1574
|
+
return agentCallOccurrence;
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
|
+
Object.defineProperty(exports, "assertAgentLogEntry", {
|
|
1578
|
+
enumerable: true,
|
|
1579
|
+
get: function() {
|
|
1580
|
+
return assertAgentLogEntry;
|
|
1581
|
+
}
|
|
1582
|
+
});
|
|
1583
|
+
Object.defineProperty(exports, "assertJsonSerializable", {
|
|
1584
|
+
enumerable: true,
|
|
1585
|
+
get: function() {
|
|
1586
|
+
return assertJsonSerializable;
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
854
1589
|
Object.defineProperty(exports, "assistantMessage", {
|
|
855
1590
|
enumerable: true,
|
|
856
1591
|
get: function() {
|
|
@@ -869,16 +1604,22 @@ Object.defineProperty(exports, "builtinTextActors", {
|
|
|
869
1604
|
return builtinTextActors;
|
|
870
1605
|
}
|
|
871
1606
|
});
|
|
872
|
-
Object.defineProperty(exports, "
|
|
1607
|
+
Object.defineProperty(exports, "canonicalEventJson", {
|
|
873
1608
|
enumerable: true,
|
|
874
1609
|
get: function() {
|
|
875
|
-
return
|
|
1610
|
+
return canonicalEventJson;
|
|
876
1611
|
}
|
|
877
1612
|
});
|
|
878
|
-
Object.defineProperty(exports, "
|
|
1613
|
+
Object.defineProperty(exports, "createReplayEntry", {
|
|
879
1614
|
enumerable: true,
|
|
880
1615
|
get: function() {
|
|
881
|
-
return
|
|
1616
|
+
return createReplayEntry;
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
Object.defineProperty(exports, "createTextLogic", {
|
|
1620
|
+
enumerable: true,
|
|
1621
|
+
get: function() {
|
|
1622
|
+
return createTextLogic;
|
|
882
1623
|
}
|
|
883
1624
|
});
|
|
884
1625
|
Object.defineProperty(exports, "executorBoundLogics", {
|
|
@@ -893,6 +1634,18 @@ Object.defineProperty(exports, "findNonSerializableContextPaths", {
|
|
|
893
1634
|
return findNonSerializableContextPaths;
|
|
894
1635
|
}
|
|
895
1636
|
});
|
|
1637
|
+
Object.defineProperty(exports, "foldLog", {
|
|
1638
|
+
enumerable: true,
|
|
1639
|
+
get: function() {
|
|
1640
|
+
return foldLog;
|
|
1641
|
+
}
|
|
1642
|
+
});
|
|
1643
|
+
Object.defineProperty(exports, "forkEventLog", {
|
|
1644
|
+
enumerable: true,
|
|
1645
|
+
get: function() {
|
|
1646
|
+
return forkEventLog;
|
|
1647
|
+
}
|
|
1648
|
+
});
|
|
896
1649
|
Object.defineProperty(exports, "getAgentExecutionOptions", {
|
|
897
1650
|
enumerable: true,
|
|
898
1651
|
get: function() {
|
|
@@ -917,6 +1670,12 @@ Object.defineProperty(exports, "getCallUsage", {
|
|
|
917
1670
|
return getCallUsage;
|
|
918
1671
|
}
|
|
919
1672
|
});
|
|
1673
|
+
Object.defineProperty(exports, "getDecisionAttempts", {
|
|
1674
|
+
enumerable: true,
|
|
1675
|
+
get: function() {
|
|
1676
|
+
return getDecisionAttempts;
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
920
1679
|
Object.defineProperty(exports, "getJsonSchema", {
|
|
921
1680
|
enumerable: true,
|
|
922
1681
|
get: function() {
|
|
@@ -929,6 +1688,12 @@ Object.defineProperty(exports, "getJsonSchemaSync", {
|
|
|
929
1688
|
return getJsonSchemaSync;
|
|
930
1689
|
}
|
|
931
1690
|
});
|
|
1691
|
+
Object.defineProperty(exports, "getLogExecutionId", {
|
|
1692
|
+
enumerable: true,
|
|
1693
|
+
get: function() {
|
|
1694
|
+
return getLogExecutionId;
|
|
1695
|
+
}
|
|
1696
|
+
});
|
|
932
1697
|
Object.defineProperty(exports, "getMachineStructuralHash", {
|
|
933
1698
|
enumerable: true,
|
|
934
1699
|
get: function() {
|
|
@@ -947,6 +1712,12 @@ Object.defineProperty(exports, "getRegisteredAgentExecutionOptions", {
|
|
|
947
1712
|
return getRegisteredAgentExecutionOptions;
|
|
948
1713
|
}
|
|
949
1714
|
});
|
|
1715
|
+
Object.defineProperty(exports, "getSnapshotStateHash", {
|
|
1716
|
+
enumerable: true,
|
|
1717
|
+
get: function() {
|
|
1718
|
+
return getSnapshotStateHash;
|
|
1719
|
+
}
|
|
1720
|
+
});
|
|
950
1721
|
Object.defineProperty(exports, "getStateMeta", {
|
|
951
1722
|
enumerable: true,
|
|
952
1723
|
get: function() {
|
|
@@ -959,6 +1730,18 @@ Object.defineProperty(exports, "getStatePath", {
|
|
|
959
1730
|
return getStatePath;
|
|
960
1731
|
}
|
|
961
1732
|
});
|
|
1733
|
+
Object.defineProperty(exports, "getUsageFromEvents", {
|
|
1734
|
+
enumerable: true,
|
|
1735
|
+
get: function() {
|
|
1736
|
+
return getUsageFromEvents;
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
Object.defineProperty(exports, "initEntry", {
|
|
1740
|
+
enumerable: true,
|
|
1741
|
+
get: function() {
|
|
1742
|
+
return initEntry;
|
|
1743
|
+
}
|
|
1744
|
+
});
|
|
962
1745
|
Object.defineProperty(exports, "isStandardSchema", {
|
|
963
1746
|
enumerable: true,
|
|
964
1747
|
get: function() {
|
|
@@ -1007,6 +1790,24 @@ Object.defineProperty(exports, "providerOutputSchema", {
|
|
|
1007
1790
|
return providerOutputSchema;
|
|
1008
1791
|
}
|
|
1009
1792
|
});
|
|
1793
|
+
Object.defineProperty(exports, "rebindActorSession", {
|
|
1794
|
+
enumerable: true,
|
|
1795
|
+
get: function() {
|
|
1796
|
+
return rebindActorSession;
|
|
1797
|
+
}
|
|
1798
|
+
});
|
|
1799
|
+
Object.defineProperty(exports, "recordLog", {
|
|
1800
|
+
enumerable: true,
|
|
1801
|
+
get: function() {
|
|
1802
|
+
return recordLog;
|
|
1803
|
+
}
|
|
1804
|
+
});
|
|
1805
|
+
Object.defineProperty(exports, "replay", {
|
|
1806
|
+
enumerable: true,
|
|
1807
|
+
get: function() {
|
|
1808
|
+
return replay;
|
|
1809
|
+
}
|
|
1810
|
+
});
|
|
1010
1811
|
Object.defineProperty(exports, "resolveMachineVersion", {
|
|
1011
1812
|
enumerable: true,
|
|
1012
1813
|
get: function() {
|
|
@@ -1049,6 +1850,12 @@ Object.defineProperty(exports, "userMessage", {
|
|
|
1049
1850
|
return userMessage;
|
|
1050
1851
|
}
|
|
1051
1852
|
});
|
|
1853
|
+
Object.defineProperty(exports, "validateReplayEntries", {
|
|
1854
|
+
enumerable: true,
|
|
1855
|
+
get: function() {
|
|
1856
|
+
return validateReplayEntries;
|
|
1857
|
+
}
|
|
1858
|
+
});
|
|
1052
1859
|
Object.defineProperty(exports, "validateSchemaSync", {
|
|
1053
1860
|
enumerable: true,
|
|
1054
1861
|
get: function() {
|