stitchkit 0.61.0 → 0.63.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 (36) hide show
  1. package/dist/agent-runtime/compaction.d.ts +14 -1
  2. package/dist/agent-runtime/compaction.d.ts.map +1 -1
  3. package/dist/agent-runtime/coordinator.d.ts +23 -2
  4. package/dist/agent-runtime/coordinator.d.ts.map +1 -1
  5. package/dist/agent-runtime/events.d.ts +140 -0
  6. package/dist/agent-runtime/events.d.ts.map +1 -1
  7. package/dist/agent-runtime/history.d.ts +39 -1
  8. package/dist/agent-runtime/history.d.ts.map +1 -1
  9. package/dist/agent-runtime/observability.d.ts +4 -0
  10. package/dist/agent-runtime/observability.d.ts.map +1 -1
  11. package/dist/agent-runtime/prompt.d.ts +1 -1
  12. package/dist/agent-runtime/prompt.d.ts.map +1 -1
  13. package/dist/agent-runtime/run-execution.d.ts +4 -0
  14. package/dist/agent-runtime/run-execution.d.ts.map +1 -1
  15. package/dist/agent-runtime/runtime-internals.d.ts +47 -0
  16. package/dist/agent-runtime/runtime-internals.d.ts.map +1 -1
  17. package/dist/agent-runtime/runtime.d.ts +1 -0
  18. package/dist/agent-runtime/runtime.d.ts.map +1 -1
  19. package/dist/agent-runtime/schemas.d.ts +208 -77
  20. package/dist/agent-runtime/schemas.d.ts.map +1 -1
  21. package/dist/agent-runtime/store-driver.d.ts +191 -0
  22. package/dist/agent-runtime/store-driver.d.ts.map +1 -1
  23. package/dist/agent-runtime/store.d.ts +583 -0
  24. package/dist/agent-runtime/store.d.ts.map +1 -1
  25. package/dist/agent-runtime/terminal-commit.d.ts +34 -4
  26. package/dist/agent-runtime/terminal-commit.d.ts.map +1 -1
  27. package/dist/agent-runtime/terminal-status.d.ts +13 -0
  28. package/dist/agent-runtime/terminal-status.d.ts.map +1 -1
  29. package/dist/agent-runtime.d.ts +1 -1
  30. package/dist/agent-runtime.d.ts.map +1 -1
  31. package/dist/agent-runtime.js +399 -99
  32. package/dist/{index-vtjgx3vv.js → index-b1k33127.js} +25 -18
  33. package/dist/testing/agent-store-conformance.d.ts.map +1 -1
  34. package/dist/testing.js +26 -4
  35. package/llms-full.txt +386 -9
  36. package/package.json +1 -1
@@ -29,7 +29,7 @@ import {
29
29
  AgentToolResultPartSchema,
30
30
  AgentUsageSchema,
31
31
  AgentUsageValueSchema
32
- } from "./index-vtjgx3vv.js";
32
+ } from "./index-b1k33127.js";
33
33
  import"./index-6djpbnda.js";
34
34
  import"./index-cby4ar3v.js";
35
35
  import {
@@ -45,14 +45,29 @@ import {
45
45
  import"./index-smpbdg6k.js";
46
46
  import"./index-xxye8j3k.js";
47
47
 
48
+ // src/agent-runtime/terminal-status.ts
49
+ function isSpeakableAssistantStatus(status) {
50
+ return status === "completed" || status === "interrupted" || status === "committed";
51
+ }
52
+ function assistantStatus(reason) {
53
+ if (reason === "success" || reason === "policy_stop" || reason === "provider_stop") {
54
+ return "completed";
55
+ }
56
+ if (reason === "superseded")
57
+ return "superseded";
58
+ if (reason === "interrupted" || reason === "cancelled" || reason === "shutdown") {
59
+ return "interrupted";
60
+ }
61
+ return "failed";
62
+ }
63
+
48
64
  // src/agent-runtime/compaction.ts
49
65
  function providerValidTurn(messages) {
50
66
  if (messages[0]?.role !== "user")
51
67
  return false;
52
68
  const assistant = messages.find((message) => message.role === "assistant");
53
- if (!assistant || assistant.status === "streaming" || assistant.status === "failed") {
69
+ if (!assistant || !isSpeakableAssistantStatus(assistant.status))
54
70
  return false;
55
- }
56
71
  const calls = new Set(assistant.parts.filter((part) => part.type === "tool-call").map((part) => part.callId));
57
72
  const results = new Set(assistant.parts.filter((part) => part.type === "tool-result").map((part) => part.callId));
58
73
  return [...calls].every((callId) => results.has(callId)) && [...results].every((callId) => calls.has(callId));
@@ -226,6 +241,8 @@ function createAgentSessionCoordinator() {
226
241
  };
227
242
  if (input.policy === "interrupt")
228
243
  lane.active?.controller.abort("user-interrupt");
244
+ if (input.policy === "supersede")
245
+ lane.active?.controller.abort("supersede");
229
246
  lane.queue.push(pending);
230
247
  startNext(input.key, lane);
231
248
  return { accepted: accepted.promise, result: result.promise };
@@ -428,11 +445,34 @@ function textContent(parts) {
428
445
  return parts.filter((part) => part.type === "text").map((part) => part.text).join(`
429
446
  `);
430
447
  }
448
+ function unrepresented(parts, rendered) {
449
+ const missing = new Set;
450
+ for (const part of parts) {
451
+ if (!rendered.has(part.type))
452
+ missing.add(part.type);
453
+ }
454
+ return [...missing];
455
+ }
456
+ function decide(message, action, reason, rendered) {
457
+ if (action === "omitted" || !rendered)
458
+ return { messageId: message.id, action, reason };
459
+ const omittedParts = unrepresented(message.parts, rendered);
460
+ return {
461
+ messageId: message.id,
462
+ action,
463
+ reason,
464
+ ...omittedParts.length > 0 && { omittedParts }
465
+ };
466
+ }
467
+ var INTERRUPTION_NOTE = "[interrupted: this turn was cut off before it finished]";
431
468
  async function userMessage(message, options) {
469
+ const rendered = new Set;
432
470
  const text = textContent(message.parts);
433
471
  const content = [];
434
- if (text)
472
+ if (text) {
435
473
  content.push({ type: "text", text });
474
+ rendered.add("text");
475
+ }
436
476
  for (const part of message.parts) {
437
477
  if (part.type !== "file")
438
478
  continue;
@@ -443,6 +483,7 @@ async function userMessage(message, options) {
443
483
  mediaType: part.mediaType,
444
484
  ...part.filename && { filename: part.filename }
445
485
  });
486
+ rendered.add("file");
446
487
  continue;
447
488
  }
448
489
  const fallback = options.unresolvedFile ?? "omit";
@@ -455,24 +496,29 @@ async function userMessage(message, options) {
455
496
  type: "text",
456
497
  text: described ? `[attachment: ${described}]` : "[attachment]"
457
498
  });
499
+ rendered.add("file");
458
500
  }
459
501
  }
460
502
  if (content.length === 0)
461
- return;
462
- return modelMessageSchema.parse({ role: "user", content });
503
+ return { rendered };
504
+ return { message: modelMessageSchema.parse({ role: "user", content }), rendered };
463
505
  }
464
- function assistantMessages(message) {
506
+ function assistantMessages(message, interrupted) {
507
+ const rendered = new Set;
465
508
  const assistantContent = [];
466
509
  const toolContent = [];
467
510
  for (const part of message.parts) {
468
- if (part.type === "text")
511
+ if (part.type === "text") {
469
512
  assistantContent.push({ type: "text", text: part.text });
513
+ rendered.add("text");
514
+ }
470
515
  if (part.type === "reasoning") {
471
516
  assistantContent.push({
472
517
  type: "reasoning",
473
518
  text: part.text,
474
519
  ...part.provider && { providerOptions: providerOptions(part.provider) }
475
520
  });
521
+ rendered.add("reasoning");
476
522
  }
477
523
  if (part.type === "tool-call") {
478
524
  assistantContent.push({
@@ -482,6 +528,7 @@ function assistantMessages(message) {
482
528
  input: part.input,
483
529
  ...part.provider && { providerOptions: providerOptions(part.provider) }
484
530
  });
531
+ rendered.add("tool-call");
485
532
  }
486
533
  if (part.type === "tool-result") {
487
534
  const output = part.outcome === "success" ? { type: "json", value: part.output ?? null } : { type: "error-json", value: part.output ?? { message: part.outcome } };
@@ -491,8 +538,13 @@ function assistantMessages(message) {
491
538
  toolName: part.toolName,
492
539
  output
493
540
  });
541
+ rendered.add("tool-result");
494
542
  }
495
543
  }
544
+ if (interrupted && (assistantContent.length > 0 || toolContent.length > 0)) {
545
+ assistantContent.push({ type: "text", text: INTERRUPTION_NOTE });
546
+ rendered.add("control");
547
+ }
496
548
  const messages = [];
497
549
  if (assistantContent.length > 0) {
498
550
  messages.push(modelMessageSchema.parse({ role: "assistant", content: assistantContent }));
@@ -500,7 +552,22 @@ function assistantMessages(message) {
500
552
  if (toolContent.length > 0) {
501
553
  messages.push(modelMessageSchema.parse({ role: "tool", content: toolContent }));
502
554
  }
503
- return messages;
555
+ return { messages, rendered };
556
+ }
557
+ function interruptedSystemNote(message) {
558
+ const rendered = new Set;
559
+ const text = textContent(message.parts);
560
+ if (!text)
561
+ return { rendered };
562
+ rendered.add("text");
563
+ rendered.add("control");
564
+ return {
565
+ message: modelMessageSchema.parse({
566
+ role: "system",
567
+ content: `[interrupted] partial response: ${text}`
568
+ }),
569
+ rendered
570
+ };
504
571
  }
505
572
  function completeToolChronology(message) {
506
573
  const calls = new Set(message.parts.filter((part) => part.type === "tool-call").map((part) => part.callId));
@@ -510,20 +577,25 @@ function completeToolChronology(message) {
510
577
  async function projectAgentHistoryDetailed(messages, options = {}) {
511
578
  const projected = [];
512
579
  const decisions = [];
580
+ const interruptedRule = options.interruptedAssistant ?? "assistant-marked";
513
581
  let observedUser = false;
514
582
  for (const message of messages) {
583
+ if (message.status === "superseded") {
584
+ decisions.push(decide(message, "omitted", "superseded"));
585
+ continue;
586
+ }
515
587
  if (message.status === "streaming" || message.status === "failed") {
516
- decisions.push({ messageId: message.id, action: "omitted", reason: "draft-or-failed" });
588
+ decisions.push(decide(message, "omitted", "draft-or-failed"));
517
589
  continue;
518
590
  }
519
591
  if (message.role === "user") {
520
592
  const user = await userMessage(message, options);
521
593
  observedUser = true;
522
- if (user) {
523
- projected.push(user);
524
- decisions.push({ messageId: message.id, action: "projected", reason: "projected" });
594
+ if (user.message) {
595
+ projected.push(user.message);
596
+ decisions.push(decide(message, "projected", "projected", user.rendered));
525
597
  } else {
526
- decisions.push({ messageId: message.id, action: "omitted", reason: "empty" });
598
+ decisions.push(decide(message, "omitted", "empty"));
527
599
  }
528
600
  continue;
529
601
  }
@@ -531,9 +603,9 @@ async function projectAgentHistoryDetailed(messages, options = {}) {
531
603
  const content = textContent(message.parts);
532
604
  if (content) {
533
605
  projected.push(modelMessageSchema.parse({ role: "system", content }));
534
- decisions.push({ messageId: message.id, action: "projected", reason: "projected" });
606
+ decisions.push(decide(message, "projected", "projected", new Set(["text"])));
535
607
  } else {
536
- decisions.push({ messageId: message.id, action: "omitted", reason: "empty" });
608
+ decisions.push(decide(message, "omitted", "empty"));
537
609
  }
538
610
  continue;
539
611
  }
@@ -541,31 +613,34 @@ async function projectAgentHistoryDetailed(messages, options = {}) {
541
613
  if (options.leadingAssistant === "error") {
542
614
  throw new Error(`Assistant message ${message.id} precedes the first user message`);
543
615
  }
544
- decisions.push({
545
- messageId: message.id,
546
- action: "omitted",
547
- reason: "leading-assistant"
548
- });
616
+ decisions.push(decide(message, "omitted", "leading-assistant"));
617
+ continue;
618
+ }
619
+ const interrupted = message.status === "interrupted";
620
+ if (interrupted && interruptedRule === "omit") {
621
+ decisions.push(decide(message, "omitted", "interrupted"));
622
+ continue;
623
+ }
624
+ if (interrupted && interruptedRule === "system-note") {
625
+ const note = interruptedSystemNote(message);
626
+ if (note.message) {
627
+ projected.push(note.message);
628
+ decisions.push(decide(message, "projected", "projected", note.rendered));
629
+ } else {
630
+ decisions.push(decide(message, "omitted", "empty"));
631
+ }
549
632
  continue;
550
633
  }
551
634
  if (!completeToolChronology(message)) {
552
635
  if (options.incompleteToolTurn === "error") {
553
636
  throw new Error(`Assistant message ${message.id} has incomplete tool chronology`);
554
637
  }
555
- decisions.push({
556
- messageId: message.id,
557
- action: "omitted",
558
- reason: "incomplete-tool-turn"
559
- });
638
+ decisions.push(decide(message, "omitted", "incomplete-tool-turn"));
560
639
  continue;
561
640
  }
562
- const assistant = assistantMessages(message);
563
- projected.push(...assistant);
564
- decisions.push({
565
- messageId: message.id,
566
- action: assistant.length > 0 ? "projected" : "omitted",
567
- reason: assistant.length > 0 ? "projected" : "empty"
568
- });
641
+ const assistant = assistantMessages(message, interrupted);
642
+ projected.push(...assistant.messages);
643
+ decisions.push(assistant.messages.length > 0 ? decide(message, "projected", "projected", assistant.rendered) : decide(message, "omitted", "empty"));
569
644
  }
570
645
  return { messages: projected, decisions };
571
646
  }
@@ -786,20 +861,21 @@ async function selectAgentHistory(options) {
786
861
  if (!Number.isSafeInteger(keepRecentTurns) || keepRecentTurns < 0) {
787
862
  throw new TypeError("keepRecentTurns must be a non-negative safe integer");
788
863
  }
864
+ const spoken = options.messages.filter((message) => message.status !== "superseded");
789
865
  const counts = new Map;
790
866
  let total = 0;
791
867
  let estimated = false;
792
- for (const message of options.messages) {
868
+ for (const message of spoken) {
793
869
  const count = AgentTokenCountSchema.parse(await options.estimateMessage(message));
794
870
  counts.set(message.id, count);
795
871
  const value = knownValue(count);
796
872
  if (value === undefined) {
797
873
  return {
798
- messages: [...options.messages],
874
+ messages: [...spoken],
799
875
  decisions: options.messages.map((candidate) => ({
800
876
  messageId: candidate.id,
801
- action: "kept",
802
- reason: "token-count-unavailable",
877
+ action: candidate.status === "superseded" ? "removed" : "kept",
878
+ reason: candidate.status === "superseded" ? "superseded" : "token-count-unavailable",
803
879
  tokens: counts.get(candidate.id) ?? { provenance: "unavailable" }
804
880
  })),
805
881
  totalTokens: { provenance: "unavailable" },
@@ -810,7 +886,7 @@ async function selectAgentHistory(options) {
810
886
  if (count.provenance === "estimated")
811
887
  estimated = true;
812
888
  }
813
- const turns = budgetTurns(options.messages);
889
+ const turns = budgetTurns(spoken);
814
890
  const completeIndexes = turns.map((turn, index) => ({ turn, index })).filter(({ turn }) => turn.complete && !turn.protectedSystem).map(({ index }) => index);
815
891
  const protectedRecent = new Set(completeIndexes.slice(-keepRecentTurns));
816
892
  const removed = new Set;
@@ -823,8 +899,16 @@ async function selectAgentHistory(options) {
823
899
  total -= knownValue(counts.get(message.id) ?? { provenance: "unavailable" }) ?? 0;
824
900
  }
825
901
  }
826
- const messages = options.messages.filter((message) => !removed.has(message.id));
902
+ const messages = spoken.filter((message) => !removed.has(message.id));
827
903
  const decisions = options.messages.map((message) => {
904
+ if (message.status === "superseded") {
905
+ return {
906
+ messageId: message.id,
907
+ action: "removed",
908
+ reason: "superseded",
909
+ tokens: { provenance: "unavailable" }
910
+ };
911
+ }
828
912
  const turnIndex = turns.findIndex((turn2) => turn2.messages.some((item) => item.id === message.id));
829
913
  const turn = turns[turnIndex];
830
914
  let reason = "within-budget";
@@ -975,21 +1059,14 @@ function createRuntimeAdmissionLanes() {
975
1059
  }
976
1060
 
977
1061
  // src/agent-runtime/run-execution.ts
978
- import { stepCountIs, streamText } from "ai";
1062
+ import {
1063
+ stepCountIs,
1064
+ streamText
1065
+ } from "ai";
979
1066
 
980
1067
  // src/agent-runtime/runtime-internals.ts
981
1068
  import { z as z5 } from "zod";
982
1069
 
983
- // src/agent-runtime/terminal-status.ts
984
- function assistantStatus(reason) {
985
- if (reason === "success" || reason === "policy_stop")
986
- return "completed";
987
- if (reason === "interrupted" || reason === "cancelled" || reason === "shutdown") {
988
- return "interrupted";
989
- }
990
- return "failed";
991
- }
992
-
993
1070
  // src/agent-runtime/terminal-commit.ts
994
1071
  class AgentRuntimeConflictError extends Error {
995
1072
  constructor(operation) {
@@ -1016,10 +1093,12 @@ function canonicalTerminal(snapshot, runId, retainedAssistant) {
1016
1093
  assistant,
1017
1094
  reason: run.terminalReason,
1018
1095
  committedByCaller: false,
1019
- ...run.terminalPolicyName && { policyName: run.terminalPolicyName }
1096
+ ...run.terminalPolicyName && { policyName: run.terminalPolicyName },
1097
+ ...run.usage && { usage: run.usage }
1020
1098
  };
1021
1099
  }
1022
1100
  function interruptedCandidate(candidate, run, now) {
1101
+ const reason = candidate.reason === "superseded" ? "superseded" : "interrupted";
1023
1102
  const hasInterruptControl = candidate.assistant.parts.some((part) => part.type === "control" && part.reason === "run-interrupted");
1024
1103
  const parts = hasInterruptControl ? candidate.assistant.parts : [
1025
1104
  ...candidate.assistant.parts,
@@ -1029,11 +1108,11 @@ function interruptedCandidate(candidate, run, now) {
1029
1108
  run,
1030
1109
  assistant: AgentMessageSchema.parse({
1031
1110
  ...candidate.assistant,
1032
- status: "interrupted",
1111
+ status: assistantStatus(reason),
1033
1112
  parts,
1034
1113
  updatedAt: now().toISOString()
1035
1114
  }),
1036
- reason: "interrupted"
1115
+ reason
1037
1116
  };
1038
1117
  }
1039
1118
  function canRetryTerminal(current, previous, runtimeEpoch) {
@@ -1052,7 +1131,8 @@ async function commitAgentRunTerminal(input) {
1052
1131
  },
1053
1132
  assistant: candidate.assistant,
1054
1133
  reason: candidate.reason,
1055
- ...candidate.policyName && { policyName: candidate.policyName }
1134
+ ...candidate.policyName && { policyName: candidate.policyName },
1135
+ ...candidate.usage && { usage: candidate.usage }
1056
1136
  });
1057
1137
  if (committed.outcome === "applied") {
1058
1138
  const terminal2 = canonicalTerminal(committed.snapshot, candidate.run.id);
@@ -1112,8 +1192,80 @@ function abortTerminalReason(signal) {
1112
1192
  return "shutdown";
1113
1193
  if (signal.reason === "timeout")
1114
1194
  return "timeout";
1195
+ if (signal.reason === "supersede")
1196
+ return "superseded";
1115
1197
  return "interrupted";
1116
1198
  }
1199
+ function addUsage(total, step) {
1200
+ const sum = (left, right) => {
1201
+ if (left?.value === undefined && right?.value === undefined) {
1202
+ return { provenance: "unavailable" };
1203
+ }
1204
+ if (left?.value === undefined)
1205
+ return { ...right, provenance: "computed" };
1206
+ if (right?.value === undefined)
1207
+ return { ...left, provenance: "computed" };
1208
+ return { value: left.value + right.value, provenance: "computed" };
1209
+ };
1210
+ const sumCost = (left, right) => {
1211
+ if (left?.value === undefined || right?.value === undefined) {
1212
+ return { provenance: "unavailable" };
1213
+ }
1214
+ if (left.currency !== right.currency)
1215
+ return { provenance: "unavailable" };
1216
+ return {
1217
+ value: left.value + right.value,
1218
+ ...left.currency && { currency: left.currency },
1219
+ provenance: "computed"
1220
+ };
1221
+ };
1222
+ if (!total)
1223
+ return step;
1224
+ const cost = sumCost(total.cost, step.cost);
1225
+ return {
1226
+ inputTokens: sum(total.inputTokens, step.inputTokens),
1227
+ outputTokens: sum(total.outputTokens, step.outputTokens),
1228
+ reasoningTokens: sum(total.reasoningTokens, step.reasoningTokens),
1229
+ cacheReadTokens: sum(total.cacheReadTokens, step.cacheReadTokens),
1230
+ cacheWriteTokens: sum(total.cacheWriteTokens, step.cacheWriteTokens),
1231
+ ...cost && { cost }
1232
+ };
1233
+ }
1234
+ function mergeRunTotals(sdkTotal, accumulated) {
1235
+ const pick = (total, ours) => total?.value !== undefined ? { value: total.value, provenance: "computed" } : ours ?? { provenance: "unavailable" };
1236
+ return {
1237
+ inputTokens: pick(sdkTotal.inputTokens, accumulated?.inputTokens),
1238
+ outputTokens: pick(sdkTotal.outputTokens, accumulated?.outputTokens),
1239
+ reasoningTokens: pick(sdkTotal.reasoningTokens, accumulated?.reasoningTokens),
1240
+ cacheReadTokens: pick(sdkTotal.cacheReadTokens, accumulated?.cacheReadTokens),
1241
+ cacheWriteTokens: pick(sdkTotal.cacheWriteTokens, accumulated?.cacheWriteTokens),
1242
+ cost: accumulated?.cost ?? { provenance: "unavailable" }
1243
+ };
1244
+ }
1245
+ function statedUsage(usage) {
1246
+ const stated = (value) => value ?? { provenance: "unavailable" };
1247
+ if (!usage)
1248
+ return unknownUsage();
1249
+ return {
1250
+ inputTokens: stated(usage.inputTokens),
1251
+ outputTokens: stated(usage.outputTokens),
1252
+ reasoningTokens: stated(usage.reasoningTokens),
1253
+ cacheReadTokens: stated(usage.cacheReadTokens),
1254
+ cacheWriteTokens: stated(usage.cacheWriteTokens),
1255
+ cost: stated(usage.cost)
1256
+ };
1257
+ }
1258
+ function unknownUsage() {
1259
+ const nothing = { provenance: "unavailable" };
1260
+ return {
1261
+ inputTokens: nothing,
1262
+ outputTokens: nothing,
1263
+ reasoningTokens: nothing,
1264
+ cacheReadTokens: nothing,
1265
+ cacheWriteTokens: nothing,
1266
+ cost: nothing
1267
+ };
1268
+ }
1117
1269
  function normalizeSdkUsage(value) {
1118
1270
  const reported = (tokens) => tokens === undefined ? { provenance: "unavailable" } : { value: tokens, provenance: "provider-reported" };
1119
1271
  return {
@@ -1219,7 +1371,8 @@ function createRunExecutor(dependencies) {
1219
1371
  let eventCount = 0;
1220
1372
  let sequence = 0;
1221
1373
  let terminalReason = "success";
1222
- let usage;
1374
+ let usage = input.acceptedRun.usage;
1375
+ let sawProviderFinish = false;
1223
1376
  let step = 0;
1224
1377
  let selectedModel;
1225
1378
  let internalCause;
@@ -1261,13 +1414,14 @@ function createRunExecutor(dependencies) {
1261
1414
  expectedRevision: run.revision,
1262
1415
  ownerId: runtimeEpoch,
1263
1416
  ...run.fencingToken !== undefined && { fencingToken: run.fencingToken },
1264
- assistant
1417
+ assistant,
1418
+ usage: statedUsage(usage)
1265
1419
  }), "assistant checkpoint");
1266
1420
  run = findRun(snapshot.runs, run.id);
1267
1421
  const checkpointMetrics = {
1268
1422
  partial: true,
1269
1423
  durationMs: performance.now() - runStartedAt,
1270
- ...usage && { usage },
1424
+ usage: statedUsage(usage),
1271
1425
  ...firstOutputAt !== undefined && { ttftMs: firstOutputAt - runStartedAt }
1272
1426
  };
1273
1427
  await publish({
@@ -1290,6 +1444,8 @@ function createRunExecutor(dependencies) {
1290
1444
  });
1291
1445
  snapshot = compacted.snapshot;
1292
1446
  run = findRun(snapshot.runs, run.id);
1447
+ if (compacted.usage)
1448
+ usage = addUsage(usage, compacted.usage);
1293
1449
  }
1294
1450
  const assertCurrent = async () => {
1295
1451
  if (executionSignal.aborted)
@@ -1338,12 +1494,52 @@ function createRunExecutor(dependencies) {
1338
1494
  if (prompt.contextDecision === "requires-compaction") {
1339
1495
  throw new Error("Agent context still exceeds the model budget after compaction");
1340
1496
  }
1341
- const history = await (config.history?.project ? config.history.project(snapshot.messages) : projectAgentHistory(snapshot.messages, {
1497
+ const projectHistory = (source) => config.history?.project ? config.history.project(source.messages) : projectAgentHistory(source.messages, {
1342
1498
  ...config.history?.resolveFile && { resolveFile: config.history.resolveFile },
1343
1499
  ...config.history?.unresolvedFile && {
1344
1500
  unresolvedFile: config.history.unresolvedFile
1501
+ },
1502
+ ...config.history?.interruptedAssistant && {
1503
+ interruptedAssistant: config.history.interruptedAssistant
1345
1504
  }
1346
- }));
1505
+ });
1506
+ const history = await projectHistory(snapshot);
1507
+ const absorbPending = async () => {
1508
+ if (!input.absorbable?.size || executionSignal.aborted)
1509
+ return;
1510
+ const latest = await config.store.loadSnapshot(run.conversationId);
1511
+ const current = latest.runs.find((candidate) => candidate.id === run.id);
1512
+ if (current?.state !== "running" || current.ownerId !== runtimeEpoch) {
1513
+ return;
1514
+ }
1515
+ const pending = latest.runs.find((candidate) => candidate.state === "queued" && input.absorbable?.has(candidate.id));
1516
+ if (!pending)
1517
+ return;
1518
+ const absorbed = await config.store.absorbQueuedRun({
1519
+ conversationId: run.conversationId,
1520
+ runningRunId: current.id,
1521
+ runningExpectedRevision: current.revision,
1522
+ ownerId: runtimeEpoch,
1523
+ ...current.fencingToken !== undefined && { fencingToken: current.fencingToken },
1524
+ queuedRunId: pending.id,
1525
+ queuedExpectedRevision: pending.revision
1526
+ });
1527
+ if (absorbed.outcome !== "applied")
1528
+ return;
1529
+ input.onAbsorbed?.(pending.id);
1530
+ snapshot = absorbed.snapshot;
1531
+ run = findRun(snapshot.runs, run.id);
1532
+ await publish({
1533
+ type: "run-state",
1534
+ eventId: agentDurableEventId("run-state", run.id, snapshot.version),
1535
+ conversationId: run.conversationId,
1536
+ runId: run.id,
1537
+ snapshotVersion: snapshot.version,
1538
+ state: run.state,
1539
+ emittedAt: now().toISOString()
1540
+ });
1541
+ return [...await projectHistory(snapshot)];
1542
+ };
1347
1543
  const maxStepCondition = stepCountIs(maxSteps);
1348
1544
  const stopConditions = [
1349
1545
  async (options) => {
@@ -1369,8 +1565,10 @@ function createRunExecutor(dependencies) {
1369
1565
  abortSignal: executionSignal,
1370
1566
  maxRetries: 0,
1371
1567
  stopWhen: stopConditions,
1372
- ...config.loop?.prepareStep && {
1373
- prepareStep: (options) => config.loop?.prepareStep?.({ ...options, ...runtimeContext })
1568
+ prepareStep: async (options) => {
1569
+ const absorbed = await absorbPending();
1570
+ const prepared = await config.loop?.prepareStep?.({ ...options, ...runtimeContext }) ?? {};
1571
+ return absorbed && !prepared.messages ? { ...prepared, messages: absorbed } : prepared;
1374
1572
  }
1375
1573
  });
1376
1574
  for await (const part of result.stream) {
@@ -1585,7 +1783,7 @@ function createRunExecutor(dependencies) {
1585
1783
  usage: part.usage,
1586
1784
  providerMetadata: part.providerMetadata
1587
1785
  }) ?? normalizeSdkUsage(part.usage);
1588
- usage = stepUsage;
1786
+ usage = addUsage(usage, stepUsage);
1589
1787
  config.observe?.emit({
1590
1788
  schemaVersion: 1,
1591
1789
  eventId: generateId(),
@@ -1603,11 +1801,14 @@ function createRunExecutor(dependencies) {
1603
1801
  });
1604
1802
  step += 1;
1605
1803
  } else if (part.type === "finish" && part.finishReason !== "stop") {
1606
- terminalReason = "policy_stop";
1804
+ if (terminalReason === "success") {
1805
+ terminalReason = part.finishReason === "error" ? "provider_failure" : "provider_stop";
1806
+ internalCause ??= { finishReason: part.finishReason };
1807
+ }
1607
1808
  }
1608
1809
  if (part.type === "finish") {
1609
- const aggregate = normalizeSdkUsage(part.totalUsage);
1610
- usage = { ...aggregate, ...usage?.cost && { cost: usage.cost } };
1810
+ sawProviderFinish = true;
1811
+ usage = mergeRunTotals(normalizeSdkUsage(part.totalUsage), usage);
1611
1812
  }
1612
1813
  if (eventCount % checkpointEveryEvents === 0)
1613
1814
  await checkpoint();
@@ -1643,47 +1844,67 @@ function createRunExecutor(dependencies) {
1643
1844
  parts,
1644
1845
  updatedAt: now().toISOString()
1645
1846
  });
1646
- const terminal = await commitAgentRunTerminal({
1647
- store: config.store,
1648
- runtimeEpoch,
1649
- candidate: {
1650
- run,
1651
- assistant,
1652
- reason: terminalReason,
1653
- ...terminalPolicyName && { policyName: terminalPolicyName }
1654
- },
1655
- now
1656
- });
1657
- snapshot = terminal.snapshot;
1658
- run = terminal.run;
1659
- assistant = terminal.assistant;
1660
- terminalReason = terminal.reason;
1661
- terminalPolicyName = terminal.policyName;
1662
- const terminalMetrics = terminal.committedByCaller ? {
1663
- partial: false,
1664
- durationMs: performance.now() - runStartedAt,
1665
- ...usage && { usage },
1666
- ...firstOutputAt !== undefined && { ttftMs: firstOutputAt - runStartedAt }
1667
- } : undefined;
1668
- if (terminalMetrics) {
1847
+ const spent = statedUsage(usage);
1848
+ const emitSpend = (report) => {
1669
1849
  config.observe?.emit({
1670
1850
  schemaVersion: 1,
1671
- eventId: agentDurableEventId("terminal", run.id, snapshot.version),
1851
+ eventId: report.eventId,
1672
1852
  type: "run-terminal",
1673
1853
  conversationId: run.conversationId,
1674
1854
  runId: run.id,
1675
1855
  traceId: trace?.traceId ?? generateId(),
1676
1856
  spanId: trace?.spanId ?? generateId(),
1677
1857
  ...trace?.parentSpanId && { parentSpanId: trace.parentSpanId },
1678
- state: run.state,
1679
- terminalReason,
1858
+ state: report.state,
1859
+ terminalReason: report.reason,
1680
1860
  ...selectedModel && { modelId: selectedModel.descriptor.modelId },
1681
- durationMs: terminalMetrics.durationMs,
1682
- ...usage && { usage },
1861
+ durationMs: performance.now() - runStartedAt,
1862
+ usage: spent,
1683
1863
  ...internalCause !== undefined && { internalCause },
1684
1864
  ...firstOutputAt !== undefined && { ttftMs: firstOutputAt - runStartedAt },
1685
1865
  emittedAt: now().toISOString()
1686
1866
  });
1867
+ };
1868
+ const unsettledEventId = (version) => agentDurableEventId("terminal", `${run.id}:${runtimeEpoch}`, version);
1869
+ let terminal;
1870
+ try {
1871
+ terminal = await commitAgentRunTerminal({
1872
+ store: config.store,
1873
+ runtimeEpoch,
1874
+ candidate: {
1875
+ run,
1876
+ assistant,
1877
+ reason: terminalReason,
1878
+ ...terminalPolicyName && { policyName: terminalPolicyName },
1879
+ usage: spent
1880
+ },
1881
+ now
1882
+ });
1883
+ } catch (error) {
1884
+ emitSpend({
1885
+ eventId: unsettledEventId(snapshot.version),
1886
+ state: run.state,
1887
+ reason: terminalReason
1888
+ });
1889
+ throw error;
1890
+ }
1891
+ snapshot = terminal.snapshot;
1892
+ run = terminal.run;
1893
+ assistant = terminal.assistant;
1894
+ terminalReason = terminal.reason;
1895
+ terminalPolicyName = terminal.policyName;
1896
+ const terminalMetrics = terminal.committedByCaller ? {
1897
+ partial: !sawProviderFinish,
1898
+ durationMs: performance.now() - runStartedAt,
1899
+ usage: spent,
1900
+ ...firstOutputAt !== undefined && { ttftMs: firstOutputAt - runStartedAt }
1901
+ } : undefined;
1902
+ emitSpend({
1903
+ eventId: terminal.committedByCaller ? agentDurableEventId("terminal", run.id, snapshot.version) : unsettledEventId(snapshot.version),
1904
+ state: run.state,
1905
+ reason: terminalReason
1906
+ });
1907
+ if (terminalMetrics) {
1687
1908
  await publish({
1688
1909
  type: "terminal",
1689
1910
  eventId: agentDurableEventId("terminal", run.id, snapshot.version),
@@ -1731,6 +1952,9 @@ function createAgentRuntime(config) {
1731
1952
  if (idleTimeoutMs !== undefined && (!Number.isSafeInteger(idleTimeoutMs) || idleTimeoutMs < 1)) {
1732
1953
  throw new TypeError("idleTimeoutMs must be a positive safe integer");
1733
1954
  }
1955
+ const absorbable = new Set;
1956
+ const absorbedInto = new Map;
1957
+ const runResults = new Map;
1734
1958
  const policyNames = new Set(["max-steps"]);
1735
1959
  for (const policy of config.loop?.stopPolicies ?? []) {
1736
1960
  if (!policy.name || policyNames.has(policy.name)) {
@@ -1863,7 +2087,8 @@ function createAgentRuntime(config) {
1863
2087
  if (existingTicket)
1864
2088
  return existingTicket;
1865
2089
  const key = config.runs?.key?.(input) ?? input.conversationId;
1866
- const policy = typeof config.runs?.inputPolicy === "function" ? config.runs.inputPolicy(input) : config.runs?.inputPolicy ?? "queue";
2090
+ const declaredPolicy = typeof config.runs?.inputPolicy === "function" ? config.runs.inputPolicy(input) : config.runs?.inputPolicy ?? "queue";
2091
+ const policy = declaredPolicy === "inject" ? "queue" : declaredPolicy;
1867
2092
  const nowIso = now().toISOString();
1868
2093
  const inputMessageId = rawInput.recordIds?.inputMessageId ?? generateId();
1869
2094
  const runId = rawInput.recordIds?.runId ?? generateId();
@@ -1989,6 +2214,8 @@ function createAgentRuntime(config) {
1989
2214
  state: acceptedRun.state,
1990
2215
  emittedAt: now().toISOString()
1991
2216
  });
2217
+ if (declaredPolicy === "inject")
2218
+ absorbable.add(acceptedRun.id);
1992
2219
  outerAccepted.resolve();
1993
2220
  if (acceptance.outcome === "duplicate") {
1994
2221
  if (!acceptedRun.terminalReason) {
@@ -2037,7 +2264,25 @@ function createAgentRuntime(config) {
2037
2264
  await waitForAdmissionAcceptances(reservation.lane);
2038
2265
  return {
2039
2266
  runId: acceptedRun.id,
2040
- execute: () => executeRun({ acceptedRun, context, signal })
2267
+ execute: () => {
2268
+ const answeredBy = absorbedInto.get(acceptedRun.id);
2269
+ const answer = answeredBy ? runResults.get(answeredBy) : undefined;
2270
+ if (answer)
2271
+ return answer;
2272
+ const running = executeRun({
2273
+ acceptedRun,
2274
+ context,
2275
+ signal,
2276
+ absorbable,
2277
+ onAbsorbed: (absorbedRunId) => {
2278
+ absorbedInto.set(absorbedRunId, acceptedRun.id);
2279
+ }
2280
+ });
2281
+ runResults.set(acceptedRun.id, running);
2282
+ return running.finally(() => {
2283
+ absorbable.delete(acceptedRun.id);
2284
+ });
2285
+ }
2041
2286
  };
2042
2287
  }
2043
2288
  });
@@ -2270,7 +2515,8 @@ var CheckpointRunAssistantSchema = z6.object({
2270
2515
  expectedRevision: AgentRecordVersionSchema,
2271
2516
  ownerId: z6.string().min(1),
2272
2517
  fencingToken: AgentRecordVersionSchema.optional(),
2273
- assistant: AgentMessageSchema
2518
+ assistant: AgentMessageSchema,
2519
+ usage: AgentUsageSchema.optional()
2274
2520
  });
2275
2521
  var CommitRunTerminalSchema = z6.object({
2276
2522
  conversationId: AgentRecordIdSchema,
@@ -2280,7 +2526,17 @@ var CommitRunTerminalSchema = z6.object({
2280
2526
  fencingToken: AgentRecordVersionSchema.optional(),
2281
2527
  assistant: AgentMessageSchema,
2282
2528
  reason: AgentTerminalReasonSchema,
2283
- policyName: z6.string().min(1).optional()
2529
+ policyName: z6.string().min(1).optional(),
2530
+ usage: AgentUsageSchema.optional()
2531
+ });
2532
+ var AbsorbQueuedRunSchema = z6.object({
2533
+ conversationId: AgentRecordIdSchema,
2534
+ runningRunId: AgentRecordIdSchema,
2535
+ runningExpectedRevision: AgentRecordVersionSchema,
2536
+ ownerId: z6.string().min(1),
2537
+ fencingToken: AgentRecordVersionSchema.optional(),
2538
+ queuedRunId: AgentRecordIdSchema,
2539
+ queuedExpectedRevision: AgentRecordVersionSchema
2284
2540
  });
2285
2541
  var RequestRunInterruptSchema = z6.object({
2286
2542
  conversationId: AgentRecordIdSchema,
@@ -2426,10 +2682,13 @@ function conflict(actualVersion) {
2426
2682
  return { outcome: "conflict", actualVersion };
2427
2683
  }
2428
2684
  function terminalState(reason) {
2429
- if (reason === "success" || reason === "policy_stop")
2685
+ if (reason === "success" || reason === "policy_stop" || reason === "provider_stop") {
2430
2686
  return "completed";
2687
+ }
2431
2688
  if (reason === "interrupted")
2432
2689
  return "interrupted";
2690
+ if (reason === "superseded")
2691
+ return "superseded";
2433
2692
  if (reason === "cancelled" || reason === "shutdown" || reason === "timeout") {
2434
2693
  return "cancelled";
2435
2694
  }
@@ -2447,6 +2706,7 @@ function applied(current, input, effects) {
2447
2706
  messages: input.messages ?? current.messages
2448
2707
  }),
2449
2708
  ...effects?.runRecord && { runRecord: effects.runRecord },
2709
+ ...effects?.secondaryRunRecord && { secondaryRunRecord: effects.secondaryRunRecord },
2450
2710
  ...effects?.admissionReceipt && { admissionReceipt: effects.admissionReceipt },
2451
2711
  ...effects?.historyMutation && { historyMutation: effects.historyMutation }
2452
2712
  };
@@ -2490,6 +2750,39 @@ function reduceStore(current, operation) {
2490
2750
  historyMutation: { type: "admit", input: input.input }
2491
2751
  });
2492
2752
  }
2753
+ if (operation.type === "absorb") {
2754
+ const input = operation.input;
2755
+ if (current.conversationId !== input.conversationId)
2756
+ return { outcome: "not_found" };
2757
+ const running = current.runs.find((candidate) => candidate.id === input.runningRunId);
2758
+ const queued = current.runs.find((candidate) => candidate.id === input.queuedRunId);
2759
+ if (!running || !queued)
2760
+ return { outcome: "not_found" };
2761
+ if (running.revision !== input.runningExpectedRevision || running.state !== "running" || running.ownerId !== input.ownerId || input.fencingToken !== undefined && running.fencingToken !== input.fencingToken) {
2762
+ return conflict(running.revision);
2763
+ }
2764
+ if (queued.revision !== input.queuedExpectedRevision || queued.state !== "queued" || queued.ownerId !== undefined || queued.terminalReason !== undefined || queued.id === running.id) {
2765
+ return conflict(queued.revision);
2766
+ }
2767
+ const stamp = new Date().toISOString();
2768
+ const grown = AgentRunSchema.parse({
2769
+ ...running,
2770
+ inputMessageIds: [...running.inputMessageIds, ...queued.inputMessageIds],
2771
+ revision: running.revision + 1,
2772
+ updatedAt: stamp
2773
+ });
2774
+ const emptied = AgentRunSchema.parse({
2775
+ ...queued,
2776
+ state: "absorbed",
2777
+ absorbedIntoRunId: running.id,
2778
+ revision: queued.revision + 1,
2779
+ updatedAt: stamp
2780
+ });
2781
+ return applied(current, { runs: replaceRun(replaceRun(current.runs, grown), emptied) }, {
2782
+ runRecord: AgentStoredRunSchema.parse({ schemaVersion: 1, run: grown }),
2783
+ secondaryRunRecord: AgentStoredRunSchema.parse({ schemaVersion: 1, run: emptied })
2784
+ });
2785
+ }
2493
2786
  const conversationId = operation.input.conversationId;
2494
2787
  const run = operation.type === "compact" ? undefined : current.runs.find((candidate) => candidate.id === operation.input.runId);
2495
2788
  if (operation.type !== "compact" && !run)
@@ -2519,6 +2812,7 @@ function reduceStore(current, operation) {
2519
2812
  }
2520
2813
  const next = AgentRunSchema.parse({
2521
2814
  ...run,
2815
+ ...input.usage && { usage: input.usage },
2522
2816
  revision: run.revision + 1,
2523
2817
  updatedAt: new Date().toISOString()
2524
2818
  });
@@ -2605,6 +2899,7 @@ function reduceStore(current, operation) {
2605
2899
  state: terminalState(input.reason),
2606
2900
  terminalReason: input.reason,
2607
2901
  ...input.policyName && { terminalPolicyName: input.policyName },
2902
+ ...input.usage && { usage: input.usage },
2608
2903
  revision: run.revision + 1,
2609
2904
  updatedAt: new Date().toISOString()
2610
2905
  });
@@ -2693,7 +2988,7 @@ function createAgentRuntimeStore(driver) {
2693
2988
  });
2694
2989
  const mutate = (operation) => driver.transaction(async (transaction) => {
2695
2990
  const conversationId = operationConversationId(operation);
2696
- const operationRunId = operation.type === "accept" ? operation.input.coalesceIntoRunId : operation.type === "compact" ? undefined : operation.input.runId;
2991
+ const operationRunId = operation.type === "accept" ? operation.input.coalesceIntoRunId : operation.type === "compact" ? undefined : operation.type === "absorb" ? operation.input.queuedRunId : operation.input.runId;
2697
2992
  const [stored, messages, activeRecords, operationRecord, duplicateReceipt] = await Promise.all([
2698
2993
  driver.head.load(transaction, conversationId),
2699
2994
  driver.history.load(transaction, conversationId),
@@ -2774,6 +3069,9 @@ function createAgentRuntimeStore(driver) {
2774
3069
  return conflict(outcome.actualVersion);
2775
3070
  if (reduced.runRecord)
2776
3071
  await driver.runs.save(transaction, reduced.runRecord);
3072
+ if (reduced.secondaryRunRecord) {
3073
+ await driver.runs.save(transaction, reduced.secondaryRunRecord);
3074
+ }
2777
3075
  if (reduced.admissionReceipt) {
2778
3076
  await driver.admissions.create(transaction, reduced.admissionReceipt);
2779
3077
  }
@@ -2797,6 +3095,7 @@ function createAgentRuntimeStore(driver) {
2797
3095
  type: "interrupt",
2798
3096
  input: RequestRunInterruptSchema.parse(input)
2799
3097
  }),
3098
+ absorbQueuedRun: (input) => mutate({ type: "absorb", input: AbsorbQueuedRunSchema.parse(input) }),
2800
3099
  recoverRun: (input) => mutate({ type: "recover", input: RecoverAgentRunSchema.parse(input) }),
2801
3100
  commitRunTerminal: (input) => mutate({ type: "terminal", input: CommitRunTerminalSchema.parse(input) }),
2802
3101
  replaceCompactedRange: (input) => mutate({
@@ -2969,6 +3268,7 @@ function createMemoryAgentRuntimeStore() {
2969
3268
  return createAgentRuntimeStore(driver);
2970
3269
  }
2971
3270
  export {
3271
+ AbsorbQueuedRunSchema,
2972
3272
  AcceptInputAndAssignRunSchema,
2973
3273
  AcquireAgentRunSchema,
2974
3274
  AgentAdmissionEventSchema,