@arnilo/prism 0.0.5 → 0.0.7

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 (75) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/dist/agent-loops.d.ts +1 -0
  3. package/dist/agent-loops.js +27 -16
  4. package/dist/agent-run-lifecycle.d.ts +28 -0
  5. package/dist/agent-run-lifecycle.js +33 -0
  6. package/dist/agent-run-state.d.ts +53 -0
  7. package/dist/agent-run-state.js +127 -0
  8. package/dist/agents.d.ts +3 -1
  9. package/dist/agents.js +337 -46
  10. package/dist/contracts.d.ts +205 -3
  11. package/dist/contracts.js +4 -0
  12. package/dist/guardrails.d.ts +25 -0
  13. package/dist/guardrails.js +133 -0
  14. package/dist/ids.d.ts +2 -0
  15. package/dist/ids.js +6 -0
  16. package/dist/index.d.ts +17 -3
  17. package/dist/index.js +10 -3
  18. package/dist/input.js +2 -0
  19. package/dist/resources.js +2 -1
  20. package/dist/run-limits.d.ts +34 -0
  21. package/dist/run-limits.js +163 -0
  22. package/dist/secure-agent.d.ts +3 -0
  23. package/dist/secure-agent.js +63 -0
  24. package/dist/session-stores.js +2 -3
  25. package/dist/testing/persistence-schema.d.ts +45 -7
  26. package/dist/testing/persistence-schema.js +138 -24
  27. package/dist/thinking.d.ts +42 -0
  28. package/dist/thinking.js +92 -0
  29. package/dist/tools.d.ts +10 -2
  30. package/dist/tools.js +56 -7
  31. package/dist/use-case-model.d.ts +63 -0
  32. package/dist/use-case-model.js +52 -0
  33. package/docs/a2a.md +4 -2
  34. package/docs/agent-events.md +23 -16
  35. package/docs/agent-loops.md +19 -8
  36. package/docs/agent-session-runtime.md +33 -1
  37. package/docs/coding-agent-tools.md +33 -12
  38. package/docs/coding-security.md +2 -2
  39. package/docs/compaction-llm.md +17 -7
  40. package/docs/compaction-observational-memory.md +28 -4
  41. package/docs/credential-storage.md +58 -9
  42. package/docs/credentials-and-redaction.md +1 -1
  43. package/docs/database-persistence.md +8 -3
  44. package/docs/guardrails.md +75 -0
  45. package/docs/host-security.md +16 -8
  46. package/docs/index.md +26 -22
  47. package/docs/mcp-tools.md +32 -12
  48. package/docs/migration.md +164 -2
  49. package/docs/node-filesystem-config.md +1 -0
  50. package/docs/node-jsonl-session-store.md +5 -4
  51. package/docs/postgres-persistence.md +3 -3
  52. package/docs/provider-caching.md +16 -4
  53. package/docs/provider-conformance.md +39 -1
  54. package/docs/provider-packages.md +60 -3
  55. package/docs/providers/ai-sdk.md +36 -0
  56. package/docs/providers/kimi.md +124 -61
  57. package/docs/providers/neuralwatt.md +19 -13
  58. package/docs/providers/openai.md +56 -13
  59. package/docs/providers/opencode-go.md +118 -30
  60. package/docs/providers/openrouter.md +105 -35
  61. package/docs/providers/zai.md +94 -45
  62. package/docs/release-and-install.md +47 -49
  63. package/docs/review-coverage-2026-07-17-provider-validation.md +192 -0
  64. package/docs/runs-and-usage.md +30 -3
  65. package/docs/server.md +5 -2
  66. package/docs/sqlite-persistence.md +2 -2
  67. package/docs/structured-output.md +1 -1
  68. package/docs/thinking-and-reasoning.md +98 -0
  69. package/docs/tool-execution-primitives.md +3 -3
  70. package/docs/tools.md +21 -1
  71. package/docs/use-case-model-selection.md +109 -0
  72. package/docs/workflow-orchestration-primitives.md +1 -0
  73. package/docs/workflows.md +18 -10
  74. package/docs/working-and-semantic-memory.md +1 -0
  75. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -5,7 +5,45 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [0.0.7] - 2026-07-19
9
+
10
+ ### Added
11
+
12
+ - Typed `Guardrails` for input, provider output, tool input, and tool output. Guardrail decisions are bounded/redacted `guardrail_decision` events; provider output is buffered before exposure when output checks are configured.
13
+ - Workflow tool nodes and MCP server tool registrations now route optional tool guardrails through shared `dispatchToolCall()`.
14
+ - `RunLimits` adds validated, narrowing-only budgets for turns, provider attempts, tool rounds/calls, wall time, request/response bytes, token usage, and optional single-currency cost. Breaches emit one `run_limit_exceeded` event and return `AgentRunError.result.limit`.
15
+ - Opt-in durable built-in agent runs can suspend before a tool side effect and resume through versioned, bounded, redacted checkpoint state with CAS approval, ownership/fingerprint checks, and no automatic replay of an ambiguous dispatched tool.
16
+ - `createSecureAgent()` composes strict tool schemas/validation, trust and permission gates, redaction, finite limits, exact ownership, and durable pre-tool approval without changing low-level `createAgent()` defaults.
17
+ - `createAgentRunLifecycle()` adds explicit, ownership-scoped durable agent status/resume capability for selected server and MCP exposures; no lifecycle route/tool is enabled by default.
18
+
19
+ ## [0.0.6] - 2026-07-19
20
+
21
+ ### Added
22
+
23
+ - Caller-gated model discovery: `listOpenAIModels`, `listKimiModels`, `listZaiModels`, `listOpenRouterModels`, and `listOpenCodeGoModels`. Provider setup remains network-free; hosts explicitly fetch and register current models.
24
+ - Shared `ThinkingLevel` helpers and use-case model bindings. Background compaction and observational-memory jobs can use an explicit provider/model or a supplied session-model fallback.
25
+ - Opt-in sequential artifact-loop tools: `loop: { strategy: "generate-validate-revise", toolCalls: "bounded" }`. Tool rounds use existing authorization/redaction/ledger paths, share `maxToolRounds` across candidates, and fail with `artifact_failed` metadata `{ reason: "tool_round_limit" }` after exhaustion.
26
+ - Checksummed SQLite/PostgreSQL migration histories and catalog-shape verification, bounded JSON Schema compilation LRU, and public `assertFiniteVector` validation.
27
+
28
+ ### Changed
29
+
30
+ - Provider packages now document and implement current cache, reasoning, streaming, and discovery behavior. OpenAI Responses replay/function-call/SSE argument handling is corrected; Kimi adds optional Moonshot support; Z.AI and OpenCode Go catalogs/routes were refreshed; OpenRouter discovery/reasoning and NeuralWatt thinking controls are hardened. AI SDK remains host-model-owned.
31
+ - Workflow definitions now require a non-empty `revision`; cancellation requires exact ownership and the current workflow definition. All workflow limits have finite hard caps.
32
+ - Coding tools now enforce bounded streamed reads, write/edit inputs, shell wall time, total output, and spill-file lifecycle. Custom coding operation interfaces now receive bounded read/stat/write/edit options and abort signals.
33
+ - Encrypted credential helpers `encryptBytes`, `decryptBytes`, and envelope rotation are asynchronous. Existing credential files must meet restrictive Unix permission requirements. Linux Secret Service/GNOME Keyring byte-array reads are accepted by the keychain store.
34
+ - MCP Streamable HTTP requires HTTPS and explicit `allowedOrigins`; loopback HTTP requires explicit opt-in. Discovery, schemas, results, and response bodies are bounded.
35
+ - Compaction and observational-memory workers now have finite turn/call/transcript/error budgets. A2A streaming uses strict incremental UTF-8 and LF/CRLF SSE parsing.
36
+ - Generated Prism, workflow, and evaluation IDs use cryptographic UUIDs; non-finite embedding vectors now fail before scoring or persistence.
37
+
38
+ ### Security
39
+
40
+ - Fixed cross-owner workflow cancellation and duplicate active-run overwrite risks.
41
+ - Added fail-closed limits and validation at file, process, credential, MCP, migration, schema, vector, provider-worker, and A2A trust boundaries.
42
+
43
+ ### Upgrade notes
44
+
45
+ - Finish or deliberately migrate pre-0.0.6 workflow runs/checkpoints before upgrading: their definition hashes lack the required revision.
46
+ - Update workflow definitions with `revision`, cancellation callers with `workflow` plus exact ownership, MCP HTTP configs with `allowedOrigins`, and custom coding/credential integrations for the changed interfaces above.
9
47
 
10
48
  ## [0.0.5] - 2026-07-16
11
49
 
@@ -6,6 +6,7 @@ export declare function generateValidateReviseLoop(opts: {
6
6
  readonly parser?: ArtifactParser<unknown>;
7
7
  readonly repairer?: ArtifactRepairer<unknown>;
8
8
  readonly maxRevisions?: number;
9
+ readonly toolCalls?: "disabled" | "bounded";
9
10
  }): AgentLoopStrategy;
10
11
  export declare function resolveToolConcurrency(options: {
11
12
  loop?: AgentLoopStrategy | AgentLoopOptions;
@@ -1,4 +1,5 @@
1
1
  import { inputMessages } from "./input.js";
2
+ import { createId } from "./ids.js";
2
3
  function throwIfAborted(signal) {
3
4
  if (signal.aborted)
4
5
  throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
@@ -55,10 +56,8 @@ function defaultRepairer() {
55
56
  });
56
57
  }
57
58
  // ponytail: GenerateValidateReviseLoop reuses LoopContext primitives only —
58
- // no provider/retry/store/event re-implementation. T is host-defined, Prism
59
- // never instantiates it. No tools in artifact revisions (roadmap scope is
60
- // generate→validate→revise; tool coupling deferred). Phase 28 fires
61
- // artifact_* events at the marked seams (noop here).
59
+ // no provider/retry/store/event re-implementation. Bounded artifact tools use
60
+ // same dispatcher at concurrency one; add parallelism only with ordering need.
62
61
  export function generateValidateReviseLoop(opts) {
63
62
  const max = opts.maxRevisions ?? 3;
64
63
  const repairer = opts.repairer ?? defaultRepairer();
@@ -68,12 +67,14 @@ export function generateValidateReviseLoop(opts) {
68
67
  let usage;
69
68
  let nextInput = ctx.input;
70
69
  let pendingHistory = [];
71
- for (let turn = 1; turn <= max + 1; turn += 1) {
70
+ let toolRounds = 0;
71
+ let attempts = 0;
72
+ for (let turn = 1; attempts <= max; turn += 1) {
72
73
  throwIfAborted(ctx.signal);
73
74
  ctx.emit({ type: "turn_started", sessionId: ctx.sessionId, runId: ctx.runId, turn });
74
75
  const request = await ctx.assemble(nextInput, undefined, turn);
75
76
  throwIfAborted(ctx.signal);
76
- const { content, messageId, started, usage: turnUsage } = await ctx.generate(request);
77
+ const { content, calls, messageId, started, usage: turnUsage } = await ctx.generate(request);
77
78
  usage = turnUsage ?? usage;
78
79
  if (pendingHistory.length > 0) {
79
80
  ctx.history.push(...pendingHistory);
@@ -88,10 +89,17 @@ export function generateValidateReviseLoop(opts) {
88
89
  ctx.emit({ type: "message_finished", sessionId: ctx.sessionId, runId: ctx.runId, message });
89
90
  }
90
91
  ctx.emit({ type: "turn_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn });
91
- const text = content
92
- .filter((b) => b.type === "text")
93
- .map((b) => b.text)
94
- .join("");
92
+ if (opts.toolCalls === "bounded" && calls.length > 0) {
93
+ if (toolRounds >= ctx.maxToolRounds) {
94
+ const result = { ok: false, errors: [{ message: "maximum tool rounds exceeded" }], metadata: { reason: "tool_round_limit" } };
95
+ ctx.emit({ type: "artifact_failed", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt: attempts + 1, result });
96
+ return usage;
97
+ }
98
+ toolRounds += 1;
99
+ await dispatchToolCallsInOrder(calls, { ...ctx, toolConcurrency: 1 });
100
+ nextInput = [];
101
+ continue;
102
+ }
95
103
  const artifactCtx = {
96
104
  sessionId: ctx.sessionId,
97
105
  runId: ctx.runId,
@@ -99,13 +107,17 @@ export function generateValidateReviseLoop(opts) {
99
107
  signal: ctx.signal,
100
108
  metadata: ctx.metadata,
101
109
  };
110
+ const text = content
111
+ .filter((b) => b.type === "text")
112
+ .map((b) => b.text)
113
+ .join("");
102
114
  const parsed = opts.parser
103
115
  ? await opts.parser(text, artifactCtx)
104
116
  : { ok: true, value: text };
105
117
  // Parse failure ends the loop silently (terminal parse errors stay on `error`).
106
118
  if (!parsed.ok || parsed.value === undefined)
107
119
  return usage;
108
- const attempt = turn;
120
+ const attempt = ++attempts;
109
121
  ctx.emit({ type: "artifact_validation_started", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt });
110
122
  const result = await opts.validator(parsed.value, artifactCtx);
111
123
  ctx.emit({ type: "artifact_validation_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
@@ -113,7 +125,7 @@ export function generateValidateReviseLoop(opts) {
113
125
  ctx.emit({ type: "artifact_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
114
126
  return usage;
115
127
  }
116
- if (turn > max) {
128
+ if (attempt > max) {
117
129
  ctx.emit({ type: "artifact_failed", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
118
130
  return usage;
119
131
  }
@@ -124,7 +136,6 @@ export function generateValidateReviseLoop(opts) {
124
136
  await ctx.appendMessage(message);
125
137
  pendingHistory = repairMessages;
126
138
  nextInput = repairMessages;
127
- continue;
128
139
  }
129
140
  return usage;
130
141
  },
@@ -148,6 +159,7 @@ export function resolveToolConcurrency(options, config) {
148
159
  export async function dispatchToolCallsInOrder(calls, ctx) {
149
160
  if (calls.length === 0)
150
161
  return;
162
+ ctx.chargeToolRound?.(calls);
151
163
  const concurrency = calls.some((call) => ctx.isToolCallExclusive?.(call))
152
164
  ? 1
153
165
  : Math.max(1, Math.min(ctx.toolConcurrency, calls.length));
@@ -195,13 +207,12 @@ export function resolveLoop(options, config) {
195
207
  parser: loop.parser,
196
208
  repairer: loop.repairer,
197
209
  maxRevisions: loop.maxRevisions,
210
+ toolCalls: loop.toolCalls,
198
211
  });
199
212
  }
200
213
  throw new Error(`Unknown agent loop strategy: ${strategy}`);
201
214
  }
202
215
  return loop;
203
216
  }
204
- function randomId(prefix) {
205
- return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
206
- }
217
+ const randomId = createId;
207
218
  //# sourceMappingURL=agent-loops.js.map
@@ -0,0 +1,28 @@
1
+ import type { Agent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunStatusResult, OwnershipScope } from "./contracts.js";
2
+ import type { CheckpointStore } from "./contracts.js";
3
+ export interface AgentRunLifecycleAgent {
4
+ readonly agent: Agent;
5
+ /** Current host-authored revision; it must match the stored revision. */
6
+ readonly definitionRevision: string;
7
+ }
8
+ export interface AgentRunLifecycleOptions {
9
+ readonly checkpoints: CheckpointStore;
10
+ readonly resolveAgent: (input: {
11
+ readonly agentId: string;
12
+ readonly ownership?: OwnershipScope;
13
+ readonly signal?: AbortSignal;
14
+ }) => AgentRunLifecycleAgent | Promise<AgentRunLifecycleAgent>;
15
+ readonly fencingToken?: number;
16
+ }
17
+ export interface AgentRunLifecycleRequest {
18
+ readonly ownership?: OwnershipScope;
19
+ readonly signal?: AbortSignal;
20
+ /** Adapter-selected capability; stored runs for another agent are non-enumerable. */
21
+ readonly agentId?: string;
22
+ }
23
+ export interface AgentRunLifecycle {
24
+ status(ref: AgentRunRef, options?: AgentRunLifecycleRequest): Promise<AgentRunStatusResult>;
25
+ resume(ref: AgentRunRef, resume: AgentRunResume, options?: AgentRunLifecycleRequest): Promise<AgentRunResult>;
26
+ }
27
+ /** Host capability for durable agent status/resume. Adapters supply authorized ownership only. */
28
+ export declare function createAgentRunLifecycle(options: AgentRunLifecycleOptions): AgentRunLifecycle;
@@ -0,0 +1,33 @@
1
+ import { AgentRunStateError } from "./contracts.js";
2
+ import { loadAgentRunState, publicState } from "./agent-run-state.js";
3
+ import { resumeAgentRun } from "./agents.js";
4
+ function assertAgentId(actual, expected) {
5
+ if (expected !== undefined && actual !== expected)
6
+ throw new AgentRunStateError("Agent run capability mismatch");
7
+ }
8
+ /** Host capability for durable agent status/resume. Adapters supply authorized ownership only. */
9
+ export function createAgentRunLifecycle(options) {
10
+ return {
11
+ async status(ref, request = {}) {
12
+ request.signal?.throwIfAborted();
13
+ const { state, record } = await loadAgentRunState(options.checkpoints, ref, request.ownership);
14
+ assertAgentId(state.agentId, request.agentId);
15
+ request.signal?.throwIfAborted();
16
+ return { state: publicState({ ...state, version: record.version }), version: record.version };
17
+ },
18
+ async resume(ref, resume, request = {}) {
19
+ request.signal?.throwIfAborted();
20
+ const { state } = await loadAgentRunState(options.checkpoints, ref, request.ownership);
21
+ assertAgentId(state.agentId, request.agentId);
22
+ const resolved = await options.resolveAgent({ agentId: state.agentId, ownership: request.ownership, signal: request.signal });
23
+ request.signal?.throwIfAborted();
24
+ return resumeAgentRun(resolved.agent, ref, resume, {
25
+ checkpoints: options.checkpoints,
26
+ ownership: request.ownership,
27
+ fencingToken: options.fencingToken,
28
+ definitionRevision: resolved.definitionRevision,
29
+ });
30
+ },
31
+ };
32
+ }
33
+ //# sourceMappingURL=agent-run-lifecycle.js.map
@@ -0,0 +1,53 @@
1
+ import type { Agent, AgentRunInterruption, AgentRunRef, AgentRunState, AgentRunStateOptions, AgentRunStatusResult, CheckpointRecord, CheckpointStore, Message, ModelConfig, OwnershipScope, RunLimitCounters, ToolCallContent } from "./contracts.js";
2
+ import type { SecretRedactor } from "./redaction.js";
3
+ export declare const AGENT_RUN_STATE_NAMESPACE = "prism.agent-run";
4
+ export declare const AGENT_RUN_STATE_SCHEMA_VERSION: 1;
5
+ export declare const DEFAULT_MAX_AGENT_RUN_STATE_BYTES: number;
6
+ export declare const HARD_MAX_AGENT_RUN_STATE_BYTES: number;
7
+ export interface StoredAgentRunState extends AgentRunState {
8
+ readonly input?: readonly Message[];
9
+ readonly pending?: {
10
+ readonly call: ToolCallContent;
11
+ readonly status: "ready" | "dispatched";
12
+ };
13
+ readonly interruptBeforeTool?: boolean;
14
+ readonly counters: RunLimitCounters;
15
+ readonly deadlineAt: string;
16
+ }
17
+ export declare function agentFingerprint(agent: Agent, revision: string): string;
18
+ export declare function agentId(agent: Agent): string;
19
+ export declare function validateRunStateOptions(options: AgentRunStateOptions): void;
20
+ export declare function loadAgentRunState(checkpoints: CheckpointStore, ref: AgentRunRef, ownership?: OwnershipScope): Promise<{
21
+ readonly record: CheckpointRecord;
22
+ readonly state: StoredAgentRunState;
23
+ }>;
24
+ export declare function saveAgentRunState(input: {
25
+ readonly checkpoints: CheckpointStore;
26
+ readonly state: StoredAgentRunState;
27
+ readonly expectedVersion: number;
28
+ readonly ownership?: OwnershipScope;
29
+ readonly fencingToken?: number;
30
+ readonly redactor?: SecretRedactor;
31
+ readonly maxStateBytes?: number;
32
+ }): Promise<{
33
+ readonly record: CheckpointRecord;
34
+ readonly state: StoredAgentRunState;
35
+ }>;
36
+ export declare function statusFromState(state: StoredAgentRunState, version: number): AgentRunStatusResult;
37
+ export declare function publicState(state: StoredAgentRunState): AgentRunState;
38
+ export declare function initialAgentRunState(input: {
39
+ readonly agent: Agent;
40
+ readonly options: AgentRunStateOptions;
41
+ readonly runId: string;
42
+ readonly sessionId: string;
43
+ readonly leafId?: string;
44
+ readonly model: ModelConfig;
45
+ readonly counters: RunLimitCounters;
46
+ readonly deadlineAt: string;
47
+ readonly status: "suspended" | "running";
48
+ readonly interruption?: AgentRunInterruption;
49
+ readonly messages?: readonly Message[];
50
+ readonly pending?: StoredAgentRunState["pending"];
51
+ readonly interruptBeforeTool?: boolean;
52
+ }): StoredAgentRunState;
53
+ export declare function parseAgentRunState(value: unknown, version?: number): StoredAgentRunState;
@@ -0,0 +1,127 @@
1
+ import { createHash } from "node:crypto";
2
+ import { AgentRunStateError } from "./contracts.js";
3
+ export const AGENT_RUN_STATE_NAMESPACE = "prism.agent-run";
4
+ export const AGENT_RUN_STATE_SCHEMA_VERSION = 1;
5
+ export const DEFAULT_MAX_AGENT_RUN_STATE_BYTES = 256 * 1024;
6
+ export const HARD_MAX_AGENT_RUN_STATE_BYTES = 1024 * 1024;
7
+ const MAX_DEPTH = 32;
8
+ const MAX_PROPERTIES = 256;
9
+ export function agentFingerprint(agent, revision) {
10
+ const config = agent.config;
11
+ const tools = !config.tools ? [] : "list" in config.tools ? config.tools.list() : config.tools;
12
+ const guardrails = [
13
+ ...(config.guardrails?.input ?? []),
14
+ ...(config.guardrails?.output ?? []),
15
+ ...(config.guardrails?.toolInput ?? []),
16
+ ...(config.guardrails?.toolOutput ?? []),
17
+ ];
18
+ const value = JSON.stringify({
19
+ id: config.id ?? config.name ?? "agent",
20
+ revision,
21
+ model: config.model,
22
+ tools: tools.map((tool) => ({ name: tool.name, parameters: tool.parameters, exclusive: tool.exclusive })),
23
+ guardrails: guardrails.map((guardrail) => ({ name: guardrail.name, stage: guardrail.stage, revision: guardrail.revision })),
24
+ loop: typeof config.loop === "object" && config.loop && "strategy" in config.loop ? config.loop.strategy : config.loop?.name ?? "single-shot",
25
+ });
26
+ return createHash("sha256").update(value).digest("hex");
27
+ }
28
+ export function agentId(agent) {
29
+ const id = agent.config.id ?? agent.config.name;
30
+ if (!id?.trim())
31
+ throw new AgentRunStateError("Durable agent runs require AgentConfig.id or name");
32
+ return id;
33
+ }
34
+ export function validateRunStateOptions(options) {
35
+ if (!options.definitionRevision.trim())
36
+ throw new AgentRunStateError("Durable agent runs require definitionRevision");
37
+ const bytes = options.maxStateBytes ?? DEFAULT_MAX_AGENT_RUN_STATE_BYTES;
38
+ if (!Number.isSafeInteger(bytes) || bytes < 1 || bytes > HARD_MAX_AGENT_RUN_STATE_BYTES) {
39
+ throw new AgentRunStateError(`maxStateBytes must be a positive safe integer at most ${HARD_MAX_AGENT_RUN_STATE_BYTES}`);
40
+ }
41
+ }
42
+ export async function loadAgentRunState(checkpoints, ref, ownership) {
43
+ const record = await checkpoints.loadCheckpoint({ namespace: AGENT_RUN_STATE_NAMESPACE, key: ref.runId, ...ownership });
44
+ if (!record)
45
+ throw new AgentRunStateError(`No durable agent run ${ref.runId}`);
46
+ if (ref.sessionId && record.value && typeof record.value === "object" && record.value.sessionId !== ref.sessionId) {
47
+ throw new AgentRunStateError("Agent run session mismatch");
48
+ }
49
+ return { record, state: parseAgentRunState(record.value, record.version) };
50
+ }
51
+ export async function saveAgentRunState(input) {
52
+ const bounded = boundState(input.redactor?.redact(input.state) ?? input.state, input.maxStateBytes ?? DEFAULT_MAX_AGENT_RUN_STATE_BYTES);
53
+ const record = await input.checkpoints.saveCheckpoint({
54
+ namespace: AGENT_RUN_STATE_NAMESPACE,
55
+ key: bounded.runId,
56
+ version: input.expectedVersion + 1,
57
+ expectedVersion: input.expectedVersion,
58
+ fencingToken: input.fencingToken,
59
+ value: bounded,
60
+ category: "agent-run",
61
+ ...input.ownership,
62
+ });
63
+ return { record, state: { ...bounded, version: record.version } };
64
+ }
65
+ export function statusFromState(state, version) {
66
+ return { state: publicState({ ...state, version }), version };
67
+ }
68
+ export function publicState(state) {
69
+ const { input: _input, pending: _pending, interruptBeforeTool: _interruptBeforeTool, counters: _counters, deadlineAt: _deadlineAt, ...publicValue } = state;
70
+ return publicValue;
71
+ }
72
+ export function initialAgentRunState(input) {
73
+ validateRunStateOptions(input.options);
74
+ return {
75
+ schemaVersion: AGENT_RUN_STATE_SCHEMA_VERSION,
76
+ agentId: agentId(input.agent),
77
+ definitionRevision: input.options.definitionRevision,
78
+ fingerprint: agentFingerprint(input.agent, input.options.definitionRevision),
79
+ runId: input.runId,
80
+ sessionId: input.sessionId,
81
+ ...(input.leafId ? { leafId: input.leafId } : {}),
82
+ model: input.model,
83
+ status: input.status,
84
+ interruption: input.interruption,
85
+ input: input.messages,
86
+ pending: input.pending,
87
+ interruptBeforeTool: input.interruptBeforeTool,
88
+ counters: input.counters,
89
+ deadlineAt: input.deadlineAt,
90
+ };
91
+ }
92
+ export function parseAgentRunState(value, version) {
93
+ if (!value || typeof value !== "object")
94
+ throw new AgentRunStateError("Agent run state must be an object");
95
+ const state = value;
96
+ if (state.schemaVersion !== AGENT_RUN_STATE_SCHEMA_VERSION)
97
+ throw new AgentRunStateError(`Unsupported agent run state schemaVersion ${String(state.schemaVersion)}`);
98
+ if (!state.agentId || !state.definitionRevision || !state.fingerprint || !state.runId || !state.sessionId || !state.model || !state.status || !state.counters || !state.deadlineAt) {
99
+ throw new AgentRunStateError("Malformed agent run state");
100
+ }
101
+ return boundState({ ...state, version }, DEFAULT_MAX_AGENT_RUN_STATE_BYTES);
102
+ }
103
+ function boundState(state, maxBytes) {
104
+ checkShape(state, 0);
105
+ let text;
106
+ try {
107
+ text = JSON.stringify(state);
108
+ }
109
+ catch {
110
+ throw new AgentRunStateError("Agent run state must be JSON serializable");
111
+ }
112
+ if (Buffer.byteLength(text) > maxBytes)
113
+ throw new AgentRunStateError(`Agent run state exceeds ${maxBytes} bytes`);
114
+ return JSON.parse(text);
115
+ }
116
+ function checkShape(value, depth) {
117
+ if (depth > MAX_DEPTH)
118
+ throw new AgentRunStateError(`Agent run state exceeds depth ${MAX_DEPTH}`);
119
+ if (!value || typeof value !== "object")
120
+ return;
121
+ const entries = Array.isArray(value) ? value : Object.values(value);
122
+ if (!Array.isArray(value) && entries.length > MAX_PROPERTIES)
123
+ throw new AgentRunStateError(`Agent run state exceeds ${MAX_PROPERTIES} properties`);
124
+ for (const item of entries)
125
+ checkShape(item, depth + 1);
126
+ }
127
+ //# sourceMappingURL=agent-run-state.js.map
package/dist/agents.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- import type { Agent, AgentConfig, AgentSession, AgentSessionConfig } from "./contracts.js";
1
+ import type { Agent, AgentConfig, AgentRunResult, AgentRunResume, AgentRunResumeOptions, AgentRunRef, AgentSession, AgentSessionConfig } from "./contracts.js";
2
2
  export declare function createAgent(config: AgentConfig): Agent;
3
3
  export declare function createAgentSession(config: AgentSessionConfig & {
4
4
  readonly agent: Agent;
5
5
  }): AgentSession;
6
+ /** Resume a persisted built-in run. A claimed/dispatched tool is never replayed automatically. */
7
+ export declare function resumeAgentRun(agent: Agent, ref: AgentRunRef, resume: AgentRunResume, options: AgentRunResumeOptions): Promise<AgentRunResult>;