stitchkit 0.56.5 → 0.58.0

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 (38) hide show
  1. package/dist/agent-runtime/compaction.d.ts +3 -0
  2. package/dist/agent-runtime/compaction.d.ts.map +1 -1
  3. package/dist/agent-runtime/events.d.ts +693 -0
  4. package/dist/agent-runtime/events.d.ts.map +1 -1
  5. package/dist/agent-runtime/history.d.ts +13 -0
  6. package/dist/agent-runtime/history.d.ts.map +1 -1
  7. package/dist/agent-runtime/managed-tools.d.ts +1 -0
  8. package/dist/agent-runtime/managed-tools.d.ts.map +1 -1
  9. package/dist/agent-runtime/models.d.ts +39 -3
  10. package/dist/agent-runtime/models.d.ts.map +1 -1
  11. package/dist/agent-runtime/observability.d.ts +3 -0
  12. package/dist/agent-runtime/observability.d.ts.map +1 -1
  13. package/dist/agent-runtime/prompt.d.ts +21 -0
  14. package/dist/agent-runtime/prompt.d.ts.map +1 -1
  15. package/dist/agent-runtime/runtime.d.ts +43 -4
  16. package/dist/agent-runtime/runtime.d.ts.map +1 -1
  17. package/dist/agent-runtime/schemas.d.ts +75 -0
  18. package/dist/agent-runtime/schemas.d.ts.map +1 -1
  19. package/dist/agent-runtime/store-driver.d.ts +409 -0
  20. package/dist/agent-runtime/store-driver.d.ts.map +1 -0
  21. package/dist/agent-runtime/store.d.ts +169 -2
  22. package/dist/agent-runtime/store.d.ts.map +1 -1
  23. package/dist/agent-runtime/testing.d.ts +10 -1
  24. package/dist/agent-runtime/testing.d.ts.map +1 -1
  25. package/dist/agent-runtime.d.ts +7 -6
  26. package/dist/agent-runtime.d.ts.map +1 -1
  27. package/dist/agent-runtime.js +1157 -511
  28. package/dist/index-vtjgx3vv.js +161 -0
  29. package/dist/server/error-hook.d.ts +8 -1
  30. package/dist/server/error-hook.d.ts.map +1 -1
  31. package/dist/server/index.js +4 -2
  32. package/dist/testing/agent-store-conformance.d.ts +7 -0
  33. package/dist/testing/agent-store-conformance.d.ts.map +1 -0
  34. package/dist/testing.d.ts +2 -0
  35. package/dist/testing.d.ts.map +1 -1
  36. package/dist/testing.js +331 -0
  37. package/llms-full.txt +201 -28
  38. package/package.json +1 -1
@@ -2,6 +2,34 @@ import {
2
2
  ToolExecutionControlError,
3
3
  isToolExecutionControlError
4
4
  } from "./index-sa2mbwa7.js";
5
+ import {
6
+ AgentAssistantPlaceholderSchema,
7
+ AgentControlPartSchema,
8
+ AgentCostValueSchema,
9
+ AgentFilePartSchema,
10
+ AgentJsonObjectSchema,
11
+ AgentMessagePartSchema,
12
+ AgentMessageRoleSchema,
13
+ AgentMessageSchema,
14
+ AgentMessageStatusSchema,
15
+ AgentOpaquePartSchema,
16
+ AgentProviderEnvelopeSchema,
17
+ AgentReasoningPartSchema,
18
+ AgentRecordIdSchema,
19
+ AgentRecordVersionSchema,
20
+ AgentRunMetricsSchema,
21
+ AgentRunSchema,
22
+ AgentRunStateSchema,
23
+ AgentSnapshotSchema,
24
+ AgentSourcePartSchema,
25
+ AgentTerminalReasonSchema,
26
+ AgentTextPartSchema,
27
+ AgentTimestampSchema,
28
+ AgentToolCallPartSchema,
29
+ AgentToolResultPartSchema,
30
+ AgentUsageSchema,
31
+ AgentUsageValueSchema
32
+ } from "./index-vtjgx3vv.js";
5
33
  import"./index-6djpbnda.js";
6
34
  import"./index-cby4ar3v.js";
7
35
  import {
@@ -58,54 +86,66 @@ function eligibleForCompaction(messages, keepRecentTurns) {
58
86
  const eligibleCount = Math.max(0, completeTurns.length - keepRecentTurns);
59
87
  return completeTurns.slice(0, eligibleCount).flatMap((turn) => turn.messages);
60
88
  }
61
- function mutationSnapshot(result, fallback) {
89
+ function mutationSnapshot(result, fallback, attempts) {
62
90
  if (result.outcome === "applied" || result.outcome === "duplicate") {
63
- return { outcome: "applied", snapshot: result.snapshot };
91
+ return { outcome: "applied", snapshot: result.snapshot, attempts };
64
92
  }
65
- return { outcome: result.outcome, snapshot: fallback };
93
+ return { outcome: result.outcome, snapshot: fallback, attempts };
66
94
  }
67
95
  function structuredCompaction(config) {
68
96
  if (!Number.isSafeInteger(config.keepRecentTurns) || config.keepRecentTurns < 1) {
69
97
  throw new TypeError("keepRecentTurns must be a positive safe integer");
70
98
  }
99
+ const maxAttempts = config.maxAttempts ?? 1;
100
+ if (!Number.isSafeInteger(maxAttempts) || maxAttempts < 1) {
101
+ throw new TypeError("maxAttempts must be a positive safe integer");
102
+ }
71
103
  return async (input) => {
72
- const snapshot = await input.store.loadSnapshot(input.conversationId);
73
- if (!await config.threshold(snapshot))
74
- return { outcome: "not_needed", snapshot };
75
- const eligibleMessages = eligibleForCompaction(snapshot.messages, config.keepRecentTurns);
76
- if (eligibleMessages.length === 0)
77
- return { outcome: "nothing_eligible", snapshot };
78
- const leadingSummary = snapshot.messages[0]?.role === "summary" ? snapshot.messages[0] : undefined;
79
- const previousSummary = input.previousSummary ?? (leadingSummary && config.readPreviousSummary ? config.schema.parse(config.readPreviousSummary(leadingSummary)) : undefined);
80
- const rawSummary = await config.summarize({
81
- conversationId: input.conversationId,
82
- snapshot,
83
- eligibleMessages,
84
- ...previousSummary !== undefined && { previousSummary },
85
- signal: input.signal
86
- });
87
- const summary = config.schema.parse(rawSummary);
88
- if (input.signal.aborted)
89
- throw input.signal.reason;
90
- const summaryMessage = config.createSummaryMessage({
91
- conversationId: input.conversationId,
92
- summary,
93
- compactedMessages: eligibleMessages
94
- });
95
- if (summaryMessage.role !== "summary" || summaryMessage.status !== "committed") {
96
- throw new TypeError("Compaction summary must be one committed summary message");
104
+ let lastSnapshot = await input.store.loadSnapshot(input.conversationId);
105
+ for (let attempt = 1;attempt <= maxAttempts; attempt += 1) {
106
+ const snapshot = attempt === 1 ? lastSnapshot : await input.store.loadSnapshot(input.conversationId);
107
+ lastSnapshot = snapshot;
108
+ if (!await config.threshold(snapshot)) {
109
+ return { outcome: "not_needed", snapshot, attempts: attempt };
110
+ }
111
+ const eligibleMessages = eligibleForCompaction(snapshot.messages, config.keepRecentTurns);
112
+ if (eligibleMessages.length === 0) {
113
+ return { outcome: "nothing_eligible", snapshot, attempts: attempt };
114
+ }
115
+ const leadingSummary = snapshot.messages[0]?.role === "summary" ? snapshot.messages[0] : undefined;
116
+ const previousSummary = leadingSummary && config.readPreviousSummary ? config.schema.parse(config.readPreviousSummary(leadingSummary)) : attempt === 1 ? input.previousSummary : undefined;
117
+ const rawSummary = await config.summarize({
118
+ conversationId: input.conversationId,
119
+ snapshot,
120
+ eligibleMessages,
121
+ ...previousSummary !== undefined && { previousSummary },
122
+ signal: input.signal
123
+ });
124
+ const summary = config.schema.parse(rawSummary);
125
+ if (input.signal.aborted)
126
+ throw input.signal.reason;
127
+ const summaryMessage = config.createSummaryMessage({
128
+ conversationId: input.conversationId,
129
+ summary,
130
+ compactedMessages: eligibleMessages
131
+ });
132
+ if (summaryMessage.role !== "summary" || summaryMessage.status !== "committed") {
133
+ throw new TypeError("Compaction summary must be one committed summary message");
134
+ }
135
+ const applied = await input.store.replaceCompactedRange({
136
+ conversationId: input.conversationId,
137
+ expectedVersion: snapshot.version,
138
+ replacedMessageIds: [
139
+ ...leadingSummary ? [leadingSummary.id] : [],
140
+ ...eligibleMessages.map((message) => message.id)
141
+ ],
142
+ summary: summaryMessage
143
+ });
144
+ if (applied.outcome !== "conflict" || attempt === maxAttempts) {
145
+ return mutationSnapshot(applied, snapshot, attempt);
146
+ }
97
147
  }
98
- const previousSummaryMessage = previousSummary !== undefined && leadingSummary ? leadingSummary : undefined;
99
- const applied = await input.store.replaceCompactedRange({
100
- conversationId: input.conversationId,
101
- expectedVersion: snapshot.version,
102
- replacedMessageIds: [
103
- ...previousSummaryMessage ? [previousSummaryMessage.id] : [],
104
- ...eligibleMessages.map((message) => message.id)
105
- ],
106
- summary: summaryMessage
107
- });
108
- return mutationSnapshot(applied, snapshot);
148
+ return { outcome: "conflict", snapshot: lastSnapshot, attempts: maxAttempts };
109
149
  };
110
150
  }
111
151
  // src/agent-runtime/coordinator.ts
@@ -226,210 +266,78 @@ function createAgentSessionCoordinator() {
226
266
  };
227
267
  }
228
268
  // src/agent-runtime/events.ts
229
- import { z as z2 } from "zod";
230
-
231
- // src/agent-runtime/schemas.ts
232
269
  import { z } from "zod";
233
- var AgentRecordIdSchema = z.string().min(1);
234
- var AgentRecordVersionSchema = z.int().nonnegative();
235
- var AgentTimestampSchema = z.iso.datetime({ offset: true });
236
- var AgentJsonObjectSchema = z.record(z.string(), z.json());
237
- var AgentProviderEnvelopeSchema = z.object({
238
- schemaVersion: z.int().positive(),
239
- provider: z.string().min(1),
240
- data: AgentJsonObjectSchema
241
- });
242
- var AgentTextPartSchema = z.object({
243
- type: z.literal("text"),
244
- text: z.string()
245
- });
246
- var AgentReasoningPartSchema = z.object({
247
- type: z.literal("reasoning"),
248
- text: z.string(),
249
- provider: AgentProviderEnvelopeSchema.optional()
250
- });
251
- var AgentFilePartSchema = z.object({
252
- type: z.literal("file"),
253
- mediaType: z.string().min(1),
254
- reference: z.string().min(1),
255
- filename: z.string().min(1).optional()
256
- });
257
- var AgentSourcePartSchema = z.object({
258
- type: z.literal("source"),
259
- sourceId: z.string().min(1),
260
- url: z.url().optional(),
261
- title: z.string().optional()
262
- });
263
- var AgentToolCallPartSchema = z.object({
264
- type: z.literal("tool-call"),
265
- callId: z.string().min(1),
266
- toolName: z.string().min(1),
267
- input: z.json(),
268
- provider: AgentProviderEnvelopeSchema.optional()
269
- });
270
- var AgentToolResultPartSchema = z.object({
271
- type: z.literal("tool-result"),
272
- callId: z.string().min(1),
273
- toolName: z.string().min(1),
274
- outcome: z.enum(["success", "error", "interrupted"]),
275
- output: z.json().optional()
276
- });
277
- var AgentOpaquePartSchema = z.object({
278
- type: z.literal("provider"),
279
- envelope: AgentProviderEnvelopeSchema
280
- });
281
- var AgentControlPartSchema = z.object({
282
- type: z.literal("control"),
283
- reason: z.enum(["run-interrupted", "stale-run"])
284
- });
285
- var AgentMessagePartSchema = z.discriminatedUnion("type", [
286
- AgentTextPartSchema,
287
- AgentReasoningPartSchema,
288
- AgentFilePartSchema,
289
- AgentSourcePartSchema,
290
- AgentToolCallPartSchema,
291
- AgentToolResultPartSchema,
292
- AgentOpaquePartSchema,
293
- AgentControlPartSchema
294
- ]);
295
- var AgentMessageRoleSchema = z.enum(["user", "assistant", "system", "summary"]);
296
- var AgentMessageStatusSchema = z.enum([
297
- "committed",
298
- "streaming",
299
- "completed",
300
- "interrupted",
301
- "failed"
302
- ]);
303
- var AgentMessageSchema = z.object({
304
- schemaVersion: z.literal(1),
305
- id: AgentRecordIdSchema,
306
- conversationId: AgentRecordIdSchema,
307
- runId: AgentRecordIdSchema.optional(),
308
- role: AgentMessageRoleSchema,
309
- status: AgentMessageStatusSchema,
310
- parts: z.array(AgentMessagePartSchema),
311
- metadata: AgentJsonObjectSchema.optional(),
312
- createdAt: AgentTimestampSchema,
313
- updatedAt: AgentTimestampSchema
314
- });
315
- var AgentRunStateSchema = z.enum([
316
- "queued",
317
- "running",
318
- "interrupt_requested",
319
- "completed",
320
- "interrupted",
321
- "failed",
322
- "cancelled",
323
- "abandoned"
324
- ]);
325
- var AgentTerminalReasonSchema = z.enum([
326
- "success",
327
- "policy_stop",
328
- "interrupted",
329
- "cancelled",
330
- "timeout",
331
- "shutdown",
332
- "provider_failure",
333
- "tool_failure",
334
- "abandoned"
335
- ]);
336
- var AgentRunSchema = z.object({
337
- schemaVersion: z.literal(1),
338
- id: AgentRecordIdSchema,
339
- conversationId: AgentRecordIdSchema,
340
- inputMessageIds: z.array(AgentRecordIdSchema).min(1),
341
- assistantMessageId: AgentRecordIdSchema,
342
- state: AgentRunStateSchema,
343
- revision: AgentRecordVersionSchema,
344
- ownerId: z.string().min(1).optional(),
345
- terminalReason: AgentTerminalReasonSchema.optional(),
346
- terminalPolicyName: z.string().min(1).optional(),
347
- createdAt: AgentTimestampSchema,
348
- updatedAt: AgentTimestampSchema
349
- });
350
- var AgentSnapshotSchema = z.object({
351
- schemaVersion: z.literal(1),
270
+ var AgentAdmissionEventSchema = z.object({
271
+ type: z.literal("admission"),
272
+ eventId: AgentRecordIdSchema,
352
273
  conversationId: AgentRecordIdSchema,
353
- version: AgentRecordVersionSchema,
354
- messages: z.array(AgentMessageSchema),
355
- runs: z.array(AgentRunSchema)
356
- });
357
- var AgentUsageValueSchema = z.object({
358
- value: z.number().nonnegative().optional(),
359
- provenance: z.enum(["provider-reported", "computed", "estimated", "unavailable"])
360
- });
361
- var AgentCostValueSchema = z.object({
362
- value: z.number().nonnegative().optional(),
363
- currency: z.string().length(3).optional(),
364
- provenance: z.enum(["provider-reported", "computed", "estimated", "unavailable"])
365
- });
366
- var AgentUsageSchema = z.object({
367
- inputTokens: AgentUsageValueSchema,
368
- outputTokens: AgentUsageValueSchema,
369
- reasoningTokens: AgentUsageValueSchema.optional(),
370
- cacheReadTokens: AgentUsageValueSchema.optional(),
371
- cacheWriteTokens: AgentUsageValueSchema.optional(),
372
- cost: AgentCostValueSchema.optional()
274
+ runId: AgentRecordIdSchema,
275
+ snapshotVersion: AgentRecordVersionSchema,
276
+ input: AgentMessageSchema,
277
+ run: AgentRunSchema,
278
+ assistant: z.union([AgentAssistantPlaceholderSchema, AgentMessageSchema]),
279
+ emittedAt: AgentTimestampSchema
373
280
  });
374
-
375
- // src/agent-runtime/events.ts
376
- var EventIdentitySchema = z2.object({
281
+ var EventIdentitySchema = z.object({
377
282
  conversationId: AgentRecordIdSchema,
378
283
  runId: AgentRecordIdSchema,
379
284
  emittedAt: AgentTimestampSchema
380
285
  });
381
286
  var AgentTransientDeltaEventSchema = EventIdentitySchema.extend({
382
- type: z2.literal("assistant-delta"),
383
- runtimeEpoch: z2.string().min(1),
384
- sequence: z2.int().nonnegative(),
385
- textDelta: z2.string()
287
+ type: z.literal("assistant-delta"),
288
+ runtimeEpoch: z.string().min(1),
289
+ sequence: z.int().nonnegative(),
290
+ textDelta: z.string()
386
291
  });
387
292
  var AgentTransientReasoningIdentitySchema = EventIdentitySchema.extend({
388
- runtimeEpoch: z2.string().min(1),
389
- sequence: z2.int().nonnegative(),
293
+ runtimeEpoch: z.string().min(1),
294
+ sequence: z.int().nonnegative(),
390
295
  provider: AgentProviderEnvelopeSchema.optional()
391
296
  });
392
297
  var AgentReasoningStartEventSchema = AgentTransientReasoningIdentitySchema.extend({
393
- type: z2.literal("reasoning-start")
298
+ type: z.literal("reasoning-start")
394
299
  });
395
300
  var AgentReasoningDeltaEventSchema = AgentTransientReasoningIdentitySchema.extend({
396
- type: z2.literal("reasoning-delta"),
397
- textDelta: z2.string()
301
+ type: z.literal("reasoning-delta"),
302
+ textDelta: z.string()
398
303
  });
399
304
  var AgentReasoningEndEventSchema = AgentTransientReasoningIdentitySchema.extend({
400
- type: z2.literal("reasoning-end")
305
+ type: z.literal("reasoning-end")
401
306
  });
402
307
  var AgentCheckpointEventSchema = EventIdentitySchema.extend({
403
- type: z2.literal("assistant-checkpoint"),
308
+ type: z.literal("assistant-checkpoint"),
404
309
  eventId: AgentRecordIdSchema,
405
310
  snapshotVersion: AgentRecordVersionSchema,
406
- message: AgentMessageSchema
311
+ message: AgentMessageSchema,
312
+ metrics: AgentRunMetricsSchema.optional()
407
313
  });
408
314
  var AgentRunStateEventSchema = EventIdentitySchema.extend({
409
- type: z2.literal("run-state"),
315
+ type: z.literal("run-state"),
410
316
  eventId: AgentRecordIdSchema,
411
317
  snapshotVersion: AgentRecordVersionSchema,
412
318
  state: AgentRunStateSchema
413
319
  });
414
320
  var AgentToolStatusEventSchema = EventIdentitySchema.extend({
415
- type: z2.literal("tool-status"),
416
- runtimeEpoch: z2.string().min(1),
417
- sequence: z2.int().nonnegative(),
321
+ type: z.literal("tool-status"),
322
+ runtimeEpoch: z.string().min(1),
323
+ sequence: z.int().nonnegative(),
418
324
  callId: AgentRecordIdSchema,
419
- toolName: z2.string().min(1),
420
- status: z2.enum(["started", "completed", "failed", "interrupted"]),
421
- input: z2.json().optional(),
422
- output: z2.json().optional()
325
+ toolName: z.string().min(1),
326
+ status: z.enum(["started", "completed", "failed", "interrupted"]),
327
+ input: z.json().optional(),
328
+ output: z.json().optional()
423
329
  });
424
330
  var AgentTerminalEventSchema = EventIdentitySchema.extend({
425
- type: z2.literal("terminal"),
331
+ type: z.literal("terminal"),
426
332
  eventId: AgentRecordIdSchema,
427
333
  snapshotVersion: AgentRecordVersionSchema,
428
334
  reason: AgentTerminalReasonSchema,
429
- policyName: z2.string().min(1).optional(),
430
- message: AgentMessageSchema
335
+ policyName: z.string().min(1).optional(),
336
+ message: AgentMessageSchema,
337
+ metrics: AgentRunMetricsSchema.optional()
431
338
  });
432
- var AgentRuntimeEventSchema = z2.discriminatedUnion("type", [
339
+ var AgentRuntimeEventSchema = z.discriminatedUnion("type", [
340
+ AgentAdmissionEventSchema,
433
341
  AgentTransientDeltaEventSchema,
434
342
  AgentReasoningStartEventSchema,
435
343
  AgentReasoningDeltaEventSchema,
@@ -439,6 +347,63 @@ var AgentRuntimeEventSchema = z2.discriminatedUnion("type", [
439
347
  AgentToolStatusEventSchema,
440
348
  AgentTerminalEventSchema
441
349
  ]);
350
+ var AgentRuntimeEventCursorSchema = z.object({
351
+ snapshotVersion: AgentRecordVersionSchema.optional(),
352
+ durableEventIds: z.array(AgentRecordIdSchema).optional(),
353
+ runtimeEpoch: z.string().min(1).optional(),
354
+ sequence: z.int().nonnegative().optional()
355
+ });
356
+ function isDurableEvent(event) {
357
+ return event.type === "admission" || event.type === "assistant-checkpoint" || event.type === "run-state" || event.type === "terminal";
358
+ }
359
+ function advanceAgentRuntimeEventCursor(rawCursor, event) {
360
+ const cursor = AgentRuntimeEventCursorSchema.parse(rawCursor);
361
+ if (isDurableEvent(event)) {
362
+ const previous = cursor.snapshotVersion;
363
+ const durableEventIds = previous === event.snapshotVersion ? cursor.durableEventIds ?? [] : [];
364
+ if (previous !== undefined && event.snapshotVersion < previous || durableEventIds.includes(event.eventId)) {
365
+ return { status: "duplicate", cursor };
366
+ }
367
+ return {
368
+ status: previous !== undefined && event.snapshotVersion > previous + 1 ? "gap" : "accepted",
369
+ cursor: {
370
+ ...cursor,
371
+ snapshotVersion: event.snapshotVersion,
372
+ durableEventIds: [...durableEventIds, event.eventId]
373
+ }
374
+ };
375
+ }
376
+ const previousSequence = cursor.runtimeEpoch === event.runtimeEpoch ? cursor.sequence : undefined;
377
+ if (previousSequence !== undefined && event.sequence <= previousSequence) {
378
+ return { status: "duplicate", cursor };
379
+ }
380
+ return {
381
+ status: previousSequence !== undefined && event.sequence > previousSequence + 1 ? "gap" : "accepted",
382
+ cursor: { ...cursor, runtimeEpoch: event.runtimeEpoch, sequence: event.sequence }
383
+ };
384
+ }
385
+ function createAgentRuntimeEventSink(config) {
386
+ const manager = createBoundedSinkManager({
387
+ write: config.write,
388
+ ...config.maxPending !== undefined && { maxPending: config.maxPending },
389
+ ...config.onSinkError && { onSinkError: config.onSinkError },
390
+ ...config.onDrop && { onDrop: config.onDrop }
391
+ });
392
+ return {
393
+ publish(rawEvent) {
394
+ const event = AgentRuntimeEventSchema.parse(rawEvent);
395
+ const projected = config.project?.(event) ?? (config.project ? undefined : event);
396
+ if (projected)
397
+ manager.submit(() => AgentRuntimeEventSchema.parse(projected));
398
+ },
399
+ flush: () => manager.flush(),
400
+ getStatus: () => manager.getStatus(),
401
+ close: () => manager.close()
402
+ };
403
+ }
404
+ function agentDurableEventId(type, runId, snapshotVersion) {
405
+ return `${runId}:${type}:${snapshotVersion}`;
406
+ }
442
407
  // src/agent-runtime/history.ts
443
408
  import { modelMessageSchema } from "ai";
444
409
  function providerOptions(envelope) {
@@ -520,26 +485,75 @@ function assistantMessages(message) {
520
485
  }
521
486
  return messages;
522
487
  }
523
- async function projectAgentHistory(messages, options = {}) {
488
+ function completeToolChronology(message) {
489
+ const calls = new Set(message.parts.filter((part) => part.type === "tool-call").map((part) => part.callId));
490
+ const results = new Set(message.parts.filter((part) => part.type === "tool-result").map((part) => part.callId));
491
+ return [...calls].every((callId) => results.has(callId)) && [...results].every((callId) => calls.has(callId));
492
+ }
493
+ async function projectAgentHistoryDetailed(messages, options = {}) {
524
494
  const projected = [];
495
+ const decisions = [];
496
+ let observedUser = false;
525
497
  for (const message of messages) {
526
- if (message.status === "streaming" || message.status === "failed")
498
+ if (message.status === "streaming" || message.status === "failed") {
499
+ decisions.push({ messageId: message.id, action: "omitted", reason: "draft-or-failed" });
527
500
  continue;
501
+ }
528
502
  if (message.role === "user") {
529
503
  const user = await userMessage(message, options);
530
- if (user)
504
+ observedUser = true;
505
+ if (user) {
531
506
  projected.push(user);
507
+ decisions.push({ messageId: message.id, action: "projected", reason: "projected" });
508
+ } else {
509
+ decisions.push({ messageId: message.id, action: "omitted", reason: "empty" });
510
+ }
532
511
  continue;
533
512
  }
534
513
  if (message.role === "system" || message.role === "summary") {
535
514
  const content = textContent(message.parts);
536
- if (content)
515
+ if (content) {
537
516
  projected.push(modelMessageSchema.parse({ role: "system", content }));
517
+ decisions.push({ messageId: message.id, action: "projected", reason: "projected" });
518
+ } else {
519
+ decisions.push({ messageId: message.id, action: "omitted", reason: "empty" });
520
+ }
538
521
  continue;
539
522
  }
540
- projected.push(...assistantMessages(message));
523
+ if (!observedUser && options.leadingAssistant !== "allow") {
524
+ if (options.leadingAssistant === "error") {
525
+ throw new Error(`Assistant message ${message.id} precedes the first user message`);
526
+ }
527
+ decisions.push({
528
+ messageId: message.id,
529
+ action: "omitted",
530
+ reason: "leading-assistant"
531
+ });
532
+ continue;
533
+ }
534
+ if (!completeToolChronology(message)) {
535
+ if (options.incompleteToolTurn === "error") {
536
+ throw new Error(`Assistant message ${message.id} has incomplete tool chronology`);
537
+ }
538
+ decisions.push({
539
+ messageId: message.id,
540
+ action: "omitted",
541
+ reason: "incomplete-tool-turn"
542
+ });
543
+ continue;
544
+ }
545
+ const assistant = assistantMessages(message);
546
+ projected.push(...assistant);
547
+ decisions.push({
548
+ messageId: message.id,
549
+ action: assistant.length > 0 ? "projected" : "omitted",
550
+ reason: assistant.length > 0 ? "projected" : "empty"
551
+ });
541
552
  }
542
- return projected;
553
+ return { messages: projected, decisions };
554
+ }
555
+ async function projectAgentHistory(messages, options = {}) {
556
+ return [...(await projectAgentHistoryDetailed(messages, options)).messages];
543
557
  }
544
558
  // src/agent-runtime/managed-tools.ts
545
559
  async function assertFence(config, input) {
@@ -565,15 +579,22 @@ function createAgentToolFenceLifecycle(config) {
565
579
  };
566
580
  }
567
581
  // src/agent-runtime/models.ts
568
- import { z as z3 } from "zod";
569
- var AgentModelCapabilitySchema = z3.enum(["tools", "vision", "reasoning", "files"]);
570
- var AgentModelDescriptorSchema = z3.object({
571
- provider: z3.string().min(1),
572
- modelId: z3.string().min(1),
573
- contextWindow: z3.int().positive(),
574
- capabilities: z3.array(AgentModelCapabilitySchema),
575
- observedAt: z3.iso.datetime({ offset: true }).optional(),
576
- source: z3.string().min(1).optional()
582
+ import { z as z2 } from "zod";
583
+ var AgentModelCapabilitySchema = z2.enum(["tools", "vision", "reasoning", "files"]);
584
+ var AgentModelDescriptorSchema = z2.object({
585
+ provider: z2.string().min(1),
586
+ modelId: z2.string().min(1),
587
+ contextWindow: z2.int().positive(),
588
+ capabilities: z2.array(AgentModelCapabilitySchema),
589
+ observedAt: z2.iso.datetime({ offset: true }).optional(),
590
+ source: z2.string().min(1).optional(),
591
+ availability: z2.enum(["available", "unavailable"]).optional()
592
+ });
593
+ var AgentModelRegistrySnapshotSchema = z2.object({
594
+ schemaVersion: z2.literal(1),
595
+ source: z2.string().min(1),
596
+ observedAt: z2.iso.datetime({ offset: true }),
597
+ models: z2.record(z2.string().min(1), AgentModelDescriptorSchema)
577
598
  });
578
599
  function defineModelRegistry(config) {
579
600
  const descriptors = new Map;
@@ -592,15 +613,26 @@ function defineModelRegistry(config) {
592
613
  const available = new Set(descriptor(key).capabilities);
593
614
  return capabilities.every((capability) => available.has(capability));
594
615
  };
616
+ const preflight = (key, required = []) => {
617
+ const selected = descriptor(key);
618
+ if (selected.availability === "unavailable") {
619
+ throw new Error(`Agent model ${key} is unavailable`);
620
+ }
621
+ if (!supports(key, required)) {
622
+ throw new Error(`Agent model ${key} does not satisfy required capabilities`);
623
+ }
624
+ if (!config.providers[selected.provider]) {
625
+ throw new Error(`Unknown agent model provider: ${selected.provider}`);
626
+ }
627
+ return selected;
628
+ };
595
629
  return {
596
630
  keys: () => [...descriptors.keys()],
597
631
  descriptor,
598
632
  supports,
633
+ preflight,
599
634
  resolve(key, required = []) {
600
- const selected = descriptor(key);
601
- if (!supports(key, required)) {
602
- throw new Error(`Agent model ${key} does not satisfy required capabilities`);
603
- }
635
+ const selected = preflight(key, required);
604
636
  const provider = config.providers[selected.provider];
605
637
  if (!provider)
606
638
  throw new Error(`Unknown agent model provider: ${selected.provider}`);
@@ -609,29 +641,49 @@ function defineModelRegistry(config) {
609
641
  model: provider.create(selected.modelId),
610
642
  ...provider.normalizeUsage && { normalizeUsage: provider.normalizeUsage }
611
643
  };
644
+ },
645
+ snapshot(input) {
646
+ return AgentModelRegistrySnapshotSchema.parse({
647
+ schemaVersion: 1,
648
+ source: input.source,
649
+ observedAt: input.observedAt,
650
+ models: Object.fromEntries(descriptors.entries())
651
+ });
612
652
  }
613
653
  };
614
654
  }
655
+ function validateAgentModelSnapshot(input, policy) {
656
+ if (!Number.isSafeInteger(policy.maxAgeMs) || policy.maxAgeMs < 0) {
657
+ throw new TypeError("maxAgeMs must be a non-negative safe integer");
658
+ }
659
+ const snapshot = AgentModelRegistrySnapshotSchema.parse(input);
660
+ const now = policy.now?.() ?? new Date;
661
+ const age = now.getTime() - new Date(snapshot.observedAt).getTime();
662
+ if (age < 0 || age > policy.maxAgeMs) {
663
+ throw new Error(`Agent model snapshot from ${snapshot.source} is stale`);
664
+ }
665
+ return snapshot;
666
+ }
615
667
  // src/agent-runtime/observability.ts
616
- import { z as z4 } from "zod";
617
- var AgentRunEventSchema = z4.object({
618
- schemaVersion: z4.literal(1),
668
+ import { z as z3 } from "zod";
669
+ var AgentRunEventSchema = z3.object({
670
+ schemaVersion: z3.literal(1),
619
671
  eventId: AgentRecordIdSchema,
620
- type: z4.enum(["run-started", "step-finished", "run-terminal"]),
672
+ type: z3.enum(["run-started", "step-finished", "run-terminal"]),
621
673
  conversationId: AgentRecordIdSchema,
622
674
  runId: AgentRecordIdSchema,
623
- traceId: z4.string().min(1),
624
- spanId: z4.string().min(1),
625
- parentSpanId: z4.string().min(1).optional(),
675
+ traceId: z3.string().min(1),
676
+ spanId: z3.string().min(1),
677
+ parentSpanId: z3.string().min(1).optional(),
626
678
  state: AgentRunStateSchema,
627
679
  terminalReason: AgentTerminalReasonSchema.optional(),
628
- modelId: z4.string().min(1).optional(),
629
- step: z4.int().nonnegative().optional(),
630
- queueWaitMs: z4.number().nonnegative().optional(),
631
- durationMs: z4.number().nonnegative().optional(),
632
- ttftMs: z4.number().nonnegative().optional(),
680
+ modelId: z3.string().min(1).optional(),
681
+ step: z3.int().nonnegative().optional(),
682
+ queueWaitMs: z3.number().nonnegative().optional(),
683
+ durationMs: z3.number().nonnegative().optional(),
684
+ ttftMs: z3.number().nonnegative().optional(),
633
685
  usage: AgentUsageSchema.optional(),
634
- internalCause: z4.unknown().optional(),
686
+ internalCause: z3.unknown().optional(),
635
687
  emittedAt: AgentTimestampSchema
636
688
  });
637
689
  function createAgentObservability(config) {
@@ -642,13 +694,18 @@ function createAgentObservability(config) {
642
694
  ...config.onSinkError && { onSinkError: config.onSinkError },
643
695
  ...config.onDrop && { onDrop: config.onDrop }
644
696
  });
697
+ const emitted = new Set;
645
698
  return {
646
699
  rootTrace(parent) {
647
700
  const trace = parent ? childSpan(parent) : createTraceContext();
648
701
  return trace;
649
702
  },
650
703
  emit(rawEvent) {
651
- manager.submit(() => AgentRunEventSchema.parse(rawEvent));
704
+ const parsed = AgentRunEventSchema.parse(rawEvent);
705
+ if ((config.deduplicate ?? true) && emitted.has(parsed.eventId))
706
+ return;
707
+ emitted.add(parsed.eventId);
708
+ manager.submit(() => config.includeInternalCause ? parsed : AgentRunEventSchema.omit({ internalCause: true }).parse(parsed));
652
709
  },
653
710
  flush: () => manager.flush(),
654
711
  getStatus: () => manager.getStatus(),
@@ -656,11 +713,115 @@ function createAgentObservability(config) {
656
713
  };
657
714
  }
658
715
  // src/agent-runtime/prompt.ts
659
- import { z as z5 } from "zod";
660
- var AgentTokenCountSchema = z5.object({
661
- value: z5.int().nonnegative().optional(),
662
- provenance: z5.enum(["measured", "estimated", "unavailable"])
716
+ import { z as z4 } from "zod";
717
+ var AgentTokenCountSchema = z4.object({
718
+ value: z4.int().nonnegative().optional(),
719
+ provenance: z4.enum(["measured", "estimated", "unavailable"])
663
720
  });
721
+ function completeTurn(messages) {
722
+ if (messages[0]?.role !== "user")
723
+ return false;
724
+ const assistant = messages.find((message) => message.role === "assistant");
725
+ if (assistant?.status !== "completed")
726
+ return false;
727
+ const calls = new Set(assistant.parts.filter((part) => part.type === "tool-call").map((part) => part.callId));
728
+ const results = new Set(assistant.parts.filter((part) => part.type === "tool-result").map((part) => part.callId));
729
+ return [...calls].every((callId) => results.has(callId)) && [...results].every((callId) => calls.has(callId));
730
+ }
731
+ function budgetTurns(messages) {
732
+ const turns = [];
733
+ let current = [];
734
+ const flush = () => {
735
+ if (current.length === 0)
736
+ return;
737
+ turns.push({ messages: current, complete: completeTurn(current), protectedSystem: false });
738
+ current = [];
739
+ };
740
+ for (const message of messages) {
741
+ if (message.role === "system" || message.role === "summary") {
742
+ flush();
743
+ turns.push({ messages: [message], complete: true, protectedSystem: true });
744
+ continue;
745
+ }
746
+ if (message.role === "user")
747
+ flush();
748
+ current.push(message);
749
+ }
750
+ flush();
751
+ return turns;
752
+ }
753
+ async function selectAgentHistory(options) {
754
+ if (!Number.isSafeInteger(options.availableTokens) || options.availableTokens < 0) {
755
+ throw new TypeError("availableTokens must be a non-negative safe integer");
756
+ }
757
+ const keepRecentTurns = options.keepRecentTurns ?? 1;
758
+ if (!Number.isSafeInteger(keepRecentTurns) || keepRecentTurns < 0) {
759
+ throw new TypeError("keepRecentTurns must be a non-negative safe integer");
760
+ }
761
+ const counts = new Map;
762
+ let total = 0;
763
+ let estimated = false;
764
+ for (const message of options.messages) {
765
+ const count = AgentTokenCountSchema.parse(await options.estimateMessage(message));
766
+ counts.set(message.id, count);
767
+ const value = knownValue(count);
768
+ if (value === undefined) {
769
+ return {
770
+ messages: [...options.messages],
771
+ decisions: options.messages.map((candidate) => ({
772
+ messageId: candidate.id,
773
+ action: "kept",
774
+ reason: "token-count-unavailable",
775
+ tokens: counts.get(candidate.id) ?? { provenance: "unavailable" }
776
+ })),
777
+ totalTokens: { provenance: "unavailable" },
778
+ outcome: "unavailable"
779
+ };
780
+ }
781
+ total += value;
782
+ if (count.provenance === "estimated")
783
+ estimated = true;
784
+ }
785
+ const turns = budgetTurns(options.messages);
786
+ const completeIndexes = turns.map((turn, index) => ({ turn, index })).filter(({ turn }) => turn.complete && !turn.protectedSystem).map(({ index }) => index);
787
+ const protectedRecent = new Set(completeIndexes.slice(-keepRecentTurns));
788
+ const removed = new Set;
789
+ for (let index = 0;index < turns.length && total > options.availableTokens; index += 1) {
790
+ const turn = turns[index];
791
+ if (!turn || turn.protectedSystem || !turn.complete || protectedRecent.has(index))
792
+ continue;
793
+ for (const message of turn.messages) {
794
+ removed.add(message.id);
795
+ total -= knownValue(counts.get(message.id) ?? { provenance: "unavailable" }) ?? 0;
796
+ }
797
+ }
798
+ const messages = options.messages.filter((message) => !removed.has(message.id));
799
+ const decisions = options.messages.map((message) => {
800
+ const turnIndex = turns.findIndex((turn2) => turn2.messages.some((item) => item.id === message.id));
801
+ const turn = turns[turnIndex];
802
+ let reason = "within-budget";
803
+ if (removed.has(message.id))
804
+ reason = "oldest-eligible-turn";
805
+ else if (turn?.protectedSystem)
806
+ reason = "protected-system";
807
+ else if (turn && !turn.complete)
808
+ reason = "protected-incomplete-turn";
809
+ else if (protectedRecent.has(turnIndex))
810
+ reason = "protected-recent-turn";
811
+ return {
812
+ messageId: message.id,
813
+ action: removed.has(message.id) ? "removed" : "kept",
814
+ reason,
815
+ tokens: counts.get(message.id) ?? { provenance: "unavailable" }
816
+ };
817
+ });
818
+ return {
819
+ messages,
820
+ decisions,
821
+ totalTokens: { value: total, provenance: estimated ? "estimated" : "measured" },
822
+ outcome: total > options.availableTokens ? "oversized" : removed.size > 0 ? "truncated" : "fits"
823
+ };
824
+ }
664
825
  function knownValue(value) {
665
826
  return value.provenance === "unavailable" ? undefined : value.value;
666
827
  }
@@ -738,7 +899,7 @@ import {
738
899
  stepCountIs,
739
900
  streamText
740
901
  } from "ai";
741
- import { z as z6 } from "zod";
902
+ import { z as z5 } from "zod";
742
903
  class AgentRuntimeConflictError extends Error {
743
904
  constructor(operation) {
744
905
  super(`Agent runtime store conflict during ${operation}`);
@@ -757,7 +918,7 @@ function findRun(runs, runId) {
757
918
  return run;
758
919
  }
759
920
  function jsonValue(value) {
760
- const parsed = z6.json().safeParse(value);
921
+ const parsed = z5.json().safeParse(value);
761
922
  return parsed.success ? parsed.data : { message: "Non-JSON tool output omitted" };
762
923
  }
763
924
  function providerEnvelope(value) {
@@ -899,7 +1060,11 @@ function createAgentRuntime(config) {
899
1060
  const publish = async (event) => {
900
1061
  try {
901
1062
  await config.publish?.(event);
902
- } catch {}
1063
+ } catch (error) {
1064
+ try {
1065
+ await config.onPublishError?.({ event, error });
1066
+ } catch {}
1067
+ }
903
1068
  };
904
1069
  const executeRun = async (input) => {
905
1070
  const queuedSnapshot = await config.store.loadSnapshot(input.acceptedRun.conversationId);
@@ -913,7 +1078,7 @@ function createAgentRuntime(config) {
913
1078
  let run = findRun(acquired.runs, input.acceptedRun.id);
914
1079
  await publish({
915
1080
  type: "run-state",
916
- eventId: generateId(),
1081
+ eventId: agentDurableEventId("run-state", run.id, acquired.version),
917
1082
  conversationId: run.conversationId,
918
1083
  runId: run.id,
919
1084
  snapshotVersion: acquired.version,
@@ -951,6 +1116,7 @@ function createAgentRuntime(config) {
951
1116
  runId: run.id,
952
1117
  expectedRevision: run.revision,
953
1118
  ownerId: runtimeEpoch,
1119
+ ...run.fencingToken !== undefined && { fencingToken: run.fencingToken },
954
1120
  assistant
955
1121
  }), "assistant draft");
956
1122
  run = findRun(snapshot.runs, run.id);
@@ -999,16 +1165,24 @@ function createAgentRuntime(config) {
999
1165
  runId: run.id,
1000
1166
  expectedRevision: run.revision,
1001
1167
  ownerId: runtimeEpoch,
1168
+ ...run.fencingToken !== undefined && { fencingToken: run.fencingToken },
1002
1169
  assistant
1003
1170
  }), "assistant checkpoint");
1004
1171
  run = findRun(snapshot.runs, run.id);
1172
+ const checkpointMetrics = {
1173
+ partial: true,
1174
+ durationMs: performance.now() - runStartedAt,
1175
+ ...usage && { usage },
1176
+ ...firstOutputAt !== undefined && { ttftMs: firstOutputAt - runStartedAt }
1177
+ };
1005
1178
  await publish({
1006
1179
  type: "assistant-checkpoint",
1007
- eventId: generateId(),
1180
+ eventId: agentDurableEventId("assistant-checkpoint", run.id, snapshot.version),
1008
1181
  conversationId: run.conversationId,
1009
1182
  runId: run.id,
1010
1183
  snapshotVersion: snapshot.version,
1011
1184
  message: assistant,
1185
+ metrics: checkpointMetrics,
1012
1186
  emittedAt: now().toISOString()
1013
1187
  });
1014
1188
  };
@@ -1029,6 +1203,8 @@ function createAgentRuntime(config) {
1029
1203
  const currentRun = current.runs.find((candidate) => candidate.id === run.id);
1030
1204
  if (!currentRun || currentRun.ownerId !== runtimeEpoch)
1031
1205
  return "stale_run";
1206
+ if (currentRun.fencingToken !== run.fencingToken)
1207
+ return "stale_run";
1032
1208
  if (currentRun.state === "interrupt_requested")
1033
1209
  return "run_interrupted";
1034
1210
  if (currentRun.state !== "running")
@@ -1037,7 +1213,10 @@ function createAgentRuntime(config) {
1037
1213
  };
1038
1214
  const toolFenceLifecycle = createAgentToolFenceLifecycle({
1039
1215
  runId: run.id,
1040
- assertCurrent
1216
+ assertCurrent,
1217
+ context: () => ({
1218
+ ...run.fencingToken !== undefined && { fencingToken: run.fencingToken }
1219
+ })
1041
1220
  });
1042
1221
  const runtimeContext = {
1043
1222
  context: input.context,
@@ -1373,14 +1552,21 @@ function createAgentRuntime(config) {
1373
1552
  runId: run.id,
1374
1553
  expectedRevision: run.revision,
1375
1554
  ownerId: runtimeEpoch,
1555
+ ...run.fencingToken !== undefined && { fencingToken: run.fencingToken },
1376
1556
  assistant,
1377
1557
  reason: terminalReason,
1378
1558
  ...terminalPolicyName && { policyName: terminalPolicyName }
1379
1559
  }), "terminal commit");
1380
1560
  run = findRun(snapshot.runs, run.id);
1561
+ const terminalMetrics = {
1562
+ partial: false,
1563
+ durationMs: performance.now() - runStartedAt,
1564
+ ...usage && { usage },
1565
+ ...firstOutputAt !== undefined && { ttftMs: firstOutputAt - runStartedAt }
1566
+ };
1381
1567
  config.observe?.emit({
1382
1568
  schemaVersion: 1,
1383
- eventId: generateId(),
1569
+ eventId: agentDurableEventId("terminal", run.id, snapshot.version),
1384
1570
  type: "run-terminal",
1385
1571
  conversationId: run.conversationId,
1386
1572
  runId: run.id,
@@ -1390,7 +1576,7 @@ function createAgentRuntime(config) {
1390
1576
  state: run.state,
1391
1577
  terminalReason,
1392
1578
  ...selectedModel && { modelId: selectedModel.descriptor.modelId },
1393
- durationMs: performance.now() - runStartedAt,
1579
+ durationMs: terminalMetrics.durationMs,
1394
1580
  ...usage && { usage },
1395
1581
  ...internalCause !== undefined && { internalCause },
1396
1582
  ...firstOutputAt !== undefined && { ttftMs: firstOutputAt - runStartedAt },
@@ -1398,13 +1584,14 @@ function createAgentRuntime(config) {
1398
1584
  });
1399
1585
  await publish({
1400
1586
  type: "terminal",
1401
- eventId: generateId(),
1587
+ eventId: agentDurableEventId("terminal", run.id, snapshot.version),
1402
1588
  conversationId: run.conversationId,
1403
1589
  runId: run.id,
1404
1590
  snapshotVersion: snapshot.version,
1405
1591
  reason: terminalReason,
1406
1592
  ...terminalPolicyName && { policyName: terminalPolicyName },
1407
1593
  message: assistant,
1594
+ metrics: terminalMetrics,
1408
1595
  emittedAt: now().toISOString()
1409
1596
  });
1410
1597
  return {
@@ -1412,9 +1599,38 @@ function createAgentRuntime(config) {
1412
1599
  message: assistant,
1413
1600
  reason: terminalReason,
1414
1601
  snapshotVersion: snapshot.version,
1602
+ metrics: terminalMetrics,
1415
1603
  ...terminalPolicyName && { policyName: terminalPolicyName }
1416
1604
  };
1417
1605
  };
1606
+ const resume = (rawInput) => {
1607
+ const context = config.protocol.parseContext(rawInput.context);
1608
+ const accepted = Promise.withResolvers();
1609
+ const result = Promise.withResolvers();
1610
+ (async () => {
1611
+ try {
1612
+ const snapshot = await config.store.loadSnapshot(rawInput.conversationId);
1613
+ const recoveredRun = findRun(snapshot.runs, rawInput.runId);
1614
+ if (recoveredRun.state !== "queued") {
1615
+ throw new Error("Only a queued recovered agent run can be resumed");
1616
+ }
1617
+ accepted.resolve();
1618
+ const ticket = coordinator.submit({
1619
+ key: rawInput.conversationKey ?? rawInput.conversationId,
1620
+ policy: "queue",
1621
+ create: (signal) => ({
1622
+ runId: recoveredRun.id,
1623
+ execute: () => executeRun({ acceptedRun: recoveredRun, context, signal })
1624
+ })
1625
+ });
1626
+ ticket.result.then(result.resolve, result.reject);
1627
+ } catch (error) {
1628
+ accepted.reject(error);
1629
+ result.reject(error);
1630
+ }
1631
+ })();
1632
+ return { accepted: accepted.promise, result: result.promise };
1633
+ };
1418
1634
  return {
1419
1635
  submit(rawInput) {
1420
1636
  const metadata = rawInput.metadata === undefined ? undefined : config.protocol.parseInputMetadata(rawInput.metadata);
@@ -1494,6 +1710,10 @@ function createAgentRuntime(config) {
1494
1710
  await previousAcceptance.catch(() => {
1495
1711
  return;
1496
1712
  });
1713
+ await config.models.preflight?.({
1714
+ context,
1715
+ conversationId: input.conversationId
1716
+ });
1497
1717
  const acceptance = await config.store.acceptInputAndAssignRun({
1498
1718
  idempotencyKey: input.idempotencyKey,
1499
1719
  input: userMessage2,
@@ -1505,14 +1725,45 @@ function createAgentRuntime(config) {
1505
1725
  const acceptedSnapshot = appliedSnapshot(acceptance, "input acceptance");
1506
1726
  const assignedRunId = acceptance.outcome === "duplicate" ? acceptance.runId : reservation?.admission.runId ?? runId;
1507
1727
  const acceptedRun = findRun(acceptedSnapshot.runs, assignedRunId);
1508
- outerAdmission.resolve({
1728
+ const actualInputMessageId = acceptance.outcome === "duplicate" ? acceptance.inputMessageId : userMessage2.id;
1729
+ const acceptedInput = acceptance.outcome === "duplicate" ? acceptance.input : acceptedSnapshot.messages.find((candidate) => candidate.id === actualInputMessageId);
1730
+ if (!acceptedInput) {
1731
+ throw new AgentRuntimeConflictError("admission input projection");
1732
+ }
1733
+ const assistantPlaceholder = AgentAssistantPlaceholderSchema.parse({
1734
+ schemaVersion: 1,
1735
+ id: acceptedRun.assistantMessageId,
1736
+ conversationId: acceptedRun.conversationId,
1509
1737
  runId: acceptedRun.id,
1510
- assistantMessageId: acceptedRun.assistantMessageId,
1738
+ status: "pending",
1739
+ createdAt: acceptedRun.createdAt,
1740
+ updatedAt: acceptedRun.updatedAt
1741
+ });
1742
+ const acceptedAssistant = acceptance.outcome === "duplicate" ? acceptedSnapshot.messages.find((candidate) => candidate.id === acceptedRun.assistantMessageId) ?? assistantPlaceholder : assistantPlaceholder;
1743
+ const admission = {
1744
+ inputMessageId: acceptedInput.id,
1745
+ runId: acceptedRun.id,
1746
+ assistantMessageId: assistantPlaceholder.id,
1747
+ input: acceptedInput,
1748
+ run: acceptedRun,
1749
+ assistant: acceptedAssistant,
1511
1750
  snapshotVersion: acceptedSnapshot.version
1751
+ };
1752
+ outerAdmission.resolve(admission);
1753
+ await publish({
1754
+ type: "admission",
1755
+ eventId: agentDurableEventId("admission", acceptedRun.id, acceptedSnapshot.version),
1756
+ conversationId: acceptedRun.conversationId,
1757
+ runId: acceptedRun.id,
1758
+ snapshotVersion: acceptedSnapshot.version,
1759
+ input: acceptedInput,
1760
+ run: acceptedRun,
1761
+ assistant: acceptedAssistant,
1762
+ emittedAt: now().toISOString()
1512
1763
  });
1513
1764
  await publish({
1514
1765
  type: "run-state",
1515
- eventId: generateId(),
1766
+ eventId: agentDurableEventId("run-state", acceptedRun.id, acceptedSnapshot.version),
1516
1767
  conversationId: acceptedRun.conversationId,
1517
1768
  runId: acceptedRun.id,
1518
1769
  snapshotVersion: acceptedSnapshot.version,
@@ -1597,34 +1848,7 @@ function createAgentRuntime(config) {
1597
1848
  })();
1598
1849
  return publicTicket;
1599
1850
  },
1600
- resume(rawInput) {
1601
- const context = config.protocol.parseContext(rawInput.context);
1602
- const accepted = Promise.withResolvers();
1603
- const result = Promise.withResolvers();
1604
- (async () => {
1605
- try {
1606
- const snapshot = await config.store.loadSnapshot(rawInput.conversationId);
1607
- const recoveredRun = findRun(snapshot.runs, rawInput.runId);
1608
- if (recoveredRun.state !== "queued") {
1609
- throw new Error("Only a queued recovered agent run can be resumed");
1610
- }
1611
- accepted.resolve();
1612
- const ticket = coordinator.submit({
1613
- key: rawInput.conversationKey ?? rawInput.conversationId,
1614
- policy: "queue",
1615
- create: (signal) => ({
1616
- runId: recoveredRun.id,
1617
- execute: () => executeRun({ acceptedRun: recoveredRun, context, signal })
1618
- })
1619
- });
1620
- ticket.result.then(result.resolve, result.reject);
1621
- } catch (error) {
1622
- accepted.reject(error);
1623
- result.reject(error);
1624
- }
1625
- })();
1626
- return { accepted: accepted.promise, result: result.promise };
1627
- },
1851
+ resume,
1628
1852
  async interrupt(input) {
1629
1853
  const snapshot = await config.store.loadSnapshot(input.conversationId);
1630
1854
  const run = findRun(snapshot.runs, input.runId);
@@ -1637,7 +1861,7 @@ function createAgentRuntime(config) {
1637
1861
  const interruptedRun = findRun(requested.snapshot.runs, input.runId);
1638
1862
  await publish({
1639
1863
  type: "run-state",
1640
- eventId: generateId(),
1864
+ eventId: agentDurableEventId("run-state", interruptedRun.id, requested.snapshot.version),
1641
1865
  conversationId: interruptedRun.conversationId,
1642
1866
  runId: interruptedRun.id,
1643
1867
  snapshotVersion: requested.snapshot.version,
@@ -1648,101 +1872,300 @@ function createAgentRuntime(config) {
1648
1872
  }
1649
1873
  return requested;
1650
1874
  },
1875
+ async recover(options) {
1876
+ if (!config.store.scanRecoverablePage) {
1877
+ throw new Error("The configured agent store does not support bounded recovery scans");
1878
+ }
1879
+ const pageSize = options.pageSize ?? 100;
1880
+ const maxRuns = options.maxRuns ?? 1000;
1881
+ if (!Number.isSafeInteger(pageSize) || pageSize < 1 || pageSize > 1000) {
1882
+ throw new TypeError("Recovery pageSize must be an integer between 1 and 1000");
1883
+ }
1884
+ if (!Number.isSafeInteger(maxRuns) || maxRuns < 1) {
1885
+ throw new TypeError("Recovery maxRuns must be a positive safe integer");
1886
+ }
1887
+ const outcomes = [];
1888
+ let cursor;
1889
+ while (outcomes.length < maxRuns && !options.signal?.aborted) {
1890
+ const page = await config.store.scanRecoverablePage({
1891
+ ...cursor && { cursor },
1892
+ limit: Math.min(pageSize, maxRuns - outcomes.length)
1893
+ });
1894
+ for (const item of page.items) {
1895
+ if (options.signal?.aborted)
1896
+ break;
1897
+ try {
1898
+ if (item.run.state === "queued") {
1899
+ const snapshot = await config.store.loadSnapshot(item.conversationId);
1900
+ const blockedByAcquiredPredecessor = snapshot.runs.some((run) => run.id !== item.run.id && (run.state === "running" || run.state === "interrupt_requested"));
1901
+ if (blockedByAcquiredPredecessor) {
1902
+ outcomes.push({
1903
+ conversationId: item.conversationId,
1904
+ runId: item.run.id,
1905
+ outcome: "skipped"
1906
+ });
1907
+ continue;
1908
+ }
1909
+ }
1910
+ const decision = await options.decide?.(item) ?? (item.run.state === "queued" ? { action: "resume" } : { action: "skip" });
1911
+ if (decision.action === "skip") {
1912
+ outcomes.push({
1913
+ conversationId: item.conversationId,
1914
+ runId: item.run.id,
1915
+ outcome: "skipped"
1916
+ });
1917
+ continue;
1918
+ }
1919
+ if (decision.action === "abandon") {
1920
+ const abandoned = await config.store.recoverRun({
1921
+ conversationId: item.conversationId,
1922
+ runId: item.run.id,
1923
+ expectedRevision: item.run.revision,
1924
+ action: "abandon"
1925
+ });
1926
+ if (abandoned.outcome !== "applied") {
1927
+ throw new AgentRuntimeConflictError("recovery abandon");
1928
+ }
1929
+ outcomes.push({
1930
+ conversationId: item.conversationId,
1931
+ runId: item.run.id,
1932
+ outcome: "abandoned"
1933
+ });
1934
+ continue;
1935
+ }
1936
+ if (decision.action === "requeue") {
1937
+ const requeued = await config.store.recoverRun({
1938
+ conversationId: item.conversationId,
1939
+ runId: item.run.id,
1940
+ expectedRevision: item.run.revision,
1941
+ action: "requeue",
1942
+ replaySafe: true
1943
+ });
1944
+ if (requeued.outcome !== "applied") {
1945
+ throw new AgentRuntimeConflictError("recovery requeue");
1946
+ }
1947
+ }
1948
+ const context = await options.resolveContext(item);
1949
+ const resumed = resume({
1950
+ conversationId: item.conversationId,
1951
+ runId: item.run.id,
1952
+ context
1953
+ });
1954
+ resumed.result.catch(() => {
1955
+ return;
1956
+ });
1957
+ await resumed.accepted;
1958
+ outcomes.push({
1959
+ conversationId: item.conversationId,
1960
+ runId: item.run.id,
1961
+ outcome: decision.action === "requeue" ? "requeued" : "resumed"
1962
+ });
1963
+ } catch (error) {
1964
+ outcomes.push({
1965
+ conversationId: item.conversationId,
1966
+ runId: item.run.id,
1967
+ outcome: "failed",
1968
+ error
1969
+ });
1970
+ }
1971
+ }
1972
+ cursor = page.nextCursor;
1973
+ if (!cursor || page.items.length === 0)
1974
+ break;
1975
+ }
1976
+ return outcomes;
1977
+ },
1651
1978
  stop: (conversationKey, reason) => coordinator.stop(conversationKey, reason),
1652
1979
  close: (options) => coordinator.close(options)
1653
1980
  };
1654
1981
  }
1655
1982
  // src/agent-runtime/store.ts
1656
- import { z as z7 } from "zod";
1657
- var AgentStoreConflictSchema = z7.object({
1658
- outcome: z7.literal("conflict"),
1983
+ import { z as z6 } from "zod";
1984
+ var AgentStoreConflictSchema = z6.object({
1985
+ outcome: z6.literal("conflict"),
1659
1986
  actualVersion: AgentRecordVersionSchema
1660
1987
  });
1661
- var AgentStoreNotFoundSchema = z7.object({
1662
- outcome: z7.literal("not_found")
1663
- });
1664
- var AgentStoreAppliedSchema = z7.object({
1665
- outcome: z7.literal("applied"),
1988
+ var AgentStoreNotFoundSchema = z6.object({ outcome: z6.literal("not_found") });
1989
+ var AgentStoreAppliedSchema = z6.object({
1990
+ outcome: z6.literal("applied"),
1666
1991
  snapshot: AgentSnapshotSchema
1667
1992
  });
1668
- var AgentStoreDuplicateSchema = z7.object({
1669
- outcome: z7.literal("duplicate"),
1993
+ var AgentStoreDuplicateSchema = z6.object({
1994
+ outcome: z6.literal("duplicate"),
1995
+ input: AgentMessageSchema,
1996
+ inputMessageId: AgentRecordIdSchema,
1670
1997
  runId: AgentRecordIdSchema,
1998
+ assistantMessageId: AgentRecordIdSchema,
1671
1999
  snapshot: AgentSnapshotSchema
1672
2000
  });
1673
- var AgentStoreMutationResultSchema = z7.discriminatedUnion("outcome", [
2001
+ var AgentStoreMutationResultSchema = z6.discriminatedUnion("outcome", [
1674
2002
  AgentStoreAppliedSchema,
1675
2003
  AgentStoreDuplicateSchema,
1676
2004
  AgentStoreConflictSchema,
1677
2005
  AgentStoreNotFoundSchema
1678
2006
  ]);
1679
- var AcceptInputAndAssignRunSchema = z7.object({
1680
- idempotencyKey: z7.string().min(1),
2007
+ var AcceptInputAndAssignRunSchema = z6.object({
2008
+ idempotencyKey: z6.string().min(1),
1681
2009
  expectedVersion: AgentRecordVersionSchema.optional(),
1682
2010
  input: AgentMessageSchema,
1683
2011
  run: AgentRunSchema,
1684
2012
  coalesceIntoRunId: AgentRecordIdSchema.optional()
1685
2013
  });
1686
- var AcquireAgentRunSchema = z7.object({
2014
+ var AcquireAgentRunSchema = z6.object({
1687
2015
  conversationId: AgentRecordIdSchema,
1688
2016
  runId: AgentRecordIdSchema,
1689
2017
  expectedRevision: AgentRecordVersionSchema,
1690
- ownerId: z7.string().min(1)
2018
+ ownerId: z6.string().min(1)
1691
2019
  });
1692
- var CheckpointRunAssistantSchema = z7.object({
2020
+ var CheckpointRunAssistantSchema = z6.object({
1693
2021
  conversationId: AgentRecordIdSchema,
1694
2022
  runId: AgentRecordIdSchema,
1695
2023
  expectedRevision: AgentRecordVersionSchema,
1696
- ownerId: z7.string().min(1),
2024
+ ownerId: z6.string().min(1),
2025
+ fencingToken: AgentRecordVersionSchema.optional(),
1697
2026
  assistant: AgentMessageSchema
1698
2027
  });
1699
- var CommitRunTerminalSchema = z7.object({
2028
+ var CommitRunTerminalSchema = z6.object({
1700
2029
  conversationId: AgentRecordIdSchema,
1701
2030
  runId: AgentRecordIdSchema,
1702
2031
  expectedRevision: AgentRecordVersionSchema,
1703
- ownerId: z7.string().min(1),
2032
+ ownerId: z6.string().min(1),
2033
+ fencingToken: AgentRecordVersionSchema.optional(),
1704
2034
  assistant: AgentMessageSchema,
1705
2035
  reason: AgentTerminalReasonSchema,
1706
- policyName: z7.string().min(1).optional()
2036
+ policyName: z6.string().min(1).optional()
1707
2037
  });
1708
- var RequestRunInterruptSchema = z7.object({
2038
+ var RequestRunInterruptSchema = z6.object({
1709
2039
  conversationId: AgentRecordIdSchema,
1710
2040
  runId: AgentRecordIdSchema,
1711
2041
  expectedRevision: AgentRecordVersionSchema
1712
2042
  });
1713
- var RecoverAgentRunSchema = z7.object({
2043
+ var RecoverAgentRunSchema = z6.object({
1714
2044
  conversationId: AgentRecordIdSchema,
1715
2045
  runId: AgentRecordIdSchema,
1716
2046
  expectedRevision: AgentRecordVersionSchema,
1717
- action: z7.enum(["requeue", "abandon"]),
1718
- replaySafe: z7.boolean().optional()
2047
+ action: z6.enum(["requeue", "abandon"]),
2048
+ replaySafe: z6.boolean().optional()
1719
2049
  });
1720
- var ReplaceCompactedRangeSchema = z7.object({
2050
+ var ReplaceCompactedRangeSchema = z6.object({
1721
2051
  conversationId: AgentRecordIdSchema,
1722
2052
  expectedVersion: AgentRecordVersionSchema,
1723
- replacedMessageIds: z7.array(AgentRecordIdSchema).min(1),
2053
+ replacedMessageIds: z6.array(AgentRecordIdSchema).min(1),
1724
2054
  summary: AgentMessageSchema
1725
2055
  });
1726
- function emptySnapshot(conversationId) {
1727
- return AgentSnapshotSchema.parse({
2056
+ // src/agent-runtime/store-driver.ts
2057
+ import { z as z7 } from "zod";
2058
+ var AgentAdmissionIdentitySchema = z7.object({
2059
+ idempotencyKey: z7.string().min(1),
2060
+ inputMessageId: AgentRecordIdSchema,
2061
+ runId: AgentRecordIdSchema,
2062
+ assistantMessageId: AgentRecordIdSchema
2063
+ });
2064
+ var AgentStoredStateSchema = z7.object({
2065
+ schemaVersion: z7.literal(1),
2066
+ conversationId: AgentRecordIdSchema,
2067
+ version: AgentRecordVersionSchema,
2068
+ runs: z7.array(AgentRunSchema),
2069
+ admissions: z7.array(AgentAdmissionIdentitySchema)
2070
+ });
2071
+ var AgentHistoryMutationSchema = z7.discriminatedUnion("type", [
2072
+ z7.object({ type: z7.literal("admit"), input: AgentMessageSchema }),
2073
+ z7.object({
2074
+ type: z7.literal("upsert-assistant"),
2075
+ message: AgentMessageSchema
2076
+ }),
2077
+ z7.object({
2078
+ type: z7.literal("replace-compacted-range"),
2079
+ replacedMessageIds: z7.array(AgentRecordIdSchema).min(1),
2080
+ summary: AgentMessageSchema
2081
+ })
2082
+ ]);
2083
+ var AgentRecoverableDescriptorSchema = z7.object({
2084
+ conversationId: AgentRecordIdSchema,
2085
+ run: AgentRunSchema
2086
+ });
2087
+ var AgentRecoverablePageSchema = z7.object({
2088
+ items: z7.array(AgentRecoverableDescriptorSchema),
2089
+ nextCursor: z7.string().min(1).optional()
2090
+ });
2091
+ var AgentRecoverableScanInputSchema = z7.object({
2092
+ cursor: z7.string().min(1).optional(),
2093
+ limit: z7.number().int().min(1).max(1000)
2094
+ });
2095
+ function emptyState(conversationId) {
2096
+ return AgentStoredStateSchema.parse({
1728
2097
  schemaVersion: 1,
1729
2098
  conversationId,
1730
2099
  version: 0,
1731
- messages: [],
1732
- runs: []
2100
+ runs: [],
2101
+ admissions: []
2102
+ });
2103
+ }
2104
+ function snapshotOf(state, messages) {
2105
+ validateAggregate(state, messages);
2106
+ return AgentSnapshotSchema.parse({
2107
+ schemaVersion: 1,
2108
+ conversationId: state.conversationId,
2109
+ version: state.version,
2110
+ messages,
2111
+ runs: state.runs
1733
2112
  });
1734
2113
  }
1735
- function cloneSnapshot(snapshot) {
1736
- return AgentSnapshotSchema.parse(structuredClone(snapshot));
2114
+ function validateAggregate(state, messages) {
2115
+ const runIds = new Set;
2116
+ const assistantIds = new Set;
2117
+ const messageIds = new Set;
2118
+ const idempotencyKeys = new Set;
2119
+ const admittedInputIds = new Set;
2120
+ for (const run of state.runs) {
2121
+ if (run.conversationId !== state.conversationId || runIds.has(run.id) || assistantIds.has(run.assistantMessageId)) {
2122
+ throw new TypeError("Stored agent state contains inconsistent run identities");
2123
+ }
2124
+ runIds.add(run.id);
2125
+ assistantIds.add(run.assistantMessageId);
2126
+ }
2127
+ for (const message of messages) {
2128
+ if (message.conversationId !== state.conversationId || messageIds.has(message.id)) {
2129
+ throw new TypeError("Stored agent history contains inconsistent message identities");
2130
+ }
2131
+ messageIds.add(message.id);
2132
+ if (assistantIds.has(message.id) && message.runId === undefined) {
2133
+ throw new TypeError("Stored history occupies a reserved assistant identity");
2134
+ }
2135
+ if (message.runId !== undefined) {
2136
+ const run = state.runs.find((candidate) => candidate.id === message.runId);
2137
+ if (!run || message.role !== "assistant" || run.assistantMessageId !== message.id) {
2138
+ throw new TypeError("Stored assistant history does not match its reserved run identity");
2139
+ }
2140
+ }
2141
+ }
2142
+ for (const admission of state.admissions) {
2143
+ const run = state.runs.find((candidate) => candidate.id === admission.runId);
2144
+ if (idempotencyKeys.has(admission.idempotencyKey) || admittedInputIds.has(admission.inputMessageId) || !run || run.assistantMessageId !== admission.assistantMessageId || !run.inputMessageIds.includes(admission.inputMessageId)) {
2145
+ throw new TypeError("Stored admission identity is inconsistent with its assigned run");
2146
+ }
2147
+ idempotencyKeys.add(admission.idempotencyKey);
2148
+ admittedInputIds.add(admission.inputMessageId);
2149
+ }
2150
+ }
2151
+ function recoverableDescriptors(state) {
2152
+ return state.runs.filter((run) => ["queued", "running", "interrupt_requested"].includes(run.state)).map((run) => ({ conversationId: state.conversationId, run }));
2153
+ }
2154
+ var RecoverableCursorSchema = z7.tuple([AgentRecordIdSchema, AgentRecordIdSchema]);
2155
+ function recoverableCursor(input) {
2156
+ return JSON.stringify([input.conversationId, input.run.id]);
2157
+ }
2158
+ function parseRecoverableCursor(cursor) {
2159
+ return RecoverableCursorSchema.parse(JSON.parse(cursor));
1737
2160
  }
1738
2161
  function replaceRun(runs, next) {
1739
2162
  return runs.map((run) => run.id === next.id ? next : run);
1740
2163
  }
1741
2164
  function replaceMessage(messages, next) {
1742
- const exists = messages.some((message) => message.id === next.id);
1743
- if (!exists)
1744
- return [...messages, next];
1745
- return messages.map((message) => message.id === next.id ? next : message);
2165
+ return messages.some((message) => message.id === next.id) ? messages.map((message) => message.id === next.id ? next : message) : [...messages, next];
2166
+ }
2167
+ function conflict(actualVersion) {
2168
+ return { outcome: "conflict", actualVersion };
1746
2169
  }
1747
2170
  function terminalState(reason) {
1748
2171
  if (reason === "success" || reason === "policy_stop")
@@ -1764,217 +2187,427 @@ function terminalMessageStatus(reason) {
1764
2187
  }
1765
2188
  return "failed";
1766
2189
  }
1767
- function createMemoryAgentRuntimeStore() {
1768
- const conversations = new Map;
1769
- const get = (conversationId) => {
1770
- const existing = conversations.get(conversationId);
1771
- if (existing)
1772
- return existing;
1773
- const created = { snapshot: emptySnapshot(conversationId), idempotency: new Map };
1774
- conversations.set(conversationId, created);
1775
- return created;
1776
- };
1777
- const conflict = (actualVersion) => ({
1778
- outcome: "conflict",
1779
- actualVersion
1780
- });
1781
- const apply = (entry, snapshot) => {
1782
- entry.snapshot = AgentSnapshotSchema.parse(snapshot);
1783
- return { outcome: "applied", snapshot: cloneSnapshot(entry.snapshot) };
1784
- };
2190
+ function applied(current, admissions, input, historyMutation) {
1785
2191
  return {
1786
- async loadSnapshot(conversationId) {
1787
- return cloneSnapshot(get(conversationId).snapshot);
1788
- },
1789
- async acceptInputAndAssignRun(rawInput) {
1790
- const input = AcceptInputAndAssignRunSchema.parse(rawInput);
1791
- const entry = get(input.input.conversationId);
1792
- const duplicateRunId = entry.idempotency.get(input.idempotencyKey);
1793
- if (duplicateRunId !== undefined) {
1794
- return {
1795
- outcome: "duplicate",
1796
- runId: duplicateRunId,
1797
- snapshot: cloneSnapshot(entry.snapshot)
1798
- };
1799
- }
1800
- if (input.expectedVersion !== undefined && input.expectedVersion !== entry.snapshot.version) {
1801
- return conflict(entry.snapshot.version);
1802
- }
1803
- const coalescedRun = input.coalesceIntoRunId ? entry.snapshot.runs.find((candidate) => candidate.id === input.coalesceIntoRunId) : undefined;
1804
- if (input.coalesceIntoRunId !== undefined && (!coalescedRun || coalescedRun.conversationId !== input.input.conversationId || coalescedRun.state !== "queued" || coalescedRun.ownerId !== undefined || coalescedRun.terminalReason !== undefined)) {
1805
- return coalescedRun ? conflict(coalescedRun.revision) : { outcome: "not_found" };
1806
- }
1807
- if (input.run.conversationId !== input.input.conversationId || input.run.inputMessageIds.length !== 1 || input.run.inputMessageIds[0] !== input.input.id || input.run.state !== "queued" || input.run.revision !== 0 || input.run.ownerId !== undefined || input.run.terminalReason !== undefined || input.run.terminalPolicyName !== undefined || input.input.role !== "user" || input.input.status !== "committed" || input.input.runId !== undefined || entry.snapshot.messages.some((message) => message.id === input.input.id) || !coalescedRun && (input.run.assistantMessageId === input.input.id || entry.snapshot.runs.some((candidate) => candidate.id === input.run.id) || entry.snapshot.runs.some((candidate) => candidate.assistantMessageId === input.run.assistantMessageId) || entry.snapshot.messages.some((message) => message.id === input.run.assistantMessageId))) {
1808
- throw new TypeError("Input and queued run do not form one valid assignment");
2192
+ outcome: "applied",
2193
+ snapshot: AgentSnapshotSchema.parse({
2194
+ ...current,
2195
+ version: current.version + 1,
2196
+ runs: input.runs ?? current.runs,
2197
+ messages: input.messages ?? current.messages
2198
+ }),
2199
+ admissions,
2200
+ ...historyMutation && { historyMutation }
2201
+ };
2202
+ }
2203
+ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2204
+ if (operation.type === "accept") {
2205
+ const input = operation.input;
2206
+ const duplicate = currentAdmissions.find((candidate) => candidate.idempotencyKey === input.idempotencyKey);
2207
+ if (duplicate) {
2208
+ if (!duplicateInput) {
2209
+ throw new Error("Duplicate admission input is unavailable from canonical history");
1809
2210
  }
1810
- const assignedRun = coalescedRun ? AgentRunSchema.parse({
1811
- ...coalescedRun,
1812
- inputMessageIds: [...coalescedRun.inputMessageIds, input.input.id],
1813
- revision: coalescedRun.revision + 1,
2211
+ return {
2212
+ outcome: "duplicate",
2213
+ input: duplicateInput,
2214
+ inputMessageId: duplicate.inputMessageId,
2215
+ runId: duplicate.runId,
2216
+ assistantMessageId: duplicate.assistantMessageId,
2217
+ snapshot: current
2218
+ };
2219
+ }
2220
+ if (input.expectedVersion !== undefined && input.expectedVersion !== current.version) {
2221
+ return conflict(current.version);
2222
+ }
2223
+ const coalescedRun = input.coalesceIntoRunId ? current.runs.find((candidate) => candidate.id === input.coalesceIntoRunId) : undefined;
2224
+ if (input.coalesceIntoRunId !== undefined && (!coalescedRun || coalescedRun.conversationId !== input.input.conversationId || coalescedRun.state !== "queued" || coalescedRun.ownerId !== undefined || coalescedRun.terminalReason !== undefined)) {
2225
+ return coalescedRun ? conflict(coalescedRun.revision) : { outcome: "not_found" };
2226
+ }
2227
+ if (input.run.conversationId !== input.input.conversationId || input.run.inputMessageIds.length !== 1 || input.run.inputMessageIds[0] !== input.input.id || input.run.state !== "queued" || input.run.revision !== 0 || input.run.ownerId !== undefined || input.run.terminalReason !== undefined || input.run.terminalPolicyName !== undefined || input.input.role !== "user" || input.input.status !== "committed" || input.input.runId !== undefined || current.messages.some((message) => message.id === input.input.id) || coalescedRun !== undefined && input.input.id === coalescedRun.assistantMessageId || !coalescedRun && (input.run.assistantMessageId === input.input.id || current.runs.some((candidate) => candidate.id === input.run.id) || current.runs.some((candidate) => candidate.assistantMessageId === input.run.assistantMessageId) || current.messages.some((message) => message.id === input.run.assistantMessageId))) {
2228
+ throw new TypeError("Input and queued run do not form one valid assignment");
2229
+ }
2230
+ const assignedRun = coalescedRun ? AgentRunSchema.parse({
2231
+ ...coalescedRun,
2232
+ inputMessageIds: [...coalescedRun.inputMessageIds, input.input.id],
2233
+ revision: coalescedRun.revision + 1,
2234
+ updatedAt: new Date().toISOString()
2235
+ }) : input.run;
2236
+ const admission = AgentAdmissionIdentitySchema.parse({
2237
+ idempotencyKey: input.idempotencyKey,
2238
+ inputMessageId: input.input.id,
2239
+ runId: assignedRun.id,
2240
+ assistantMessageId: assignedRun.assistantMessageId
2241
+ });
2242
+ return applied(current, [...currentAdmissions, admission], {
2243
+ messages: [...current.messages, input.input],
2244
+ runs: coalescedRun ? replaceRun(current.runs, assignedRun) : [...current.runs, assignedRun]
2245
+ }, { type: "admit", input: input.input });
2246
+ }
2247
+ const conversationId = operation.input.conversationId;
2248
+ const run = operation.type === "compact" ? undefined : current.runs.find((candidate) => candidate.id === operation.input.runId);
2249
+ if (operation.type !== "compact" && !run)
2250
+ return { outcome: "not_found" };
2251
+ if (operation.type === "acquire" && run) {
2252
+ const runPosition = current.runs.findIndex((candidate) => candidate.id === run.id);
2253
+ const acquisitionBlocked = current.runs.some((candidate, index) => candidate.id !== run.id && (candidate.state === "running" || candidate.state === "interrupt_requested" || index < runPosition && candidate.state === "queued"));
2254
+ if (run.revision !== operation.input.expectedRevision || run.state !== "queued" || acquisitionBlocked) {
2255
+ return conflict(run.revision);
2256
+ }
2257
+ const next = AgentRunSchema.parse({
2258
+ ...run,
2259
+ state: "running",
2260
+ ownerId: operation.input.ownerId,
2261
+ fencingToken: (run.fencingToken ?? 0) + 1,
2262
+ revision: run.revision + 1,
2263
+ updatedAt: new Date().toISOString()
2264
+ });
2265
+ return applied(current, currentAdmissions, {
2266
+ runs: replaceRun(current.runs, next)
2267
+ });
2268
+ }
2269
+ if (operation.type === "checkpoint" && run) {
2270
+ const input = operation.input;
2271
+ if (run.revision !== input.expectedRevision || run.state !== "running" || run.ownerId !== input.ownerId || input.fencingToken !== undefined && run.fencingToken !== input.fencingToken || input.assistant.runId !== run.id || input.assistant.id !== run.assistantMessageId || input.assistant.conversationId !== run.conversationId || input.assistant.role !== "assistant" || input.assistant.status !== "streaming") {
2272
+ return conflict(run.revision);
2273
+ }
2274
+ const next = AgentRunSchema.parse({
2275
+ ...run,
2276
+ revision: run.revision + 1,
2277
+ updatedAt: new Date().toISOString()
2278
+ });
2279
+ return applied(current, currentAdmissions, {
2280
+ runs: replaceRun(current.runs, next),
2281
+ messages: replaceMessage(current.messages, input.assistant)
2282
+ }, { type: "upsert-assistant", message: input.assistant });
2283
+ }
2284
+ if (operation.type === "interrupt" && run) {
2285
+ if (run.revision !== operation.input.expectedRevision || run.state !== "running") {
2286
+ return conflict(run.revision);
2287
+ }
2288
+ const next = AgentRunSchema.parse({
2289
+ ...run,
2290
+ state: "interrupt_requested",
2291
+ revision: run.revision + 1,
2292
+ updatedAt: new Date().toISOString()
2293
+ });
2294
+ return applied(current, currentAdmissions, {
2295
+ runs: replaceRun(current.runs, next)
2296
+ });
2297
+ }
2298
+ if (operation.type === "recover" && run) {
2299
+ const input = operation.input;
2300
+ if (run.revision !== input.expectedRevision || !["queued", "running", "interrupt_requested"].includes(run.state)) {
2301
+ return conflict(run.revision);
2302
+ }
2303
+ if (input.action === "requeue" && run.state !== "queued" && input.replaySafe !== true) {
2304
+ throw new TypeError("Recovering an acquired run requires explicit replaySafe evidence");
2305
+ }
2306
+ const next = AgentRunSchema.parse({
2307
+ schemaVersion: 1,
2308
+ id: run.id,
2309
+ conversationId: run.conversationId,
2310
+ inputMessageIds: run.inputMessageIds,
2311
+ assistantMessageId: run.assistantMessageId,
2312
+ state: input.action === "requeue" ? "queued" : "abandoned",
2313
+ revision: run.revision + 1,
2314
+ ...input.action === "abandon" && { terminalReason: "abandoned" },
2315
+ createdAt: run.createdAt,
2316
+ updatedAt: new Date().toISOString()
2317
+ });
2318
+ if (input.action === "abandon") {
2319
+ const existingAssistant = current.messages.find((message) => message.id === run.assistantMessageId);
2320
+ const assistant = AgentMessageSchema.parse({
2321
+ ...existingAssistant ?? {
2322
+ schemaVersion: 1,
2323
+ id: run.assistantMessageId,
2324
+ conversationId: run.conversationId,
2325
+ runId: run.id,
2326
+ role: "assistant",
2327
+ parts: [],
2328
+ createdAt: run.createdAt
2329
+ },
2330
+ status: "failed",
1814
2331
  updatedAt: new Date().toISOString()
1815
- }) : input.run;
1816
- const next = AgentSnapshotSchema.parse({
1817
- ...entry.snapshot,
1818
- version: entry.snapshot.version + 1,
1819
- messages: [...entry.snapshot.messages, input.input],
1820
- runs: coalescedRun ? replaceRun(entry.snapshot.runs, assignedRun) : [...entry.snapshot.runs, assignedRun]
1821
2332
  });
1822
- entry.idempotency.set(input.idempotencyKey, assignedRun.id);
1823
- return apply(entry, next);
2333
+ return applied(current, currentAdmissions, {
2334
+ runs: replaceRun(current.runs, next),
2335
+ messages: replaceMessage(current.messages, assistant)
2336
+ }, { type: "upsert-assistant", message: assistant });
2337
+ }
2338
+ return applied(current, currentAdmissions, {
2339
+ runs: replaceRun(current.runs, next)
2340
+ });
2341
+ }
2342
+ if (operation.type === "terminal" && run) {
2343
+ const input = operation.input;
2344
+ if (run.revision !== input.expectedRevision || run.state !== "running" && run.state !== "interrupt_requested" || run.ownerId !== input.ownerId || input.fencingToken !== undefined && run.fencingToken !== input.fencingToken || input.assistant.runId !== run.id || input.assistant.id !== run.assistantMessageId || input.assistant.conversationId !== run.conversationId || input.assistant.role !== "assistant" || input.assistant.status !== terminalMessageStatus(input.reason)) {
2345
+ return conflict(run.revision);
2346
+ }
2347
+ const next = AgentRunSchema.parse({
2348
+ ...run,
2349
+ state: terminalState(input.reason),
2350
+ terminalReason: input.reason,
2351
+ ...input.policyName && { terminalPolicyName: input.policyName },
2352
+ revision: run.revision + 1,
2353
+ updatedAt: new Date().toISOString()
2354
+ });
2355
+ return applied(current, currentAdmissions, {
2356
+ runs: replaceRun(current.runs, next),
2357
+ messages: replaceMessage(current.messages, input.assistant)
2358
+ }, { type: "upsert-assistant", message: input.assistant });
2359
+ }
2360
+ if (operation.type === "compact") {
2361
+ const input = operation.input;
2362
+ if (current.conversationId !== conversationId)
2363
+ return { outcome: "not_found" };
2364
+ if (current.version !== input.expectedVersion)
2365
+ return conflict(current.version);
2366
+ const replaced = new Set(input.replacedMessageIds);
2367
+ if (!input.replacedMessageIds.every((id) => current.messages.some((m) => m.id === id))) {
2368
+ return { outcome: "not_found" };
2369
+ }
2370
+ const positions = current.messages.map((message, index) => replaced.has(message.id) ? index : undefined).filter((index) => index !== undefined);
2371
+ const first = positions[0];
2372
+ if (first === undefined || positions.some((position, offset) => position !== first + offset) || input.summary.conversationId !== input.conversationId || input.summary.runId !== undefined || input.summary.role !== "summary" || input.summary.status !== "committed" || current.messages.some((message) => message.id === input.summary.id) || current.runs.some((candidate) => candidate.assistantMessageId === input.summary.id)) {
2373
+ throw new TypeError("Compaction replacement must be one valid contiguous history range");
2374
+ }
2375
+ const messages = [
2376
+ ...current.messages.slice(0, first),
2377
+ input.summary,
2378
+ ...current.messages.slice(first + positions.length)
2379
+ ];
2380
+ return applied(current, currentAdmissions, { messages }, {
2381
+ type: "replace-compacted-range",
2382
+ replacedMessageIds: input.replacedMessageIds,
2383
+ summary: input.summary
2384
+ });
2385
+ }
2386
+ return { outcome: "not_found" };
2387
+ }
2388
+ function operationConversationId(operation) {
2389
+ return operation.type === "accept" ? operation.input.input.conversationId : operation.input.conversationId;
2390
+ }
2391
+ function createAgentRuntimeStore(driver) {
2392
+ const loadSnapshot = (conversationId) => driver.transaction(async (transaction) => {
2393
+ const [stored, messages] = await Promise.all([
2394
+ driver.state.load(transaction, conversationId),
2395
+ driver.history.load(transaction, conversationId)
2396
+ ]);
2397
+ return snapshotOf(stored ?? emptyState(conversationId), messages);
2398
+ });
2399
+ const mutate = (operation) => driver.transaction(async (transaction) => {
2400
+ const conversationId = operationConversationId(operation);
2401
+ const [stored, messages] = await Promise.all([
2402
+ driver.state.load(transaction, conversationId),
2403
+ driver.history.load(transaction, conversationId)
2404
+ ]);
2405
+ const state = AgentStoredStateSchema.parse(stored ?? emptyState(conversationId));
2406
+ const current = snapshotOf(state, messages);
2407
+ const duplicateIdentity = operation.type === "accept" ? state.admissions.find((candidate) => candidate.idempotencyKey === operation.input.idempotencyKey) : undefined;
2408
+ const duplicateInput = duplicateIdentity ? await driver.history.loadById(transaction, {
2409
+ conversationId,
2410
+ messageId: duplicateIdentity.inputMessageId
2411
+ }) : undefined;
2412
+ if (duplicateIdentity && duplicateInput && (duplicateInput.id !== duplicateIdentity.inputMessageId || duplicateInput.conversationId !== conversationId || duplicateInput.role !== "user" || duplicateInput.status !== "committed" || duplicateInput.runId !== undefined)) {
2413
+ throw new TypeError("Canonical duplicate input does not match its admission identity");
2414
+ }
2415
+ const reduced = reduceStore(current, state.admissions, operation, duplicateInput);
2416
+ if (reduced.outcome !== "applied")
2417
+ return reduced;
2418
+ const nextState = AgentStoredStateSchema.parse({
2419
+ schemaVersion: 1,
2420
+ conversationId,
2421
+ version: reduced.snapshot.version,
2422
+ runs: reduced.snapshot.runs,
2423
+ admissions: reduced.admissions
2424
+ });
2425
+ const outcome = await driver.state.compareAndSwap(transaction, {
2426
+ conversationId,
2427
+ expectedVersion: current.version,
2428
+ next: nextState,
2429
+ recoverable: recoverableDescriptors(nextState)
2430
+ });
2431
+ if (outcome.outcome === "conflict")
2432
+ return conflict(outcome.actualVersion);
2433
+ if (reduced.historyMutation) {
2434
+ await driver.history.apply(transaction, reduced.historyMutation);
2435
+ }
2436
+ return { outcome: "applied", snapshot: reduced.snapshot };
2437
+ });
2438
+ return {
2439
+ loadSnapshot,
2440
+ acceptInputAndAssignRun: (input) => mutate({
2441
+ type: "accept",
2442
+ input: AcceptInputAndAssignRunSchema.parse(input)
2443
+ }),
2444
+ acquireRun: (input) => mutate({ type: "acquire", input: AcquireAgentRunSchema.parse(input) }),
2445
+ checkpointRunAssistant: (input) => mutate({
2446
+ type: "checkpoint",
2447
+ input: CheckpointRunAssistantSchema.parse(input)
2448
+ }),
2449
+ requestRunInterrupt: (input) => mutate({
2450
+ type: "interrupt",
2451
+ input: RequestRunInterruptSchema.parse(input)
2452
+ }),
2453
+ recoverRun: (input) => mutate({ type: "recover", input: RecoverAgentRunSchema.parse(input) }),
2454
+ commitRunTerminal: (input) => mutate({ type: "terminal", input: CommitRunTerminalSchema.parse(input) }),
2455
+ replaceCompactedRange: (input) => mutate({
2456
+ type: "compact",
2457
+ input: ReplaceCompactedRangeSchema.parse(input)
2458
+ }),
2459
+ async scanRecoverable() {
2460
+ const snapshots = [];
2461
+ const seenConversationIds = new Set;
2462
+ let cursor;
2463
+ do {
2464
+ const page = AgentRecoverablePageSchema.parse(await driver.scanRecoverable({
2465
+ ...cursor && { cursor },
2466
+ limit: 100
2467
+ }));
2468
+ const conversationIds = [
2469
+ ...new Set(page.items.map((item) => item.conversationId))
2470
+ ].filter((conversationId) => !seenConversationIds.has(conversationId));
2471
+ for (const conversationId of conversationIds)
2472
+ seenConversationIds.add(conversationId);
2473
+ snapshots.push(...await Promise.all(conversationIds.map(loadSnapshot)));
2474
+ cursor = page.nextCursor;
2475
+ } while (cursor !== undefined);
2476
+ return snapshots;
1824
2477
  },
1825
- async acquireRun(rawInput) {
1826
- const input = AcquireAgentRunSchema.parse(rawInput);
1827
- const entry = get(input.conversationId);
1828
- const run = entry.snapshot.runs.find((candidate) => candidate.id === input.runId);
1829
- if (!run)
1830
- return { outcome: "not_found" };
1831
- if (run.revision !== input.expectedRevision || run.state !== "queued") {
1832
- return conflict(run.revision);
1833
- }
1834
- const nextRun = AgentRunSchema.parse({
1835
- ...run,
1836
- state: "running",
1837
- ownerId: input.ownerId,
1838
- revision: run.revision + 1,
1839
- updatedAt: new Date().toISOString()
2478
+ async scanRecoverablePage(input) {
2479
+ const parsed = AgentRecoverableScanInputSchema.parse(input);
2480
+ return AgentRecoverablePageSchema.parse(await driver.scanRecoverable(parsed));
2481
+ }
2482
+ };
2483
+ }
2484
+ function cloneStateMap(source) {
2485
+ return new Map([...source].map(([key, value]) => [
2486
+ key,
2487
+ AgentStoredStateSchema.parse(structuredClone(value))
2488
+ ]));
2489
+ }
2490
+ function cloneHistoryMap(source) {
2491
+ return new Map([...source].map(([key, value]) => [
2492
+ key,
2493
+ value.map((message) => AgentMessageSchema.parse(structuredClone(message)))
2494
+ ]));
2495
+ }
2496
+ function createMemoryAgentRuntimeStore() {
2497
+ let states = new Map;
2498
+ let histories = new Map;
2499
+ let archivedMessages = new Map;
2500
+ let transactionTail = Promise.resolve();
2501
+ const driver = {
2502
+ async transaction(work) {
2503
+ const previous = transactionTail;
2504
+ const release = Promise.withResolvers();
2505
+ transactionTail = previous.catch(() => {
2506
+ return;
2507
+ }).then(() => release.promise);
2508
+ await previous.catch(() => {
2509
+ return;
1840
2510
  });
1841
- return apply(entry, AgentSnapshotSchema.parse({
1842
- ...entry.snapshot,
1843
- version: entry.snapshot.version + 1,
1844
- runs: replaceRun(entry.snapshot.runs, nextRun)
1845
- }));
1846
- },
1847
- async checkpointRunAssistant(rawInput) {
1848
- const input = CheckpointRunAssistantSchema.parse(rawInput);
1849
- const entry = get(input.conversationId);
1850
- const run = entry.snapshot.runs.find((candidate) => candidate.id === input.runId);
1851
- if (!run)
1852
- return { outcome: "not_found" };
1853
- if (run.revision !== input.expectedRevision || run.state !== "running" || run.ownerId !== input.ownerId || input.assistant.runId !== run.id || input.assistant.id !== run.assistantMessageId || input.assistant.conversationId !== run.conversationId || input.assistant.role !== "assistant" || input.assistant.status !== "streaming") {
1854
- return conflict(run.revision);
2511
+ const transaction = {
2512
+ states: cloneStateMap(states),
2513
+ histories: cloneHistoryMap(histories),
2514
+ archivedMessages: new Map([...archivedMessages].map(([conversationId, messages]) => [
2515
+ conversationId,
2516
+ new Map([...messages].map(([messageId, message]) => [
2517
+ messageId,
2518
+ AgentMessageSchema.parse(structuredClone(message))
2519
+ ]))
2520
+ ]))
2521
+ };
2522
+ try {
2523
+ const result = await work(transaction);
2524
+ states = transaction.states;
2525
+ histories = transaction.histories;
2526
+ archivedMessages = transaction.archivedMessages;
2527
+ return result;
2528
+ } finally {
2529
+ release.resolve();
1855
2530
  }
1856
- const nextRun = AgentRunSchema.parse({
1857
- ...run,
1858
- revision: run.revision + 1,
1859
- updatedAt: new Date().toISOString()
1860
- });
1861
- return apply(entry, AgentSnapshotSchema.parse({
1862
- ...entry.snapshot,
1863
- version: entry.snapshot.version + 1,
1864
- messages: replaceMessage(entry.snapshot.messages, input.assistant),
1865
- runs: replaceRun(entry.snapshot.runs, nextRun)
1866
- }));
1867
2531
  },
1868
- async requestRunInterrupt(rawInput) {
1869
- const input = RequestRunInterruptSchema.parse(rawInput);
1870
- const entry = get(input.conversationId);
1871
- const run = entry.snapshot.runs.find((candidate) => candidate.id === input.runId);
1872
- if (!run)
1873
- return { outcome: "not_found" };
1874
- if (run.revision !== input.expectedRevision || run.state !== "running") {
1875
- return conflict(run.revision);
2532
+ state: {
2533
+ async load(transaction, conversationId) {
2534
+ const state = transaction.states.get(conversationId);
2535
+ return state ? AgentStoredStateSchema.parse(structuredClone(state)) : undefined;
2536
+ },
2537
+ async compareAndSwap(transaction, input) {
2538
+ const current = transaction.states.get(input.conversationId);
2539
+ const actualVersion = current?.version ?? 0;
2540
+ if (actualVersion !== input.expectedVersion) {
2541
+ return { outcome: "conflict", actualVersion };
2542
+ }
2543
+ transaction.states.set(input.conversationId, AgentStoredStateSchema.parse(structuredClone(input.next)));
2544
+ return { outcome: "applied" };
1876
2545
  }
1877
- const nextRun = AgentRunSchema.parse({
1878
- ...run,
1879
- state: "interrupt_requested",
1880
- revision: run.revision + 1,
1881
- updatedAt: new Date().toISOString()
1882
- });
1883
- return apply(entry, AgentSnapshotSchema.parse({
1884
- ...entry.snapshot,
1885
- version: entry.snapshot.version + 1,
1886
- runs: replaceRun(entry.snapshot.runs, nextRun)
1887
- }));
1888
2546
  },
1889
- async recoverRun(rawInput) {
1890
- const input = RecoverAgentRunSchema.parse(rawInput);
1891
- const entry = get(input.conversationId);
1892
- const run = entry.snapshot.runs.find((candidate) => candidate.id === input.runId);
1893
- if (!run)
1894
- return { outcome: "not_found" };
1895
- if (run.revision !== input.expectedRevision || !["queued", "running", "interrupt_requested"].includes(run.state)) {
1896
- return conflict(run.revision);
1897
- }
1898
- if (input.action === "requeue" && run.state !== "queued" && input.replaySafe !== true) {
1899
- throw new TypeError("Recovering an acquired run requires explicit replaySafe evidence");
2547
+ history: {
2548
+ async load(transaction, conversationId) {
2549
+ return (transaction.histories.get(conversationId) ?? []).map((message) => AgentMessageSchema.parse(structuredClone(message)));
2550
+ },
2551
+ async loadById(transaction, input) {
2552
+ const active = (transaction.histories.get(input.conversationId) ?? []).find((message2) => message2.id === input.messageId);
2553
+ const message = active ?? transaction.archivedMessages.get(input.conversationId)?.get(input.messageId);
2554
+ return message ? AgentMessageSchema.parse(structuredClone(message)) : undefined;
2555
+ },
2556
+ async apply(transaction, rawMutation) {
2557
+ const mutation = AgentHistoryMutationSchema.parse(rawMutation);
2558
+ const conversationId = mutation.type === "admit" ? mutation.input.conversationId : mutation.type === "upsert-assistant" ? mutation.message.conversationId : mutation.summary.conversationId;
2559
+ const current = transaction.histories.get(conversationId) ?? [];
2560
+ if (mutation.type === "admit") {
2561
+ transaction.histories.set(conversationId, [...current, mutation.input]);
2562
+ return;
2563
+ }
2564
+ if (mutation.type === "upsert-assistant") {
2565
+ transaction.histories.set(conversationId, replaceMessage(current, mutation.message));
2566
+ return;
2567
+ }
2568
+ const replaced = new Set(mutation.replacedMessageIds);
2569
+ const positions = current.map((message, index) => replaced.has(message.id) ? index : undefined).filter((index) => index !== undefined);
2570
+ const first = positions[0];
2571
+ if (first === undefined)
2572
+ throw new Error("Compaction history range disappeared");
2573
+ const archive = transaction.archivedMessages.get(conversationId) ?? new Map;
2574
+ for (const message of current.filter((candidate) => replaced.has(candidate.id))) {
2575
+ archive.set(message.id, AgentMessageSchema.parse(structuredClone(message)));
2576
+ }
2577
+ transaction.archivedMessages.set(conversationId, archive);
2578
+ transaction.histories.set(conversationId, [
2579
+ ...current.slice(0, first),
2580
+ mutation.summary,
2581
+ ...current.slice(first + positions.length)
2582
+ ]);
1900
2583
  }
1901
- const nextRun = AgentRunSchema.parse({
1902
- schemaVersion: 1,
1903
- id: run.id,
1904
- conversationId: run.conversationId,
1905
- inputMessageIds: run.inputMessageIds,
1906
- assistantMessageId: run.assistantMessageId,
1907
- state: input.action === "requeue" ? "queued" : "abandoned",
1908
- revision: run.revision + 1,
1909
- ...input.action === "abandon" && { terminalReason: "abandoned" },
1910
- createdAt: run.createdAt,
1911
- updatedAt: new Date().toISOString()
1912
- });
1913
- return apply(entry, AgentSnapshotSchema.parse({
1914
- ...entry.snapshot,
1915
- version: entry.snapshot.version + 1,
1916
- runs: replaceRun(entry.snapshot.runs, nextRun)
1917
- }));
1918
2584
  },
1919
- async commitRunTerminal(rawInput) {
1920
- const input = CommitRunTerminalSchema.parse(rawInput);
1921
- const entry = get(input.conversationId);
1922
- const run = entry.snapshot.runs.find((candidate) => candidate.id === input.runId);
1923
- if (!run)
1924
- return { outcome: "not_found" };
1925
- if (run.revision !== input.expectedRevision || run.state !== "running" && run.state !== "interrupt_requested" || run.ownerId !== input.ownerId || input.assistant.runId !== run.id || input.assistant.id !== run.assistantMessageId || input.assistant.conversationId !== run.conversationId || input.assistant.role !== "assistant" || input.assistant.status !== terminalMessageStatus(input.reason)) {
1926
- return conflict(run.revision);
1927
- }
1928
- const nextRun = AgentRunSchema.parse({
1929
- ...run,
1930
- state: terminalState(input.reason),
1931
- terminalReason: input.reason,
1932
- ...input.policyName && { terminalPolicyName: input.policyName },
1933
- revision: run.revision + 1,
1934
- updatedAt: new Date().toISOString()
2585
+ async scanRecoverable(input) {
2586
+ const descriptors = [...states.values()].flatMap((state) => state.runs.filter((run) => ["queued", "running", "interrupt_requested"].includes(run.state)).map((run) => ({ conversationId: state.conversationId, run }))).sort((left, right) => left.conversationId.localeCompare(right.conversationId) || left.run.id.localeCompare(right.run.id));
2587
+ const cursorTuple = input.cursor ? parseRecoverableCursor(input.cursor) : undefined;
2588
+ const start = cursorTuple ? descriptors.findIndex((item) => item.conversationId === cursorTuple[0] && item.run.id === cursorTuple[1]) + 1 : 0;
2589
+ const items = descriptors.slice(start, start + input.limit);
2590
+ const last = items.at(-1);
2591
+ const hasMore = start + items.length < descriptors.length;
2592
+ return AgentRecoverablePageSchema.parse({
2593
+ items,
2594
+ ...hasMore && last && { nextCursor: recoverableCursor(last) }
1935
2595
  });
1936
- return apply(entry, AgentSnapshotSchema.parse({
1937
- ...entry.snapshot,
1938
- version: entry.snapshot.version + 1,
1939
- messages: replaceMessage(entry.snapshot.messages, input.assistant),
1940
- runs: replaceRun(entry.snapshot.runs, nextRun)
1941
- }));
1942
- },
1943
- async replaceCompactedRange(rawInput) {
1944
- const input = ReplaceCompactedRangeSchema.parse(rawInput);
1945
- const entry = get(input.conversationId);
1946
- if (entry.snapshot.version !== input.expectedVersion) {
1947
- return conflict(entry.snapshot.version);
1948
- }
1949
- const replaced = new Set(input.replacedMessageIds);
1950
- if (!input.replacedMessageIds.every((id) => entry.snapshot.messages.some((m) => m.id === id))) {
1951
- return { outcome: "not_found" };
1952
- }
1953
- const positions = entry.snapshot.messages.map((message, index) => replaced.has(message.id) ? index : undefined).filter((index) => index !== undefined);
1954
- const first = positions[0];
1955
- if (first === undefined || positions.some((position, offset) => position !== first + offset) || input.summary.conversationId !== input.conversationId || input.summary.runId !== undefined || input.summary.role !== "summary" || input.summary.status !== "committed") {
1956
- throw new TypeError("Compaction replacement must be one valid contiguous history range");
1957
- }
1958
- const before = entry.snapshot.messages.slice(0, first);
1959
- const after = entry.snapshot.messages.slice(first + positions.length);
1960
- return apply(entry, AgentSnapshotSchema.parse({
1961
- ...entry.snapshot,
1962
- version: entry.snapshot.version + 1,
1963
- messages: [...before, input.summary, ...after]
1964
- }));
1965
- },
1966
- async scanRecoverable() {
1967
- return [...conversations.values()].filter((entry) => entry.snapshot.runs.some((run) => ["queued", "running", "interrupt_requested"].includes(run.state))).map((entry) => cloneSnapshot(entry.snapshot));
1968
2596
  }
1969
2597
  };
2598
+ return createAgentRuntimeStore(driver);
1970
2599
  }
1971
2600
  export {
1972
2601
  AcceptInputAndAssignRunSchema,
1973
2602
  AcquireAgentRunSchema,
2603
+ AgentAdmissionEventSchema,
2604
+ AgentAdmissionIdentitySchema,
2605
+ AgentAssistantPlaceholderSchema,
1974
2606
  AgentCheckpointEventSchema,
1975
2607
  AgentControlPartSchema,
1976
2608
  AgentCostValueSchema,
1977
2609
  AgentFilePartSchema,
2610
+ AgentHistoryMutationSchema,
1978
2611
  AgentJsonObjectSchema,
1979
2612
  AgentMessagePartSchema,
1980
2613
  AgentMessageRoleSchema,
@@ -1982,6 +2615,7 @@ export {
1982
2615
  AgentMessageStatusSchema,
1983
2616
  AgentModelCapabilitySchema,
1984
2617
  AgentModelDescriptorSchema,
2618
+ AgentModelRegistrySnapshotSchema,
1985
2619
  AgentOpaquePartSchema,
1986
2620
  AgentProviderEnvelopeSchema,
1987
2621
  AgentReasoningDeltaEventSchema,
@@ -1990,10 +2624,14 @@ export {
1990
2624
  AgentReasoningStartEventSchema,
1991
2625
  AgentRecordIdSchema,
1992
2626
  AgentRecordVersionSchema,
2627
+ AgentRecoverableDescriptorSchema,
2628
+ AgentRecoverablePageSchema,
1993
2629
  AgentRunEventSchema,
2630
+ AgentRunMetricsSchema,
1994
2631
  AgentRunSchema,
1995
2632
  AgentRunStateEventSchema,
1996
2633
  AgentRunStateSchema,
2634
+ AgentRuntimeEventCursorSchema,
1997
2635
  AgentRuntimeEventSchema,
1998
2636
  AgentSnapshotSchema,
1999
2637
  AgentSourcePartSchema,
@@ -2002,6 +2640,7 @@ export {
2002
2640
  AgentStoreDuplicateSchema,
2003
2641
  AgentStoreMutationResultSchema,
2004
2642
  AgentStoreNotFoundSchema,
2643
+ AgentStoredStateSchema,
2005
2644
  AgentTerminalEventSchema,
2006
2645
  AgentTerminalReasonSchema,
2007
2646
  AgentTextPartSchema,
@@ -2018,14 +2657,21 @@ export {
2018
2657
  RecoverAgentRunSchema,
2019
2658
  ReplaceCompactedRangeSchema,
2020
2659
  RequestRunInterruptSchema,
2660
+ advanceAgentRuntimeEventCursor,
2661
+ agentDurableEventId,
2021
2662
  composeAgentPrompt,
2022
2663
  createAgentObservability,
2023
2664
  createAgentRuntime,
2665
+ createAgentRuntimeEventSink,
2666
+ createAgentRuntimeStore,
2024
2667
  createAgentSessionCoordinator,
2025
2668
  createAgentToolFenceLifecycle,
2026
2669
  createMemoryAgentRuntimeStore,
2027
2670
  defineAgentProtocol,
2028
2671
  defineModelRegistry,
2029
2672
  projectAgentHistory,
2030
- structuredCompaction
2673
+ projectAgentHistoryDetailed,
2674
+ selectAgentHistory,
2675
+ structuredCompaction,
2676
+ validateAgentModelSnapshot
2031
2677
  };