@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/dist/agents.js CHANGED
@@ -1,5 +1,7 @@
1
- import { AgentRunError } from "./contracts.js";
1
+ import { AgentRunError, AgentRunStateError } from "./contracts.js";
2
2
  import { resolveLoop, resolveToolConcurrency } from "./agent-loops.js";
3
+ import { createId } from "./ids.js";
4
+ import { assertGuardrailsAllowed, GuardrailError, runGuardrails } from "./guardrails.js";
3
5
  import { createProviderTurnMetadata, readProviderHttpStatus } from "./observability.js";
4
6
  import { createDefaultCompactionStrategy, isCompactionEntryData } from "./compaction.js";
5
7
  import { assembleProviderInput } from "./input.js";
@@ -11,6 +13,8 @@ import { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.j
11
13
  import { createDefaultRetryPolicy, waitForRetry } from "./retry.js";
12
14
  import { createMemorySessionStore, createSessionEntry, getSessionBranchEntries, rebuildSessionContext } from "./session-stores.js";
13
15
  import { createToolRegistry, dispatchToolCall } from "./tools.js";
16
+ import { RunLimitError, RunLimitTracker, resolveRunLimits } from "./run-limits.js";
17
+ import { agentFingerprint, initialAgentRunState, loadAgentRunState, publicState, saveAgentRunState, validateRunStateOptions } from "./agent-run-state.js";
14
18
  import { resolveActiveSkills } from "./skills.js";
15
19
  export function createAgent(config) {
16
20
  return {
@@ -23,6 +27,60 @@ export function createAgent(config) {
23
27
  export function createAgentSession(config) {
24
28
  return new RuntimeAgentSession(config);
25
29
  }
30
+ /** Resume a persisted built-in run. A claimed/dispatched tool is never replayed automatically. */
31
+ export async function resumeAgentRun(agent, ref, resume, options) {
32
+ const { record, state } = await loadAgentRunState(options.checkpoints, ref, options.ownership);
33
+ if (state.definitionRevision !== options.definitionRevision || state.agentId !== (agent.config.id ?? agent.config.name) || state.fingerprint !== agentFingerprint(agent, options.definitionRevision)) {
34
+ throw new AgentRunStateError("Agent definition revision or fingerprint mismatch on resume");
35
+ }
36
+ if (record.version !== resume.expectedVersion || state.status !== "suspended") {
37
+ throw new AgentRunStateError("Stale or non-suspended agent run resume");
38
+ }
39
+ if (resume.decision === "deny") {
40
+ const denied = await saveAgentRunState({
41
+ checkpoints: options.checkpoints,
42
+ state: { ...state, status: "denied" },
43
+ expectedVersion: record.version,
44
+ ownership: options.ownership,
45
+ fencingToken: options.fencingToken,
46
+ });
47
+ const session = new RuntimeAgentSession({ agent, id: state.sessionId, leafId: state.leafId });
48
+ await session.recordDurableDenial(state.runId, state.interruption, denied.record.version, options.ownership);
49
+ const runState = publicState(denied.state);
50
+ return { sessionId: state.sessionId, runId: state.runId, status: "denied", leafId: state.leafId, text: "", content: [], runState, interruption: state.interruption };
51
+ }
52
+ if (state.pending?.status === "dispatched")
53
+ throw new AgentRunStateError("Ambiguous dispatched tool requires operator resolution");
54
+ const configured = agent.config.runState;
55
+ if (configured && (configured.checkpoints !== options.checkpoints || configured.definitionRevision !== options.definitionRevision)) {
56
+ throw new AgentRunStateError("Agent durable run-state configuration mismatch on resume");
57
+ }
58
+ const claimed = await saveAgentRunState({
59
+ checkpoints: options.checkpoints,
60
+ state: { ...state, status: "running", interruption: undefined },
61
+ expectedVersion: record.version,
62
+ ownership: options.ownership,
63
+ fencingToken: options.fencingToken,
64
+ });
65
+ const session = new RuntimeAgentSession({ agent, id: state.sessionId, leafId: state.leafId });
66
+ return session.resumeDurable(claimed.state, configured ?? {
67
+ checkpoints: options.checkpoints,
68
+ definitionRevision: options.definitionRevision,
69
+ interruptBeforeTool: state.interruptBeforeTool,
70
+ fencingToken: options.fencingToken,
71
+ }, options.ownership);
72
+ }
73
+ class AgentRunSuspended extends Error {
74
+ state;
75
+ interruption;
76
+ code = "ERR_PRISM_AGENT_RUN_SUSPENDED";
77
+ constructor(state, interruption) {
78
+ super("Agent run suspended");
79
+ this.state = state;
80
+ this.interruption = interruption;
81
+ this.name = "AgentRunSuspended";
82
+ }
83
+ }
26
84
  class RuntimeAgentSession {
27
85
  id;
28
86
  agent;
@@ -37,6 +95,11 @@ class RuntimeAgentSession {
37
95
  activeLedger;
38
96
  activeOwnership;
39
97
  activeIdempotencyKey;
98
+ activeGuardrails;
99
+ activeMetadata;
100
+ activeLimits;
101
+ activeLimitOutputBuffer = false;
102
+ activeDurable;
40
103
  activeLoopTurn = 1;
41
104
  ledgerChain = Promise.resolve();
42
105
  ledgerFailure;
@@ -56,7 +119,41 @@ class RuntimeAgentSession {
56
119
  return subscriber;
57
120
  }
58
121
  async run(input, options = {}) {
59
- const runId = randomId("run");
122
+ return this.runInternal(input, options, randomId("run"));
123
+ }
124
+ async resumeDurable(state, runState, ownership) {
125
+ return this.runInternal(state.input ?? [], { runState, ownership }, state.runId, { options: runState, state, version: state.version });
126
+ }
127
+ async recordDurableDenial(runId, interruption, version, ownership) {
128
+ this.activeLedger = this.agent.config.runLedger;
129
+ this.activeOwnership = ownership ?? this.agent.config.ownership;
130
+ this.activeRedactor = this.agent.config.redactor;
131
+ this.emit({ type: "agent_denied", sessionId: this.id, runId, interruption, version });
132
+ await this.drainLedger();
133
+ this.activeLedger = undefined;
134
+ this.activeOwnership = undefined;
135
+ this.activeRedactor = undefined;
136
+ }
137
+ async runInternal(input, options, runId, resumed) {
138
+ if (this.agent.config.secure && (options.redactor !== undefined || options.ownership !== undefined || options.validate !== undefined || options.runState !== undefined)) {
139
+ throw new AgentRunStateError("Secure agent defaults cannot be replaced per run");
140
+ }
141
+ const requestedLimits = options.maxToolRounds === undefined
142
+ ? options.limits
143
+ : { ...options.limits, maxToolRounds: Math.min(options.maxToolRounds, options.limits?.maxToolRounds ?? options.maxToolRounds) };
144
+ const resolvedLimits = resolveRunLimits(this.agent.config.limits, requestedLimits);
145
+ const durableOptions = options.runState ?? this.agent.config.runState;
146
+ if (this.agent.config.runState && options.runState && this.agent.config.runState !== options.runState) {
147
+ throw new AgentRunStateError("RunOptions cannot replace agent durable run-state configuration");
148
+ }
149
+ if (durableOptions) {
150
+ validateRunStateOptions(durableOptions);
151
+ if (options.model || options.guardrails || options.loop)
152
+ throw new AgentRunStateError("Durable runs require model, guardrails, and loop on AgentConfig for fingerprinting");
153
+ const configuredLoop = this.agent.config.loop;
154
+ if (configuredLoop && !isBuiltInLoop(configuredLoop))
155
+ throw new AgentRunStateError("Custom AgentLoopStrategy is not durable");
156
+ }
60
157
  if (this.activeRun) {
61
158
  const error = new Error("Agent session already has an active run");
62
159
  this.emit({ type: "error", sessionId: this.id, runId, error: errorToErrorInfo(error) });
@@ -69,15 +166,32 @@ class RuntimeAgentSession {
69
166
  this.activeLedger = options.runLedger ?? this.agent.config.runLedger;
70
167
  this.activeOwnership = options.ownership ?? this.agent.config.ownership;
71
168
  this.activeIdempotencyKey = options.idempotencyKey ?? this.agent.config.idempotencyKey;
169
+ this.activeGuardrails = mergeGuardrails(this.agent.config.guardrails, options.guardrails);
170
+ this.activeDurable = resumed ?? (durableOptions ? { options: durableOptions, version: 0 } : undefined);
72
171
  const model = options.model ?? this.agent.config.model;
73
172
  const startedAt = new Date().toISOString();
74
173
  let runError;
174
+ let runStatus = "succeeded";
75
175
  const runUsage = createUsageAccumulator();
76
176
  let usage;
177
+ const metadata = { ...this.agent.config.metadata, ...this.metadata, ...options.metadata };
178
+ this.activeMetadata = metadata;
179
+ const limits = new RunLimitTracker(resolvedLimits, {
180
+ onExceeded: (breach) => {
181
+ this.emit({ type: "run_limit_exceeded", sessionId: this.id, runId, breach });
182
+ controller.abort(new RunLimitError(breach));
183
+ },
184
+ snapshot: resumed?.state?.counters,
185
+ deadlineAt: resumed?.state?.deadlineAt,
186
+ });
187
+ this.activeLimits = limits;
188
+ this.activeLimitOutputBuffer = [this.agent.config.limits, requestedLimits].some((value) => value?.maxOutputTokens !== undefined || value?.maxTotalTokens !== undefined || value?.maxCost !== undefined);
77
189
  try {
78
190
  this.resolveRunProvider(options);
79
191
  throwIfAborted(controller.signal);
80
192
  this.emit({ type: "agent_started", sessionId: this.id, runId });
193
+ if (resumed)
194
+ this.emit({ type: "agent_resumed", sessionId: this.id, runId, version: resumed.version });
81
195
  const startRecord = {
82
196
  id: runId,
83
197
  sessionId: this.id,
@@ -97,11 +211,27 @@ class RuntimeAgentSession {
97
211
  await this.appendEntry(createSessionEntry({ sessionId: this.id, parentId: this.currentLeafId, runId, kind: "model_change", previousModel: this.agent.config.model, model: options.model }));
98
212
  }
99
213
  const inputMessages = inputToMessages(input).map((message) => this.redact(message));
214
+ const inputGuardrails = await runGuardrails({
215
+ stage: "input",
216
+ guardrails: this.activeGuardrails,
217
+ value: inputMessages,
218
+ context: { sessionId: this.id, runId, metadata, signal: controller.signal },
219
+ redactor: this.activeRedactor,
220
+ emit: (event) => this.emit(event),
221
+ });
222
+ if (inputGuardrails.terminal?.action === "interrupt" && this.activeDurable) {
223
+ if (!resumed) {
224
+ const interruption = { kind: "input_guardrail", reason: inputGuardrails.terminal.reason ?? "Input requires approval" };
225
+ throw new AgentRunSuspended(await this.suspendDurable({ runId, model, limits, interruption, messages: inputMessages }), interruption);
226
+ }
227
+ }
228
+ else {
229
+ assertGuardrailsAllowed(inputGuardrails);
230
+ }
100
231
  for (const message of inputMessages)
101
232
  await this.appendMessage(message, runId);
102
233
  await this.autoCompact(runId, options, controller.signal, inputMessages);
103
- const metadata = { ...this.agent.config.metadata, ...this.metadata, ...options.metadata };
104
- const maxToolRounds = options.maxToolRounds ?? 1;
234
+ const maxToolRounds = resolvedLimits.maxToolRounds;
105
235
  const systemInstructions = composeSystemPrompt(mergeSystemPromptConfig(this.agent.config.systemPrompt, options.systemPrompt), { base: this.agent.config.instructions });
106
236
  const contextProviders = [
107
237
  ...(this.agent.config.context ?? []),
@@ -118,6 +248,9 @@ class RuntimeAgentSession {
118
248
  const toolConcurrency = resolveToolConcurrency(options, this.agent.config);
119
249
  this.activeLoopTurn = 1;
120
250
  const recordProviderUsage = async (turnUsage, turn, attempt) => {
251
+ limits.recordUsage(turnUsage);
252
+ if (!turnUsage)
253
+ return;
121
254
  runUsage.add(turnUsage);
122
255
  if (!this.activeLedger)
123
256
  return;
@@ -135,6 +268,7 @@ class RuntimeAgentSession {
135
268
  await this.activeLedger.appendUsage(redactRunLedgerRecord(usageRecord, this.activeRedactor));
136
269
  };
137
270
  // ponytail: LoopContext binds existing private helpers; loop orchestrates only.
271
+ let assembledTurn = false;
138
272
  const ctx = {
139
273
  sessionId: this.id,
140
274
  runId,
@@ -145,31 +279,45 @@ class RuntimeAgentSession {
145
279
  inputMessages,
146
280
  maxToolRounds,
147
281
  toolConcurrency,
148
- assemble: async (nextInput, toolResults, turn) => assembleProviderInput({
149
- model: options.model ?? this.agent.config.model,
150
- input: nextInput,
151
- history: this.history,
152
- summaries: (await this.snapshot()).summaries,
153
- toolResults: toolResults ?? [],
154
- turn,
155
- instructionInjectors,
156
- inputLayout,
157
- systemInstructions,
158
- inputBuilder: this.agent.config.inputBuilder,
159
- promptBuilder: this.agent.config.promptBuilder,
160
- contextProviders,
161
- skills: activeSkills,
162
- tools,
163
- resourceLoader: this.agent.config.resourceLoader,
164
- providerOptions,
165
- redactor: this.activeRedactor,
166
- middleware: this.agent.config.middleware,
167
- sessionId: this.id,
168
- runId,
169
- metadata,
170
- signal: controller.signal,
171
- }),
282
+ assemble: async (nextInput, toolResults, turn) => {
283
+ limits.charge("maxTurns");
284
+ const request = await assembleProviderInput({
285
+ model: options.model ?? this.agent.config.model,
286
+ input: nextInput,
287
+ history: this.history,
288
+ summaries: (await this.snapshot()).summaries,
289
+ toolResults: toolResults ?? [],
290
+ turn,
291
+ instructionInjectors,
292
+ inputLayout,
293
+ systemInstructions,
294
+ inputBuilder: this.agent.config.inputBuilder,
295
+ promptBuilder: this.agent.config.promptBuilder,
296
+ contextProviders,
297
+ skills: activeSkills,
298
+ tools,
299
+ resourceLoader: this.agent.config.resourceLoader,
300
+ permission: this.agent.config.permission,
301
+ trust: this.agent.config.trust,
302
+ providerOptions,
303
+ redactor: this.activeRedactor,
304
+ middleware: this.agent.config.middleware,
305
+ sessionId: this.id,
306
+ runId,
307
+ metadata,
308
+ signal: controller.signal,
309
+ });
310
+ assembledTurn = true;
311
+ return request;
312
+ },
313
+ chargeToolRound: (calls) => {
314
+ if (calls.length > 0)
315
+ limits.charge("maxToolRounds");
316
+ },
172
317
  generate: async (request) => {
318
+ if (!assembledTurn)
319
+ limits.charge("maxTurns");
320
+ assembledTurn = false;
173
321
  const policyResult = await this.applyProviderRequestPolicies(request, runId, options, metadata, controller.signal);
174
322
  const middlewareRequest = await this.agent.config.middleware?.run("provider_request", policyResult.request) ?? policyResult.request;
175
323
  return this.generateWithRetry(this.redactProviderRequest(middlewareRequest), runId, options, controller.signal, policyResult.secrets, this.activeLoopTurn, recordProviderUsage);
@@ -182,9 +330,32 @@ class RuntimeAgentSession {
182
330
  middleware: this.agent.config.middleware,
183
331
  emit: (event) => this.emit(event),
184
332
  permission: this.agent.config.permission,
333
+ trust: this.agent.config.trust,
185
334
  redactor: this.activeRedactor,
186
335
  ledger: this.activeLedger,
187
336
  ownership: this.activeOwnership,
337
+ guardrails: this.activeGuardrails,
338
+ limitTracker: limits,
339
+ beforeExecute: async (mediatedCall) => {
340
+ const durable = this.activeDurable;
341
+ if (!durable)
342
+ return;
343
+ const pending = durable.state?.pending;
344
+ if (pending?.call.id === mediatedCall.id && pending.status === "ready") {
345
+ await this.persistDurable({ ...durable.state, status: "running", pending: { ...pending, status: "dispatched" }, interruption: undefined });
346
+ return;
347
+ }
348
+ if (!durable.options.interruptBeforeTool)
349
+ return;
350
+ const interruption = { kind: "tool_approval", reason: "Tool side effect requires approval", toolCallId: mediatedCall.id, toolName: mediatedCall.name };
351
+ throw new AgentRunSuspended(await this.suspendDurable({
352
+ runId,
353
+ model,
354
+ limits,
355
+ interruption,
356
+ pending: { call: mediatedCall, status: "ready" },
357
+ }), interruption);
358
+ },
188
359
  // ponytail: RunOptions wins; array-compose deferred (roadmap: compose-later).
189
360
  validate,
190
361
  }),
@@ -195,6 +366,14 @@ class RuntimeAgentSession {
195
366
  this.emit(event);
196
367
  },
197
368
  };
369
+ if (resumed?.state?.pending?.status === "ready") {
370
+ const result = await ctx.dispatchToolCall(resumed.state.pending.call);
371
+ await ctx.appendMessage({
372
+ role: "tool",
373
+ content: [{ type: "tool_result", toolCallId: result.toolCallId, name: result.name, result: result.value, error: result.error }, ...(result.content ?? [])],
374
+ metadata: result.metadata,
375
+ });
376
+ }
198
377
  const loopUsage = await loop.run(ctx);
199
378
  usage = runUsage.value() ?? loopUsage;
200
379
  if (usage && this.activeLedger) {
@@ -210,22 +389,34 @@ class RuntimeAgentSession {
210
389
  await this.activeLedger.appendUsage(redactRunLedgerRecord(usageRecord, this.activeRedactor));
211
390
  }
212
391
  await this.drainLedger();
392
+ const runState = this.activeDurable?.state
393
+ ? await this.persistDurable({ ...this.activeDurable.state, status: "succeeded", pending: undefined, interruption: undefined })
394
+ : undefined;
213
395
  this.emit({ type: "agent_finished", sessionId: this.id, runId, usage });
214
- return this.buildRunResult({
215
- runId,
216
- status: "succeeded",
217
- usage,
218
- });
396
+ return this.buildRunResult({ runId, status: "succeeded", usage, runState });
219
397
  }
220
398
  catch (error) {
399
+ if (error instanceof AgentRunSuspended) {
400
+ runStatus = "suspended";
401
+ const version = error.state.version;
402
+ this.emit({ type: "agent_suspended", sessionId: this.id, runId, interruption: error.interruption, version });
403
+ return this.buildRunResult({ runId, status: "suspended", runState: error.state, interruption: error.interruption });
404
+ }
221
405
  runError = errorToErrorInfo(error);
222
406
  this.emit({ type: "error", sessionId: this.id, runId, error: runError });
407
+ const breach = error instanceof RunLimitError ? error.breach : limits.breach;
408
+ runStatus = breach ? "failed" : controller.signal.aborted ? "aborted" : "failed";
409
+ const runState = this.activeDurable?.state
410
+ ? await this.persistDurable({ ...this.activeDurable.state, status: runStatus, interruption: undefined })
411
+ : undefined;
223
412
  const result = this.buildRunResult({
224
413
  runId,
225
- status: controller.signal.aborted ? "aborted" : "failed",
414
+ status: runStatus,
226
415
  usage: runUsage.value() ?? usage,
416
+ limit: breach,
227
417
  error: runError,
228
- abortReason: controller.signal.aborted ? String(controller.signal.reason) : undefined,
418
+ abortReason: !breach && controller.signal.aborted ? String(controller.signal.reason) : undefined,
419
+ runState,
229
420
  });
230
421
  throw new AgentRunError(result, { cause: error });
231
422
  }
@@ -235,7 +426,7 @@ class RuntimeAgentSession {
235
426
  try {
236
427
  await this.drainLedger();
237
428
  if (this.activeLedger) {
238
- const status = controller.signal.aborted ? "aborted" : runError ? "failed" : "succeeded";
429
+ const status = runStatus;
239
430
  const finishRecord = {
240
431
  id: runId,
241
432
  sessionId: this.id,
@@ -257,6 +448,11 @@ class RuntimeAgentSession {
257
448
  this.activeLedger = undefined;
258
449
  this.activeOwnership = undefined;
259
450
  this.activeIdempotencyKey = undefined;
451
+ this.activeGuardrails = undefined;
452
+ this.activeMetadata = undefined;
453
+ this.activeLimits?.dispose();
454
+ this.activeLimits = undefined;
455
+ this.activeLimitOutputBuffer = false;
260
456
  this.activeRedactor = undefined;
261
457
  this.activeProvider = undefined;
262
458
  cleanupSignal();
@@ -305,10 +501,59 @@ class RuntimeAgentSession {
305
501
  content: final.content,
306
502
  message: final.message,
307
503
  usage: input.usage,
504
+ limit: input.limit,
308
505
  error: input.error,
309
506
  abortReason: input.abortReason,
507
+ runState: input.runState,
508
+ interruption: input.interruption,
310
509
  };
311
510
  }
511
+ async suspendDurable(input) {
512
+ const durable = this.activeDurable;
513
+ if (!durable)
514
+ throw new AgentRunStateError("Durable interruption is not configured");
515
+ const state = durable.state ?? initialAgentRunState({
516
+ agent: this.agent,
517
+ options: durable.options,
518
+ runId: input.runId,
519
+ sessionId: this.id,
520
+ leafId: this.currentLeafId,
521
+ model: input.model,
522
+ counters: input.limits.snapshot(),
523
+ deadlineAt: input.limits.deadlineAt,
524
+ status: "suspended",
525
+ interruption: input.interruption,
526
+ messages: input.messages,
527
+ pending: input.pending,
528
+ interruptBeforeTool: durable.options.interruptBeforeTool,
529
+ });
530
+ return this.persistDurable({
531
+ ...state,
532
+ leafId: this.currentLeafId,
533
+ status: "suspended",
534
+ interruption: input.interruption,
535
+ ...(input.messages ? { input: input.messages } : {}),
536
+ ...(input.pending ? { pending: input.pending } : {}),
537
+ counters: input.limits.snapshot(),
538
+ });
539
+ }
540
+ async persistDurable(state) {
541
+ const durable = this.activeDurable;
542
+ if (!durable)
543
+ throw new AgentRunStateError("Durable run state is not configured");
544
+ const saved = await saveAgentRunState({
545
+ checkpoints: durable.options.checkpoints,
546
+ state,
547
+ expectedVersion: durable.version,
548
+ ownership: this.activeOwnership,
549
+ fencingToken: durable.options.fencingToken,
550
+ redactor: this.activeRedactor,
551
+ maxStateBytes: durable.options.maxStateBytes,
552
+ });
553
+ durable.state = saved.state;
554
+ durable.version = saved.record.version;
555
+ return publicState(saved.state);
556
+ }
312
557
  async compact(options = {}) {
313
558
  if (this.activeRun)
314
559
  throw new Error("Agent session already has an active run");
@@ -425,6 +670,8 @@ class RuntimeAgentSession {
425
670
  return await this.generateProviderTurn(request, runId, signal, secrets, turn, attempt, recordUsage);
426
671
  }
427
672
  catch (error) {
673
+ if (error instanceof GuardrailError)
674
+ throw error;
428
675
  const failure = error instanceof ProviderTurnFailure ? error : undefined;
429
676
  const info = failure ? redactSecrets(failure.info, secrets) : errorToErrorInfo(error, secrets);
430
677
  if (!policy || failure?.observable)
@@ -442,6 +689,8 @@ class RuntimeAgentSession {
442
689
  }
443
690
  }
444
691
  async generateProviderTurn(request, runId, signal, secrets = [], turn = 1, attempt = 1, recordUsage) {
692
+ this.activeLimits.charge("maxProviderAttempts");
693
+ this.activeLimits.charge("maxRequestBytes", jsonBytes(request));
445
694
  const startedAt = performance.now();
446
695
  const providerId = this.activeProvider?.id ?? request.model.provider;
447
696
  const buildMetadata = (extra = {}) => createProviderTurnMetadata(request, providerId, { attempt, ...extra });
@@ -459,8 +708,16 @@ class RuntimeAgentSession {
459
708
  let started = false;
460
709
  let usage;
461
710
  let usageRecorded = false;
711
+ const bufferedOutput = [];
712
+ const bufferOutput = Boolean(this.activeGuardrails?.output?.length || this.activeLimitOutputBuffer);
713
+ const emitOutput = (event) => {
714
+ if (bufferOutput)
715
+ bufferedOutput.push(event);
716
+ else
717
+ this.emit(event);
718
+ };
462
719
  const recordTurnUsage = async () => {
463
- if (!usage || usageRecorded)
720
+ if (usageRecorded)
464
721
  return;
465
722
  usageRecorded = true;
466
723
  await recordUsage?.(usage, turn, attempt);
@@ -468,6 +725,7 @@ class RuntimeAgentSession {
468
725
  try {
469
726
  for await (const event of this.activeProvider.generate(request)) {
470
727
  throwIfAborted(signal);
728
+ this.activeLimits.charge("maxResponseBytes", jsonBytes(event));
471
729
  if (event.type === "error")
472
730
  throw new ProviderTurnFailure(event.error, started);
473
731
  if (event.type === "usage")
@@ -479,32 +737,45 @@ class RuntimeAgentSession {
479
737
  if (event.type === "message_start") {
480
738
  started = true;
481
739
  messageId = event.messageId;
482
- this.emit({ type: "message_started", sessionId: this.id, runId, message: { id: messageId, role: "assistant", content: [] } });
740
+ emitOutput({ type: "message_started", sessionId: this.id, runId, message: { id: messageId, role: "assistant", content: [] } });
483
741
  continue;
484
742
  }
485
743
  if (event.type === "content_delta" || event.type === "tool_call" || event.type === "tool_call_delta") {
486
744
  if (!started) {
487
745
  started = true;
488
- this.emit({ type: "message_started", sessionId: this.id, runId, message: { role: "assistant", content: [] } });
746
+ emitOutput({ type: "message_started", sessionId: this.id, runId, message: { role: "assistant", content: [] } });
489
747
  }
490
748
  if (event.type === "tool_call_delta") {
491
749
  toolDeltas.push(event);
492
- this.emit({ type: "message_delta", sessionId: this.id, runId, content: providerToolCallDeltaContent(event) });
750
+ emitOutput({ type: "message_delta", sessionId: this.id, runId, content: providerToolCallDeltaContent(event) });
493
751
  continue;
494
752
  }
495
753
  const block = providerContent(event);
496
754
  content.push(block);
497
755
  if (block.type === "tool_call")
498
756
  calls.push(block);
499
- this.emit({ type: "message_delta", sessionId: this.id, runId, content: block });
757
+ emitOutput({ type: "message_delta", sessionId: this.id, runId, content: block });
500
758
  }
501
759
  }
502
760
  for (const call of reconstructMissingToolCalls(toolDeltas, calls)) {
503
761
  content.push(call);
504
762
  calls.push(call);
505
- this.emit({ type: "message_delta", sessionId: this.id, runId, content: call });
763
+ emitOutput({ type: "message_delta", sessionId: this.id, runId, content: call });
506
764
  }
507
765
  await recordTurnUsage();
766
+ if (this.activeGuardrails?.output?.length) {
767
+ assertGuardrailsAllowed(await runGuardrails({
768
+ stage: "output",
769
+ guardrails: this.activeGuardrails,
770
+ value: { content, calls, messageId, started, usage },
771
+ context: { sessionId: this.id, runId, metadata: this.activeMetadata ?? {}, signal },
772
+ redactor: this.activeRedactor,
773
+ emit: (event) => this.emit(event),
774
+ }));
775
+ }
776
+ if (bufferOutput)
777
+ for (const event of bufferedOutput)
778
+ this.emit(event);
508
779
  const latencyMs = Math.round(performance.now() - startedAt);
509
780
  this.emit({
510
781
  type: "provider_turn_finished",
@@ -529,7 +800,7 @@ class RuntimeAgentSession {
529
800
  usage,
530
801
  error: info,
531
802
  });
532
- if (error instanceof ProviderTurnFailure)
803
+ if (error instanceof GuardrailError || error instanceof ProviderTurnFailure)
533
804
  throw error;
534
805
  throw new ProviderTurnFailure(info, started);
535
806
  }
@@ -734,6 +1005,20 @@ function mergeCompaction(agent, run) {
734
1005
  return { ...(agent || {}), ...run };
735
1006
  return agent || undefined;
736
1007
  }
1008
+ function isBuiltInLoop(loop) {
1009
+ return typeof loop === "object" && loop !== null && "strategy" in loop;
1010
+ }
1011
+ function mergeGuardrails(agent, run) {
1012
+ if (!agent && !run)
1013
+ return undefined;
1014
+ return {
1015
+ input: [...(agent?.input ?? []), ...(run?.input ?? [])],
1016
+ output: [...(agent?.output ?? []), ...(run?.output ?? [])],
1017
+ toolInput: [...(agent?.toolInput ?? []), ...(run?.toolInput ?? [])],
1018
+ toolOutput: [...(agent?.toolOutput ?? []), ...(run?.toolOutput ?? [])],
1019
+ maxConcurrency: run?.maxConcurrency ?? agent?.maxConcurrency,
1020
+ };
1021
+ }
737
1022
  function withoutTrailingInput(messages, input) {
738
1023
  const next = [...messages];
739
1024
  for (let i = input.length - 1; i >= 0; i -= 1) {
@@ -760,6 +1045,14 @@ function throwIfAbortedSignal(signal) {
760
1045
  if (signal?.aborted)
761
1046
  throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
762
1047
  }
1048
+ function jsonBytes(value) {
1049
+ try {
1050
+ return new TextEncoder().encode(JSON.stringify(value)).byteLength;
1051
+ }
1052
+ catch {
1053
+ throw new TypeError("Provider request or event must be JSON-serializable for run limits");
1054
+ }
1055
+ }
763
1056
  function createUsageAccumulator() {
764
1057
  const sums = new Map();
765
1058
  let costCurrency;
@@ -800,7 +1093,5 @@ function createUsageAccumulator() {
800
1093
  },
801
1094
  };
802
1095
  }
803
- function randomId(prefix) {
804
- return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
805
- }
1096
+ const randomId = createId;
806
1097
  //# sourceMappingURL=agents.js.map