@arnilo/prism 0.0.3 → 0.0.5

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 (131) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +62 -26
  3. package/dist/agent-loops.d.ts +8 -1
  4. package/dist/agent-loops.js +57 -11
  5. package/dist/agents.js +212 -32
  6. package/dist/checkpoints.d.ts +11 -0
  7. package/dist/checkpoints.js +144 -0
  8. package/dist/cli-init.d.ts +41 -0
  9. package/dist/cli-init.js +390 -0
  10. package/dist/cli-runner.d.ts +7 -1
  11. package/dist/cli-runner.js +13 -1
  12. package/dist/compaction.js +9 -1
  13. package/dist/content.d.ts +121 -0
  14. package/dist/content.js +538 -0
  15. package/dist/contracts.d.ts +236 -11
  16. package/dist/contracts.js +8 -0
  17. package/dist/event-multiplexer.d.ts +23 -0
  18. package/dist/event-multiplexer.js +136 -0
  19. package/dist/execution-policy.d.ts +28 -0
  20. package/dist/execution-policy.js +24 -0
  21. package/dist/feedback.d.ts +48 -0
  22. package/dist/feedback.js +230 -0
  23. package/dist/index.d.ts +20 -6
  24. package/dist/index.js +13 -5
  25. package/dist/input.js +11 -1
  26. package/dist/leases.d.ts +8 -0
  27. package/dist/leases.js +111 -0
  28. package/dist/node/agent-definitions.js +3 -5
  29. package/dist/node/config.d.ts +1 -0
  30. package/dist/node/config.js +5 -3
  31. package/dist/node/contribution-discovery.js +5 -8
  32. package/dist/node/session-store-jsonl.js +8 -5
  33. package/dist/node/settings.js +2 -2
  34. package/dist/node/trust.js +2 -4
  35. package/dist/observability.d.ts +3 -0
  36. package/dist/observability.js +18 -0
  37. package/dist/providers/media.d.ts +44 -0
  38. package/dist/providers/media.js +126 -0
  39. package/dist/providers/openai-compatible.js +18 -119
  40. package/dist/providers/openai-primitives.d.ts +9 -0
  41. package/dist/providers/openai-primitives.js +129 -0
  42. package/dist/providers/transport.d.ts +40 -0
  43. package/dist/providers/transport.js +221 -0
  44. package/dist/redaction.js +40 -13
  45. package/dist/resources.d.ts +5 -0
  46. package/dist/resources.js +4 -0
  47. package/dist/structured-output.d.ts +11 -0
  48. package/dist/structured-output.js +59 -0
  49. package/dist/testing/feedback.d.ts +6 -0
  50. package/dist/testing/feedback.js +37 -0
  51. package/dist/testing/persistence-schema.d.ts +102 -0
  52. package/dist/testing/persistence-schema.js +487 -0
  53. package/dist/testing/provider-conformance.js +10 -1
  54. package/dist/testing/run-ledger-conformance.d.ts +33 -0
  55. package/dist/testing/run-ledger-conformance.js +178 -0
  56. package/dist/testing/session-store-conformance.d.ts +16 -0
  57. package/dist/testing/session-store-conformance.js +73 -0
  58. package/dist/tools.d.ts +17 -0
  59. package/dist/tools.js +29 -2
  60. package/docs/a2a.md +73 -0
  61. package/docs/agent-events.md +17 -10
  62. package/docs/agent-loops.md +11 -5
  63. package/docs/agent-session-runtime.md +15 -16
  64. package/docs/cli-rpc.md +36 -5
  65. package/docs/coding-agent-tools.md +43 -9
  66. package/docs/coding-security.md +88 -0
  67. package/docs/compaction-observational-memory.md +2 -0
  68. package/docs/context-and-skills.md +1 -0
  69. package/docs/credential-storage.md +177 -0
  70. package/docs/credentials-and-redaction.md +4 -3
  71. package/docs/database-persistence.md +52 -7
  72. package/docs/evaluations.md +122 -0
  73. package/docs/extensions.md +2 -2
  74. package/docs/host-security.md +33 -2
  75. package/docs/index.md +46 -18
  76. package/docs/input-and-prompt-assembly.md +6 -5
  77. package/docs/mcp-tools.md +184 -0
  78. package/docs/middleware-hooks.md +2 -0
  79. package/docs/migration.md +51 -28
  80. package/docs/model-registry.md +5 -3
  81. package/docs/multimodal-content.md +156 -0
  82. package/docs/observability.md +171 -0
  83. package/docs/performance.md +249 -1
  84. package/docs/persistence-credentials-multimodality-primitives.md +303 -0
  85. package/docs/postgres-persistence.md +143 -0
  86. package/docs/provider-conformance.md +18 -0
  87. package/docs/provider-layer.md +1 -1
  88. package/docs/provider-packages.md +2 -0
  89. package/docs/provider-primitives.md +281 -0
  90. package/docs/providers/ai-sdk.md +113 -0
  91. package/docs/providers/kimi.md +1 -0
  92. package/docs/providers/neuralwatt.md +1 -0
  93. package/docs/providers/openai-compatible.md +2 -1
  94. package/docs/providers/openai.md +8 -1
  95. package/docs/providers/opencode-go.md +1 -0
  96. package/docs/providers/openrouter.md +1 -0
  97. package/docs/providers/zai.md +1 -0
  98. package/docs/public-contracts.md +13 -5
  99. package/docs/rag.md +113 -0
  100. package/docs/release-and-install.md +237 -30
  101. package/docs/resource-loading.md +14 -4
  102. package/docs/review-coverage-2026-07-14.md +260 -0
  103. package/docs/review-coverage-2026-07-15.md +193 -0
  104. package/docs/run-ledger-conformance.md +96 -0
  105. package/docs/runs-and-usage.md +43 -4
  106. package/docs/server.md +139 -0
  107. package/docs/session-store-conformance.md +16 -0
  108. package/docs/session-stores-and-branching.md +1 -0
  109. package/docs/settings-auth-trust-security.md +6 -5
  110. package/docs/sqlite-persistence.md +123 -0
  111. package/docs/structured-output.md +9 -0
  112. package/docs/supervisors.md +71 -0
  113. package/docs/tool-conformance.md +1 -0
  114. package/docs/tool-execution-primitives.md +374 -0
  115. package/docs/tools.md +39 -1
  116. package/docs/workflow-orchestration-primitives.md +581 -0
  117. package/docs/workflow-tui-primitives.md +5 -0
  118. package/docs/workflows.md +293 -0
  119. package/docs/working-and-semantic-memory.md +169 -0
  120. package/package.json +43 -5
  121. package/templates/init/README.md.tmpl +28 -0
  122. package/templates/init/env.example.tmpl +1 -0
  123. package/templates/init/gitignore.tmpl +11 -0
  124. package/templates/init/optional/evals-example.ts.tmpl +17 -0
  125. package/templates/init/optional/workflows-example.ts.tmpl +27 -0
  126. package/templates/init/package.json.tmpl +22 -0
  127. package/templates/init/providers.json +76 -0
  128. package/templates/init/src/agent.ts.tmpl +10 -0
  129. package/templates/init/src/index.ts.tmpl +12 -0
  130. package/templates/init/src/tests/agent.test.ts.tmpl +24 -0
  131. package/templates/init/tsconfig.json.tmpl +15 -0
package/dist/agents.js CHANGED
@@ -1,8 +1,11 @@
1
- import { resolveLoop } from "./agent-loops.js";
1
+ import { AgentRunError } from "./contracts.js";
2
+ import { resolveLoop, resolveToolConcurrency } from "./agent-loops.js";
3
+ import { createProviderTurnMetadata, readProviderHttpStatus } from "./observability.js";
2
4
  import { createDefaultCompactionStrategy, isCompactionEntryData } from "./compaction.js";
3
5
  import { assembleProviderInput } from "./input.js";
4
6
  import { providerToolCallDeltaContent, reconstructToolCallDeltas } from "./provider-events.js";
5
- import { createProviderRequestPolicyChain, mergeProviderRequestOptions, normalizeProviderRequestPolicyResult } from "./provider-request-policy.js";
7
+ import { createProviderRequestPolicyChain, normalizeProviderRequestPolicyResult } from "./provider-request-policy.js";
8
+ import { assertStructuredOutputRequestSupported, resolveRunProviderOptions } from "./structured-output.js";
6
9
  import { errorToErrorInfo, redactAgentEvent, redactProviderRequest, redactRunLedgerRecord, redactSecrets, redactSessionEntry } from "./redaction.js";
7
10
  import { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.js";
8
11
  import { createDefaultRetryPolicy, waitForRetry } from "./retry.js";
@@ -34,7 +37,9 @@ class RuntimeAgentSession {
34
37
  activeLedger;
35
38
  activeOwnership;
36
39
  activeIdempotencyKey;
37
- ledgerPromises = [];
40
+ activeLoopTurn = 1;
41
+ ledgerChain = Promise.resolve();
42
+ ledgerFailure;
38
43
  constructor(config) {
39
44
  this.id = config.id ?? randomId("session");
40
45
  this.agent = config.agent;
@@ -67,6 +72,8 @@ class RuntimeAgentSession {
67
72
  const model = options.model ?? this.agent.config.model;
68
73
  const startedAt = new Date().toISOString();
69
74
  let runError;
75
+ const runUsage = createUsageAccumulator();
76
+ let usage;
70
77
  try {
71
78
  this.resolveRunProvider(options);
72
79
  throwIfAborted(controller.signal);
@@ -101,12 +108,32 @@ class RuntimeAgentSession {
101
108
  // ponytail: skill context after host context; no per-skill token budget yet.
102
109
  ...activeSkills.flatMap((skill) => skill.context ?? []),
103
110
  ];
104
- const providerOptions = mergeProviderRequestOptions(this.agent.config.providerOptions, options.providerOptions);
111
+ const providerOptions = resolveRunProviderOptions(options, this.agent.config);
112
+ assertStructuredOutputRequestSupported(options.model ?? this.agent.config.model, providerOptions);
105
113
  const validate = options.validate ?? this.agent.config.validator;
106
114
  // ponytail: RunOptions.instructionInjectors overrides AgentConfig.instructionInjectors (mirrors validate/loop).
107
115
  const instructionInjectors = options.instructionInjectors ?? this.agent.config.instructionInjectors ?? [];
108
116
  const inputLayout = options.inputLayout ?? this.agent.config.inputLayout;
109
117
  const loop = resolveLoop(options, this.agent.config);
118
+ const toolConcurrency = resolveToolConcurrency(options, this.agent.config);
119
+ this.activeLoopTurn = 1;
120
+ const recordProviderUsage = async (turnUsage, turn, attempt) => {
121
+ runUsage.add(turnUsage);
122
+ if (!this.activeLedger)
123
+ return;
124
+ const usageRecord = {
125
+ id: randomId("usage"),
126
+ sessionId: this.id,
127
+ runId,
128
+ scope: "provider_turn",
129
+ turn,
130
+ attempt,
131
+ usage: turnUsage,
132
+ recordedAt: new Date().toISOString(),
133
+ ...this.activeOwnership,
134
+ };
135
+ await this.activeLedger.appendUsage(redactRunLedgerRecord(usageRecord, this.activeRedactor));
136
+ };
110
137
  // ponytail: LoopContext binds existing private helpers; loop orchestrates only.
111
138
  const ctx = {
112
139
  sessionId: this.id,
@@ -117,6 +144,7 @@ class RuntimeAgentSession {
117
144
  input,
118
145
  inputMessages,
119
146
  maxToolRounds,
147
+ toolConcurrency,
120
148
  assemble: async (nextInput, toolResults, turn) => assembleProviderInput({
121
149
  model: options.model ?? this.agent.config.model,
122
150
  input: nextInput,
@@ -144,8 +172,9 @@ class RuntimeAgentSession {
144
172
  generate: async (request) => {
145
173
  const policyResult = await this.applyProviderRequestPolicies(request, runId, options, metadata, controller.signal);
146
174
  const middlewareRequest = await this.agent.config.middleware?.run("provider_request", policyResult.request) ?? policyResult.request;
147
- return this.generateWithRetry(this.redactProviderRequest(middlewareRequest), runId, options, controller.signal, policyResult.secrets);
175
+ return this.generateWithRetry(this.redactProviderRequest(middlewareRequest), runId, options, controller.signal, policyResult.secrets, this.activeLoopTurn, recordProviderUsage);
148
176
  },
177
+ isToolCallExclusive: (call) => registry.get(call.name)?.exclusive === true,
149
178
  dispatchToolCall: (call) => dispatchToolCall({
150
179
  call,
151
180
  registry,
@@ -160,14 +189,20 @@ class RuntimeAgentSession {
160
189
  validate,
161
190
  }),
162
191
  appendMessage: (message) => this.appendMessage(message, runId),
163
- emit: (event) => this.emit(event),
192
+ emit: (event) => {
193
+ if (event.type === "turn_started")
194
+ this.activeLoopTurn = event.turn;
195
+ this.emit(event);
196
+ },
164
197
  };
165
- const usage = await loop.run(ctx);
198
+ const loopUsage = await loop.run(ctx);
199
+ usage = runUsage.value() ?? loopUsage;
166
200
  if (usage && this.activeLedger) {
167
201
  const usageRecord = {
168
202
  id: randomId("usage"),
169
203
  sessionId: this.id,
170
204
  runId,
205
+ scope: "run_total",
171
206
  usage,
172
207
  recordedAt: new Date().toISOString(),
173
208
  ...this.activeOwnership,
@@ -176,11 +211,23 @@ class RuntimeAgentSession {
176
211
  }
177
212
  await this.drainLedger();
178
213
  this.emit({ type: "agent_finished", sessionId: this.id, runId, usage });
214
+ return this.buildRunResult({
215
+ runId,
216
+ status: "succeeded",
217
+ usage,
218
+ });
179
219
  }
180
220
  catch (error) {
181
221
  runError = errorToErrorInfo(error);
182
222
  this.emit({ type: "error", sessionId: this.id, runId, error: runError });
183
- throw error;
223
+ const result = this.buildRunResult({
224
+ runId,
225
+ status: controller.signal.aborted ? "aborted" : "failed",
226
+ usage: runUsage.value() ?? usage,
227
+ error: runError,
228
+ abortReason: controller.signal.aborted ? String(controller.signal.reason) : undefined,
229
+ });
230
+ throw new AgentRunError(result, { cause: error });
184
231
  }
185
232
  finally {
186
233
  if (this.activeRun === controller)
@@ -220,6 +267,48 @@ class RuntimeAgentSession {
220
267
  prompt(input, options) {
221
268
  return this.run(input, options);
222
269
  }
270
+ async *stream(input, options = {}) {
271
+ const { maxQueuedEvents, overflow, ...runOptions } = options;
272
+ const subscription = this.subscribe({ maxQueuedEvents, overflow });
273
+ let runOwnedId;
274
+ let settled = false;
275
+ const runPromise = this.run(input, runOptions).finally(() => {
276
+ settled = true;
277
+ });
278
+ try {
279
+ for await (const event of subscription) {
280
+ if ("runId" in event && typeof event.runId === "string") {
281
+ if (runOwnedId === undefined && event.type === "agent_started")
282
+ runOwnedId = event.runId;
283
+ if (runOwnedId !== undefined && event.runId !== runOwnedId)
284
+ continue;
285
+ }
286
+ yield event;
287
+ }
288
+ await runPromise;
289
+ }
290
+ finally {
291
+ if (!settled) {
292
+ this.abort(new Error("stream consumer closed"));
293
+ await runPromise.catch(() => undefined);
294
+ }
295
+ }
296
+ }
297
+ buildRunResult(input) {
298
+ const final = finalAssistantMessage(this.history);
299
+ return {
300
+ sessionId: this.id,
301
+ runId: input.runId,
302
+ status: input.status,
303
+ leafId: this.currentLeafId,
304
+ text: final.text,
305
+ content: final.content,
306
+ message: final.message,
307
+ usage: input.usage,
308
+ error: input.error,
309
+ abortReason: input.abortReason,
310
+ };
311
+ }
223
312
  async compact(options = {}) {
224
313
  if (this.activeRun)
225
314
  throw new Error("Agent session already has an active run");
@@ -301,7 +390,17 @@ class RuntimeAgentSession {
301
390
  redacted: Boolean(this.activeRedactor),
302
391
  ...this.activeOwnership,
303
392
  };
304
- this.ledgerPromises.push(this.activeLedger.appendEvent(redactRunLedgerRecord(record, this.activeRedactor)));
393
+ const ledger = this.activeLedger;
394
+ this.ledgerChain = this.ledgerChain.then(async () => {
395
+ if (this.ledgerFailure)
396
+ return;
397
+ try {
398
+ await ledger.appendEvent(redactRunLedgerRecord(record, this.activeRedactor));
399
+ }
400
+ catch (error) {
401
+ this.ledgerFailure = error;
402
+ }
403
+ });
305
404
  }
306
405
  }
307
406
  closeSubscribers() {
@@ -310,19 +409,20 @@ class RuntimeAgentSession {
310
409
  this.subscribers.clear();
311
410
  }
312
411
  async drainLedger() {
313
- if (this.ledgerPromises.length === 0)
314
- return;
315
- const pending = this.ledgerPromises;
316
- this.ledgerPromises = [];
317
- await Promise.all(pending);
318
- }
319
- async generateWithRetry(request, runId, options, signal, requestSecrets = []) {
412
+ await this.ledgerChain;
413
+ const failure = this.ledgerFailure;
414
+ this.ledgerChain = Promise.resolve();
415
+ this.ledgerFailure = undefined;
416
+ if (failure)
417
+ throw failure;
418
+ }
419
+ async generateWithRetry(request, runId, options, signal, requestSecrets = [], turn = 1, recordUsage) {
320
420
  const retry = mergeRetry(this.agent.config.retry, options.retry);
321
421
  const secrets = [...requestSecrets, ...(retry?.secrets ?? [])];
322
422
  const policy = retry?.policy ?? (retry ? createDefaultRetryPolicy(retry) : undefined);
323
423
  for (let attempt = 1;; attempt += 1) {
324
424
  try {
325
- return await this.generateProviderTurn(request, runId, signal, secrets);
425
+ return await this.generateProviderTurn(request, runId, signal, secrets, turn, attempt, recordUsage);
326
426
  }
327
427
  catch (error) {
328
428
  const failure = error instanceof ProviderTurnFailure ? error : undefined;
@@ -341,32 +441,37 @@ class RuntimeAgentSession {
341
441
  }
342
442
  }
343
443
  }
344
- async generateProviderTurn(request, runId, signal, secrets = []) {
444
+ async generateProviderTurn(request, runId, signal, secrets = [], turn = 1, attempt = 1, recordUsage) {
445
+ const startedAt = performance.now();
446
+ const providerId = this.activeProvider?.id ?? request.model.provider;
447
+ const buildMetadata = (extra = {}) => createProviderTurnMetadata(request, providerId, { attempt, ...extra });
448
+ this.emit({
449
+ type: "provider_turn_started",
450
+ sessionId: this.id,
451
+ runId,
452
+ turn,
453
+ metadata: buildMetadata(),
454
+ });
345
455
  const content = [];
346
456
  const calls = [];
347
457
  const toolDeltas = [];
348
458
  let messageId;
349
459
  let started = false;
350
460
  let usage;
461
+ let usageRecorded = false;
462
+ const recordTurnUsage = async () => {
463
+ if (!usage || usageRecorded)
464
+ return;
465
+ usageRecorded = true;
466
+ await recordUsage?.(usage, turn, attempt);
467
+ };
351
468
  try {
352
469
  for await (const event of this.activeProvider.generate(request)) {
353
470
  throwIfAborted(signal);
354
471
  if (event.type === "error")
355
472
  throw new ProviderTurnFailure(event.error, started);
356
- if (event.type === "usage") {
473
+ if (event.type === "usage")
357
474
  usage = event.usage;
358
- if (this.activeLedger) {
359
- const usageRecord = {
360
- id: randomId("usage"),
361
- sessionId: this.id,
362
- runId,
363
- usage: event.usage,
364
- recordedAt: new Date().toISOString(),
365
- ...this.activeOwnership,
366
- };
367
- await this.activeLedger.appendUsage(redactRunLedgerRecord(usageRecord, this.activeRedactor));
368
- }
369
- }
370
475
  if (event.type === "done") {
371
476
  usage = event.usage ?? usage;
372
477
  break;
@@ -399,12 +504,34 @@ class RuntimeAgentSession {
399
504
  calls.push(call);
400
505
  this.emit({ type: "message_delta", sessionId: this.id, runId, content: call });
401
506
  }
507
+ await recordTurnUsage();
508
+ const latencyMs = Math.round(performance.now() - startedAt);
509
+ this.emit({
510
+ type: "provider_turn_finished",
511
+ sessionId: this.id,
512
+ runId,
513
+ turn,
514
+ metadata: buildMetadata({ latencyMs }),
515
+ usage,
516
+ });
402
517
  return { content, calls, messageId, started, usage };
403
518
  }
404
519
  catch (error) {
520
+ const latencyMs = Math.round(performance.now() - startedAt);
521
+ const info = error instanceof ProviderTurnFailure ? redactSecrets(error.info, secrets) : errorToErrorInfo(error, secrets);
522
+ await recordTurnUsage();
523
+ this.emit({
524
+ type: "provider_turn_finished",
525
+ sessionId: this.id,
526
+ runId,
527
+ turn,
528
+ metadata: buildMetadata({ latencyMs, httpStatus: readProviderHttpStatus(info) }),
529
+ usage,
530
+ error: info,
531
+ });
405
532
  if (error instanceof ProviderTurnFailure)
406
533
  throw error;
407
- throw new ProviderTurnFailure(errorToErrorInfo(error, secrets), started);
534
+ throw new ProviderTurnFailure(info, started);
408
535
  }
409
536
  }
410
537
  async applyProviderRequestPolicies(request, runId, options, metadata, signal) {
@@ -555,6 +682,19 @@ function inputToMessages(input) {
555
682
  return [input];
556
683
  return [...input];
557
684
  }
685
+ function finalAssistantMessage(history) {
686
+ for (let index = history.length - 1; index >= 0; index -= 1) {
687
+ const message = history[index];
688
+ if (message.role !== "assistant")
689
+ continue;
690
+ const text = message.content
691
+ .filter((block) => block.type === "text")
692
+ .map((block) => block.text)
693
+ .join("");
694
+ return { message, content: message.content, text };
695
+ }
696
+ return { content: [], text: "" };
697
+ }
558
698
  function activeTools(tools) {
559
699
  if (!tools)
560
700
  return { registry: createToolRegistry(), tools: [] };
@@ -620,6 +760,46 @@ function throwIfAbortedSignal(signal) {
620
760
  if (signal?.aborted)
621
761
  throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
622
762
  }
763
+ function createUsageAccumulator() {
764
+ const sums = new Map();
765
+ let costCurrency;
766
+ let costCompatible = true;
767
+ return {
768
+ add(usage) {
769
+ for (const key of ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"]) {
770
+ const value = usage[key];
771
+ if (value !== undefined)
772
+ sums.set(key, (sums.get(key) ?? 0) + value);
773
+ }
774
+ const total = usage.totalTokens
775
+ ?? (usage.inputTokens !== undefined || usage.outputTokens !== undefined
776
+ ? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0)
777
+ : undefined);
778
+ if (total !== undefined)
779
+ sums.set("totalTokens", (sums.get("totalTokens") ?? 0) + total);
780
+ if (usage.cost !== undefined && costCompatible) {
781
+ if (!sums.has("cost"))
782
+ costCurrency = usage.currency;
783
+ else if (usage.currency !== costCurrency)
784
+ costCompatible = false;
785
+ if (costCompatible)
786
+ sums.set("cost", (sums.get("cost") ?? 0) + usage.cost);
787
+ }
788
+ },
789
+ value() {
790
+ if (sums.size === 0)
791
+ return undefined;
792
+ const usage = {};
793
+ for (const [key, value] of sums) {
794
+ if (key !== "cost" || costCompatible)
795
+ usage[key] = value;
796
+ }
797
+ if (costCompatible && sums.has("cost") && costCurrency !== undefined)
798
+ usage.currency = costCurrency;
799
+ return Object.keys(usage).length > 0 ? usage : undefined;
800
+ },
801
+ };
802
+ }
623
803
  function randomId(prefix) {
624
804
  return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
625
805
  }
@@ -0,0 +1,11 @@
1
+ import type { CheckpointStore } from "./contracts.js";
2
+ export declare const CHECKPOINT_CONFLICT_CODE = "ERR_PRISM_CHECKPOINT_CONFLICT";
3
+ export declare class CheckpointConflictError extends Error {
4
+ readonly code = "ERR_PRISM_CHECKPOINT_CONFLICT";
5
+ constructor(message: string);
6
+ }
7
+ export interface MemoryCheckpointStoreOptions {
8
+ readonly maxPageSize?: number;
9
+ }
10
+ /** In-process reference implementation of the generic checkpoint contract. */
11
+ export declare function createMemoryCheckpointStore(options?: MemoryCheckpointStoreOptions): CheckpointStore;
@@ -0,0 +1,144 @@
1
+ export const CHECKPOINT_CONFLICT_CODE = "ERR_PRISM_CHECKPOINT_CONFLICT";
2
+ export class CheckpointConflictError extends Error {
3
+ code = CHECKPOINT_CONFLICT_CODE;
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "CheckpointConflictError";
7
+ }
8
+ }
9
+ /** In-process reference implementation of the generic checkpoint contract. */
10
+ export function createMemoryCheckpointStore(options = {}) {
11
+ const records = new Map();
12
+ const maxPageSize = Math.max(1, options.maxPageSize ?? 500);
13
+ return {
14
+ async saveCheckpoint(input) {
15
+ throwIfAborted(input.signal);
16
+ assertKey(input);
17
+ if (!Number.isSafeInteger(input.version) || input.version < 1) {
18
+ throw new CheckpointConflictError("Checkpoint version must be a positive safe integer");
19
+ }
20
+ const id = recordKey(input);
21
+ const existing = records.get(id);
22
+ if (existing)
23
+ assertOwnership(input, existing);
24
+ if (input.expectedVersion !== undefined && input.expectedVersion !== (existing?.version ?? 0)) {
25
+ throw new CheckpointConflictError(`Checkpoint compare-and-swap failed (expected ${input.expectedVersion}, current ${existing?.version ?? 0})`);
26
+ }
27
+ if (existing && input.version <= existing.version) {
28
+ throw new CheckpointConflictError(`Stale checkpoint version ${input.version} (current ${existing.version})`);
29
+ }
30
+ if (existing?.fencingToken !== undefined && (input.fencingToken === undefined || input.fencingToken < existing.fencingToken)) {
31
+ throw new CheckpointConflictError(`Stale checkpoint fencing token ${input.fencingToken ?? "missing"} (current ${existing.fencingToken})`);
32
+ }
33
+ const now = new Date().toISOString();
34
+ const value = cloneJson(input.value, "Checkpoint value");
35
+ const metadata = input.metadata === undefined ? undefined : cloneJson(input.metadata, "Checkpoint metadata");
36
+ const record = {
37
+ namespace: input.namespace,
38
+ key: input.key,
39
+ version: input.version,
40
+ ...(input.fencingToken === undefined ? {} : { fencingToken: input.fencingToken }),
41
+ value,
42
+ ...(input.category === undefined ? {} : { category: input.category }),
43
+ ...ownership(input),
44
+ createdAt: existing?.createdAt ?? now,
45
+ updatedAt: now,
46
+ ...(metadata === undefined ? {} : { metadata }),
47
+ };
48
+ records.set(id, record);
49
+ return record;
50
+ },
51
+ async loadCheckpoint(input) {
52
+ throwIfAborted(input.signal);
53
+ assertKey(input);
54
+ const record = records.get(recordKey(input));
55
+ if (!record)
56
+ return null;
57
+ assertOwnership(input, record);
58
+ return record;
59
+ },
60
+ async listCheckpoints(query = {}) {
61
+ throwIfAborted(query.signal);
62
+ const offset = decodeCursor(query.cursor);
63
+ const limit = Math.min(Math.max(1, query.limit ?? 100), maxPageSize);
64
+ const categories = query.category === undefined
65
+ ? undefined
66
+ : new Set(Array.isArray(query.category) ? query.category : [query.category]);
67
+ const items = [...records.values()]
68
+ .filter((record) => query.namespace === undefined || record.namespace === query.namespace)
69
+ .filter((record) => query.keyPrefix === undefined || record.key.startsWith(query.keyPrefix))
70
+ .filter((record) => categories === undefined || (record.category !== undefined && categories.has(record.category)))
71
+ .filter((record) => ownershipFilterMatches(query, record))
72
+ .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt) || a.key.localeCompare(b.key));
73
+ const page = items.slice(offset, offset + limit);
74
+ const next = offset + page.length;
75
+ return {
76
+ items: page,
77
+ ...(next < items.length ? { nextCursor: String(next) } : {}),
78
+ };
79
+ },
80
+ async deleteCheckpoint(input) {
81
+ throwIfAborted(input.signal);
82
+ assertKey(input);
83
+ const id = recordKey(input);
84
+ const record = records.get(id);
85
+ if (!record)
86
+ return false;
87
+ assertOwnership(input, record);
88
+ return records.delete(id);
89
+ },
90
+ };
91
+ }
92
+ function recordKey(input) {
93
+ return `${input.namespace}\0${input.key}`;
94
+ }
95
+ function assertKey(input) {
96
+ if (!input.namespace || !input.key)
97
+ throw new CheckpointConflictError("Checkpoint namespace and key are required");
98
+ }
99
+ function ownership(input) {
100
+ return {
101
+ ...(input.tenantId === undefined ? {} : { tenantId: input.tenantId }),
102
+ ...(input.accountId === undefined ? {} : { accountId: input.accountId }),
103
+ ...(input.userId === undefined ? {} : { userId: input.userId }),
104
+ };
105
+ }
106
+ function ownershipMatches(expected, actual) {
107
+ return expected.tenantId === actual.tenantId
108
+ && expected.accountId === actual.accountId
109
+ && expected.userId === actual.userId;
110
+ }
111
+ function ownershipFilterMatches(expected, actual) {
112
+ return (expected.tenantId === undefined || expected.tenantId === actual.tenantId)
113
+ && (expected.accountId === undefined || expected.accountId === actual.accountId)
114
+ && (expected.userId === undefined || expected.userId === actual.userId);
115
+ }
116
+ function assertOwnership(expected, actual) {
117
+ if (!ownershipMatches(expected, actual)) {
118
+ throw new CheckpointConflictError("Checkpoint ownership mismatch");
119
+ }
120
+ }
121
+ function cloneJson(value, label) {
122
+ try {
123
+ const encoded = JSON.stringify(value);
124
+ if (encoded === undefined)
125
+ throw new Error("not JSON serializable");
126
+ return JSON.parse(encoded);
127
+ }
128
+ catch (error) {
129
+ throw new CheckpointConflictError(`${label} must be JSON serializable: ${error instanceof Error ? error.message : String(error)}`);
130
+ }
131
+ }
132
+ function decodeCursor(cursor) {
133
+ if (cursor === undefined)
134
+ return 0;
135
+ const value = Number(cursor);
136
+ if (!Number.isSafeInteger(value) || value < 0)
137
+ throw new CheckpointConflictError("Invalid checkpoint cursor");
138
+ return value;
139
+ }
140
+ function throwIfAborted(signal) {
141
+ if (signal?.aborted)
142
+ throw signal.reason ?? new DOMException("Aborted", "AbortError");
143
+ }
144
+ //# sourceMappingURL=checkpoints.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;