stitchkit 0.57.0 → 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 (34) 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 +40 -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 +9 -1
  16. package/dist/agent-runtime/runtime.d.ts.map +1 -1
  17. package/dist/agent-runtime/schemas.d.ts +2 -0
  18. package/dist/agent-runtime/schemas.d.ts.map +1 -1
  19. package/dist/agent-runtime/store-driver.d.ts +3 -0
  20. package/dist/agent-runtime/store-driver.d.ts.map +1 -1
  21. package/dist/agent-runtime/store.d.ts +7 -0
  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 +4 -4
  26. package/dist/agent-runtime.d.ts.map +1 -1
  27. package/dist/agent-runtime.js +356 -64
  28. package/dist/{index-1f4fcj0b.js → index-vtjgx3vv.js} +1 -0
  29. package/dist/testing/agent-store-conformance.d.ts.map +1 -1
  30. package/dist/testing.d.ts +1 -0
  31. package/dist/testing.d.ts.map +1 -1
  32. package/dist/testing.js +169 -4
  33. package/llms-full.txt +102 -5
  34. package/package.json +1 -1
@@ -29,7 +29,7 @@ import {
29
29
  AgentToolResultPartSchema,
30
30
  AgentUsageSchema,
31
31
  AgentUsageValueSchema
32
- } from "./index-1f4fcj0b.js";
32
+ } from "./index-vtjgx3vv.js";
33
33
  import"./index-6djpbnda.js";
34
34
  import"./index-cby4ar3v.js";
35
35
  import {
@@ -86,54 +86,66 @@ function eligibleForCompaction(messages, keepRecentTurns) {
86
86
  const eligibleCount = Math.max(0, completeTurns.length - keepRecentTurns);
87
87
  return completeTurns.slice(0, eligibleCount).flatMap((turn) => turn.messages);
88
88
  }
89
- function mutationSnapshot(result, fallback) {
89
+ function mutationSnapshot(result, fallback, attempts) {
90
90
  if (result.outcome === "applied" || result.outcome === "duplicate") {
91
- return { outcome: "applied", snapshot: result.snapshot };
91
+ return { outcome: "applied", snapshot: result.snapshot, attempts };
92
92
  }
93
- return { outcome: result.outcome, snapshot: fallback };
93
+ return { outcome: result.outcome, snapshot: fallback, attempts };
94
94
  }
95
95
  function structuredCompaction(config) {
96
96
  if (!Number.isSafeInteger(config.keepRecentTurns) || config.keepRecentTurns < 1) {
97
97
  throw new TypeError("keepRecentTurns must be a positive safe integer");
98
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
+ }
99
103
  return async (input) => {
100
- const snapshot = await input.store.loadSnapshot(input.conversationId);
101
- if (!await config.threshold(snapshot))
102
- return { outcome: "not_needed", snapshot };
103
- const eligibleMessages = eligibleForCompaction(snapshot.messages, config.keepRecentTurns);
104
- if (eligibleMessages.length === 0)
105
- return { outcome: "nothing_eligible", snapshot };
106
- const leadingSummary = snapshot.messages[0]?.role === "summary" ? snapshot.messages[0] : undefined;
107
- const previousSummary = input.previousSummary ?? (leadingSummary && config.readPreviousSummary ? config.schema.parse(config.readPreviousSummary(leadingSummary)) : undefined);
108
- const rawSummary = await config.summarize({
109
- conversationId: input.conversationId,
110
- snapshot,
111
- eligibleMessages,
112
- ...previousSummary !== undefined && { previousSummary },
113
- signal: input.signal
114
- });
115
- const summary = config.schema.parse(rawSummary);
116
- if (input.signal.aborted)
117
- throw input.signal.reason;
118
- const summaryMessage = config.createSummaryMessage({
119
- conversationId: input.conversationId,
120
- summary,
121
- compactedMessages: eligibleMessages
122
- });
123
- if (summaryMessage.role !== "summary" || summaryMessage.status !== "committed") {
124
- 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
+ }
125
147
  }
126
- const previousSummaryMessage = previousSummary !== undefined && leadingSummary ? leadingSummary : undefined;
127
- const applied = await input.store.replaceCompactedRange({
128
- conversationId: input.conversationId,
129
- expectedVersion: snapshot.version,
130
- replacedMessageIds: [
131
- ...previousSummaryMessage ? [previousSummaryMessage.id] : [],
132
- ...eligibleMessages.map((message) => message.id)
133
- ],
134
- summary: summaryMessage
135
- });
136
- return mutationSnapshot(applied, snapshot);
148
+ return { outcome: "conflict", snapshot: lastSnapshot, attempts: maxAttempts };
137
149
  };
138
150
  }
139
151
  // src/agent-runtime/coordinator.ts
@@ -335,6 +347,63 @@ var AgentRuntimeEventSchema = z.discriminatedUnion("type", [
335
347
  AgentToolStatusEventSchema,
336
348
  AgentTerminalEventSchema
337
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
+ }
338
407
  // src/agent-runtime/history.ts
339
408
  import { modelMessageSchema } from "ai";
340
409
  function providerOptions(envelope) {
@@ -416,26 +485,75 @@ function assistantMessages(message) {
416
485
  }
417
486
  return messages;
418
487
  }
419
- 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 = {}) {
420
494
  const projected = [];
495
+ const decisions = [];
496
+ let observedUser = false;
421
497
  for (const message of messages) {
422
- 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" });
423
500
  continue;
501
+ }
424
502
  if (message.role === "user") {
425
503
  const user = await userMessage(message, options);
426
- if (user)
504
+ observedUser = true;
505
+ if (user) {
427
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
+ }
428
511
  continue;
429
512
  }
430
513
  if (message.role === "system" || message.role === "summary") {
431
514
  const content = textContent(message.parts);
432
- if (content)
515
+ if (content) {
433
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
+ }
521
+ continue;
522
+ }
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
+ });
434
543
  continue;
435
544
  }
436
- projected.push(...assistantMessages(message));
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
+ });
437
552
  }
438
- return projected;
553
+ return { messages: projected, decisions };
554
+ }
555
+ async function projectAgentHistory(messages, options = {}) {
556
+ return [...(await projectAgentHistoryDetailed(messages, options)).messages];
439
557
  }
440
558
  // src/agent-runtime/managed-tools.ts
441
559
  async function assertFence(config, input) {
@@ -469,7 +587,14 @@ var AgentModelDescriptorSchema = z2.object({
469
587
  contextWindow: z2.int().positive(),
470
588
  capabilities: z2.array(AgentModelCapabilitySchema),
471
589
  observedAt: z2.iso.datetime({ offset: true }).optional(),
472
- source: z2.string().min(1).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)
473
598
  });
474
599
  function defineModelRegistry(config) {
475
600
  const descriptors = new Map;
@@ -488,15 +613,26 @@ function defineModelRegistry(config) {
488
613
  const available = new Set(descriptor(key).capabilities);
489
614
  return capabilities.every((capability) => available.has(capability));
490
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
+ };
491
629
  return {
492
630
  keys: () => [...descriptors.keys()],
493
631
  descriptor,
494
632
  supports,
633
+ preflight,
495
634
  resolve(key, required = []) {
496
- const selected = descriptor(key);
497
- if (!supports(key, required)) {
498
- throw new Error(`Agent model ${key} does not satisfy required capabilities`);
499
- }
635
+ const selected = preflight(key, required);
500
636
  const provider = config.providers[selected.provider];
501
637
  if (!provider)
502
638
  throw new Error(`Unknown agent model provider: ${selected.provider}`);
@@ -505,9 +641,29 @@ function defineModelRegistry(config) {
505
641
  model: provider.create(selected.modelId),
506
642
  ...provider.normalizeUsage && { normalizeUsage: provider.normalizeUsage }
507
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
+ });
508
652
  }
509
653
  };
510
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
+ }
511
667
  // src/agent-runtime/observability.ts
512
668
  import { z as z3 } from "zod";
513
669
  var AgentRunEventSchema = z3.object({
@@ -538,13 +694,18 @@ function createAgentObservability(config) {
538
694
  ...config.onSinkError && { onSinkError: config.onSinkError },
539
695
  ...config.onDrop && { onDrop: config.onDrop }
540
696
  });
697
+ const emitted = new Set;
541
698
  return {
542
699
  rootTrace(parent) {
543
700
  const trace = parent ? childSpan(parent) : createTraceContext();
544
701
  return trace;
545
702
  },
546
703
  emit(rawEvent) {
547
- 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));
548
709
  },
549
710
  flush: () => manager.flush(),
550
711
  getStatus: () => manager.getStatus(),
@@ -557,6 +718,110 @@ var AgentTokenCountSchema = z4.object({
557
718
  value: z4.int().nonnegative().optional(),
558
719
  provenance: z4.enum(["measured", "estimated", "unavailable"])
559
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
+ }
560
825
  function knownValue(value) {
561
826
  return value.provenance === "unavailable" ? undefined : value.value;
562
827
  }
@@ -795,7 +1060,11 @@ function createAgentRuntime(config) {
795
1060
  const publish = async (event) => {
796
1061
  try {
797
1062
  await config.publish?.(event);
798
- } catch {}
1063
+ } catch (error) {
1064
+ try {
1065
+ await config.onPublishError?.({ event, error });
1066
+ } catch {}
1067
+ }
799
1068
  };
800
1069
  const executeRun = async (input) => {
801
1070
  const queuedSnapshot = await config.store.loadSnapshot(input.acceptedRun.conversationId);
@@ -809,7 +1078,7 @@ function createAgentRuntime(config) {
809
1078
  let run = findRun(acquired.runs, input.acceptedRun.id);
810
1079
  await publish({
811
1080
  type: "run-state",
812
- eventId: generateId(),
1081
+ eventId: agentDurableEventId("run-state", run.id, acquired.version),
813
1082
  conversationId: run.conversationId,
814
1083
  runId: run.id,
815
1084
  snapshotVersion: acquired.version,
@@ -847,6 +1116,7 @@ function createAgentRuntime(config) {
847
1116
  runId: run.id,
848
1117
  expectedRevision: run.revision,
849
1118
  ownerId: runtimeEpoch,
1119
+ ...run.fencingToken !== undefined && { fencingToken: run.fencingToken },
850
1120
  assistant
851
1121
  }), "assistant draft");
852
1122
  run = findRun(snapshot.runs, run.id);
@@ -895,6 +1165,7 @@ function createAgentRuntime(config) {
895
1165
  runId: run.id,
896
1166
  expectedRevision: run.revision,
897
1167
  ownerId: runtimeEpoch,
1168
+ ...run.fencingToken !== undefined && { fencingToken: run.fencingToken },
898
1169
  assistant
899
1170
  }), "assistant checkpoint");
900
1171
  run = findRun(snapshot.runs, run.id);
@@ -906,7 +1177,7 @@ function createAgentRuntime(config) {
906
1177
  };
907
1178
  await publish({
908
1179
  type: "assistant-checkpoint",
909
- eventId: generateId(),
1180
+ eventId: agentDurableEventId("assistant-checkpoint", run.id, snapshot.version),
910
1181
  conversationId: run.conversationId,
911
1182
  runId: run.id,
912
1183
  snapshotVersion: snapshot.version,
@@ -932,6 +1203,8 @@ function createAgentRuntime(config) {
932
1203
  const currentRun = current.runs.find((candidate) => candidate.id === run.id);
933
1204
  if (!currentRun || currentRun.ownerId !== runtimeEpoch)
934
1205
  return "stale_run";
1206
+ if (currentRun.fencingToken !== run.fencingToken)
1207
+ return "stale_run";
935
1208
  if (currentRun.state === "interrupt_requested")
936
1209
  return "run_interrupted";
937
1210
  if (currentRun.state !== "running")
@@ -940,7 +1213,10 @@ function createAgentRuntime(config) {
940
1213
  };
941
1214
  const toolFenceLifecycle = createAgentToolFenceLifecycle({
942
1215
  runId: run.id,
943
- assertCurrent
1216
+ assertCurrent,
1217
+ context: () => ({
1218
+ ...run.fencingToken !== undefined && { fencingToken: run.fencingToken }
1219
+ })
944
1220
  });
945
1221
  const runtimeContext = {
946
1222
  context: input.context,
@@ -1276,6 +1552,7 @@ function createAgentRuntime(config) {
1276
1552
  runId: run.id,
1277
1553
  expectedRevision: run.revision,
1278
1554
  ownerId: runtimeEpoch,
1555
+ ...run.fencingToken !== undefined && { fencingToken: run.fencingToken },
1279
1556
  assistant,
1280
1557
  reason: terminalReason,
1281
1558
  ...terminalPolicyName && { policyName: terminalPolicyName }
@@ -1289,7 +1566,7 @@ function createAgentRuntime(config) {
1289
1566
  };
1290
1567
  config.observe?.emit({
1291
1568
  schemaVersion: 1,
1292
- eventId: generateId(),
1569
+ eventId: agentDurableEventId("terminal", run.id, snapshot.version),
1293
1570
  type: "run-terminal",
1294
1571
  conversationId: run.conversationId,
1295
1572
  runId: run.id,
@@ -1307,7 +1584,7 @@ function createAgentRuntime(config) {
1307
1584
  });
1308
1585
  await publish({
1309
1586
  type: "terminal",
1310
- eventId: generateId(),
1587
+ eventId: agentDurableEventId("terminal", run.id, snapshot.version),
1311
1588
  conversationId: run.conversationId,
1312
1589
  runId: run.id,
1313
1590
  snapshotVersion: snapshot.version,
@@ -1433,6 +1710,10 @@ function createAgentRuntime(config) {
1433
1710
  await previousAcceptance.catch(() => {
1434
1711
  return;
1435
1712
  });
1713
+ await config.models.preflight?.({
1714
+ context,
1715
+ conversationId: input.conversationId
1716
+ });
1436
1717
  const acceptance = await config.store.acceptInputAndAssignRun({
1437
1718
  idempotencyKey: input.idempotencyKey,
1438
1719
  input: userMessage2,
@@ -1471,7 +1752,7 @@ function createAgentRuntime(config) {
1471
1752
  outerAdmission.resolve(admission);
1472
1753
  await publish({
1473
1754
  type: "admission",
1474
- eventId: generateId(),
1755
+ eventId: agentDurableEventId("admission", acceptedRun.id, acceptedSnapshot.version),
1475
1756
  conversationId: acceptedRun.conversationId,
1476
1757
  runId: acceptedRun.id,
1477
1758
  snapshotVersion: acceptedSnapshot.version,
@@ -1482,7 +1763,7 @@ function createAgentRuntime(config) {
1482
1763
  });
1483
1764
  await publish({
1484
1765
  type: "run-state",
1485
- eventId: generateId(),
1766
+ eventId: agentDurableEventId("run-state", acceptedRun.id, acceptedSnapshot.version),
1486
1767
  conversationId: acceptedRun.conversationId,
1487
1768
  runId: acceptedRun.id,
1488
1769
  snapshotVersion: acceptedSnapshot.version,
@@ -1580,7 +1861,7 @@ function createAgentRuntime(config) {
1580
1861
  const interruptedRun = findRun(requested.snapshot.runs, input.runId);
1581
1862
  await publish({
1582
1863
  type: "run-state",
1583
- eventId: generateId(),
1864
+ eventId: agentDurableEventId("run-state", interruptedRun.id, requested.snapshot.version),
1584
1865
  conversationId: interruptedRun.conversationId,
1585
1866
  runId: interruptedRun.id,
1586
1867
  snapshotVersion: requested.snapshot.version,
@@ -1741,6 +2022,7 @@ var CheckpointRunAssistantSchema = z6.object({
1741
2022
  runId: AgentRecordIdSchema,
1742
2023
  expectedRevision: AgentRecordVersionSchema,
1743
2024
  ownerId: z6.string().min(1),
2025
+ fencingToken: AgentRecordVersionSchema.optional(),
1744
2026
  assistant: AgentMessageSchema
1745
2027
  });
1746
2028
  var CommitRunTerminalSchema = z6.object({
@@ -1748,6 +2030,7 @@ var CommitRunTerminalSchema = z6.object({
1748
2030
  runId: AgentRecordIdSchema,
1749
2031
  expectedRevision: AgentRecordVersionSchema,
1750
2032
  ownerId: z6.string().min(1),
2033
+ fencingToken: AgentRecordVersionSchema.optional(),
1751
2034
  assistant: AgentMessageSchema,
1752
2035
  reason: AgentTerminalReasonSchema,
1753
2036
  policyName: z6.string().min(1).optional()
@@ -1975,6 +2258,7 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
1975
2258
  ...run,
1976
2259
  state: "running",
1977
2260
  ownerId: operation.input.ownerId,
2261
+ fencingToken: (run.fencingToken ?? 0) + 1,
1978
2262
  revision: run.revision + 1,
1979
2263
  updatedAt: new Date().toISOString()
1980
2264
  });
@@ -1984,7 +2268,7 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
1984
2268
  }
1985
2269
  if (operation.type === "checkpoint" && run) {
1986
2270
  const input = operation.input;
1987
- 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") {
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") {
1988
2272
  return conflict(run.revision);
1989
2273
  }
1990
2274
  const next = AgentRunSchema.parse({
@@ -2057,7 +2341,7 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2057
2341
  }
2058
2342
  if (operation.type === "terminal" && run) {
2059
2343
  const input = operation.input;
2060
- 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)) {
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)) {
2061
2345
  return conflict(run.revision);
2062
2346
  }
2063
2347
  const next = AgentRunSchema.parse({
@@ -2331,6 +2615,7 @@ export {
2331
2615
  AgentMessageStatusSchema,
2332
2616
  AgentModelCapabilitySchema,
2333
2617
  AgentModelDescriptorSchema,
2618
+ AgentModelRegistrySnapshotSchema,
2334
2619
  AgentOpaquePartSchema,
2335
2620
  AgentProviderEnvelopeSchema,
2336
2621
  AgentReasoningDeltaEventSchema,
@@ -2346,6 +2631,7 @@ export {
2346
2631
  AgentRunSchema,
2347
2632
  AgentRunStateEventSchema,
2348
2633
  AgentRunStateSchema,
2634
+ AgentRuntimeEventCursorSchema,
2349
2635
  AgentRuntimeEventSchema,
2350
2636
  AgentSnapshotSchema,
2351
2637
  AgentSourcePartSchema,
@@ -2371,9 +2657,12 @@ export {
2371
2657
  RecoverAgentRunSchema,
2372
2658
  ReplaceCompactedRangeSchema,
2373
2659
  RequestRunInterruptSchema,
2660
+ advanceAgentRuntimeEventCursor,
2661
+ agentDurableEventId,
2374
2662
  composeAgentPrompt,
2375
2663
  createAgentObservability,
2376
2664
  createAgentRuntime,
2665
+ createAgentRuntimeEventSink,
2377
2666
  createAgentRuntimeStore,
2378
2667
  createAgentSessionCoordinator,
2379
2668
  createAgentToolFenceLifecycle,
@@ -2381,5 +2670,8 @@ export {
2381
2670
  defineAgentProtocol,
2382
2671
  defineModelRegistry,
2383
2672
  projectAgentHistory,
2384
- structuredCompaction
2673
+ projectAgentHistoryDetailed,
2674
+ selectAgentHistory,
2675
+ structuredCompaction,
2676
+ validateAgentModelSnapshot
2385
2677
  };
@@ -121,6 +121,7 @@ var AgentRunSchema = z.object({
121
121
  state: AgentRunStateSchema,
122
122
  revision: AgentRecordVersionSchema,
123
123
  ownerId: z.string().min(1).optional(),
124
+ fencingToken: AgentRecordVersionSchema.optional(),
124
125
  terminalReason: AgentTerminalReasonSchema.optional(),
125
126
  terminalPolicyName: z.string().min(1).optional(),
126
127
  createdAt: AgentTimestampSchema,