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.
- package/dist/agent-runtime/compaction.d.ts +14 -1
- package/dist/agent-runtime/compaction.d.ts.map +1 -1
- package/dist/agent-runtime/coordinator.d.ts +23 -2
- package/dist/agent-runtime/coordinator.d.ts.map +1 -1
- package/dist/agent-runtime/events.d.ts +140 -0
- package/dist/agent-runtime/events.d.ts.map +1 -1
- package/dist/agent-runtime/history.d.ts +39 -1
- package/dist/agent-runtime/history.d.ts.map +1 -1
- package/dist/agent-runtime/observability.d.ts +4 -0
- package/dist/agent-runtime/observability.d.ts.map +1 -1
- package/dist/agent-runtime/prompt.d.ts +1 -1
- package/dist/agent-runtime/prompt.d.ts.map +1 -1
- package/dist/agent-runtime/run-execution.d.ts +4 -0
- package/dist/agent-runtime/run-execution.d.ts.map +1 -1
- package/dist/agent-runtime/runtime-internals.d.ts +47 -0
- package/dist/agent-runtime/runtime-internals.d.ts.map +1 -1
- package/dist/agent-runtime/runtime.d.ts +1 -0
- package/dist/agent-runtime/runtime.d.ts.map +1 -1
- package/dist/agent-runtime/schemas.d.ts +208 -77
- package/dist/agent-runtime/schemas.d.ts.map +1 -1
- package/dist/agent-runtime/store-driver.d.ts +191 -0
- package/dist/agent-runtime/store-driver.d.ts.map +1 -1
- package/dist/agent-runtime/store.d.ts +583 -0
- package/dist/agent-runtime/store.d.ts.map +1 -1
- package/dist/agent-runtime/terminal-commit.d.ts +34 -4
- package/dist/agent-runtime/terminal-commit.d.ts.map +1 -1
- package/dist/agent-runtime/terminal-status.d.ts +13 -0
- package/dist/agent-runtime/terminal-status.d.ts.map +1 -1
- package/dist/agent-runtime.d.ts +1 -1
- package/dist/agent-runtime.d.ts.map +1 -1
- package/dist/agent-runtime.js +399 -99
- package/dist/{index-vtjgx3vv.js → index-b1k33127.js} +25 -18
- package/dist/testing/agent-store-conformance.d.ts.map +1 -1
- package/dist/testing.js +26 -4
- package/llms-full.txt +386 -9
- package/package.json +1 -1
package/dist/agent-runtime.js
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
AgentToolResultPartSchema,
|
|
30
30
|
AgentUsageSchema,
|
|
31
31
|
AgentUsageValueSchema
|
|
32
|
-
} from "./index-
|
|
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
|
|
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(
|
|
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(
|
|
594
|
+
if (user.message) {
|
|
595
|
+
projected.push(user.message);
|
|
596
|
+
decisions.push(decide(message, "projected", "projected", user.rendered));
|
|
525
597
|
} else {
|
|
526
|
-
decisions.push(
|
|
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(
|
|
606
|
+
decisions.push(decide(message, "projected", "projected", new Set(["text"])));
|
|
535
607
|
} else {
|
|
536
|
-
decisions.push(
|
|
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
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
|
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: [...
|
|
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(
|
|
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 =
|
|
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 {
|
|
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:
|
|
1111
|
+
status: assistantStatus(reason),
|
|
1033
1112
|
parts,
|
|
1034
1113
|
updatedAt: now().toISOString()
|
|
1035
1114
|
}),
|
|
1036
|
-
reason
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1373
|
-
|
|
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
|
|
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
|
-
|
|
1610
|
-
usage =
|
|
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
|
|
1647
|
-
|
|
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:
|
|
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:
|
|
1679
|
-
terminalReason,
|
|
1858
|
+
state: report.state,
|
|
1859
|
+
terminalReason: report.reason,
|
|
1680
1860
|
...selectedModel && { modelId: selectedModel.descriptor.modelId },
|
|
1681
|
-
durationMs:
|
|
1682
|
-
|
|
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
|
|
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: () =>
|
|
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,
|