@arnilo/prism 0.0.4 → 0.0.6

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 (97) hide show
  1. package/CHANGELOG.md +46 -1
  2. package/README.md +34 -10
  3. package/dist/agent-loops.d.ts +1 -0
  4. package/dist/agent-loops.js +26 -16
  5. package/dist/agents.js +147 -21
  6. package/dist/cli-init.d.ts +41 -0
  7. package/dist/cli-init.js +390 -0
  8. package/dist/cli-runner.d.ts +7 -1
  9. package/dist/cli-runner.js +13 -1
  10. package/dist/content.d.ts +19 -0
  11. package/dist/content.js +197 -69
  12. package/dist/contracts.d.ts +96 -9
  13. package/dist/contracts.js +8 -0
  14. package/dist/feedback.d.ts +48 -0
  15. package/dist/feedback.js +230 -0
  16. package/dist/ids.d.ts +2 -0
  17. package/dist/ids.js +6 -0
  18. package/dist/index.d.ts +10 -4
  19. package/dist/index.js +6 -3
  20. package/dist/providers/media.d.ts +3 -1
  21. package/dist/providers/media.js +11 -1
  22. package/dist/session-stores.js +2 -3
  23. package/dist/testing/feedback.d.ts +6 -0
  24. package/dist/testing/feedback.js +37 -0
  25. package/dist/testing/persistence-schema.d.ts +48 -10
  26. package/dist/testing/persistence-schema.js +166 -22
  27. package/dist/testing/run-ledger-conformance.js +7 -1
  28. package/dist/thinking.d.ts +42 -0
  29. package/dist/thinking.js +92 -0
  30. package/dist/tools.js +2 -3
  31. package/dist/use-case-model.d.ts +63 -0
  32. package/dist/use-case-model.js +52 -0
  33. package/docs/a2a.md +75 -0
  34. package/docs/agent-events.md +14 -21
  35. package/docs/agent-loops.md +12 -9
  36. package/docs/agent-session-runtime.md +14 -16
  37. package/docs/cli-rpc.md +35 -7
  38. package/docs/coding-agent-tools.md +35 -14
  39. package/docs/coding-security.md +7 -3
  40. package/docs/compaction-llm.md +17 -7
  41. package/docs/compaction-observational-memory.md +30 -4
  42. package/docs/context-and-skills.md +1 -0
  43. package/docs/credential-storage.md +58 -9
  44. package/docs/credentials-and-redaction.md +3 -3
  45. package/docs/database-persistence.md +17 -9
  46. package/docs/evaluations.md +122 -0
  47. package/docs/extensions.md +2 -2
  48. package/docs/host-security.md +26 -5
  49. package/docs/index.md +43 -28
  50. package/docs/mcp-tools.md +74 -13
  51. package/docs/migration.md +177 -3
  52. package/docs/multimodal-content.md +14 -6
  53. package/docs/node-filesystem-config.md +1 -0
  54. package/docs/node-jsonl-session-store.md +5 -4
  55. package/docs/observability.md +14 -6
  56. package/docs/performance.md +209 -0
  57. package/docs/postgres-persistence.md +8 -6
  58. package/docs/provider-caching.md +16 -4
  59. package/docs/provider-conformance.md +40 -1
  60. package/docs/provider-packages.md +62 -3
  61. package/docs/providers/ai-sdk.md +149 -0
  62. package/docs/providers/kimi.md +124 -61
  63. package/docs/providers/neuralwatt.md +19 -13
  64. package/docs/providers/openai.md +56 -13
  65. package/docs/providers/opencode-go.md +118 -30
  66. package/docs/providers/openrouter.md +105 -35
  67. package/docs/providers/zai.md +94 -45
  68. package/docs/public-contracts.md +6 -5
  69. package/docs/rag.md +113 -0
  70. package/docs/release-and-install.md +100 -79
  71. package/docs/review-coverage-2026-07-15.md +193 -0
  72. package/docs/review-coverage-2026-07-17-provider-validation.md +192 -0
  73. package/docs/runs-and-usage.md +42 -5
  74. package/docs/server.md +139 -0
  75. package/docs/settings-auth-trust-security.md +5 -5
  76. package/docs/sqlite-persistence.md +6 -5
  77. package/docs/structured-output.md +1 -1
  78. package/docs/supervisors.md +71 -0
  79. package/docs/thinking-and-reasoning.md +98 -0
  80. package/docs/tool-execution-primitives.md +3 -3
  81. package/docs/tools.md +15 -0
  82. package/docs/use-case-model-selection.md +109 -0
  83. package/docs/workflow-orchestration-primitives.md +20 -3
  84. package/docs/workflows.md +114 -33
  85. package/docs/working-and-semantic-memory.md +170 -0
  86. package/package.json +13 -3
  87. package/templates/init/README.md.tmpl +28 -0
  88. package/templates/init/env.example.tmpl +1 -0
  89. package/templates/init/gitignore.tmpl +11 -0
  90. package/templates/init/optional/evals-example.ts.tmpl +17 -0
  91. package/templates/init/optional/workflows-example.ts.tmpl +27 -0
  92. package/templates/init/package.json.tmpl +22 -0
  93. package/templates/init/providers.json +76 -0
  94. package/templates/init/src/agent.ts.tmpl +10 -0
  95. package/templates/init/src/index.ts.tmpl +12 -0
  96. package/templates/init/src/tests/agent.test.ts.tmpl +24 -0
  97. package/templates/init/tsconfig.json.tmpl +15 -0
package/CHANGELOG.md CHANGED
@@ -5,7 +5,52 @@ 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.6] - 2026-07-19
9
+
10
+ ### Added
11
+
12
+ - Caller-gated model discovery: `listOpenAIModels`, `listKimiModels`, `listZaiModels`, `listOpenRouterModels`, and `listOpenCodeGoModels`. Provider setup remains network-free; hosts explicitly fetch and register current models.
13
+ - 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.
14
+ - 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.
15
+ - Checksummed SQLite/PostgreSQL migration histories and catalog-shape verification, bounded JSON Schema compilation LRU, and public `assertFiniteVector` validation.
16
+
17
+ ### Changed
18
+
19
+ - 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.
20
+ - Workflow definitions now require a non-empty `revision`; cancellation requires exact ownership and the current workflow definition. All workflow limits have finite hard caps.
21
+ - 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.
22
+ - 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.
23
+ - MCP Streamable HTTP requires HTTPS and explicit `allowedOrigins`; loopback HTTP requires explicit opt-in. Discovery, schemas, results, and response bodies are bounded.
24
+ - 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.
25
+ - Generated Prism, workflow, and evaluation IDs use cryptographic UUIDs; non-finite embedding vectors now fail before scoring or persistence.
26
+
27
+ ### Security
28
+
29
+ - Fixed cross-owner workflow cancellation and duplicate active-run overwrite risks.
30
+ - Added fail-closed limits and validation at file, process, credential, MCP, migration, schema, vector, provider-worker, and A2A trust boundaries.
31
+
32
+ ### Upgrade notes
33
+
34
+ - Finish or deliberately migrate pre-0.0.6 workflow runs/checkpoints before upgrading: their definition hashes lack the required revision.
35
+ - 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.
36
+
37
+ ## [0.0.5] - 2026-07-16
38
+
39
+ - `@arnilo/prism-providers` now installs all seven first-party adapters including AI SDK interoperability; `@arnilo/prism-all` now installs every first-party package while activating none automatically.
40
+
41
+ - Added optional `@arnilo/prism-supervisor` with bounded explicit child delegation, derived memory scope IDs, narrowing-only permissions, A2A 1.0 cards/ES256 signatures, authorized JSON-RPC/SSE serving, and an exact-origin remote client.
42
+
43
+ - Added bounded immutable run/trace feedback with exact ownership, evaluation linkage, memory/SQLite/PostgreSQL stores, schema migration 003, and safe OpenTelemetry projection.
44
+
45
+ - Phase 11 extends workflows with explicit durable schedules/background execution, nested composition, bounded validated state, immutable-lineage replay, and optional command/Web bindings over existing checkpoint/lease primitives.
46
+
47
+ - Optional `@arnilo/prism-server` package with authorized bounded Web-standard direct/SSE agent and durable workflow routes; `@arnilo/prism-mcp` now supports explicit authorized Prism tool/command server exposure and bounded Web-standard Streamable HTTP handling.
48
+ - Optional `@arnilo/prism-rag` package: bounded deterministic text/Markdown chunking, Phase 7 vector indexing/retrieval, stable citations, metadata filters, redaction, and explicit ContextProvider injection.
49
+ - Workflows now support durable human `suspend()`/approve/deny, expected-version exact-once resume, validated/redacted resume payloads, and opt-in tool approval with execution-policy recheck.
50
+
51
+ ### Added
52
+
53
+ - Optional `@arnilo/prism-memory` package: schema/template-backed working memory, semantic recall, package-owned `Embedder`/`VectorStore` contracts, in-memory adapters, context provider, opt-in processor, shared conformance, and PostgreSQL/pgvector production path.
9
54
 
10
55
  ## [0.0.4] - 2026-07-14
11
56
 
package/README.md CHANGED
@@ -26,13 +26,14 @@ packages. Prism defines contracts, not apps.
26
26
  - **Input/prompt/context**: default input and prompt builders, system-prompt
27
27
  layering, and provider-input assembly — every stage replaceable.
28
28
  - **Sessions and memory**: in-memory and JSONL session stores, branching/fork/
29
- clone, default and LLM compaction strategies, retry policy, and
30
- observational-memory recall/status/view.
29
+ clone, default and LLM compaction strategies, retry policy,
30
+ observational-memory recall/status/view, optional working/semantic memory
31
+ (`@arnilo/prism-memory`), and bounded text/Markdown RAG (`@arnilo/prism-rag`).
31
32
  - **Extensions and manifests**: extension kernel + event bus, contribution
32
33
  registries, middleware hooks, and data-only package manifests.
33
34
  - **Config, settings, security**: layered config merge, settings providers,
34
35
  credential resolvers, trust/permission policies, and secret redaction.
35
- - **CLI/RPC**: `prism --mode print|json|rpc` over the same `AgentSession` API.
36
+ - **CLI/RPC/server**: `prism --mode print|json|rpc`, `prism init`, optional framework-free authorized Web agent/workflow routes, and explicit MCP server exposure.
36
37
 
37
38
  ## Install
38
39
 
@@ -50,6 +51,8 @@ npm install @arnilo/prism-base # core + compaction
50
51
  npm install @arnilo/prism-code @arnilo/prism-provider-openai # coding-agent profile
51
52
  npm install @arnilo/prism-sdk @arnilo/prism-provider-openai # application profile
52
53
  npm install @arnilo/prism-all # every first-party package
54
+ npm install @arnilo/prism-server @arnilo/prism-workflows # optional Web API boundary
55
+ npm install @arnilo/prism-supervisor # optional local delegation + A2A 1.0
53
56
  ```
54
57
 
55
58
  See [docs/release-and-install.md](docs/release-and-install.md) for install
@@ -57,6 +60,17 @@ specifiers, tarball contents, and the offline test budget.
57
60
 
58
61
  ## Quick start
59
62
 
63
+ Scaffold a tiny project (offline mock test included):
64
+
65
+ ```bash
66
+ npx --package @arnilo/prism prism init my-agent
67
+ # or, with a real provider package selected:
68
+ npx --package @arnilo/prism prism init my-agent --provider openai
69
+ cd my-agent && npm install && npm test
70
+ ```
71
+
72
+ Or embed Prism directly:
73
+
60
74
  ```ts
61
75
  import { createAgent, createAgentSession, createMockProvider } from "@arnilo/prism";
62
76
 
@@ -68,9 +82,18 @@ const agent = createAgent({
68
82
 
69
83
  const session = createAgentSession({ agent });
70
84
 
71
- // Consume the event stream concurrently with the run. `subscribe()` only
72
- // emits while a run is in progress, so the loop and `run()` must run together;
73
- // awaiting the loop before calling `run()` would deadlock.
85
+ // Direct result: run/prompt return AgentRunResult (text, usage, status, ids).
86
+ const result = await session.run("Hi");
87
+ console.log(result.text, result.usage?.totalTokens);
88
+
89
+ // Integrated streaming: subscribe-before-run for one owned run.
90
+ for await (const event of session.stream("Hi again")) {
91
+ // AgentEvent: agent_started, message_delta, turn_finished, ...
92
+ }
93
+
94
+ // Long-lived subscribe() still works when you need a subscriber across runs.
95
+ // `subscribe()` only emits while a run is in progress, so the loop and `run()`
96
+ // must run together; awaiting the loop before calling `run()` would deadlock.
74
97
  (async () => {
75
98
  const consumer = (async () => {
76
99
  for await (const event of session.subscribe()) {
@@ -132,12 +155,13 @@ printf '{"id":"1","command":"prompt","params":{"input":"Hi"}}\n' \
132
155
  | `@arnilo/prism-coding-security` | coding approval, containment, and sandbox adapters |
133
156
  | `@arnilo/prism-tool-validator-json-schema` | bounded JSON Schema tool validation |
134
157
  | `@arnilo/prism-mcp` | MCP client/tool bridge |
135
- | `@arnilo/prism-workflows` | bounded DAG workflows and durable coordination |
158
+ | `@arnilo/prism-workflows` | bounded DAG workflows, durable suspend/resume, schedules/background runs, composition/state/replay, and multi-process coordination |
159
+ | `@arnilo/prism-supervisor` | bounded local child delegation and A2A 1.0 interoperability |
136
160
  | `@arnilo/prism-observability-opentelemetry` | optional OpenTelemetry adapter |
137
161
  | `@arnilo/prism-credentials-node` | encrypted-file and keychain credentials |
138
- | `@arnilo/prism-session-store-sqlite` | SQLite persistence/checkpoints/leases |
139
- | `@arnilo/prism-session-store-postgres` | PostgreSQL persistence/checkpoints/leases |
140
- | `@arnilo/prism-providers` | family: all 6 provider adapters |
162
+ | `@arnilo/prism-session-store-sqlite` | SQLite persistence/checkpoints/leases/owned run feedback |
163
+ | `@arnilo/prism-session-store-postgres` | PostgreSQL persistence/checkpoints/leases/owned run feedback |
164
+ | `@arnilo/prism-providers` | family: all 7 provider adapters, including AI SDK interoperability |
141
165
  | `@arnilo/prism-compaction` | family: both compaction strategies |
142
166
  | `@arnilo/prism-base` | profile: core + compaction + JSON Schema validation |
143
167
  | `@arnilo/prism-code` | profile: base + coding tools/security + MCP |
@@ -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
  },
@@ -195,13 +206,12 @@ export function resolveLoop(options, config) {
195
206
  parser: loop.parser,
196
207
  repairer: loop.repairer,
197
208
  maxRevisions: loop.maxRevisions,
209
+ toolCalls: loop.toolCalls,
198
210
  });
199
211
  }
200
212
  throw new Error(`Unknown agent loop strategy: ${strategy}`);
201
213
  }
202
214
  return loop;
203
215
  }
204
- function randomId(prefix) {
205
- return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
206
- }
216
+ const randomId = createId;
207
217
  //# sourceMappingURL=agent-loops.js.map
package/dist/agents.js CHANGED
@@ -1,4 +1,6 @@
1
+ import { AgentRunError } from "./contracts.js";
1
2
  import { resolveLoop, resolveToolConcurrency } from "./agent-loops.js";
3
+ import { createId } from "./ids.js";
2
4
  import { createProviderTurnMetadata, readProviderHttpStatus } from "./observability.js";
3
5
  import { createDefaultCompactionStrategy, isCompactionEntryData } from "./compaction.js";
4
6
  import { assembleProviderInput } from "./input.js";
@@ -71,6 +73,8 @@ class RuntimeAgentSession {
71
73
  const model = options.model ?? this.agent.config.model;
72
74
  const startedAt = new Date().toISOString();
73
75
  let runError;
76
+ const runUsage = createUsageAccumulator();
77
+ let usage;
74
78
  try {
75
79
  this.resolveRunProvider(options);
76
80
  throwIfAborted(controller.signal);
@@ -114,6 +118,23 @@ class RuntimeAgentSession {
114
118
  const loop = resolveLoop(options, this.agent.config);
115
119
  const toolConcurrency = resolveToolConcurrency(options, this.agent.config);
116
120
  this.activeLoopTurn = 1;
121
+ const recordProviderUsage = async (turnUsage, turn, attempt) => {
122
+ runUsage.add(turnUsage);
123
+ if (!this.activeLedger)
124
+ return;
125
+ const usageRecord = {
126
+ id: randomId("usage"),
127
+ sessionId: this.id,
128
+ runId,
129
+ scope: "provider_turn",
130
+ turn,
131
+ attempt,
132
+ usage: turnUsage,
133
+ recordedAt: new Date().toISOString(),
134
+ ...this.activeOwnership,
135
+ };
136
+ await this.activeLedger.appendUsage(redactRunLedgerRecord(usageRecord, this.activeRedactor));
137
+ };
117
138
  // ponytail: LoopContext binds existing private helpers; loop orchestrates only.
118
139
  const ctx = {
119
140
  sessionId: this.id,
@@ -152,7 +173,7 @@ class RuntimeAgentSession {
152
173
  generate: async (request) => {
153
174
  const policyResult = await this.applyProviderRequestPolicies(request, runId, options, metadata, controller.signal);
154
175
  const middlewareRequest = await this.agent.config.middleware?.run("provider_request", policyResult.request) ?? policyResult.request;
155
- return this.generateWithRetry(this.redactProviderRequest(middlewareRequest), runId, options, controller.signal, policyResult.secrets, this.activeLoopTurn);
176
+ return this.generateWithRetry(this.redactProviderRequest(middlewareRequest), runId, options, controller.signal, policyResult.secrets, this.activeLoopTurn, recordProviderUsage);
156
177
  },
157
178
  isToolCallExclusive: (call) => registry.get(call.name)?.exclusive === true,
158
179
  dispatchToolCall: (call) => dispatchToolCall({
@@ -175,12 +196,14 @@ class RuntimeAgentSession {
175
196
  this.emit(event);
176
197
  },
177
198
  };
178
- const usage = await loop.run(ctx);
199
+ const loopUsage = await loop.run(ctx);
200
+ usage = runUsage.value() ?? loopUsage;
179
201
  if (usage && this.activeLedger) {
180
202
  const usageRecord = {
181
203
  id: randomId("usage"),
182
204
  sessionId: this.id,
183
205
  runId,
206
+ scope: "run_total",
184
207
  usage,
185
208
  recordedAt: new Date().toISOString(),
186
209
  ...this.activeOwnership,
@@ -189,11 +212,23 @@ class RuntimeAgentSession {
189
212
  }
190
213
  await this.drainLedger();
191
214
  this.emit({ type: "agent_finished", sessionId: this.id, runId, usage });
215
+ return this.buildRunResult({
216
+ runId,
217
+ status: "succeeded",
218
+ usage,
219
+ });
192
220
  }
193
221
  catch (error) {
194
222
  runError = errorToErrorInfo(error);
195
223
  this.emit({ type: "error", sessionId: this.id, runId, error: runError });
196
- throw error;
224
+ const result = this.buildRunResult({
225
+ runId,
226
+ status: controller.signal.aborted ? "aborted" : "failed",
227
+ usage: runUsage.value() ?? usage,
228
+ error: runError,
229
+ abortReason: controller.signal.aborted ? String(controller.signal.reason) : undefined,
230
+ });
231
+ throw new AgentRunError(result, { cause: error });
197
232
  }
198
233
  finally {
199
234
  if (this.activeRun === controller)
@@ -233,6 +268,48 @@ class RuntimeAgentSession {
233
268
  prompt(input, options) {
234
269
  return this.run(input, options);
235
270
  }
271
+ async *stream(input, options = {}) {
272
+ const { maxQueuedEvents, overflow, ...runOptions } = options;
273
+ const subscription = this.subscribe({ maxQueuedEvents, overflow });
274
+ let runOwnedId;
275
+ let settled = false;
276
+ const runPromise = this.run(input, runOptions).finally(() => {
277
+ settled = true;
278
+ });
279
+ try {
280
+ for await (const event of subscription) {
281
+ if ("runId" in event && typeof event.runId === "string") {
282
+ if (runOwnedId === undefined && event.type === "agent_started")
283
+ runOwnedId = event.runId;
284
+ if (runOwnedId !== undefined && event.runId !== runOwnedId)
285
+ continue;
286
+ }
287
+ yield event;
288
+ }
289
+ await runPromise;
290
+ }
291
+ finally {
292
+ if (!settled) {
293
+ this.abort(new Error("stream consumer closed"));
294
+ await runPromise.catch(() => undefined);
295
+ }
296
+ }
297
+ }
298
+ buildRunResult(input) {
299
+ const final = finalAssistantMessage(this.history);
300
+ return {
301
+ sessionId: this.id,
302
+ runId: input.runId,
303
+ status: input.status,
304
+ leafId: this.currentLeafId,
305
+ text: final.text,
306
+ content: final.content,
307
+ message: final.message,
308
+ usage: input.usage,
309
+ error: input.error,
310
+ abortReason: input.abortReason,
311
+ };
312
+ }
236
313
  async compact(options = {}) {
237
314
  if (this.activeRun)
238
315
  throw new Error("Agent session already has an active run");
@@ -340,13 +417,13 @@ class RuntimeAgentSession {
340
417
  if (failure)
341
418
  throw failure;
342
419
  }
343
- async generateWithRetry(request, runId, options, signal, requestSecrets = [], turn = 1) {
420
+ async generateWithRetry(request, runId, options, signal, requestSecrets = [], turn = 1, recordUsage) {
344
421
  const retry = mergeRetry(this.agent.config.retry, options.retry);
345
422
  const secrets = [...requestSecrets, ...(retry?.secrets ?? [])];
346
423
  const policy = retry?.policy ?? (retry ? createDefaultRetryPolicy(retry) : undefined);
347
424
  for (let attempt = 1;; attempt += 1) {
348
425
  try {
349
- return await this.generateProviderTurn(request, runId, signal, secrets, turn, attempt);
426
+ return await this.generateProviderTurn(request, runId, signal, secrets, turn, attempt, recordUsage);
350
427
  }
351
428
  catch (error) {
352
429
  const failure = error instanceof ProviderTurnFailure ? error : undefined;
@@ -365,7 +442,7 @@ class RuntimeAgentSession {
365
442
  }
366
443
  }
367
444
  }
368
- async generateProviderTurn(request, runId, signal, secrets = [], turn = 1, attempt = 1) {
445
+ async generateProviderTurn(request, runId, signal, secrets = [], turn = 1, attempt = 1, recordUsage) {
369
446
  const startedAt = performance.now();
370
447
  const providerId = this.activeProvider?.id ?? request.model.provider;
371
448
  const buildMetadata = (extra = {}) => createProviderTurnMetadata(request, providerId, { attempt, ...extra });
@@ -382,25 +459,20 @@ class RuntimeAgentSession {
382
459
  let messageId;
383
460
  let started = false;
384
461
  let usage;
462
+ let usageRecorded = false;
463
+ const recordTurnUsage = async () => {
464
+ if (!usage || usageRecorded)
465
+ return;
466
+ usageRecorded = true;
467
+ await recordUsage?.(usage, turn, attempt);
468
+ };
385
469
  try {
386
470
  for await (const event of this.activeProvider.generate(request)) {
387
471
  throwIfAborted(signal);
388
472
  if (event.type === "error")
389
473
  throw new ProviderTurnFailure(event.error, started);
390
- if (event.type === "usage") {
474
+ if (event.type === "usage")
391
475
  usage = event.usage;
392
- if (this.activeLedger) {
393
- const usageRecord = {
394
- id: randomId("usage"),
395
- sessionId: this.id,
396
- runId,
397
- usage: event.usage,
398
- recordedAt: new Date().toISOString(),
399
- ...this.activeOwnership,
400
- };
401
- await this.activeLedger.appendUsage(redactRunLedgerRecord(usageRecord, this.activeRedactor));
402
- }
403
- }
404
476
  if (event.type === "done") {
405
477
  usage = event.usage ?? usage;
406
478
  break;
@@ -433,6 +505,7 @@ class RuntimeAgentSession {
433
505
  calls.push(call);
434
506
  this.emit({ type: "message_delta", sessionId: this.id, runId, content: call });
435
507
  }
508
+ await recordTurnUsage();
436
509
  const latencyMs = Math.round(performance.now() - startedAt);
437
510
  this.emit({
438
511
  type: "provider_turn_finished",
@@ -447,12 +520,14 @@ class RuntimeAgentSession {
447
520
  catch (error) {
448
521
  const latencyMs = Math.round(performance.now() - startedAt);
449
522
  const info = error instanceof ProviderTurnFailure ? redactSecrets(error.info, secrets) : errorToErrorInfo(error, secrets);
523
+ await recordTurnUsage();
450
524
  this.emit({
451
525
  type: "provider_turn_finished",
452
526
  sessionId: this.id,
453
527
  runId,
454
528
  turn,
455
529
  metadata: buildMetadata({ latencyMs, httpStatus: readProviderHttpStatus(info) }),
530
+ usage,
456
531
  error: info,
457
532
  });
458
533
  if (error instanceof ProviderTurnFailure)
@@ -608,6 +683,19 @@ function inputToMessages(input) {
608
683
  return [input];
609
684
  return [...input];
610
685
  }
686
+ function finalAssistantMessage(history) {
687
+ for (let index = history.length - 1; index >= 0; index -= 1) {
688
+ const message = history[index];
689
+ if (message.role !== "assistant")
690
+ continue;
691
+ const text = message.content
692
+ .filter((block) => block.type === "text")
693
+ .map((block) => block.text)
694
+ .join("");
695
+ return { message, content: message.content, text };
696
+ }
697
+ return { content: [], text: "" };
698
+ }
611
699
  function activeTools(tools) {
612
700
  if (!tools)
613
701
  return { registry: createToolRegistry(), tools: [] };
@@ -673,7 +761,45 @@ function throwIfAbortedSignal(signal) {
673
761
  if (signal?.aborted)
674
762
  throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
675
763
  }
676
- function randomId(prefix) {
677
- return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
764
+ function createUsageAccumulator() {
765
+ const sums = new Map();
766
+ let costCurrency;
767
+ let costCompatible = true;
768
+ return {
769
+ add(usage) {
770
+ for (const key of ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"]) {
771
+ const value = usage[key];
772
+ if (value !== undefined)
773
+ sums.set(key, (sums.get(key) ?? 0) + value);
774
+ }
775
+ const total = usage.totalTokens
776
+ ?? (usage.inputTokens !== undefined || usage.outputTokens !== undefined
777
+ ? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0)
778
+ : undefined);
779
+ if (total !== undefined)
780
+ sums.set("totalTokens", (sums.get("totalTokens") ?? 0) + total);
781
+ if (usage.cost !== undefined && costCompatible) {
782
+ if (!sums.has("cost"))
783
+ costCurrency = usage.currency;
784
+ else if (usage.currency !== costCurrency)
785
+ costCompatible = false;
786
+ if (costCompatible)
787
+ sums.set("cost", (sums.get("cost") ?? 0) + usage.cost);
788
+ }
789
+ },
790
+ value() {
791
+ if (sums.size === 0)
792
+ return undefined;
793
+ const usage = {};
794
+ for (const [key, value] of sums) {
795
+ if (key !== "cost" || costCompatible)
796
+ usage[key] = value;
797
+ }
798
+ if (costCompatible && sums.has("cost") && costCurrency !== undefined)
799
+ usage.currency = costCurrency;
800
+ return Object.keys(usage).length > 0 ? usage : undefined;
801
+ },
802
+ };
678
803
  }
804
+ const randomId = createId;
679
805
  //# sourceMappingURL=agents.js.map
@@ -0,0 +1,41 @@
1
+ import type { Writable } from "node:stream";
2
+ export declare class InitUsageError extends Error {
3
+ }
4
+ export type InitProvider = string;
5
+ export interface InitOptions {
6
+ readonly directory: string;
7
+ readonly provider: InitProvider;
8
+ readonly withWorkflows: boolean;
9
+ readonly withEvals: boolean;
10
+ readonly force: boolean;
11
+ readonly help: boolean;
12
+ }
13
+ export interface InitRuntime {
14
+ readonly stdout: Writable;
15
+ readonly stderr: Writable;
16
+ /** Override template root (tests). Defaults to package `templates/init`. */
17
+ readonly templatesRoot?: string;
18
+ /** Override package version stamped into generated package.json. */
19
+ readonly packageVersion?: string;
20
+ /** Working directory used to resolve relative destinations. Defaults to process.cwd(). */
21
+ readonly cwd?: string;
22
+ }
23
+ export interface InitResult {
24
+ readonly targetDir: string;
25
+ readonly writtenFiles: readonly string[];
26
+ readonly provider: InitProvider;
27
+ readonly withWorkflows: boolean;
28
+ readonly withEvals: boolean;
29
+ readonly totalBytes: number;
30
+ }
31
+ /** Provider ids supported by `prism init --provider`. Loaded from templates data. */
32
+ export declare function listInitProviders(templatesRoot?: string): readonly string[];
33
+ /** @deprecated Prefer listInitProviders(); retained for tests that import the name. */
34
+ export declare const INIT_PROVIDERS: readonly string[];
35
+ export declare function getInitUsage(templatesRoot?: string): string;
36
+ export declare const initUsage: string;
37
+ export declare function parseInitArgs(argv: readonly string[], templatesRoot?: string): InitOptions;
38
+ export declare function runInitCommand(argv: readonly string[], runtime: InitRuntime): Promise<number>;
39
+ export declare function createInitProject(options: InitOptions, runtime?: InitRuntime): Promise<InitResult>;
40
+ export declare function defaultTemplatesRoot(): string;
41
+ export declare function isInitProvider(value: string, templatesRoot?: string): value is InitProvider;