stitchkit 0.59.4 → 0.60.1
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/README.md +7 -3
- package/dist/agent-runtime/admission-lanes.d.ts +35 -0
- package/dist/agent-runtime/admission-lanes.d.ts.map +1 -0
- package/dist/agent-runtime/coordinator.d.ts +46 -2
- package/dist/agent-runtime/coordinator.d.ts.map +1 -1
- package/dist/agent-runtime/events.d.ts +7 -0
- package/dist/agent-runtime/events.d.ts.map +1 -1
- package/dist/agent-runtime/history.d.ts +7 -0
- package/dist/agent-runtime/history.d.ts.map +1 -1
- package/dist/agent-runtime/models.d.ts +2 -3
- package/dist/agent-runtime/models.d.ts.map +1 -1
- package/dist/agent-runtime/observability.d.ts.map +1 -1
- package/dist/agent-runtime/run-execution.d.ts +31 -0
- package/dist/agent-runtime/run-execution.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-internals.d.ts +25 -0
- package/dist/agent-runtime/runtime-internals.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-result.d.ts +11 -0
- package/dist/agent-runtime/runtime-result.d.ts.map +1 -0
- package/dist/agent-runtime/runtime.d.ts +16 -15
- package/dist/agent-runtime/runtime.d.ts.map +1 -1
- package/dist/agent-runtime/schemas.d.ts.map +1 -1
- package/dist/agent-runtime/store-driver.d.ts.map +1 -1
- package/dist/agent-runtime/store.d.ts +9 -2
- package/dist/agent-runtime/store.d.ts.map +1 -1
- 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 -0
- package/dist/agent-runtime.d.ts +5 -3
- package/dist/agent-runtime.d.ts.map +1 -1
- package/dist/agent-runtime.js +303 -163
- package/dist/application/activity.d.ts +7 -2
- package/dist/application/activity.d.ts.map +1 -1
- package/dist/application/grammy.d.ts +14 -2
- package/dist/application/grammy.d.ts.map +1 -1
- package/dist/application/health.d.ts +9 -2
- package/dist/application/health.d.ts.map +1 -1
- package/dist/application/kernel.d.ts +61 -5
- package/dist/application/kernel.d.ts.map +1 -1
- package/dist/application/schemas.d.ts +42 -0
- package/dist/application/schemas.d.ts.map +1 -1
- package/dist/application-grammy.js +5 -3
- package/dist/application-opentelemetry.js +2 -2
- package/dist/application.d.ts +3 -3
- package/dist/application.d.ts.map +1 -1
- package/dist/application.js +17 -8
- package/dist/cli.js +7 -6
- package/dist/contract/errors.d.ts +7 -0
- package/dist/contract/errors.d.ts.map +1 -1
- package/dist/contract/index.js +1 -1
- package/dist/declaration.d.ts +345 -0
- package/dist/declaration.d.ts.map +1 -0
- package/dist/declaration.js +183 -0
- package/dist/{index-3nwng7hs.js → index-1pgeyyee.js} +4 -4
- package/dist/{index-s8w2y7kr.js → index-2cgbdckv.js} +26 -18
- package/dist/{index-gnvzyw0k.js → index-6taryy00.js} +2 -2
- package/dist/{index-5c8n2123.js → index-82e74yfx.js} +69 -33
- package/dist/{index-mgf6cdkm.js → index-8eywc9zv.js} +35 -1
- package/dist/{index-0nc0cddp.js → index-a59da114.js} +17 -28
- package/dist/{index-f6pymtqe.js → index-mfw1pec7.js} +7 -5
- package/dist/{index-y2rb7dwx.js → index-pzyt11ch.js} +5 -1
- package/dist/{index-mr617n62.js → index-svqhcrrj.js} +1 -1
- package/dist/{index-28cqssm3.js → index-t8xrqc9g.js} +7 -0
- package/dist/{index-7c0gkyvj.js → index-tvwcrx2d.js} +5 -3
- package/dist/{index-1tkyng3g.js → index-vkk06pv1.js} +1 -9
- package/dist/{index-ps5pxp3h.js → index-wd8g8z6e.js} +2 -79
- package/dist/{index-sa2mbwa7.js → index-wrhf06ak.js} +7 -5
- package/dist/{index-2nzpcfbp.js → index-x8v8b7rc.js} +2 -2
- package/dist/index-xxye8j3k.js +85 -0
- package/dist/{index-41wm56v0.js → index-y91zd0ch.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/observability-sink.d.ts.map +1 -1
- package/dist/internal/route-pattern.d.ts +9 -0
- package/dist/internal/route-pattern.d.ts.map +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +8 -7
- package/dist/observability/index.js +6 -5
- package/dist/observability/sanitize.d.ts +3 -1
- package/dist/observability/sanitize.d.ts.map +1 -1
- package/dist/remote.js +6 -4
- package/dist/server/error-hook.d.ts +10 -3
- package/dist/server/error-hook.d.ts.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +16 -18
- package/dist/server/middleware/cors.d.ts +5 -2
- package/dist/server/middleware/cors.d.ts.map +1 -1
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/socket-io-config.d.ts +68 -3
- package/dist/server/socket-io-config.d.ts.map +1 -1
- package/dist/server/socket-io-node.d.ts +1 -1
- package/dist/server/socket-io-node.d.ts.map +1 -1
- package/dist/server/socket-io.d.ts +1 -1
- package/dist/server/socket-io.d.ts.map +1 -1
- package/dist/testing/surface-manifest.d.ts.map +1 -1
- package/dist/testing.js +9 -10
- package/dist/tools/flatten-join.d.ts +2 -0
- package/dist/tools/flatten-join.d.ts.map +1 -1
- package/dist/tools/flatten.d.ts.map +1 -1
- package/dist/tools/mcp-prepare.d.ts +0 -2
- package/dist/tools/mcp-prepare.d.ts.map +1 -1
- package/dist/tools/runtime-tool.d.ts +0 -3
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools/schema.d.ts +0 -4
- package/dist/tools/schema.d.ts.map +1 -1
- package/dist/tools.js +13 -11
- package/llms-full.txt +926 -68
- package/llms.txt +1 -0
- package/package.json +10 -6
package/dist/agent-runtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ToolExecutionControlError,
|
|
3
3
|
isToolExecutionControlError
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-wrhf06ak.js";
|
|
5
5
|
import {
|
|
6
6
|
AgentAssistantPlaceholderSchema,
|
|
7
7
|
AgentControlPartSchema,
|
|
@@ -34,15 +34,16 @@ import"./index-6djpbnda.js";
|
|
|
34
34
|
import"./index-cby4ar3v.js";
|
|
35
35
|
import {
|
|
36
36
|
createBoundedSinkManager
|
|
37
|
-
} from "./index-
|
|
38
|
-
import"./index-
|
|
39
|
-
import"./index-
|
|
37
|
+
} from "./index-a59da114.js";
|
|
38
|
+
import"./index-tvwcrx2d.js";
|
|
39
|
+
import"./index-wd8g8z6e.js";
|
|
40
40
|
import"./index-6k1937bx.js";
|
|
41
41
|
import {
|
|
42
42
|
childSpan,
|
|
43
43
|
createTraceContext
|
|
44
|
-
} from "./index-
|
|
44
|
+
} from "./index-pzyt11ch.js";
|
|
45
45
|
import"./index-smpbdg6k.js";
|
|
46
|
+
import"./index-xxye8j3k.js";
|
|
46
47
|
|
|
47
48
|
// src/agent-runtime/compaction.ts
|
|
48
49
|
function providerValidTurn(messages) {
|
|
@@ -148,6 +149,13 @@ function structuredCompaction(config) {
|
|
|
148
149
|
};
|
|
149
150
|
}
|
|
150
151
|
// src/agent-runtime/coordinator.ts
|
|
152
|
+
function assertCloseBudgets(options) {
|
|
153
|
+
for (const [name, value] of Object.entries(options)) {
|
|
154
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
|
|
155
|
+
throw new TypeError(`${name} must be a non-negative safe integer`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
151
159
|
function createAgentSessionCoordinator() {
|
|
152
160
|
const lanes = new Map;
|
|
153
161
|
let closed = false;
|
|
@@ -230,11 +238,7 @@ function createAgentSessionCoordinator() {
|
|
|
230
238
|
return true;
|
|
231
239
|
},
|
|
232
240
|
async close(options = {}) {
|
|
233
|
-
|
|
234
|
-
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
|
|
235
|
-
throw new TypeError(`${name} must be a non-negative safe integer`);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
241
|
+
assertCloseBudgets(options);
|
|
238
242
|
closed = true;
|
|
239
243
|
const error = new Error("Agent session coordinator is closed");
|
|
240
244
|
for (const lane of lanes.values()) {
|
|
@@ -243,21 +247,31 @@ function createAgentSessionCoordinator() {
|
|
|
243
247
|
}
|
|
244
248
|
const active = [...lanes.values()].map((lane) => lane.active).filter((run) => run !== undefined);
|
|
245
249
|
const settlements = active.map((run) => run.settled);
|
|
250
|
+
const outstanding = new Set(settlements);
|
|
251
|
+
for (const settled of settlements) {
|
|
252
|
+
settled.then(() => {
|
|
253
|
+
outstanding.delete(settled);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
246
256
|
const drain = Promise.all(settlements).then(() => {
|
|
247
257
|
return;
|
|
248
258
|
});
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
259
|
+
const finished = () => ({
|
|
260
|
+
settled: outstanding.size === 0,
|
|
261
|
+
timedOut: outstanding.size > 0,
|
|
262
|
+
remaining: outstanding.size
|
|
263
|
+
});
|
|
264
|
+
if (options.gracePeriodMs !== undefined && await waitWithin(drain, options.gracePeriodMs)) {
|
|
265
|
+
return finished();
|
|
253
266
|
}
|
|
254
267
|
for (const run of active)
|
|
255
268
|
run.controller.abort("shutdown");
|
|
256
|
-
if (options.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
269
|
+
if (options.forceTimeoutMs === undefined) {
|
|
270
|
+
await drain;
|
|
271
|
+
return finished();
|
|
272
|
+
}
|
|
260
273
|
await waitWithin(drain, options.forceTimeoutMs);
|
|
274
|
+
return finished();
|
|
261
275
|
},
|
|
262
276
|
isRunning(key) {
|
|
263
277
|
return lanes.get(key)?.active !== undefined;
|
|
@@ -431,12 +445,16 @@ async function userMessage(message, options) {
|
|
|
431
445
|
});
|
|
432
446
|
continue;
|
|
433
447
|
}
|
|
434
|
-
const fallback = options.unresolvedFile ?? "
|
|
448
|
+
const fallback = options.unresolvedFile ?? "omit";
|
|
435
449
|
if (fallback === "error") {
|
|
436
450
|
throw new Error(`No history file resolver configured for ${part.reference}`);
|
|
437
451
|
}
|
|
438
452
|
if (fallback === "text") {
|
|
439
|
-
|
|
453
|
+
const described = part.filename ?? part.mediaType;
|
|
454
|
+
content.push({
|
|
455
|
+
type: "text",
|
|
456
|
+
text: described ? `[attachment: ${described}]` : "[attachment]"
|
|
457
|
+
});
|
|
440
458
|
}
|
|
441
459
|
}
|
|
442
460
|
if (content.length === 0)
|
|
@@ -693,7 +711,18 @@ function createAgentObservability(config) {
|
|
|
693
711
|
...config.onSinkError && { onSinkError: config.onSinkError },
|
|
694
712
|
...config.onDrop && { onDrop: config.onDrop }
|
|
695
713
|
});
|
|
714
|
+
const DEDUPLICATION_WINDOW = 1e4;
|
|
696
715
|
const emitted = new Set;
|
|
716
|
+
const ring = new Array(DEDUPLICATION_WINDOW).fill(undefined);
|
|
717
|
+
let cursor = 0;
|
|
718
|
+
const remember = (eventId) => {
|
|
719
|
+
emitted.add(eventId);
|
|
720
|
+
const evicted = ring[cursor];
|
|
721
|
+
ring[cursor] = eventId;
|
|
722
|
+
cursor = (cursor + 1) % DEDUPLICATION_WINDOW;
|
|
723
|
+
if (evicted !== undefined)
|
|
724
|
+
emitted.delete(evicted);
|
|
725
|
+
};
|
|
697
726
|
return {
|
|
698
727
|
rootTrace(parent) {
|
|
699
728
|
const trace = parent ? childSpan(parent) : createTraceContext();
|
|
@@ -703,7 +732,7 @@ function createAgentObservability(config) {
|
|
|
703
732
|
const parsed = AgentRunEventSchema.parse(rawEvent);
|
|
704
733
|
if ((config.deduplicate ?? true) && emitted.has(parsed.eventId))
|
|
705
734
|
return;
|
|
706
|
-
|
|
735
|
+
remember(parsed.eventId);
|
|
707
736
|
manager.submit(() => config.includeInternalCause ? parsed : AgentRunEventSchema.omit({ internalCause: true }).parse(parsed));
|
|
708
737
|
},
|
|
709
738
|
flush: () => manager.flush(),
|
|
@@ -893,13 +922,74 @@ function defineAgentProtocol(config) {
|
|
|
893
922
|
parsePart: (input) => AgentMessageSchema.shape.parts.element.parse(input)
|
|
894
923
|
};
|
|
895
924
|
}
|
|
896
|
-
// src/agent-runtime/
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
925
|
+
// src/agent-runtime/admission-lanes.ts
|
|
926
|
+
function createRuntimeAdmissionLanes() {
|
|
927
|
+
const lanes = new Map;
|
|
928
|
+
const admit = (runId) => {
|
|
929
|
+
const admission = { runId, completion: Promise.withResolvers() };
|
|
930
|
+
admission.completion.promise.catch(() => {
|
|
931
|
+
return;
|
|
932
|
+
});
|
|
933
|
+
return admission;
|
|
934
|
+
};
|
|
935
|
+
return {
|
|
936
|
+
reserve(key, runId) {
|
|
937
|
+
const existing = lanes.get(key);
|
|
938
|
+
const lane = existing ?? { acceptanceTail: Promise.resolve() };
|
|
939
|
+
if (!existing)
|
|
940
|
+
lanes.set(key, lane);
|
|
941
|
+
if (!lane.head) {
|
|
942
|
+
lane.head = admit(runId);
|
|
943
|
+
return { lane, admission: lane.head, shouldSchedule: true };
|
|
944
|
+
}
|
|
945
|
+
if (!lane.pending) {
|
|
946
|
+
lane.pending = admit(runId);
|
|
947
|
+
return { lane, admission: lane.pending, shouldSchedule: true };
|
|
948
|
+
}
|
|
949
|
+
return { lane, admission: lane.pending, shouldSchedule: false };
|
|
950
|
+
},
|
|
951
|
+
settle(key, admission) {
|
|
952
|
+
const lane = lanes.get(key);
|
|
953
|
+
if (!lane)
|
|
954
|
+
return;
|
|
955
|
+
if (lane.head?.runId === admission.runId) {
|
|
956
|
+
lane.head = lane.pending;
|
|
957
|
+
lane.pending = undefined;
|
|
958
|
+
} else if (lane.pending?.runId === admission.runId) {
|
|
959
|
+
lane.pending = undefined;
|
|
960
|
+
}
|
|
961
|
+
if (!lane.head && !lane.pending)
|
|
962
|
+
lanes.delete(key);
|
|
963
|
+
},
|
|
964
|
+
async waitForAcceptances(lane) {
|
|
965
|
+
while (true) {
|
|
966
|
+
const tail = lane.acceptanceTail;
|
|
967
|
+
await tail.catch(() => {
|
|
968
|
+
return;
|
|
969
|
+
});
|
|
970
|
+
if (tail === lane.acceptanceTail)
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// src/agent-runtime/run-execution.ts
|
|
978
|
+
import { stepCountIs, streamText } from "ai";
|
|
979
|
+
|
|
980
|
+
// src/agent-runtime/runtime-internals.ts
|
|
901
981
|
import { z as z5 } from "zod";
|
|
902
982
|
|
|
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
|
+
|
|
903
993
|
// src/agent-runtime/terminal-commit.ts
|
|
904
994
|
class AgentRuntimeConflictError extends Error {
|
|
905
995
|
constructor(operation) {
|
|
@@ -912,20 +1002,12 @@ function appliedSnapshot(result, operation) {
|
|
|
912
1002
|
return result.snapshot;
|
|
913
1003
|
throw new AgentRuntimeConflictError(operation);
|
|
914
1004
|
}
|
|
915
|
-
function terminalMessageStatus(reason) {
|
|
916
|
-
if (reason === "success" || reason === "policy_stop")
|
|
917
|
-
return "completed";
|
|
918
|
-
if (reason === "interrupted" || reason === "cancelled" || reason === "shutdown") {
|
|
919
|
-
return "interrupted";
|
|
920
|
-
}
|
|
921
|
-
return "failed";
|
|
922
|
-
}
|
|
923
1005
|
function canonicalTerminal(snapshot, runId, retainedAssistant) {
|
|
924
1006
|
const run = snapshot.runs.find((candidate) => candidate.id === runId);
|
|
925
1007
|
if (!run?.terminalReason)
|
|
926
1008
|
return;
|
|
927
1009
|
const assistant = snapshot.messages.find((message) => message.id === run.assistantMessageId) ?? retainedAssistant;
|
|
928
|
-
if (snapshot.conversationId !== run.conversationId || !assistant || assistant.id !== run.assistantMessageId || assistant.conversationId !== run.conversationId || assistant.runId !== run.id || assistant.role !== "assistant" || assistant.status !==
|
|
1010
|
+
if (snapshot.conversationId !== run.conversationId || !assistant || assistant.id !== run.assistantMessageId || assistant.conversationId !== run.conversationId || assistant.runId !== run.id || assistant.role !== "assistant" || assistant.status !== assistantStatus(run.terminalReason)) {
|
|
929
1011
|
throw new AgentRuntimeConflictError("terminal result projection");
|
|
930
1012
|
}
|
|
931
1013
|
return {
|
|
@@ -999,7 +1081,7 @@ async function commitAgentRunTerminal(input) {
|
|
|
999
1081
|
}
|
|
1000
1082
|
}
|
|
1001
1083
|
|
|
1002
|
-
// src/agent-runtime/runtime.ts
|
|
1084
|
+
// src/agent-runtime/runtime-internals.ts
|
|
1003
1085
|
function findRun(runs, runId) {
|
|
1004
1086
|
const run = runs.find((candidate) => candidate.id === runId);
|
|
1005
1087
|
if (!run)
|
|
@@ -1025,14 +1107,6 @@ function appendText(parts, text) {
|
|
|
1025
1107
|
}
|
|
1026
1108
|
parts.push(AgentMessagePartSchema.parse({ type: "text", text }));
|
|
1027
1109
|
}
|
|
1028
|
-
function assistantStatus(reason) {
|
|
1029
|
-
if (reason === "success" || reason === "policy_stop")
|
|
1030
|
-
return "completed";
|
|
1031
|
-
if (reason === "interrupted" || reason === "cancelled" || reason === "shutdown") {
|
|
1032
|
-
return "interrupted";
|
|
1033
|
-
}
|
|
1034
|
-
return "failed";
|
|
1035
|
-
}
|
|
1036
1110
|
function abortTerminalReason(signal) {
|
|
1037
1111
|
if (signal.reason === "shutdown")
|
|
1038
1112
|
return "shutdown";
|
|
@@ -1074,88 +1148,20 @@ function createIdleDeadline(parent, timeoutMs) {
|
|
|
1074
1148
|
}
|
|
1075
1149
|
};
|
|
1076
1150
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
const
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
admission.completion.promise.catch(() => {
|
|
1092
|
-
return;
|
|
1093
|
-
});
|
|
1094
|
-
lane.head = admission;
|
|
1095
|
-
return { lane, admission, shouldSchedule: true };
|
|
1096
|
-
}
|
|
1097
|
-
if (!lane.pending) {
|
|
1098
|
-
const admission = { runId, completion: Promise.withResolvers() };
|
|
1099
|
-
admission.completion.promise.catch(() => {
|
|
1100
|
-
return;
|
|
1101
|
-
});
|
|
1102
|
-
lane.pending = admission;
|
|
1103
|
-
return { lane, admission, shouldSchedule: true };
|
|
1104
|
-
}
|
|
1105
|
-
return { lane, admission: lane.pending, shouldSchedule: false };
|
|
1106
|
-
};
|
|
1107
|
-
const settleAdmission = (key, admission) => {
|
|
1108
|
-
const lane = admissionLanes.get(key);
|
|
1109
|
-
if (!lane)
|
|
1110
|
-
return;
|
|
1111
|
-
if (lane.head?.runId === admission.runId) {
|
|
1112
|
-
lane.head = lane.pending;
|
|
1113
|
-
lane.pending = undefined;
|
|
1114
|
-
} else if (lane.pending?.runId === admission.runId) {
|
|
1115
|
-
lane.pending = undefined;
|
|
1116
|
-
}
|
|
1117
|
-
if (!lane.head && !lane.pending)
|
|
1118
|
-
admissionLanes.delete(key);
|
|
1119
|
-
};
|
|
1120
|
-
const waitForAdmissionAcceptances = async (lane) => {
|
|
1121
|
-
while (true) {
|
|
1122
|
-
const tail = lane.acceptanceTail;
|
|
1123
|
-
await tail.catch(() => {
|
|
1124
|
-
return;
|
|
1125
|
-
});
|
|
1126
|
-
if (tail === lane.acceptanceTail)
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
};
|
|
1130
|
-
const checkpointEveryEvents = config.loop?.checkpointEveryEvents ?? 20;
|
|
1131
|
-
const maxSteps = config.loop?.maxSteps ?? 50;
|
|
1132
|
-
const idleTimeoutMs = config.loop?.idleTimeoutMs;
|
|
1133
|
-
if (!Number.isSafeInteger(checkpointEveryEvents) || checkpointEveryEvents < 1) {
|
|
1134
|
-
throw new TypeError("checkpointEveryEvents must be a positive safe integer");
|
|
1135
|
-
}
|
|
1136
|
-
if (!Number.isSafeInteger(maxSteps) || maxSteps < 1) {
|
|
1137
|
-
throw new TypeError("maxSteps must be a positive safe integer");
|
|
1138
|
-
}
|
|
1139
|
-
if (idleTimeoutMs !== undefined && (!Number.isSafeInteger(idleTimeoutMs) || idleTimeoutMs < 1)) {
|
|
1140
|
-
throw new TypeError("idleTimeoutMs must be a positive safe integer");
|
|
1141
|
-
}
|
|
1142
|
-
const policyNames = new Set(["max-steps"]);
|
|
1143
|
-
for (const policy of config.loop?.stopPolicies ?? []) {
|
|
1144
|
-
if (!policy.name || policyNames.has(policy.name)) {
|
|
1145
|
-
throw new TypeError("Agent stop policy names must be non-empty and unique");
|
|
1146
|
-
}
|
|
1147
|
-
policyNames.add(policy.name);
|
|
1148
|
-
}
|
|
1149
|
-
const publish = async (event) => {
|
|
1150
|
-
try {
|
|
1151
|
-
await config.publish?.(event);
|
|
1152
|
-
} catch (error) {
|
|
1153
|
-
try {
|
|
1154
|
-
await config.onPublishError?.({ event, error });
|
|
1155
|
-
} catch {}
|
|
1156
|
-
}
|
|
1157
|
-
};
|
|
1158
|
-
const executeRun = async (input) => {
|
|
1151
|
+
|
|
1152
|
+
// src/agent-runtime/run-execution.ts
|
|
1153
|
+
function createRunExecutor(dependencies) {
|
|
1154
|
+
const {
|
|
1155
|
+
config,
|
|
1156
|
+
publish,
|
|
1157
|
+
runtimeEpoch,
|
|
1158
|
+
generateId,
|
|
1159
|
+
now,
|
|
1160
|
+
checkpointEveryEvents,
|
|
1161
|
+
maxSteps,
|
|
1162
|
+
idleTimeoutMs
|
|
1163
|
+
} = dependencies;
|
|
1164
|
+
return async function executeRun(input) {
|
|
1159
1165
|
const queuedSnapshot = await config.store.loadSnapshot(input.acceptedRun.conversationId);
|
|
1160
1166
|
const queuedRun = findRun(queuedSnapshot.runs, input.acceptedRun.id);
|
|
1161
1167
|
const acquired = appliedSnapshot(await config.store.acquireRun({
|
|
@@ -1628,8 +1634,9 @@ function createAgentRuntime(config) {
|
|
|
1628
1634
|
} else {
|
|
1629
1635
|
terminalReason = "provider_failure";
|
|
1630
1636
|
}
|
|
1637
|
+
} finally {
|
|
1638
|
+
idleDeadline.dispose();
|
|
1631
1639
|
}
|
|
1632
|
-
idleDeadline.dispose();
|
|
1633
1640
|
assistant = AgentMessageSchema.parse({
|
|
1634
1641
|
...assistant,
|
|
1635
1642
|
status: assistantStatus(terminalReason),
|
|
@@ -1699,10 +1706,110 @@ function createAgentRuntime(config) {
|
|
|
1699
1706
|
...terminalPolicyName && { policyName: terminalPolicyName }
|
|
1700
1707
|
};
|
|
1701
1708
|
};
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
// src/agent-runtime/runtime.ts
|
|
1712
|
+
function createAgentRuntime(config) {
|
|
1713
|
+
const coordinator = createAgentSessionCoordinator();
|
|
1714
|
+
const tickets = new Map;
|
|
1715
|
+
const generateId = config.generateId ?? (() => crypto.randomUUID());
|
|
1716
|
+
const now = config.now ?? (() => new Date);
|
|
1717
|
+
const runtimeEpoch = generateId();
|
|
1718
|
+
const admissionLanes = createRuntimeAdmissionLanes();
|
|
1719
|
+
const reserveAdmission = admissionLanes.reserve;
|
|
1720
|
+
const settleAdmission = admissionLanes.settle;
|
|
1721
|
+
const waitForAdmissionAcceptances = admissionLanes.waitForAcceptances;
|
|
1722
|
+
const checkpointEveryEvents = config.loop?.checkpointEveryEvents ?? 20;
|
|
1723
|
+
const maxSteps = config.loop?.maxSteps ?? 50;
|
|
1724
|
+
const idleTimeoutMs = config.loop?.idleTimeoutMs;
|
|
1725
|
+
if (!Number.isSafeInteger(checkpointEveryEvents) || checkpointEveryEvents < 1) {
|
|
1726
|
+
throw new TypeError("checkpointEveryEvents must be a positive safe integer");
|
|
1727
|
+
}
|
|
1728
|
+
if (!Number.isSafeInteger(maxSteps) || maxSteps < 1) {
|
|
1729
|
+
throw new TypeError("maxSteps must be a positive safe integer");
|
|
1730
|
+
}
|
|
1731
|
+
if (idleTimeoutMs !== undefined && (!Number.isSafeInteger(idleTimeoutMs) || idleTimeoutMs < 1)) {
|
|
1732
|
+
throw new TypeError("idleTimeoutMs must be a positive safe integer");
|
|
1733
|
+
}
|
|
1734
|
+
const policyNames = new Set(["max-steps"]);
|
|
1735
|
+
for (const policy of config.loop?.stopPolicies ?? []) {
|
|
1736
|
+
if (!policy.name || policyNames.has(policy.name)) {
|
|
1737
|
+
throw new TypeError("Agent stop policy names must be non-empty and unique");
|
|
1738
|
+
}
|
|
1739
|
+
policyNames.add(policy.name);
|
|
1740
|
+
}
|
|
1741
|
+
const publish = async (event) => {
|
|
1742
|
+
try {
|
|
1743
|
+
await config.publish?.(event);
|
|
1744
|
+
} catch (error) {
|
|
1745
|
+
try {
|
|
1746
|
+
await config.onPublishError?.({ event, error });
|
|
1747
|
+
} catch {}
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1750
|
+
const executeRun = createRunExecutor({
|
|
1751
|
+
config,
|
|
1752
|
+
publish,
|
|
1753
|
+
runtimeEpoch,
|
|
1754
|
+
generateId,
|
|
1755
|
+
now,
|
|
1756
|
+
checkpointEveryEvents,
|
|
1757
|
+
maxSteps,
|
|
1758
|
+
...idleTimeoutMs !== undefined && { idleTimeoutMs }
|
|
1759
|
+
});
|
|
1760
|
+
let admissionClosed = false;
|
|
1761
|
+
const closedError = () => new Error("[stitchkit] agent runtime is closed and admits no further work");
|
|
1762
|
+
const admissionsInFlight = new Set;
|
|
1763
|
+
const beginAdmission = () => {
|
|
1764
|
+
const handoff = Promise.withResolvers();
|
|
1765
|
+
admissionsInFlight.add(handoff);
|
|
1766
|
+
return () => {
|
|
1767
|
+
if (admissionsInFlight.delete(handoff))
|
|
1768
|
+
handoff.resolve();
|
|
1769
|
+
};
|
|
1770
|
+
};
|
|
1771
|
+
const drainAdmissions = async (budgetMs) => {
|
|
1772
|
+
const pending = [...admissionsInFlight].map((handoff) => handoff.promise);
|
|
1773
|
+
if (pending.length === 0)
|
|
1774
|
+
return 0;
|
|
1775
|
+
let stranded = pending.length;
|
|
1776
|
+
const settled = Promise.all(pending.map((promise) => promise.then(() => {
|
|
1777
|
+
stranded -= 1;
|
|
1778
|
+
})));
|
|
1779
|
+
if (budgetMs === undefined) {
|
|
1780
|
+
await settled;
|
|
1781
|
+
return 0;
|
|
1782
|
+
}
|
|
1783
|
+
let timer;
|
|
1784
|
+
await Promise.race([
|
|
1785
|
+
settled,
|
|
1786
|
+
new Promise((resolve) => {
|
|
1787
|
+
timer = setTimeout(resolve, budgetMs);
|
|
1788
|
+
})
|
|
1789
|
+
]);
|
|
1790
|
+
if (timer !== undefined)
|
|
1791
|
+
clearTimeout(timer);
|
|
1792
|
+
return stranded;
|
|
1793
|
+
};
|
|
1794
|
+
const refuse = () => {
|
|
1795
|
+
const error = closedError();
|
|
1796
|
+
const accepted = Promise.reject(error);
|
|
1797
|
+
const result = Promise.reject(error);
|
|
1798
|
+
accepted.catch(() => {
|
|
1799
|
+
return;
|
|
1800
|
+
});
|
|
1801
|
+
result.catch(() => {
|
|
1802
|
+
return;
|
|
1803
|
+
});
|
|
1804
|
+
return { accepted, result };
|
|
1805
|
+
};
|
|
1702
1806
|
const resume = (rawInput) => {
|
|
1807
|
+
if (admissionClosed)
|
|
1808
|
+
return refuse();
|
|
1703
1809
|
const context = config.protocol.parseContext(rawInput.context);
|
|
1704
1810
|
const accepted = Promise.withResolvers();
|
|
1705
1811
|
const result = Promise.withResolvers();
|
|
1812
|
+
const handedOff = beginAdmission();
|
|
1706
1813
|
(async () => {
|
|
1707
1814
|
try {
|
|
1708
1815
|
const snapshot = await config.store.loadSnapshot(rawInput.conversationId);
|
|
@@ -1719,16 +1826,29 @@ function createAgentRuntime(config) {
|
|
|
1719
1826
|
execute: () => executeRun({ acceptedRun: recoveredRun, context, signal })
|
|
1720
1827
|
})
|
|
1721
1828
|
});
|
|
1829
|
+
ticket.accepted.catch(() => {
|
|
1830
|
+
return;
|
|
1831
|
+
});
|
|
1722
1832
|
ticket.result.then(result.resolve, result.reject);
|
|
1723
1833
|
} catch (error) {
|
|
1724
1834
|
accepted.reject(error);
|
|
1725
1835
|
result.reject(error);
|
|
1836
|
+
} finally {
|
|
1837
|
+
handedOff();
|
|
1726
1838
|
}
|
|
1727
1839
|
})();
|
|
1728
1840
|
return { accepted: accepted.promise, result: result.promise };
|
|
1729
1841
|
};
|
|
1730
1842
|
return {
|
|
1731
1843
|
submit(rawInput) {
|
|
1844
|
+
if (admissionClosed) {
|
|
1845
|
+
const refused = refuse();
|
|
1846
|
+
const admission = Promise.reject(closedError());
|
|
1847
|
+
admission.catch(() => {
|
|
1848
|
+
return;
|
|
1849
|
+
});
|
|
1850
|
+
return { ...refused, admission };
|
|
1851
|
+
}
|
|
1732
1852
|
const metadata = rawInput.metadata === undefined ? undefined : config.protocol.parseInputMetadata(rawInput.metadata);
|
|
1733
1853
|
const input = {
|
|
1734
1854
|
conversationId: rawInput.conversationId,
|
|
@@ -1801,6 +1921,7 @@ function createAgentRuntime(config) {
|
|
|
1801
1921
|
tickets.delete(input.conversationId);
|
|
1802
1922
|
};
|
|
1803
1923
|
outerResult.promise.then(forgetTicket, forgetTicket);
|
|
1924
|
+
const handedOff = beginAdmission();
|
|
1804
1925
|
(async () => {
|
|
1805
1926
|
try {
|
|
1806
1927
|
await previousAcceptance.catch(() => {
|
|
@@ -1810,6 +1931,8 @@ function createAgentRuntime(config) {
|
|
|
1810
1931
|
context,
|
|
1811
1932
|
conversationId: input.conversationId
|
|
1812
1933
|
});
|
|
1934
|
+
if (admissionClosed)
|
|
1935
|
+
throw closedError();
|
|
1813
1936
|
const acceptance = await config.store.acceptInputAndAssignRun({
|
|
1814
1937
|
idempotencyKey: input.idempotencyKey,
|
|
1815
1938
|
input: userMessage2,
|
|
@@ -1918,6 +2041,9 @@ function createAgentRuntime(config) {
|
|
|
1918
2041
|
};
|
|
1919
2042
|
}
|
|
1920
2043
|
});
|
|
2044
|
+
ticket.accepted.catch(() => {
|
|
2045
|
+
return;
|
|
2046
|
+
});
|
|
1921
2047
|
if (reservation) {
|
|
1922
2048
|
ticket.result.then(reservation.admission.completion.resolve, reservation.admission.completion.reject);
|
|
1923
2049
|
reservation.admission.completion.promise.then((value) => {
|
|
@@ -1940,6 +2066,7 @@ function createAgentRuntime(config) {
|
|
|
1940
2066
|
}
|
|
1941
2067
|
} finally {
|
|
1942
2068
|
acceptanceDone.resolve();
|
|
2069
|
+
handedOff();
|
|
1943
2070
|
}
|
|
1944
2071
|
})();
|
|
1945
2072
|
return publicTicket;
|
|
@@ -1969,9 +2096,8 @@ function createAgentRuntime(config) {
|
|
|
1969
2096
|
return requested;
|
|
1970
2097
|
},
|
|
1971
2098
|
async recover(options) {
|
|
1972
|
-
if (
|
|
1973
|
-
throw
|
|
1974
|
-
}
|
|
2099
|
+
if (admissionClosed)
|
|
2100
|
+
throw closedError();
|
|
1975
2101
|
const pageSize = options.pageSize ?? 100;
|
|
1976
2102
|
const maxRuns = options.maxRuns ?? 1000;
|
|
1977
2103
|
if (!Number.isSafeInteger(pageSize) || pageSize < 1 || pageSize > 1000) {
|
|
@@ -1982,14 +2108,17 @@ function createAgentRuntime(config) {
|
|
|
1982
2108
|
}
|
|
1983
2109
|
const outcomes = [];
|
|
1984
2110
|
let cursor;
|
|
1985
|
-
while (outcomes.length < maxRuns && !options.signal?.aborted) {
|
|
1986
|
-
const page = await config.store.
|
|
2111
|
+
while (outcomes.length < maxRuns && !options.signal?.aborted && !admissionClosed) {
|
|
2112
|
+
const page = await config.store.scanRecoverable({
|
|
1987
2113
|
...cursor && { cursor },
|
|
1988
2114
|
limit: Math.min(pageSize, maxRuns - outcomes.length)
|
|
1989
2115
|
});
|
|
1990
2116
|
for (const item of page.items) {
|
|
1991
2117
|
if (options.signal?.aborted)
|
|
1992
2118
|
break;
|
|
2119
|
+
if (admissionClosed)
|
|
2120
|
+
break;
|
|
2121
|
+
const handedOff = beginAdmission();
|
|
1993
2122
|
try {
|
|
1994
2123
|
if (item.run.state === "queued") {
|
|
1995
2124
|
const snapshot = await config.store.loadSnapshot(item.conversationId);
|
|
@@ -2004,6 +2133,8 @@ function createAgentRuntime(config) {
|
|
|
2004
2133
|
}
|
|
2005
2134
|
}
|
|
2006
2135
|
const decision = await options.decide?.(item) ?? (item.run.state === "queued" ? { action: "resume" } : { action: "skip" });
|
|
2136
|
+
if (admissionClosed)
|
|
2137
|
+
throw closedError();
|
|
2007
2138
|
if (decision.action === "skip") {
|
|
2008
2139
|
outcomes.push({
|
|
2009
2140
|
conversationId: item.conversationId,
|
|
@@ -2063,6 +2194,8 @@ function createAgentRuntime(config) {
|
|
|
2063
2194
|
outcome: "failed",
|
|
2064
2195
|
error
|
|
2065
2196
|
});
|
|
2197
|
+
} finally {
|
|
2198
|
+
handedOff();
|
|
2066
2199
|
}
|
|
2067
2200
|
}
|
|
2068
2201
|
cursor = page.nextCursor;
|
|
@@ -2072,7 +2205,23 @@ function createAgentRuntime(config) {
|
|
|
2072
2205
|
return outcomes;
|
|
2073
2206
|
},
|
|
2074
2207
|
stop: (conversationKey, reason) => coordinator.stop(conversationKey, reason),
|
|
2075
|
-
close: (options) =>
|
|
2208
|
+
close: async (options = {}) => {
|
|
2209
|
+
assertCloseBudgets(options);
|
|
2210
|
+
admissionClosed = true;
|
|
2211
|
+
const startedAt = performance.now();
|
|
2212
|
+
const stranded = await drainAdmissions(options.forceTimeoutMs);
|
|
2213
|
+
const spent = Math.max(0, Math.round(performance.now() - startedAt));
|
|
2214
|
+
const remainingBudget = (value) => value === undefined ? undefined : Math.max(0, value - spent);
|
|
2215
|
+
const grace = remainingBudget(options.gracePeriodMs);
|
|
2216
|
+
const force = remainingBudget(options.forceTimeoutMs);
|
|
2217
|
+
const result = await coordinator.close({
|
|
2218
|
+
...grace !== undefined && { gracePeriodMs: grace },
|
|
2219
|
+
...force !== undefined && { forceTimeoutMs: force }
|
|
2220
|
+
});
|
|
2221
|
+
if (stranded === 0)
|
|
2222
|
+
return result;
|
|
2223
|
+
return { settled: false, timedOut: true, remaining: result.remaining + stranded };
|
|
2224
|
+
}
|
|
2076
2225
|
};
|
|
2077
2226
|
}
|
|
2078
2227
|
// src/agent-runtime/store.ts
|
|
@@ -2202,14 +2351,31 @@ function emptyHead(conversationId) {
|
|
|
2202
2351
|
version: 0
|
|
2203
2352
|
});
|
|
2204
2353
|
}
|
|
2354
|
+
function historyPositions(messages) {
|
|
2355
|
+
const positions = new Map;
|
|
2356
|
+
messages.forEach((message, index) => {
|
|
2357
|
+
if (!positions.has(message.id))
|
|
2358
|
+
positions.set(message.id, index);
|
|
2359
|
+
});
|
|
2360
|
+
return (run) => {
|
|
2361
|
+
let earliest = Number.MAX_SAFE_INTEGER;
|
|
2362
|
+
for (const id of [...run.inputMessageIds, run.assistantMessageId]) {
|
|
2363
|
+
const position = positions.get(id);
|
|
2364
|
+
if (position !== undefined && position < earliest)
|
|
2365
|
+
earliest = position;
|
|
2366
|
+
}
|
|
2367
|
+
return earliest;
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2205
2370
|
function snapshotOf(head, messages, records) {
|
|
2206
2371
|
validateSnapshot(head, messages, records);
|
|
2372
|
+
const positionOf = historyPositions(messages);
|
|
2207
2373
|
return AgentSnapshotSchema.parse({
|
|
2208
2374
|
schemaVersion: 1,
|
|
2209
2375
|
conversationId: head.conversationId,
|
|
2210
2376
|
version: head.version,
|
|
2211
2377
|
messages,
|
|
2212
|
-
runs: records.map((record) => record.run).sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id))
|
|
2378
|
+
runs: records.map((record) => record.run).sort((left, right) => left.createdAt.localeCompare(right.createdAt) || positionOf(left) - positionOf(right) || left.id.localeCompare(right.id))
|
|
2213
2379
|
});
|
|
2214
2380
|
}
|
|
2215
2381
|
function validateSnapshot(head, messages, records) {
|
|
@@ -2271,14 +2437,6 @@ function terminalState(reason) {
|
|
|
2271
2437
|
return "abandoned";
|
|
2272
2438
|
return "failed";
|
|
2273
2439
|
}
|
|
2274
|
-
function terminalMessageStatus2(reason) {
|
|
2275
|
-
if (reason === "success" || reason === "policy_stop")
|
|
2276
|
-
return "completed";
|
|
2277
|
-
if (reason === "interrupted" || reason === "cancelled" || reason === "shutdown") {
|
|
2278
|
-
return "interrupted";
|
|
2279
|
-
}
|
|
2280
|
-
return "failed";
|
|
2281
|
-
}
|
|
2282
2440
|
function applied(current, input, effects) {
|
|
2283
2441
|
return {
|
|
2284
2442
|
outcome: "applied",
|
|
@@ -2439,7 +2597,7 @@ function reduceStore(current, operation) {
|
|
|
2439
2597
|
}
|
|
2440
2598
|
if (operation.type === "terminal" && run) {
|
|
2441
2599
|
const input = operation.input;
|
|
2442
|
-
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 !==
|
|
2600
|
+
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 !== assistantStatus(input.reason)) {
|
|
2443
2601
|
return conflict(run.revision);
|
|
2444
2602
|
}
|
|
2445
2603
|
const next = AgentRunSchema.parse({
|
|
@@ -2645,26 +2803,7 @@ function createAgentRuntimeStore(driver) {
|
|
|
2645
2803
|
type: "compact",
|
|
2646
2804
|
input: ReplaceCompactedRangeSchema.parse(input)
|
|
2647
2805
|
}),
|
|
2648
|
-
async scanRecoverable() {
|
|
2649
|
-
const snapshots = [];
|
|
2650
|
-
const seenConversationIds = new Set;
|
|
2651
|
-
let cursor;
|
|
2652
|
-
do {
|
|
2653
|
-
const page = AgentRecoverablePageSchema.parse(await driver.scanRecoverable({
|
|
2654
|
-
...cursor && { cursor },
|
|
2655
|
-
limit: 100
|
|
2656
|
-
}));
|
|
2657
|
-
const conversationIds = [
|
|
2658
|
-
...new Set(page.items.map((item) => item.conversationId))
|
|
2659
|
-
].filter((conversationId) => !seenConversationIds.has(conversationId));
|
|
2660
|
-
for (const conversationId of conversationIds)
|
|
2661
|
-
seenConversationIds.add(conversationId);
|
|
2662
|
-
snapshots.push(...await Promise.all(conversationIds.map(loadSnapshot)));
|
|
2663
|
-
cursor = page.nextCursor;
|
|
2664
|
-
} while (cursor !== undefined);
|
|
2665
|
-
return snapshots;
|
|
2666
|
-
},
|
|
2667
|
-
async scanRecoverablePage(input) {
|
|
2806
|
+
async scanRecoverable(input) {
|
|
2668
2807
|
const parsed = AgentRecoverableScanInputSchema.parse(input);
|
|
2669
2808
|
return AgentRecoverablePageSchema.parse(await driver.scanRecoverable(parsed));
|
|
2670
2809
|
}
|
|
@@ -2863,6 +3002,7 @@ export {
|
|
|
2863
3002
|
AgentRunSchema,
|
|
2864
3003
|
AgentRunStateEventSchema,
|
|
2865
3004
|
AgentRunStateSchema,
|
|
3005
|
+
AgentRuntimeConflictError,
|
|
2866
3006
|
AgentRuntimeEventCursorSchema,
|
|
2867
3007
|
AgentRuntimeEventSchema,
|
|
2868
3008
|
AgentRuntimeHeadSchema,
|