@fevex/core 0.1.0-alpha.4 → 0.1.0-alpha.6
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 +240 -11
- package/dist/agents/index.d.mts +1 -1
- package/dist/artifacts/index.d.mts +2 -0
- package/dist/artifacts/index.mjs +2 -0
- package/dist/artifacts-gUzemET7.mjs +440 -0
- package/dist/channels/index.d.mts +1 -1
- package/dist/evals/index.d.mts +4 -4
- package/dist/fevex-B4QY5Z6e.d.mts +119 -0
- package/dist/http/index.d.mts +65 -5
- package/dist/http/index.mjs +238 -16
- package/dist/{index-C-IFfCNw.d.mts → index-1cHr7KB_.d.mts} +3 -3
- package/dist/{index-CqOjvtQ9.d.mts → index-5p3q2yIp.d.mts} +4 -2
- package/dist/{index-hhbgvfIt.d.mts → index-BJktlD4E.d.mts} +2 -2
- package/dist/{index-CTIwrVf9.d.mts → index-BgKFd9yw.d.mts} +1 -1
- package/dist/{index-dkFyTNh4.d.mts → index-CbACw6xZ.d.mts} +2 -2
- package/dist/index-CpLDt6Rw.d.mts +118 -0
- package/dist/{index-CknU-glw.d.mts → index-CseuTR-F.d.mts} +2 -2
- package/dist/{index-CaE864Kz.d.mts → index-CzAi2Y_Z.d.mts} +45 -6
- package/dist/{index-DyyNLuwO.d.mts → index-DVw0zOc_.d.mts} +3 -3
- package/dist/{index-CrOId2px.d.mts → index-Df84VWmN.d.mts} +1 -1
- package/dist/index.d.mts +13 -13
- package/dist/index.mjs +904 -153
- package/dist/knowledge/index.d.mts +1 -1
- package/dist/model-stream-JQdHDDcj.mjs +131 -0
- package/dist/models/index.d.mts +1 -1
- package/dist/observability/index.d.mts +2 -2
- package/dist/policies/index.d.mts +1 -1
- package/dist/{model-stream-u2xMsEaf.mjs → run-support-CmZ6Xo6H.mjs} +41 -130
- package/dist/runtime/index.d.mts +2 -2
- package/dist/runtime/index.mjs +43 -1
- package/dist/sandbox/index.d.mts +1 -1
- package/dist/sandbox/index.mjs +1 -1
- package/dist/teams/index.d.mts +1 -1
- package/dist/testing/index.d.mts +8 -7
- package/dist/testing/index.mjs +94 -2
- package/dist/tools/index.d.mts +2 -2
- package/dist/types-B8laW1hM.d.mts +561 -0
- package/dist/workflows/index.d.mts +1 -1
- package/package.json +6 -2
- package/dist/fevex-BoPbZ-Tw.d.mts +0 -60
- package/dist/index-voIZhQXB.d.mts +0 -80
- package/dist/types-CuoRx12Q.d.mts +0 -333
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { defineAgent } from "./agents/index.mjs";
|
|
2
|
+
import { a as CatalogedArtifactStore, c as assertArtifactId, i as FileSystemArtifactStore, n as InMemoryArtifactStore, o as ArtifactError, r as InMemoryArtifactCatalog, s as artifactTenantId, t as S3ArtifactStore } from "./artifacts-gUzemET7.mjs";
|
|
2
3
|
import { n as RunPausedError, t as FevexRunError } from "./run-error-Cr17wPGH.mjs";
|
|
3
4
|
import { ChannelError, deliverChannelRun, handleChannelInput } from "./channels/index.mjs";
|
|
4
5
|
import { InMemoryMemoryStore, createMemoryContextProvider, defineContextProvider, defineSkill } from "./knowledge/index.mjs";
|
|
5
6
|
import { n as teamAsWorkflow, t as defineTeam } from "./teams-Ctz676G4.mjs";
|
|
6
|
-
import { InMemoryRunStore, isDurableRunStore, validateListEventsOptions } from "./runtime/index.mjs";
|
|
7
|
+
import { InMemoryRunStore, isDurableRunStore, isRetainableRunStore, validateListEventsOptions, validatePurgeRunsOptions } from "./runtime/index.mjs";
|
|
7
8
|
import { FEVEX_JSON_SCHEMA_PROFILE_VERSION, IntegrationError, compileFevexJsonSchema, defineConnection, defineTool, toToolSpec, validateFevexJsonSchemaProfile } from "./tools/index.mjs";
|
|
8
|
-
import { a as assertTokenBudget, c as
|
|
9
|
+
import { _ as serializeValue, a as assertTokenBudget, c as eventUsage, d as remainingDeadlineMs, f as remainingOutputTokens, g as serializeJsonValue, h as toErrorMessage, i as assertContinuationBudget, l as isTimeoutError, m as runSignal, n as abortable, o as cancellationReason, p as resumeDeadlineSignal, r as addUsage, s as deadlineAtFromMs, u as minDeadline, v as toJsonValue } from "./run-support-CmZ6Xo6H.mjs";
|
|
10
|
+
import { t as readModelStream } from "./model-stream-JQdHDDcj.mjs";
|
|
9
11
|
import "./models/index.mjs";
|
|
10
12
|
import { definePolicy } from "./policies/index.mjs";
|
|
11
13
|
import { SandboxError, createLocalSandbox, sandboxResultToJson } from "./sandbox/index.mjs";
|
|
@@ -103,6 +105,9 @@ function isRunStore(value) {
|
|
|
103
105
|
function isSandbox(value) {
|
|
104
106
|
return isRecord(value) && typeof value.run === "function";
|
|
105
107
|
}
|
|
108
|
+
function isArtifactStore(value) {
|
|
109
|
+
return (typeof value === "object" && value !== null || typeof value === "function") && typeof value.put === "function" && typeof value.get === "function";
|
|
110
|
+
}
|
|
106
111
|
function isJsonObject(value) {
|
|
107
112
|
return isRecord(value);
|
|
108
113
|
}
|
|
@@ -300,7 +305,21 @@ function createComposition(config) {
|
|
|
300
305
|
assertConfiguration(config.runStore === void 0 || isRunStore(config.runStore), "INVALID_CONFIG", "Fevex config \"runStore\" must implement RunStore");
|
|
301
306
|
assertConfiguration(config.credentialStore === void 0 || typeof config.credentialStore?.resolve === "function", "INVALID_CONFIG", "Fevex config \"credentialStore\" must implement resolve");
|
|
302
307
|
assertConfiguration(config.sandbox === void 0 || isSandbox(config.sandbox), "INVALID_CONFIG", "Fevex config \"sandbox\" must implement Sandbox");
|
|
308
|
+
assertConfiguration(config.artifacts === void 0 || isArtifactStore(config.artifacts), "INVALID_CONFIG", "Fevex config \"artifacts\" must implement ArtifactStore");
|
|
303
309
|
assertConfiguration(config.policies === void 0 || Array.isArray(config.policies), "INVALID_CONFIG", "Fevex config \"policies\" must be an array");
|
|
310
|
+
if (config.admission !== void 0) {
|
|
311
|
+
assertConfiguration(isRecord(config.admission), "INVALID_CONFIG", "Fevex config \"admission\" must be an object");
|
|
312
|
+
for (const name of ["maxActiveRuns", "maxActiveRunsPerTenant"]) {
|
|
313
|
+
const value = config.admission[name];
|
|
314
|
+
assertConfiguration(value === void 0 || typeof value === "number" && Number.isSafeInteger(value) && value > 0, "INVALID_CONFIG", `Fevex config "admission.${name}" must be a positive safe integer`);
|
|
315
|
+
}
|
|
316
|
+
assertConfiguration(config.admission.tenantKey === void 0 || typeof config.admission.tenantKey === "function", "INVALID_CONFIG", "Fevex config \"admission.tenantKey\" must be a function");
|
|
317
|
+
assertConfiguration(config.admission.authorize === void 0 || typeof config.admission.authorize === "function", "INVALID_CONFIG", "Fevex config \"admission.authorize\" must be a function");
|
|
318
|
+
}
|
|
319
|
+
if (config.limits !== void 0) {
|
|
320
|
+
assertConfiguration(isRecord(config.limits), "INVALID_CONFIG", "Fevex config \"limits\" must be an object");
|
|
321
|
+
assertConfiguration(config.limits.maxEventsPerRun === void 0 || typeof config.limits.maxEventsPerRun === "number" && Number.isSafeInteger(config.limits.maxEventsPerRun) && config.limits.maxEventsPerRun > 0, "INVALID_CONFIG", "Fevex config \"limits.maxEventsPerRun\" must be a positive safe integer");
|
|
322
|
+
}
|
|
304
323
|
if (config.observability !== void 0) {
|
|
305
324
|
assertConfiguration(isRecord(config.observability), "INVALID_CONFIG", "Fevex config \"observability\" must be an object");
|
|
306
325
|
assertConfiguration(Array.isArray(config.observability.exporters) && config.observability.exporters.every((exporter) => isRecord(exporter) && typeof exporter.export === "function"), "INVALID_CONFIG", "Fevex config \"observability.exporters\" must contain TraceExporters");
|
|
@@ -313,6 +332,18 @@ function createComposition(config) {
|
|
|
313
332
|
"error.message"
|
|
314
333
|
].includes(kind)) && (content.redact === void 0 || typeof content.redact === "function"), "INVALID_CONFIG", "Fevex config \"observability.content\" is invalid");
|
|
315
334
|
}
|
|
335
|
+
if (config.observability.exportQueue !== void 0) {
|
|
336
|
+
const queue = config.observability.exportQueue;
|
|
337
|
+
assertConfiguration(isRecord(queue) && [
|
|
338
|
+
"concurrency",
|
|
339
|
+
"maxQueued",
|
|
340
|
+
"timeoutMs",
|
|
341
|
+
"maxFailures"
|
|
342
|
+
].every((key) => {
|
|
343
|
+
const value = queue[key];
|
|
344
|
+
return value === void 0 || typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
345
|
+
}), "INVALID_CONFIG", "Fevex config \"observability.exportQueue\" must contain positive safe integers");
|
|
346
|
+
}
|
|
316
347
|
}
|
|
317
348
|
const models = /* @__PURE__ */ new Map();
|
|
318
349
|
const agents = /* @__PURE__ */ new Map();
|
|
@@ -594,15 +625,19 @@ function createComposition(config) {
|
|
|
594
625
|
runStore: config.runStore ?? new InMemoryRunStore(),
|
|
595
626
|
credentialStore: config.credentialStore,
|
|
596
627
|
sandbox: config.sandbox,
|
|
628
|
+
artifacts: config.artifacts,
|
|
597
629
|
policies,
|
|
598
630
|
onEvent: config.onEvent,
|
|
631
|
+
admission: config.admission === void 0 ? void 0 : { ...config.admission },
|
|
632
|
+
limits: config.limits === void 0 ? void 0 : { ...config.limits },
|
|
599
633
|
observability: config.observability === void 0 ? void 0 : {
|
|
600
634
|
exporters: [...config.observability.exporters],
|
|
601
635
|
...config.observability.calculateCost ? { calculateCost: config.observability.calculateCost } : {},
|
|
602
636
|
...config.observability.content ? { content: {
|
|
603
637
|
include: [...config.observability.content.include],
|
|
604
638
|
...config.observability.content.redact ? { redact: config.observability.content.redact } : {}
|
|
605
|
-
} } : {}
|
|
639
|
+
} } : {},
|
|
640
|
+
...config.observability.exportQueue ? { exportQueue: { ...config.observability.exportQueue } } : {}
|
|
606
641
|
}
|
|
607
642
|
};
|
|
608
643
|
}
|
|
@@ -732,6 +767,7 @@ const toolErrorPayload = (tool, error) => ({
|
|
|
732
767
|
} : {}
|
|
733
768
|
});
|
|
734
769
|
const runErrorPayload = (error) => {
|
|
770
|
+
if (error instanceof FevexRunError) return { errorCode: error.code };
|
|
735
771
|
if (!(error instanceof IntegrationError)) return {};
|
|
736
772
|
return {
|
|
737
773
|
errorCode: error.code,
|
|
@@ -866,9 +902,10 @@ function addWorkflowBudget(first, second) {
|
|
|
866
902
|
* engine — that direction is what keeps the two separable.
|
|
867
903
|
*/
|
|
868
904
|
function createAgentEngine(composition, core) {
|
|
869
|
-
const { models, agents, tools, contextProviders, memoryStore, runStore, credentialStore, sandbox, policies } = composition;
|
|
870
|
-
const { activeRuns, activeSessions, runtimeOwner, resolveModel, resolveCheckpointModel, notifyObserver, createEvent, emitEvent, releaseExecution, commit, startLease, cancelExecution, authorize } = core;
|
|
871
|
-
const
|
|
905
|
+
const { models, agents, tools, contextProviders, memoryStore, runStore, credentialStore, sandbox, artifacts, policies } = composition;
|
|
906
|
+
const { activeRuns, activeSessions, runtimeOwner, isClosed, admitStart, markInternalRun, resolveModel, resolveCheckpointModel, notifyObserver, createEvent, emitEvent, releaseExecution, commit, startLease, cancelExecution, authorize } = core;
|
|
907
|
+
const prepareExecutionBody = async (name, request, internal = false, correlation = {}) => {
|
|
908
|
+
if (!internal && isClosed()) throw new FevexRunError("RUNTIME_CLOSED", "Runtime is not accepting new runs");
|
|
872
909
|
const agent = agents.get(name);
|
|
873
910
|
if (!agent) throw new FevexRunError("AGENT_NOT_FOUND", `Agent "${name}" is not registered`);
|
|
874
911
|
const { modelName, model } = resolveModel(agent, request.model);
|
|
@@ -887,20 +924,25 @@ function createAgentEngine(composition, core) {
|
|
|
887
924
|
session = stored;
|
|
888
925
|
}
|
|
889
926
|
if (activeSessions.has(session.id)) throw new FevexRunError("RUN_CONFLICT", `Session "${session.id}" already has an active run`);
|
|
890
|
-
activeSessions.add(session.id);
|
|
891
927
|
const controller = new AbortController();
|
|
892
|
-
const signal =
|
|
928
|
+
const signal = runSignal(controller, request);
|
|
929
|
+
const deadlineAt = deadlineAtFromMs(request.deadlineMs);
|
|
930
|
+
activeSessions.add(session.id);
|
|
893
931
|
const run = {
|
|
894
932
|
id: crypto.randomUUID(),
|
|
895
933
|
sessionId: session.id,
|
|
896
934
|
agentName: name,
|
|
897
935
|
modelName,
|
|
898
936
|
...model.metadata === void 0 ? {} : { modelMetadata: structuredClone(model.metadata) },
|
|
937
|
+
...correlation.parentRunId === void 0 ? {} : { parentRunId: correlation.parentRunId },
|
|
938
|
+
...correlation.rootRunId === void 0 ? {} : { rootRunId: correlation.rootRunId },
|
|
899
939
|
status: "running",
|
|
900
940
|
revision: 0,
|
|
901
941
|
createdAt: now,
|
|
902
|
-
updatedAt: now
|
|
942
|
+
updatedAt: now,
|
|
943
|
+
...deadlineAt === void 0 ? {} : { deadlineAt }
|
|
903
944
|
};
|
|
945
|
+
if (internal) markInternalRun(run.id);
|
|
904
946
|
const state = {
|
|
905
947
|
run,
|
|
906
948
|
session,
|
|
@@ -981,6 +1023,17 @@ function createAgentEngine(composition, core) {
|
|
|
981
1023
|
activeRuns.set(run.id, state);
|
|
982
1024
|
return state;
|
|
983
1025
|
};
|
|
1026
|
+
const prepareExecution = async (name, request, options = {}) => {
|
|
1027
|
+
const release = options.admit === false ? void 0 : await admitStart("agent", name, request);
|
|
1028
|
+
try {
|
|
1029
|
+
return await prepareExecutionBody(name, request, options.admit === false, {
|
|
1030
|
+
...options.parentRunId === void 0 ? {} : { parentRunId: options.parentRunId },
|
|
1031
|
+
...options.rootRunId === void 0 ? {} : { rootRunId: options.rootRunId }
|
|
1032
|
+
});
|
|
1033
|
+
} finally {
|
|
1034
|
+
release?.();
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
984
1037
|
const durableCheckpoint = async (state, model, modelName, messages, inputContent, usage, providerState, step, toolCallCount, seenToolCallIds, pendingTools, pendingIndex) => {
|
|
985
1038
|
const agent = agents.get(state.run.agentName);
|
|
986
1039
|
if (agent.model !== void 0 && typeof agent.model !== "string") throw new FevexRunError("RUN_NOT_RESUMABLE", "Durable continuation requires a named model", state.run.id);
|
|
@@ -1519,6 +1572,20 @@ function createAgentEngine(composition, core) {
|
|
|
1519
1572
|
}
|
|
1520
1573
|
events.push(started);
|
|
1521
1574
|
yield started;
|
|
1575
|
+
const createdArtifacts = [];
|
|
1576
|
+
const toolArtifacts = artifacts === void 0 ? void 0 : scopedArtifacts(artifacts, {
|
|
1577
|
+
runId: state.run.id,
|
|
1578
|
+
sessionId: state.session.id,
|
|
1579
|
+
request,
|
|
1580
|
+
emit: async (descriptor) => {
|
|
1581
|
+
createdArtifacts.push(await emit("artifact.created", {
|
|
1582
|
+
descriptor,
|
|
1583
|
+
toolCallId: pending.call.id,
|
|
1584
|
+
toolName: pending.call.name,
|
|
1585
|
+
...toolSourcePayload(tool)
|
|
1586
|
+
}));
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1522
1589
|
try {
|
|
1523
1590
|
const raw = await abortable(() => tool.execute(pending.input, {
|
|
1524
1591
|
runId: state.run.id,
|
|
@@ -1527,9 +1594,11 @@ function createAgentEngine(composition, core) {
|
|
|
1527
1594
|
idempotencyKey: pending.idempotencyKey,
|
|
1528
1595
|
getCredential,
|
|
1529
1596
|
...scopedSandbox === void 0 ? {} : { sandbox: scopedSandbox },
|
|
1597
|
+
...toolArtifacts === void 0 ? {} : { artifacts: toolArtifacts },
|
|
1530
1598
|
context: request.context,
|
|
1531
1599
|
signal: request.signal
|
|
1532
1600
|
}), request.signal);
|
|
1601
|
+
for (const event of createdArtifacts.splice(0)) yield event;
|
|
1533
1602
|
output = toJsonValue(tool.outputSchema === void 0 ? raw : await validateSchema(tool.outputSchema, raw, `Output from tool "${tool.name}" does not match outputSchema`), `Output from tool "${tool.name}" must be JSON-serializable`);
|
|
1534
1603
|
if (containsSecret(output, secrets)) throw new Error(`Output from tool "${tool.name}" contains a credential`);
|
|
1535
1604
|
record = {
|
|
@@ -1557,6 +1626,7 @@ function createAgentEngine(composition, core) {
|
|
|
1557
1626
|
yield completed;
|
|
1558
1627
|
break;
|
|
1559
1628
|
} catch (error) {
|
|
1629
|
+
for (const event of createdArtifacts.splice(0)) yield event;
|
|
1560
1630
|
if (request.signal.aborted) throw error;
|
|
1561
1631
|
const safeError = redact(toErrorMessage(error), secrets);
|
|
1562
1632
|
record = {
|
|
@@ -1664,7 +1734,7 @@ function createAgentEngine(composition, core) {
|
|
|
1664
1734
|
}
|
|
1665
1735
|
}
|
|
1666
1736
|
};
|
|
1667
|
-
const
|
|
1737
|
+
const resumeAgentBody = async (runId, resolution, waitForCompletion = false, recoveryActor) => {
|
|
1668
1738
|
if (resolution?.type === "timer" || resolution?.type === "event") throw new FevexRunError("RUN_NOT_RESUMABLE", `Run "${runId}" cannot be resumed`, runId);
|
|
1669
1739
|
if (!isDurableRunStore(runStore)) throw new FevexRunError("DURABLE_STORE_REQUIRED", "resumeRun requires DurableRunStore", runId);
|
|
1670
1740
|
const run = await runStore.getRun(runId);
|
|
@@ -1672,6 +1742,11 @@ function createAgentEngine(composition, core) {
|
|
|
1672
1742
|
if (!run || !checkpoint) throw new FevexRunError(resolution ? "RUN_NOT_RESUMABLE" : "RUN_NOT_RECOVERABLE", `Run "${runId}" cannot be ${resolution ? "resumed" : "recovered"}`, runId);
|
|
1673
1743
|
if (checkpoint.version !== 2) throw new FevexRunError("CHECKPOINT_UNSUPPORTED", `Checkpoint for run "${runId}" is unsupported`, runId);
|
|
1674
1744
|
if (resolution && run.status !== "paused" && run.status !== "running" || !resolution && run.status !== "running") throw new FevexRunError(resolution ? "RUN_NOT_RESUMABLE" : "RUN_NOT_RECOVERABLE", `Run "${runId}" is not ${resolution ? "resumable" : "recoverable"}`, runId);
|
|
1745
|
+
if (run.deadlineAt !== void 0 && Date.parse(run.deadlineAt) <= Date.now()) {
|
|
1746
|
+
if (!await cancelStoredAgent(runId, "timeout")) throw new FevexRunError(resolution ? "RUN_NOT_RESUMABLE" : "RUN_NOT_RECOVERABLE", `Run "${runId}" deadline has passed`, runId);
|
|
1747
|
+
return await runStore.getRun(runId);
|
|
1748
|
+
}
|
|
1749
|
+
const deadlineSignal = resumeDeadlineSignal(run.deadlineAt, runId, resolution ? "RUN_NOT_RESUMABLE" : "RUN_NOT_RECOVERABLE");
|
|
1675
1750
|
const agent = agents.get(run.agentName);
|
|
1676
1751
|
if (!agent) throw new FevexRunError("RUN_DEFINITION_CHANGED", `Agent "${run.agentName}" is unavailable`, runId);
|
|
1677
1752
|
const checkpointModel = await resolveCheckpointModel(run.agentName, agent, checkpoint);
|
|
@@ -1695,6 +1770,7 @@ function createAgentEngine(composition, core) {
|
|
|
1695
1770
|
if (!await runStore.acquireLease(lease)) throw new FevexRunError("RUN_CONFLICT", `Run "${runId}" is leased`, runId);
|
|
1696
1771
|
try {
|
|
1697
1772
|
const controller = new AbortController();
|
|
1773
|
+
const signal = deadlineSignal ? AbortSignal.any([controller.signal, deadlineSignal]) : controller.signal;
|
|
1698
1774
|
const state = {
|
|
1699
1775
|
run,
|
|
1700
1776
|
session,
|
|
@@ -1705,7 +1781,7 @@ function createAgentEngine(composition, core) {
|
|
|
1705
1781
|
sessionId: session.id,
|
|
1706
1782
|
context: checkpoint.context,
|
|
1707
1783
|
limits: checkpoint.limits,
|
|
1708
|
-
signal
|
|
1784
|
+
signal
|
|
1709
1785
|
},
|
|
1710
1786
|
controller,
|
|
1711
1787
|
eventSequence: (await runStore.listEvents(runId)).at(-1)?.sequence ?? 0,
|
|
@@ -1874,10 +1950,27 @@ function createAgentEngine(composition, core) {
|
|
|
1874
1950
|
throw error;
|
|
1875
1951
|
}
|
|
1876
1952
|
};
|
|
1877
|
-
const
|
|
1953
|
+
const resumeAgent = async (runId, resolution, waitForCompletion = false, recoveryActor, options = {}) => {
|
|
1954
|
+
let release;
|
|
1955
|
+
if (options.admit !== false) {
|
|
1956
|
+
const run = await runStore.getRun(runId);
|
|
1957
|
+
const checkpoint = isDurableRunStore(runStore) ? await runStore.getCheckpoint(runId) : void 0;
|
|
1958
|
+
if (run && checkpoint && (run.status === "paused" || run.status === "running")) release = await admitStart("agent", run.agentName, {
|
|
1959
|
+
input: "",
|
|
1960
|
+
...checkpoint.context === void 0 ? {} : { context: checkpoint.context }
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
try {
|
|
1964
|
+
if (options.internal) markInternalRun(runId);
|
|
1965
|
+
return await resumeAgentBody(runId, resolution, waitForCompletion, recoveryActor);
|
|
1966
|
+
} finally {
|
|
1967
|
+
release?.();
|
|
1968
|
+
}
|
|
1969
|
+
};
|
|
1970
|
+
const cancelStoredAgent = async (runId, reason = "aborted") => {
|
|
1878
1971
|
const active = activeRuns.get(runId);
|
|
1879
1972
|
if (active && !active.request.signal.aborted) {
|
|
1880
|
-
active.controller.abort(new DOMException("Run cancelled", "AbortError"));
|
|
1973
|
+
active.controller.abort(new DOMException(reason === "timeout" ? "Run deadline exceeded" : "Run cancelled", reason === "timeout" ? "TimeoutError" : "AbortError"));
|
|
1881
1974
|
if (!active.advancing) {
|
|
1882
1975
|
await cancelExecution(active);
|
|
1883
1976
|
return true;
|
|
@@ -1920,8 +2013,8 @@ function createAgentEngine(composition, core) {
|
|
|
1920
2013
|
};
|
|
1921
2014
|
run.status = "cancelled";
|
|
1922
2015
|
run.pause = void 0;
|
|
1923
|
-
run.error =
|
|
1924
|
-
const cancelled = createEvent(state, "run.cancelled", { reason
|
|
2016
|
+
run.error = reason;
|
|
2017
|
+
const cancelled = createEvent(state, "run.cancelled", { reason });
|
|
1925
2018
|
await commit(state, {
|
|
1926
2019
|
checkpoint: null,
|
|
1927
2020
|
events: [cancelled]
|
|
@@ -1943,21 +2036,67 @@ function createAgentEngine(composition, core) {
|
|
|
1943
2036
|
cancelStoredAgent
|
|
1944
2037
|
};
|
|
1945
2038
|
}
|
|
2039
|
+
function scopedArtifacts(store, options) {
|
|
2040
|
+
const scopedContext = (value) => {
|
|
2041
|
+
const contextTenant = options.request.context?.attributes?.tenantId;
|
|
2042
|
+
const tenantId = typeof contextTenant === "string" ? contextTenant : value?.tenantId;
|
|
2043
|
+
const actor = options.request.context?.actor;
|
|
2044
|
+
return {
|
|
2045
|
+
...value,
|
|
2046
|
+
runId: options.runId,
|
|
2047
|
+
sessionId: options.sessionId,
|
|
2048
|
+
...tenantId === void 0 ? {} : { tenantId },
|
|
2049
|
+
...actor === void 0 ? {} : { actor },
|
|
2050
|
+
signal: options.request.signal
|
|
2051
|
+
};
|
|
2052
|
+
};
|
|
2053
|
+
return {
|
|
2054
|
+
async put(input, context) {
|
|
2055
|
+
const descriptor = await store.put(input, scopedContext(context));
|
|
2056
|
+
await options.emit(descriptor);
|
|
2057
|
+
return descriptor;
|
|
2058
|
+
},
|
|
2059
|
+
get(artifactId, context) {
|
|
2060
|
+
return store.get(artifactId, scopedContext(context));
|
|
2061
|
+
},
|
|
2062
|
+
...store.head === void 0 ? {} : { head(artifactId, context) {
|
|
2063
|
+
return store.head(artifactId, scopedContext(context));
|
|
2064
|
+
} },
|
|
2065
|
+
...store.delete === void 0 ? {} : { delete(artifactId, context) {
|
|
2066
|
+
return store.delete(artifactId, scopedContext(context));
|
|
2067
|
+
} }
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
1946
2070
|
//#endregion
|
|
1947
2071
|
//#region src/internal/observability.ts
|
|
1948
2072
|
const TERMINAL_TYPES = /* @__PURE__ */ new Set([
|
|
1949
2073
|
"run.completed",
|
|
1950
2074
|
"run.failed",
|
|
1951
|
-
"run.cancelled"
|
|
2075
|
+
"run.cancelled",
|
|
2076
|
+
"workflow.run.completed",
|
|
2077
|
+
"workflow.run.failed",
|
|
2078
|
+
"workflow.run.cancelled",
|
|
2079
|
+
"team.run.completed",
|
|
2080
|
+
"team.run.failed",
|
|
2081
|
+
"team.run.cancelled"
|
|
1952
2082
|
]);
|
|
1953
2083
|
const SENSITIVE_KEY = /api[-_]?key|authorization|cookie|credential|password|secret|token/i;
|
|
2084
|
+
function isTraceTerminalEvent(event) {
|
|
2085
|
+
return TERMINAL_TYPES.has(event.type);
|
|
2086
|
+
}
|
|
1954
2087
|
function durationMs(startedAt, endedAt) {
|
|
1955
2088
|
return Math.max(0, Date.parse(endedAt) - Date.parse(startedAt));
|
|
1956
2089
|
}
|
|
1957
2090
|
function traceStatus(event) {
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
2091
|
+
switch (event.type) {
|
|
2092
|
+
case "run.completed":
|
|
2093
|
+
case "workflow.run.completed":
|
|
2094
|
+
case "team.run.completed": return "completed";
|
|
2095
|
+
case "run.cancelled":
|
|
2096
|
+
case "workflow.run.cancelled":
|
|
2097
|
+
case "team.run.cancelled": return "cancelled";
|
|
2098
|
+
default: return "failed";
|
|
2099
|
+
}
|
|
1961
2100
|
}
|
|
1962
2101
|
function redactKeys(value) {
|
|
1963
2102
|
if (Array.isArray(value)) return value.map(redactKeys);
|
|
@@ -2064,20 +2203,74 @@ function annotation(event) {
|
|
|
2064
2203
|
toolName: event.payload.toolName
|
|
2065
2204
|
}
|
|
2066
2205
|
};
|
|
2206
|
+
case "workflow.run.paused":
|
|
2207
|
+
case "team.run.paused": {
|
|
2208
|
+
const stepId = event.type === "workflow.run.paused" ? event.payload.stepId : event.payload.delegationId;
|
|
2209
|
+
return {
|
|
2210
|
+
name: event.type,
|
|
2211
|
+
timestamp: event.timestamp,
|
|
2212
|
+
attributes: {
|
|
2213
|
+
stepId,
|
|
2214
|
+
reason: event.payload.reason,
|
|
2215
|
+
...event.payload.childRunId === void 0 ? {} : { childRunId: event.payload.childRunId }
|
|
2216
|
+
}
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
case "workflow.run.resumed":
|
|
2220
|
+
case "team.run.resumed": return {
|
|
2221
|
+
name: event.type,
|
|
2222
|
+
timestamp: event.timestamp
|
|
2223
|
+
};
|
|
2224
|
+
case "team.handoff.created": return {
|
|
2225
|
+
name: event.type,
|
|
2226
|
+
timestamp: event.timestamp,
|
|
2227
|
+
attributes: {
|
|
2228
|
+
delegationId: event.payload.delegationId,
|
|
2229
|
+
from: event.payload.from,
|
|
2230
|
+
to: event.payload.to,
|
|
2231
|
+
reason: event.payload.reason
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2067
2234
|
default: return;
|
|
2068
2235
|
}
|
|
2069
2236
|
}
|
|
2070
|
-
function
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
if (
|
|
2237
|
+
function terminalContent(options, terminal) {
|
|
2238
|
+
if (terminal.type === "run.completed" || terminal.type === "workflow.run.completed" || terminal.type === "team.run.completed") {
|
|
2239
|
+
const output = content(options, "run.output", terminal.payload.output);
|
|
2240
|
+
return output ? [output] : [];
|
|
2241
|
+
}
|
|
2242
|
+
if (terminal.type === "run.failed" || terminal.type === "workflow.run.failed" || terminal.type === "team.run.failed") {
|
|
2243
|
+
const error = content(options, "error.message", terminal.payload.error);
|
|
2244
|
+
return error ? [error] : [];
|
|
2245
|
+
}
|
|
2246
|
+
return [];
|
|
2247
|
+
}
|
|
2248
|
+
function sortedEvents(rawEvents) {
|
|
2249
|
+
return [...rawEvents].sort((left, right) => left.sequence - right.sequence);
|
|
2250
|
+
}
|
|
2251
|
+
function terminalEvent(runId, events) {
|
|
2252
|
+
const terminal = [...events].reverse().find((event) => isTraceTerminalEvent(event));
|
|
2253
|
+
if (!terminal) throw new TypeError(`Run "${runId}" has no terminal event`);
|
|
2254
|
+
return terminal;
|
|
2255
|
+
}
|
|
2256
|
+
function startEvent(events) {
|
|
2257
|
+
const started = events.find((event) => event.type === "run.started" || event.type === "workflow.run.started" || event.type === "team.run.started") ?? events[0];
|
|
2258
|
+
if (!started) throw new TypeError("Run has no events");
|
|
2259
|
+
return started;
|
|
2260
|
+
}
|
|
2261
|
+
function traceKind(run) {
|
|
2262
|
+
if (isTeamRun(run)) return "team";
|
|
2263
|
+
return run.kind === "workflow" ? "workflow" : "agent";
|
|
2264
|
+
}
|
|
2265
|
+
function buildAgentTrace(run, events, modelRegistryName, model, options) {
|
|
2266
|
+
const terminal = terminalEvent(run.id, events);
|
|
2267
|
+
const started = startEvent(events);
|
|
2076
2268
|
const status = traceStatus(terminal);
|
|
2077
2269
|
const annotations = [];
|
|
2078
2270
|
const spans = [];
|
|
2079
2271
|
const openModels = /* @__PURE__ */ new Map();
|
|
2080
2272
|
const openTools = /* @__PURE__ */ new Map();
|
|
2273
|
+
const modelSpanByStep = /* @__PURE__ */ new Map();
|
|
2081
2274
|
const previousUsage = {};
|
|
2082
2275
|
const closeModel = (step, endedAt, spanStatus) => {
|
|
2083
2276
|
const open = openModels.get(step);
|
|
@@ -2108,6 +2301,7 @@ function buildRunTrace(run, rawEvents, modelRegistryName, model, options) {
|
|
|
2108
2301
|
const spanCost = costFor(options, modelRegistryName, model, usage);
|
|
2109
2302
|
if (spanCost) span.cost = spanCost;
|
|
2110
2303
|
spans.push(span);
|
|
2304
|
+
modelSpanByStep.set(step, span.id);
|
|
2111
2305
|
};
|
|
2112
2306
|
const findOpenTool = (toolCallId) => {
|
|
2113
2307
|
const entries = [...openTools.entries()];
|
|
@@ -2127,7 +2321,7 @@ function buildRunTrace(run, rawEvents, modelRegistryName, model, options) {
|
|
|
2127
2321
|
const errorContent = error ? content(options, "error.message", error) : void 0;
|
|
2128
2322
|
spans.push({
|
|
2129
2323
|
id: event.id,
|
|
2130
|
-
parentId: run.id,
|
|
2324
|
+
parentId: modelSpanByStep.get(event.payload.step) ?? run.id,
|
|
2131
2325
|
kind: "tool",
|
|
2132
2326
|
name: event.payload.toolName,
|
|
2133
2327
|
startedAt: event.timestamp,
|
|
@@ -2192,19 +2386,15 @@ function buildRunTrace(run, rawEvents, modelRegistryName, model, options) {
|
|
|
2192
2386
|
for (const open of [...openTools.values()]) if (open.event.type === "tool.started") closeTool(open.event.payload.toolCallId, terminal.timestamp, status);
|
|
2193
2387
|
const finalUsage = run.usage ?? previousUsage;
|
|
2194
2388
|
const totalCost = costFor(options, modelRegistryName, model, finalUsage);
|
|
2195
|
-
const traceContent =
|
|
2196
|
-
if (terminal.type === "run.completed") {
|
|
2197
|
-
const output = content(options, "run.output", terminal.payload.output);
|
|
2198
|
-
if (output) traceContent.push(output);
|
|
2199
|
-
} else if (terminal.type === "run.failed") {
|
|
2200
|
-
const error = content(options, "error.message", terminal.payload.error);
|
|
2201
|
-
if (error) traceContent.push(error);
|
|
2202
|
-
}
|
|
2389
|
+
const traceContent = terminalContent(options, terminal);
|
|
2203
2390
|
return {
|
|
2204
2391
|
schemaVersion: "1",
|
|
2392
|
+
kind: "agent",
|
|
2205
2393
|
traceId: run.id,
|
|
2206
2394
|
runId: run.id,
|
|
2207
2395
|
sessionId: run.sessionId,
|
|
2396
|
+
...run.parentRunId === void 0 ? {} : { parentRunId: run.parentRunId },
|
|
2397
|
+
rootRunId: run.rootRunId ?? run.parentRunId ?? run.id,
|
|
2208
2398
|
agentName: run.agentName,
|
|
2209
2399
|
modelRegistryName,
|
|
2210
2400
|
...model ? { model: { ...model } } : {},
|
|
@@ -2224,6 +2414,249 @@ function buildRunTrace(run, rawEvents, modelRegistryName, model, options) {
|
|
|
2224
2414
|
...traceContent.length ? { content: traceContent } : {}
|
|
2225
2415
|
};
|
|
2226
2416
|
}
|
|
2417
|
+
function buildCoordinatorTrace(run, events, options) {
|
|
2418
|
+
const terminal = terminalEvent(run.id, events);
|
|
2419
|
+
const started = startEvent(events);
|
|
2420
|
+
const status = traceStatus(terminal);
|
|
2421
|
+
const annotations = [];
|
|
2422
|
+
const spans = [];
|
|
2423
|
+
const openSteps = /* @__PURE__ */ new Map();
|
|
2424
|
+
const stepKey = (scope, stepId) => `${scope}:${stepId}`;
|
|
2425
|
+
const modelCallCount = events.filter(({ type }) => type === "model.completed").length;
|
|
2426
|
+
const toolCallCount = events.filter(({ type }) => type === "tool.completed" || type === "tool.failed").length;
|
|
2427
|
+
const openStep = (key, event, name, attributes) => {
|
|
2428
|
+
openSteps.set(key, {
|
|
2429
|
+
event,
|
|
2430
|
+
name,
|
|
2431
|
+
attributes
|
|
2432
|
+
});
|
|
2433
|
+
};
|
|
2434
|
+
const closeStep = (key, endedAt, spanStatus, error) => {
|
|
2435
|
+
const open = openSteps.get(key);
|
|
2436
|
+
if (!open) return;
|
|
2437
|
+
openSteps.delete(key);
|
|
2438
|
+
const errorContent = error ? content(options, "error.message", error) : void 0;
|
|
2439
|
+
spans.push({
|
|
2440
|
+
id: open.event.id,
|
|
2441
|
+
parentId: run.id,
|
|
2442
|
+
kind: "step",
|
|
2443
|
+
name: open.name,
|
|
2444
|
+
startedAt: open.event.timestamp,
|
|
2445
|
+
endedAt,
|
|
2446
|
+
durationMs: durationMs(open.event.timestamp, endedAt),
|
|
2447
|
+
status: spanStatus,
|
|
2448
|
+
attributes: open.attributes,
|
|
2449
|
+
annotations: [],
|
|
2450
|
+
...errorContent ? { content: [errorContent] } : {}
|
|
2451
|
+
});
|
|
2452
|
+
};
|
|
2453
|
+
for (const event of events) {
|
|
2454
|
+
const item = annotation(event);
|
|
2455
|
+
if (item) annotations.push(item);
|
|
2456
|
+
switch (event.type) {
|
|
2457
|
+
case "workflow.step.started":
|
|
2458
|
+
openStep(stepKey("workflow", event.payload.stepId), event, event.payload.stepId, {
|
|
2459
|
+
stepId: event.payload.stepId,
|
|
2460
|
+
stepKind: event.payload.kind,
|
|
2461
|
+
...event.payload.agentName === void 0 ? {} : { agentName: event.payload.agentName },
|
|
2462
|
+
...event.payload.childRunId === void 0 ? {} : { childRunId: event.payload.childRunId }
|
|
2463
|
+
});
|
|
2464
|
+
break;
|
|
2465
|
+
case "workflow.step.completed":
|
|
2466
|
+
closeStep(stepKey("workflow", event.payload.stepId), event.timestamp, "completed");
|
|
2467
|
+
break;
|
|
2468
|
+
case "workflow.step.failed":
|
|
2469
|
+
closeStep(stepKey("workflow", event.payload.stepId), event.timestamp, "failed", event.payload.error);
|
|
2470
|
+
break;
|
|
2471
|
+
case "workflow.wait.started":
|
|
2472
|
+
openStep(stepKey("workflow", event.payload.stepId), event, event.payload.stepId, {
|
|
2473
|
+
stepId: event.payload.stepId,
|
|
2474
|
+
stepKind: "wait",
|
|
2475
|
+
waitKind: event.payload.kind,
|
|
2476
|
+
...event.payload.resumeAt === void 0 ? {} : { resumeAt: event.payload.resumeAt },
|
|
2477
|
+
...event.payload.eventName === void 0 ? {} : { eventName: event.payload.eventName }
|
|
2478
|
+
});
|
|
2479
|
+
break;
|
|
2480
|
+
case "workflow.wait.completed":
|
|
2481
|
+
closeStep(stepKey("workflow", event.payload.stepId), event.timestamp, "completed");
|
|
2482
|
+
break;
|
|
2483
|
+
case "team.agent.assigned":
|
|
2484
|
+
openStep(stepKey("team", event.payload.delegationId), event, event.payload.delegationId, {
|
|
2485
|
+
stepId: event.payload.delegationId,
|
|
2486
|
+
stepKind: "agent",
|
|
2487
|
+
agentName: event.payload.agentName,
|
|
2488
|
+
action: event.payload.action,
|
|
2489
|
+
...event.payload.expectedOutput === void 0 ? {} : { expectedOutput: event.payload.expectedOutput },
|
|
2490
|
+
...event.payload.constraints === void 0 ? {} : { constraints: event.payload.constraints },
|
|
2491
|
+
...event.payload.childRunId === void 0 ? {} : { childRunId: event.payload.childRunId }
|
|
2492
|
+
});
|
|
2493
|
+
break;
|
|
2494
|
+
case "team.task.completed":
|
|
2495
|
+
closeStep(stepKey("team", event.payload.delegationId), event.timestamp, "completed");
|
|
2496
|
+
break;
|
|
2497
|
+
case "team.task.failed":
|
|
2498
|
+
closeStep(stepKey("team", event.payload.delegationId), event.timestamp, "failed", event.payload.error);
|
|
2499
|
+
break;
|
|
2500
|
+
case "team.merge.started":
|
|
2501
|
+
openStep(stepKey("team", event.payload.stepId), event, event.payload.stepId, {
|
|
2502
|
+
stepId: event.payload.stepId,
|
|
2503
|
+
stepKind: "merge",
|
|
2504
|
+
width: event.payload.width
|
|
2505
|
+
});
|
|
2506
|
+
break;
|
|
2507
|
+
case "team.merge.completed":
|
|
2508
|
+
closeStep(stepKey("team", event.payload.stepId), event.timestamp, "completed");
|
|
2509
|
+
break;
|
|
2510
|
+
case "team.merge.failed":
|
|
2511
|
+
closeStep(stepKey("team", event.payload.stepId), event.timestamp, "failed", event.payload.error);
|
|
2512
|
+
break;
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
for (const key of [...openSteps.keys()]) closeStep(key, terminal.timestamp, status);
|
|
2516
|
+
const traceContent = terminalContent(options, terminal);
|
|
2517
|
+
return {
|
|
2518
|
+
schemaVersion: "1",
|
|
2519
|
+
kind: traceKind(run),
|
|
2520
|
+
traceId: run.id,
|
|
2521
|
+
runId: run.id,
|
|
2522
|
+
sessionId: run.sessionId,
|
|
2523
|
+
...run.parentRunId === void 0 ? {} : { parentRunId: run.parentRunId },
|
|
2524
|
+
rootRunId: run.rootRunId ?? run.parentRunId ?? run.id,
|
|
2525
|
+
agentName: coordinatorName(run),
|
|
2526
|
+
startedAt: started.timestamp,
|
|
2527
|
+
endedAt: terminal.timestamp,
|
|
2528
|
+
durationMs: durationMs(started.timestamp, terminal.timestamp),
|
|
2529
|
+
status,
|
|
2530
|
+
attributes: {
|
|
2531
|
+
eventCount: events.length,
|
|
2532
|
+
stepCount: spans.filter(({ kind }) => kind === "step").length,
|
|
2533
|
+
modelCallCount,
|
|
2534
|
+
toolCallCount
|
|
2535
|
+
},
|
|
2536
|
+
annotations,
|
|
2537
|
+
spans,
|
|
2538
|
+
...run.usage && Object.keys(run.usage).length ? { usage: { ...run.usage } } : {},
|
|
2539
|
+
...traceContent.length ? { content: traceContent } : {}
|
|
2540
|
+
};
|
|
2541
|
+
}
|
|
2542
|
+
function buildRunTrace(run, rawEvents, modelRegistryName, model, options) {
|
|
2543
|
+
const events = sortedEvents(rawEvents);
|
|
2544
|
+
if (isCoordinatorRun(run)) return buildCoordinatorTrace(run, events, options);
|
|
2545
|
+
if (!modelRegistryName) throw new TypeError(`Run "${run.id}" has no model registry name`);
|
|
2546
|
+
return buildAgentTrace(run, events, modelRegistryName, model, options);
|
|
2547
|
+
}
|
|
2548
|
+
//#endregion
|
|
2549
|
+
//#region src/internal/trace-export.ts
|
|
2550
|
+
const DEFAULT_CONCURRENCY = 4;
|
|
2551
|
+
const DEFAULT_MAX_QUEUED = 100;
|
|
2552
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
2553
|
+
const DEFAULT_MAX_FAILURES = 100;
|
|
2554
|
+
const DEDUPE_WINDOW = 1e4;
|
|
2555
|
+
function withTimeout(promise, timeoutMs, message) {
|
|
2556
|
+
return new Promise((resolve, reject) => {
|
|
2557
|
+
const timer = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
2558
|
+
promise.then((value) => {
|
|
2559
|
+
clearTimeout(timer);
|
|
2560
|
+
resolve(value);
|
|
2561
|
+
}, (error) => {
|
|
2562
|
+
clearTimeout(timer);
|
|
2563
|
+
reject(error);
|
|
2564
|
+
});
|
|
2565
|
+
});
|
|
2566
|
+
}
|
|
2567
|
+
function withDefault(value, fallback) {
|
|
2568
|
+
return value ?? fallback;
|
|
2569
|
+
}
|
|
2570
|
+
/**
|
|
2571
|
+
* Bounded, deduplicated trace export queue. Jobs include building the trace
|
|
2572
|
+
* from the store, so a slow store or exporter cannot grow memory without limit.
|
|
2573
|
+
*/
|
|
2574
|
+
function createTraceExportQueue(options = {}) {
|
|
2575
|
+
const concurrency = withDefault(options.concurrency, DEFAULT_CONCURRENCY);
|
|
2576
|
+
const maxQueued = withDefault(options.maxQueued, DEFAULT_MAX_QUEUED);
|
|
2577
|
+
const timeoutMs = withDefault(options.timeoutMs, DEFAULT_TIMEOUT_MS);
|
|
2578
|
+
const maxFailures = withDefault(options.maxFailures, DEFAULT_MAX_FAILURES);
|
|
2579
|
+
const queue = [];
|
|
2580
|
+
const pending = /* @__PURE__ */ new Set();
|
|
2581
|
+
const exportedRuns = /* @__PURE__ */ new Set();
|
|
2582
|
+
const failures = [];
|
|
2583
|
+
let active = 0;
|
|
2584
|
+
let exported = 0;
|
|
2585
|
+
let failed = 0;
|
|
2586
|
+
let dropped = 0;
|
|
2587
|
+
let unreported = 0;
|
|
2588
|
+
const rememberRun = (runId) => {
|
|
2589
|
+
if (exportedRuns.has(runId)) return false;
|
|
2590
|
+
if (exportedRuns.size >= DEDUPE_WINDOW) {
|
|
2591
|
+
const oldest = exportedRuns.values().next().value;
|
|
2592
|
+
if (oldest !== void 0) exportedRuns.delete(oldest);
|
|
2593
|
+
}
|
|
2594
|
+
exportedRuns.add(runId);
|
|
2595
|
+
return true;
|
|
2596
|
+
};
|
|
2597
|
+
const recordFailure = (error) => {
|
|
2598
|
+
failed += 1;
|
|
2599
|
+
unreported += 1;
|
|
2600
|
+
failures.push(error);
|
|
2601
|
+
if (failures.length > maxFailures) failures.shift();
|
|
2602
|
+
};
|
|
2603
|
+
const startNext = () => {
|
|
2604
|
+
while (active < concurrency && queue.length) {
|
|
2605
|
+
const task = queue.shift();
|
|
2606
|
+
active += 1;
|
|
2607
|
+
let job;
|
|
2608
|
+
job = Promise.resolve().then(task).then(() => {
|
|
2609
|
+
exported += 1;
|
|
2610
|
+
}, (error) => {
|
|
2611
|
+
recordFailure(error);
|
|
2612
|
+
}).finally(() => {
|
|
2613
|
+
active -= 1;
|
|
2614
|
+
pending.delete(job);
|
|
2615
|
+
startNext();
|
|
2616
|
+
});
|
|
2617
|
+
pending.add(job);
|
|
2618
|
+
}
|
|
2619
|
+
};
|
|
2620
|
+
return {
|
|
2621
|
+
timeoutMs,
|
|
2622
|
+
enqueue(runId, task) {
|
|
2623
|
+
if (!rememberRun(runId)) return;
|
|
2624
|
+
if (queue.length >= maxQueued) {
|
|
2625
|
+
dropped += 1;
|
|
2626
|
+
recordFailure(/* @__PURE__ */ new Error(`Observability export queue is full; trace for run "${runId}" was dropped`));
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
queue.push(task);
|
|
2630
|
+
startNext();
|
|
2631
|
+
},
|
|
2632
|
+
async flush(flushOptions) {
|
|
2633
|
+
const timeout = flushOptions?.timeoutMs;
|
|
2634
|
+
if (timeout !== void 0 && (typeof timeout !== "number" || !Number.isSafeInteger(timeout) || timeout <= 0)) throw new Error("Observability flush timeoutMs must be a positive safe integer");
|
|
2635
|
+
const drain = async () => {
|
|
2636
|
+
while (pending.size) await Promise.all([...pending]);
|
|
2637
|
+
};
|
|
2638
|
+
if (timeout === void 0) await drain();
|
|
2639
|
+
else await withTimeout(drain(), timeout, `Observability flush timed out after ${timeout}ms`);
|
|
2640
|
+
if (unreported === 0) return;
|
|
2641
|
+
const errors = [...failures];
|
|
2642
|
+
const omitted = unreported - failures.length;
|
|
2643
|
+
if (omitted > 0) errors.push(/* @__PURE__ */ new Error(`${omitted} additional export failures were not retained`));
|
|
2644
|
+
failures.length = 0;
|
|
2645
|
+
unreported = 0;
|
|
2646
|
+
throw new AggregateError(errors, `Observability export failed (${failed} failure(s), ${dropped} dropped trace(s))`);
|
|
2647
|
+
},
|
|
2648
|
+
stats() {
|
|
2649
|
+
return {
|
|
2650
|
+
exported,
|
|
2651
|
+
failed,
|
|
2652
|
+
dropped,
|
|
2653
|
+
queued: queue.length,
|
|
2654
|
+
active,
|
|
2655
|
+
retainedFailures: failures.length
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
};
|
|
2659
|
+
}
|
|
2227
2660
|
//#endregion
|
|
2228
2661
|
//#region src/internal/run-core.ts
|
|
2229
2662
|
/**
|
|
@@ -2236,14 +2669,118 @@ function buildRunTrace(run, rawEvents, modelRegistryName, model, options) {
|
|
|
2236
2669
|
* the lease table, so a second instance would corrupt event ordering and lease
|
|
2237
2670
|
* ownership without raising an error.
|
|
2238
2671
|
*/
|
|
2239
|
-
function createRunCore({ models, agents, tools, runStore, policies, onEvent, observability }) {
|
|
2672
|
+
function createRunCore({ models, agents, tools, runStore, policies, onEvent, observability, admission, limits }) {
|
|
2240
2673
|
const activeRuns = /* @__PURE__ */ new Map();
|
|
2241
2674
|
const activeWorkflowRuns = /* @__PURE__ */ new Map();
|
|
2242
2675
|
const activeSessions = /* @__PURE__ */ new Set();
|
|
2243
|
-
const
|
|
2244
|
-
const
|
|
2245
|
-
const
|
|
2676
|
+
const internalRuns = /* @__PURE__ */ new Set();
|
|
2677
|
+
const pendingTenantStarts = /* @__PURE__ */ new Map();
|
|
2678
|
+
const idleWaiters = /* @__PURE__ */ new Set();
|
|
2679
|
+
let pendingStarts = 0;
|
|
2680
|
+
let closed = false;
|
|
2681
|
+
const traceExports = createTraceExportQueue(observability?.exportQueue);
|
|
2246
2682
|
const runtimeOwner = crypto.randomUUID();
|
|
2683
|
+
const isIdle = () => activeRuns.size === 0 && activeWorkflowRuns.size === 0;
|
|
2684
|
+
const notifyIdle = () => {
|
|
2685
|
+
if (!isIdle()) return;
|
|
2686
|
+
for (const resolve of idleWaiters) resolve();
|
|
2687
|
+
idleWaiters.clear();
|
|
2688
|
+
};
|
|
2689
|
+
const waitForIdle = () => {
|
|
2690
|
+
if (isIdle()) return Promise.resolve();
|
|
2691
|
+
return new Promise((resolve) => idleWaiters.add(resolve));
|
|
2692
|
+
};
|
|
2693
|
+
const closeAdmission = () => {
|
|
2694
|
+
closed = true;
|
|
2695
|
+
};
|
|
2696
|
+
const isClosed = () => closed;
|
|
2697
|
+
const unboundedEvents = /* @__PURE__ */ new Set([
|
|
2698
|
+
"run.started",
|
|
2699
|
+
"run.recovered",
|
|
2700
|
+
"run.paused",
|
|
2701
|
+
"run.resumed",
|
|
2702
|
+
"elicitation.requested",
|
|
2703
|
+
"elicitation.resolved",
|
|
2704
|
+
"approval.requested",
|
|
2705
|
+
"approval.resolved",
|
|
2706
|
+
"tool.execution_unknown",
|
|
2707
|
+
"run.completed",
|
|
2708
|
+
"run.failed",
|
|
2709
|
+
"run.cancelled",
|
|
2710
|
+
"workflow.run.started",
|
|
2711
|
+
"workflow.run.recovered",
|
|
2712
|
+
"workflow.run.paused",
|
|
2713
|
+
"workflow.run.resumed",
|
|
2714
|
+
"workflow.wait.started",
|
|
2715
|
+
"workflow.wait.completed",
|
|
2716
|
+
"workflow.run.completed",
|
|
2717
|
+
"workflow.run.failed",
|
|
2718
|
+
"workflow.run.cancelled",
|
|
2719
|
+
"team.run.started",
|
|
2720
|
+
"team.run.recovered",
|
|
2721
|
+
"team.run.paused",
|
|
2722
|
+
"team.run.resumed",
|
|
2723
|
+
"team.run.completed",
|
|
2724
|
+
"team.run.failed",
|
|
2725
|
+
"team.run.cancelled"
|
|
2726
|
+
]);
|
|
2727
|
+
const tenantOf = (context) => {
|
|
2728
|
+
const configured = admission?.tenantKey?.(context);
|
|
2729
|
+
if (configured !== void 0) return configured;
|
|
2730
|
+
const tenantId = context?.attributes?.tenantId;
|
|
2731
|
+
if (typeof tenantId !== "string") return void 0;
|
|
2732
|
+
return tenantId.trim() || void 0;
|
|
2733
|
+
};
|
|
2734
|
+
const countsForAdmission = (runId, state) => !internalRuns.has(runId) && !state.request.signal?.aborted;
|
|
2735
|
+
const activeTopLevelRuns = () => {
|
|
2736
|
+
let count = 0;
|
|
2737
|
+
for (const [runId, state] of activeRuns) if (countsForAdmission(runId, state)) count += 1;
|
|
2738
|
+
for (const [runId, state] of activeWorkflowRuns) if (countsForAdmission(runId, state)) count += 1;
|
|
2739
|
+
return count;
|
|
2740
|
+
};
|
|
2741
|
+
const activeTenantRuns = (tenant) => {
|
|
2742
|
+
let count = 0;
|
|
2743
|
+
for (const [runId, state] of activeRuns) if (countsForAdmission(runId, state) && tenantOf(state.request.context) === tenant) count += 1;
|
|
2744
|
+
for (const [runId, state] of activeWorkflowRuns) if (countsForAdmission(runId, state) && tenantOf(state.request.context) === tenant) count += 1;
|
|
2745
|
+
return count;
|
|
2746
|
+
};
|
|
2747
|
+
const admitStart = async (kind, name, request) => {
|
|
2748
|
+
if (closed) throw new FevexRunError("RUNTIME_CLOSED", "Runtime is not accepting new runs");
|
|
2749
|
+
if (!admission) return () => {};
|
|
2750
|
+
const running = activeTopLevelRuns() + pendingStarts;
|
|
2751
|
+
const tenant = tenantOf(request.context);
|
|
2752
|
+
const tenantRuns = tenant === void 0 ? 0 : activeTenantRuns(tenant) + (pendingTenantStarts.get(tenant) ?? 0);
|
|
2753
|
+
if (admission.maxActiveRuns !== void 0 && running >= admission.maxActiveRuns) throw new FevexRunError("ADMISSION_DENIED", `Runtime reached maxActiveRuns limit of ${admission.maxActiveRuns}`);
|
|
2754
|
+
if (admission.maxActiveRunsPerTenant !== void 0 && tenant !== void 0 && tenantRuns >= admission.maxActiveRunsPerTenant) throw new FevexRunError("ADMISSION_DENIED", `Tenant "${tenant}" reached maxActiveRunsPerTenant limit of ${admission.maxActiveRunsPerTenant}`);
|
|
2755
|
+
pendingStarts += 1;
|
|
2756
|
+
if (tenant !== void 0) pendingTenantStarts.set(tenant, (pendingTenantStarts.get(tenant) ?? 0) + 1);
|
|
2757
|
+
let released = false;
|
|
2758
|
+
const release = () => {
|
|
2759
|
+
if (released) return;
|
|
2760
|
+
released = true;
|
|
2761
|
+
pendingStarts = Math.max(0, pendingStarts - 1);
|
|
2762
|
+
if (tenant === void 0) return;
|
|
2763
|
+
const next = (pendingTenantStarts.get(tenant) ?? 1) - 1;
|
|
2764
|
+
if (next <= 0) pendingTenantStarts.delete(tenant);
|
|
2765
|
+
else pendingTenantStarts.set(tenant, next);
|
|
2766
|
+
};
|
|
2767
|
+
try {
|
|
2768
|
+
if (admission.authorize && !await admission.authorize({
|
|
2769
|
+
kind,
|
|
2770
|
+
name,
|
|
2771
|
+
request,
|
|
2772
|
+
activeRuns: running,
|
|
2773
|
+
activeTenantRuns: tenantRuns
|
|
2774
|
+
})) throw new FevexRunError("ADMISSION_DENIED", "Run admission was denied by the host");
|
|
2775
|
+
} catch (error) {
|
|
2776
|
+
release();
|
|
2777
|
+
throw error;
|
|
2778
|
+
}
|
|
2779
|
+
return release;
|
|
2780
|
+
};
|
|
2781
|
+
const markInternalRun = (runId) => {
|
|
2782
|
+
internalRuns.add(runId);
|
|
2783
|
+
};
|
|
2247
2784
|
const resolveModel = (agent, requestedModel) => {
|
|
2248
2785
|
const modelName = requestedModel ?? (typeof agent.model === "string" ? agent.model : "default");
|
|
2249
2786
|
if (!modelName.trim()) throw new FevexRunError("MODEL_NOT_FOUND", "Model name cannot be empty");
|
|
@@ -2265,35 +2802,30 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
|
|
|
2265
2802
|
}
|
|
2266
2803
|
};
|
|
2267
2804
|
const scheduleTraceExport = (event) => {
|
|
2268
|
-
if (!observability || !
|
|
2269
|
-
|
|
2270
|
-
"run.failed",
|
|
2271
|
-
"run.cancelled"
|
|
2272
|
-
].includes(event.type) || exportedRuns.has(event.runId)) return;
|
|
2273
|
-
exportedRuns.add(event.runId);
|
|
2274
|
-
let task;
|
|
2275
|
-
task = (async () => {
|
|
2805
|
+
if (!observability || !isTraceTerminalEvent(event)) return;
|
|
2806
|
+
traceExports.enqueue(event.runId, async () => {
|
|
2276
2807
|
const run = await runStore.getRun(event.runId);
|
|
2277
2808
|
if (!run) throw new Error(`Run "${event.runId}" does not exist`);
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
let
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2809
|
+
const coordinator = isCoordinatorRun(run);
|
|
2810
|
+
let modelRegistryName;
|
|
2811
|
+
let modelMetadata;
|
|
2812
|
+
if (!coordinator) {
|
|
2813
|
+
const agentRun = run;
|
|
2814
|
+
modelRegistryName = agentRun.modelName;
|
|
2815
|
+
modelMetadata = agentRun.modelMetadata;
|
|
2816
|
+
if (modelRegistryName === void 0) {
|
|
2817
|
+
const agent = agents.get(agentRun.agentName);
|
|
2818
|
+
if (!agent) throw new Error(`Agent "${agentRun.agentName}" is not registered`);
|
|
2819
|
+
const checkpoint = isDurableRunStore(runStore) ? await runStore.getCheckpoint(agentRun.id) : void 0;
|
|
2820
|
+
const resolved = resolveModel(agent, checkpoint?.modelName);
|
|
2821
|
+
modelRegistryName = resolved.modelName;
|
|
2822
|
+
modelMetadata = resolved.model.metadata;
|
|
2823
|
+
}
|
|
2289
2824
|
}
|
|
2290
|
-
const trace = buildRunTrace(
|
|
2291
|
-
const failures = (await Promise.allSettled(observability.exporters.map((exporter) => Promise.resolve().then(() => exporter.export(trace))))).filter((result) => result.status === "rejected").map(({ reason }) => reason);
|
|
2825
|
+
const trace = buildRunTrace(run, await runStore.listEvents(run.id), modelRegistryName, modelMetadata, observability);
|
|
2826
|
+
const failures = (await Promise.allSettled(observability.exporters.map((exporter) => withTimeout(Promise.resolve().then(() => exporter.export(trace)), traceExports.timeoutMs, `Trace exporter timed out after ${traceExports.timeoutMs}ms for run "${run.id}"`)))).filter((result) => result.status === "rejected").map(({ reason }) => reason);
|
|
2292
2827
|
if (failures.length) throw new AggregateError(failures, "Trace export failed");
|
|
2293
|
-
})
|
|
2294
|
-
exportFailures.push(error);
|
|
2295
|
-
}).finally(() => pendingExports.delete(task));
|
|
2296
|
-
pendingExports.add(task);
|
|
2828
|
+
});
|
|
2297
2829
|
};
|
|
2298
2830
|
const notifyObserver = (event) => {
|
|
2299
2831
|
scheduleTraceExport(event);
|
|
@@ -2302,14 +2834,17 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
|
|
|
2302
2834
|
Promise.resolve(onEvent(event)).catch(() => {});
|
|
2303
2835
|
} catch {}
|
|
2304
2836
|
};
|
|
2305
|
-
const createEvent = (state, type, payload) =>
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2837
|
+
const createEvent = (state, type, payload) => {
|
|
2838
|
+
if (limits?.maxEventsPerRun !== void 0 && !unboundedEvents.has(type) && state.eventSequence >= limits.maxEventsPerRun) throw new FevexRunError("EVENT_LIMIT_EXCEEDED", `Run exceeded maxEventsPerRun limit of ${limits.maxEventsPerRun}`, state.run.id);
|
|
2839
|
+
return {
|
|
2840
|
+
id: crypto.randomUUID(),
|
|
2841
|
+
sequence: state.eventSequence += 1,
|
|
2842
|
+
type,
|
|
2843
|
+
runId: state.run.id,
|
|
2844
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2845
|
+
...payload === void 0 ? {} : { payload }
|
|
2846
|
+
};
|
|
2847
|
+
};
|
|
2313
2848
|
const createCoordinatorEvent = (state, workflowType, workflowPayload, teamType, teamPayload = workflowPayload) => createEvent(state, isTeamRun(state.run) ? teamType : workflowType, isTeamRun(state.run) ? teamPayload : workflowPayload);
|
|
2314
2849
|
const emitEvent = async (state, type, payload) => {
|
|
2315
2850
|
const event = createEvent(state, type, payload);
|
|
@@ -2323,11 +2858,13 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
|
|
|
2323
2858
|
const releaseExecution = async (state) => {
|
|
2324
2859
|
activeRuns.delete(state.run.id);
|
|
2325
2860
|
activeSessions.delete(state.session.id);
|
|
2861
|
+
internalRuns.delete(state.run.id);
|
|
2326
2862
|
if (state.leaseTimer) clearInterval(state.leaseTimer);
|
|
2327
2863
|
if (state.leaseExpiryTimer) clearTimeout(state.leaseExpiryTimer);
|
|
2328
2864
|
state.leaseTimer = void 0;
|
|
2329
2865
|
state.leaseExpiryTimer = void 0;
|
|
2330
2866
|
if (state.lease && isDurableRunStore(runStore)) await runStore.releaseLease(state.run.id, state.lease.ownerId, state.lease.generation).catch(() => {});
|
|
2867
|
+
notifyIdle();
|
|
2331
2868
|
};
|
|
2332
2869
|
const commit = async (state, options = {}) => {
|
|
2333
2870
|
if (!isDurableRunStore(runStore)) throw new FevexRunError("DURABLE_STORE_REQUIRED", "This run requires a DurableRunStore", state.run.id);
|
|
@@ -2411,11 +2948,13 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
|
|
|
2411
2948
|
const releaseWorkflowExecution = async (state) => {
|
|
2412
2949
|
activeWorkflowRuns.delete(state.run.id);
|
|
2413
2950
|
activeSessions.delete(state.session.id);
|
|
2951
|
+
internalRuns.delete(state.run.id);
|
|
2414
2952
|
if (state.leaseTimer) clearInterval(state.leaseTimer);
|
|
2415
2953
|
if (state.leaseExpiryTimer) clearTimeout(state.leaseExpiryTimer);
|
|
2416
2954
|
state.leaseTimer = void 0;
|
|
2417
2955
|
state.leaseExpiryTimer = void 0;
|
|
2418
2956
|
if (state.lease && isDurableRunStore(runStore)) await runStore.releaseLease(state.run.id, state.lease.ownerId, state.lease.generation).catch(() => {});
|
|
2957
|
+
notifyIdle();
|
|
2419
2958
|
};
|
|
2420
2959
|
const commitWorkflow = async (state, update, events = []) => {
|
|
2421
2960
|
state.commitQueue = state.commitQueue.then(async () => {
|
|
@@ -2449,9 +2988,14 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
|
|
|
2449
2988
|
activeRuns,
|
|
2450
2989
|
activeWorkflowRuns,
|
|
2451
2990
|
activeSessions,
|
|
2452
|
-
|
|
2453
|
-
|
|
2991
|
+
isIdle,
|
|
2992
|
+
waitForIdle,
|
|
2993
|
+
closeAdmission,
|
|
2994
|
+
isClosed,
|
|
2995
|
+
traceExports,
|
|
2454
2996
|
runtimeOwner,
|
|
2997
|
+
admitStart,
|
|
2998
|
+
markInternalRun,
|
|
2455
2999
|
resolveModel,
|
|
2456
3000
|
resolveCheckpointModel,
|
|
2457
3001
|
notifyObserver,
|
|
@@ -2479,7 +3023,7 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
|
|
|
2479
3023
|
*/
|
|
2480
3024
|
function createWorkflowEngine(composition, core, agentEngine) {
|
|
2481
3025
|
const { models, agents, workflows, teams, runStore } = composition;
|
|
2482
|
-
const { activeRuns, activeWorkflowRuns, activeSessions, runtimeOwner, createEvent, createCoordinatorEvent, notifyObserver, commit, startLease, releaseWorkflowExecution, commitWorkflow } = core;
|
|
3026
|
+
const { activeRuns, activeWorkflowRuns, activeSessions, runtimeOwner, isClosed, admitStart, createEvent, createCoordinatorEvent, notifyObserver, commit, startLease, releaseWorkflowExecution, commitWorkflow } = core;
|
|
2483
3027
|
const { prepareExecution, executeAgent, nextEvent, resumeAgent, cancelStoredAgent } = agentEngine;
|
|
2484
3028
|
const cancelWorkflowExecution = async (state, reason = cancellationReason(state.request.signal)) => {
|
|
2485
3029
|
if (state.leaseLost) {
|
|
@@ -2488,7 +3032,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
2488
3032
|
}
|
|
2489
3033
|
if (state.run.status === "completed" || state.run.status === "failed" || state.run.status === "cancelled") return;
|
|
2490
3034
|
const childRunIds = Object.values(state.checkpoint.steps).filter((record) => record.type === "agent" && record.status === "running").map(({ childRunId }) => childRunId);
|
|
2491
|
-
for (const childRunId of childRunIds) if (!await cancelStoredAgent(childRunId)) throw new FevexRunError("RUN_CONFLICT", `Workflow child "${childRunId}" could not be cancelled`, state.run.id);
|
|
3035
|
+
for (const childRunId of childRunIds) if (!await cancelStoredAgent(childRunId, reason === "timeout" ? "timeout" : "aborted")) throw new FevexRunError("RUN_CONFLICT", `Workflow child "${childRunId}" could not be cancelled`, state.run.id);
|
|
2492
3036
|
state.run.status = "cancelled";
|
|
2493
3037
|
state.run.pause = void 0;
|
|
2494
3038
|
state.run.error = reason;
|
|
@@ -2501,7 +3045,77 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
2501
3045
|
await releaseWorkflowExecution(state);
|
|
2502
3046
|
return event;
|
|
2503
3047
|
};
|
|
2504
|
-
const
|
|
3048
|
+
const cancelStoredWorkflow = async (runId, reason = "aborted") => {
|
|
3049
|
+
const active = activeWorkflowRuns.get(runId);
|
|
3050
|
+
if (active && !active.request.signal.aborted) {
|
|
3051
|
+
active.controller.abort(new DOMException(reason === "timeout" ? "Run deadline exceeded" : "Run cancelled", reason === "timeout" ? "TimeoutError" : "AbortError"));
|
|
3052
|
+
if (!active.advancing) {
|
|
3053
|
+
await cancelWorkflowExecution(active);
|
|
3054
|
+
return true;
|
|
3055
|
+
}
|
|
3056
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
3057
|
+
const current = await runStore.getRun(runId);
|
|
3058
|
+
if (current?.status === "cancelled" || current?.status === "completed" || current?.status === "failed") return true;
|
|
3059
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
3060
|
+
}
|
|
3061
|
+
return false;
|
|
3062
|
+
}
|
|
3063
|
+
if (!isDurableRunStore(runStore)) return false;
|
|
3064
|
+
const run = await runStore.getRun(runId);
|
|
3065
|
+
if (!run) return false;
|
|
3066
|
+
if (run.status === "completed" || run.status === "failed" || run.status === "cancelled") return true;
|
|
3067
|
+
const session = await runStore.getSession(run.sessionId);
|
|
3068
|
+
const checkpoint = await runStore.getCheckpoint(runId);
|
|
3069
|
+
if (!session || !checkpoint) return false;
|
|
3070
|
+
const ownerId = `${runtimeOwner}:${crypto.randomUUID()}`;
|
|
3071
|
+
const lease = {
|
|
3072
|
+
generation: 0,
|
|
3073
|
+
runId,
|
|
3074
|
+
ownerId,
|
|
3075
|
+
expiresAt: new Date(Date.now() + LEASE_MS).toISOString()
|
|
3076
|
+
};
|
|
3077
|
+
if (!await runStore.acquireLease(lease)) return false;
|
|
3078
|
+
try {
|
|
3079
|
+
const childRunIds = Object.values(checkpoint.steps).filter((record) => record.type === "agent" && record.status === "running").map(({ childRunId }) => childRunId);
|
|
3080
|
+
for (const childRunId of childRunIds) if (!await cancelStoredAgent(childRunId, reason)) return false;
|
|
3081
|
+
const state = {
|
|
3082
|
+
run,
|
|
3083
|
+
session,
|
|
3084
|
+
request: {
|
|
3085
|
+
input: checkpoint.input,
|
|
3086
|
+
...checkpoint.model === void 0 ? {} : { model: checkpoint.model },
|
|
3087
|
+
...checkpoint.reasoning === void 0 ? {} : { reasoning: checkpoint.reasoning },
|
|
3088
|
+
sessionId: session.id,
|
|
3089
|
+
context: checkpoint.context,
|
|
3090
|
+
limits: checkpoint.limits,
|
|
3091
|
+
signal: new AbortController().signal
|
|
3092
|
+
},
|
|
3093
|
+
controller: new AbortController(),
|
|
3094
|
+
eventSequence: (await runStore.listEvents(runId)).at(-1)?.sequence ?? 0,
|
|
3095
|
+
checkpoint,
|
|
3096
|
+
advancing: false,
|
|
3097
|
+
lease,
|
|
3098
|
+
commitQueue: Promise.resolve()
|
|
3099
|
+
};
|
|
3100
|
+
run.status = "cancelled";
|
|
3101
|
+
run.pause = void 0;
|
|
3102
|
+
run.error = reason;
|
|
3103
|
+
run.usage = checkpoint.budget?.usage;
|
|
3104
|
+
const cancelled = createCoordinatorEvent(state, "workflow.run.cancelled", { reason }, "team.run.cancelled");
|
|
3105
|
+
await commit(state, {
|
|
3106
|
+
checkpoint: null,
|
|
3107
|
+
events: [cancelled]
|
|
3108
|
+
});
|
|
3109
|
+
return true;
|
|
3110
|
+
} catch (error) {
|
|
3111
|
+
if (error instanceof FevexRunError && error.code === "RUN_CONFLICT" && (await runStore.getRun(runId))?.status !== "paused") return false;
|
|
3112
|
+
throw error;
|
|
3113
|
+
} finally {
|
|
3114
|
+
await runStore.releaseLease(runId, ownerId, lease.generation).catch(() => {});
|
|
3115
|
+
}
|
|
3116
|
+
};
|
|
3117
|
+
const prepareWorkflowExecutionBody = async (name, request, kind = "workflow") => {
|
|
3118
|
+
if (isClosed()) throw new FevexRunError("RUNTIME_CLOSED", "Runtime is not accepting new runs");
|
|
2505
3119
|
const workflow = workflows.get(name);
|
|
2506
3120
|
const team = kind === "team" ? teams.get(name) : void 0;
|
|
2507
3121
|
if (!workflow || kind === "team" && !team || kind === "workflow" && teams.has(name)) throw new FevexRunError(kind === "team" ? "TEAM_NOT_FOUND" : "WORKFLOW_NOT_FOUND", `${kind === "team" ? "Team" : "Workflow"} "${name}" is not registered`);
|
|
@@ -2520,9 +3134,9 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
2520
3134
|
session = stored;
|
|
2521
3135
|
}
|
|
2522
3136
|
if (activeSessions.has(session.id)) throw new FevexRunError("RUN_CONFLICT", `Session "${session.id}" already has an active run`);
|
|
2523
|
-
activeSessions.add(session.id);
|
|
2524
3137
|
const controller = new AbortController();
|
|
2525
|
-
const signal =
|
|
3138
|
+
const signal = runSignal(controller, request);
|
|
3139
|
+
const deadlineAt = deadlineAtFromMs(request.deadlineMs);
|
|
2526
3140
|
const input = toJsonValue(workflow.inputSchema === void 0 ? request.input : await abortable(() => validateSchema(workflow.inputSchema, request.input, `Input for ${kind} "${name}" does not match inputSchema`), signal), `${kind === "team" ? "Team" : "Workflow"} input must be a string or JSON-serializable value`);
|
|
2527
3141
|
const id = crypto.randomUUID();
|
|
2528
3142
|
const run = kind === "team" ? {
|
|
@@ -2533,7 +3147,8 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
2533
3147
|
status: "running",
|
|
2534
3148
|
revision: 0,
|
|
2535
3149
|
createdAt: now,
|
|
2536
|
-
updatedAt: now
|
|
3150
|
+
updatedAt: now,
|
|
3151
|
+
...deadlineAt === void 0 ? {} : { deadlineAt }
|
|
2537
3152
|
} : {
|
|
2538
3153
|
kind: "workflow",
|
|
2539
3154
|
id,
|
|
@@ -2542,7 +3157,8 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
2542
3157
|
status: "running",
|
|
2543
3158
|
revision: 0,
|
|
2544
3159
|
createdAt: now,
|
|
2545
|
-
updatedAt: now
|
|
3160
|
+
updatedAt: now,
|
|
3161
|
+
...deadlineAt === void 0 ? {} : { deadlineAt }
|
|
2546
3162
|
};
|
|
2547
3163
|
const checkpoint = kind === "team" ? {
|
|
2548
3164
|
version: 2,
|
|
@@ -2569,6 +3185,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
2569
3185
|
steps: {},
|
|
2570
3186
|
limits: combineLimits(workflow.limits, request.limits)
|
|
2571
3187
|
};
|
|
3188
|
+
activeSessions.add(session.id);
|
|
2572
3189
|
const state = {
|
|
2573
3190
|
run,
|
|
2574
3191
|
session,
|
|
@@ -2610,6 +3227,14 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
2610
3227
|
activeWorkflowRuns.set(run.id, state);
|
|
2611
3228
|
return state;
|
|
2612
3229
|
};
|
|
3230
|
+
const prepareWorkflowExecution = async (name, request, kind = "workflow") => {
|
|
3231
|
+
const release = await admitStart(kind, name, request);
|
|
3232
|
+
try {
|
|
3233
|
+
return await prepareWorkflowExecutionBody(name, request, kind);
|
|
3234
|
+
} finally {
|
|
3235
|
+
release();
|
|
3236
|
+
}
|
|
3237
|
+
};
|
|
2613
3238
|
async function* executeWorkflow(name, state) {
|
|
2614
3239
|
const workflow = workflows.get(name);
|
|
2615
3240
|
const events = [];
|
|
@@ -2771,7 +3396,10 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
2771
3396
|
if (!child) throw new Error(`Child run "${existing.childRunId}" does not exist`);
|
|
2772
3397
|
if (child.status === "running") {
|
|
2773
3398
|
if (!state.recoveryActor) throw new FevexRunError("RUN_NOT_RECOVERABLE", `Child run "${child.id}" is still running`, state.run.id);
|
|
2774
|
-
child = await resumeAgent(child.id, void 0, true, state.recoveryActor
|
|
3399
|
+
child = await resumeAgent(child.id, void 0, true, state.recoveryActor, {
|
|
3400
|
+
admit: false,
|
|
3401
|
+
internal: true
|
|
3402
|
+
});
|
|
2775
3403
|
}
|
|
2776
3404
|
if (child.status === "completed") return completeAgentStep(stepId, existing.agentName, existing.childRunId, toJsonValue(child.output, "Child agent output must be JSON-serializable"), options);
|
|
2777
3405
|
if (child.status === "paused" && child.pause) return pauseForChild(stepId, new RunPausedError(child.id, child.pause));
|
|
@@ -2782,11 +3410,16 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
2782
3410
|
const childSignal = request.signal ? AbortSignal.any([state.request.signal, request.signal]) : state.request.signal;
|
|
2783
3411
|
const childState = await prepareExecution(agentName, {
|
|
2784
3412
|
...request,
|
|
3413
|
+
deadlineMs: minDeadline(remainingDeadlineMs(state.run.deadlineAt, state.run.id), request.deadlineMs),
|
|
2785
3414
|
model: request.model ?? state.request.model,
|
|
2786
3415
|
reasoning: request.reasoning ?? state.request.reasoning,
|
|
2787
3416
|
context: mergeExecutionContext(state.request.context, request.context),
|
|
2788
3417
|
limits: combineLimits(request.limits, remainingWorkflowLimits(state.checkpoint.limits, state.checkpoint.budget)),
|
|
2789
3418
|
signal: childSignal
|
|
3419
|
+
}, {
|
|
3420
|
+
admit: false,
|
|
3421
|
+
parentRunId: state.run.id,
|
|
3422
|
+
rootRunId: state.run.rootRunId ?? state.run.id
|
|
2790
3423
|
});
|
|
2791
3424
|
const action = options?.metadata?.teamAction === "handoff" ? "handoff" : "delegate";
|
|
2792
3425
|
const lifecycleEvents = [];
|
|
@@ -2799,11 +3432,13 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
2799
3432
|
const started = createCoordinatorEvent(state, "workflow.step.started", {
|
|
2800
3433
|
stepId,
|
|
2801
3434
|
kind: "agent",
|
|
2802
|
-
agentName
|
|
3435
|
+
agentName,
|
|
3436
|
+
childRunId: childState.run.id
|
|
2803
3437
|
}, "team.agent.assigned", {
|
|
2804
3438
|
delegationId: stepId,
|
|
2805
3439
|
agentName,
|
|
2806
3440
|
action,
|
|
3441
|
+
childRunId: childState.run.id,
|
|
2807
3442
|
...typeof options?.metadata?.expectedOutput === "string" ? { expectedOutput: options.metadata.expectedOutput } : {},
|
|
2808
3443
|
...Array.isArray(options?.metadata?.constraints) ? { constraints: options.metadata.constraints } : {}
|
|
2809
3444
|
});
|
|
@@ -3121,8 +3756,10 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
3121
3756
|
};
|
|
3122
3757
|
} catch (error) {
|
|
3123
3758
|
let thrown = error;
|
|
3124
|
-
|
|
3125
|
-
|
|
3759
|
+
const parentDeadline = state.run.deadlineAt === void 0 ? void 0 : Date.parse(state.run.deadlineAt);
|
|
3760
|
+
const parentTimedOut = isTimeoutError(error) && parentDeadline !== void 0 && Date.now() >= parentDeadline - 10;
|
|
3761
|
+
if (state.request.signal.aborted || parentTimedOut) try {
|
|
3762
|
+
const event = await cancelWorkflowExecution(state, parentTimedOut ? "timeout" : void 0);
|
|
3126
3763
|
if (event) {
|
|
3127
3764
|
events.push(event);
|
|
3128
3765
|
yield event;
|
|
@@ -3184,13 +3821,18 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
3184
3821
|
}
|
|
3185
3822
|
}
|
|
3186
3823
|
};
|
|
3187
|
-
const
|
|
3824
|
+
const resumeWorkflowBody = async (runId, resolution, recoveryActor) => {
|
|
3188
3825
|
if (!isDurableRunStore(runStore)) throw new FevexRunError("DURABLE_STORE_REQUIRED", "resumeRun requires DurableRunStore", runId);
|
|
3189
3826
|
const run = await runStore.getRun(runId);
|
|
3190
3827
|
const checkpoint = await runStore.getCheckpoint(runId);
|
|
3191
3828
|
if (!run || !checkpoint) throw new FevexRunError(resolution ? "RUN_NOT_RESUMABLE" : "RUN_NOT_RECOVERABLE", `Run "${runId}" cannot be ${resolution ? "resumed" : "recovered"}`, runId);
|
|
3192
3829
|
if (checkpoint.version !== 2) throw new FevexRunError("CHECKPOINT_UNSUPPORTED", `Checkpoint for run "${runId}" is unsupported`, runId);
|
|
3193
3830
|
if (resolution && run.status !== "paused" && run.status !== "running" || !resolution && run.status !== "running") throw new FevexRunError(resolution ? "RUN_NOT_RESUMABLE" : "RUN_NOT_RECOVERABLE", `Run "${runId}" is not ${resolution ? "resumable" : "recoverable"}`, runId);
|
|
3831
|
+
if (run.deadlineAt !== void 0 && Date.parse(run.deadlineAt) <= Date.now()) {
|
|
3832
|
+
if (!await cancelStoredWorkflow(runId, "timeout")) throw new FevexRunError(resolution ? "RUN_NOT_RESUMABLE" : "RUN_NOT_RECOVERABLE", `Run "${runId}" deadline has passed`, runId);
|
|
3833
|
+
return await runStore.getRun(runId);
|
|
3834
|
+
}
|
|
3835
|
+
const deadlineSignal = resumeDeadlineSignal(run.deadlineAt, runId, resolution ? "RUN_NOT_RESUMABLE" : "RUN_NOT_RECOVERABLE");
|
|
3194
3836
|
const name = coordinatorName(run);
|
|
3195
3837
|
const workflow = workflows.get(name);
|
|
3196
3838
|
const team = isTeamRun(run) ? teams.get(name) : void 0;
|
|
@@ -3213,6 +3855,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
3213
3855
|
if (!resolution) {
|
|
3214
3856
|
if (!recoveryActor?.id.trim()) throw new FevexRunError("RUN_NOT_RECOVERABLE", "Recovery actor is required", runId);
|
|
3215
3857
|
const controller = new AbortController();
|
|
3858
|
+
const signal = deadlineSignal ? AbortSignal.any([controller.signal, deadlineSignal]) : controller.signal;
|
|
3216
3859
|
const state = {
|
|
3217
3860
|
run,
|
|
3218
3861
|
session,
|
|
@@ -3223,7 +3866,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
3223
3866
|
sessionId: session.id,
|
|
3224
3867
|
context: checkpoint.context,
|
|
3225
3868
|
limits: checkpoint.limits,
|
|
3226
|
-
signal
|
|
3869
|
+
signal
|
|
3227
3870
|
},
|
|
3228
3871
|
controller,
|
|
3229
3872
|
eventSequence: (await runStore.listEvents(runId)).at(-1)?.sequence ?? 0,
|
|
@@ -3266,6 +3909,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
3266
3909
|
if (validatedPayload !== void 0) eventPayload = toJsonValue(validatedPayload, "Workflow event payload must be JSON-serializable");
|
|
3267
3910
|
}
|
|
3268
3911
|
const controller = new AbortController();
|
|
3912
|
+
const signal = deadlineSignal ? AbortSignal.any([controller.signal, deadlineSignal]) : controller.signal;
|
|
3269
3913
|
const state = {
|
|
3270
3914
|
run,
|
|
3271
3915
|
session,
|
|
@@ -3276,7 +3920,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
3276
3920
|
sessionId: session.id,
|
|
3277
3921
|
context: checkpoint.context,
|
|
3278
3922
|
limits: checkpoint.limits,
|
|
3279
|
-
signal
|
|
3923
|
+
signal
|
|
3280
3924
|
},
|
|
3281
3925
|
controller,
|
|
3282
3926
|
eventSequence: (await runStore.listEvents(runId)).at(-1)?.sequence ?? 0,
|
|
@@ -3314,8 +3958,93 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
3314
3958
|
if (resolution.type === "timer" || resolution.type === "event") throw new FevexRunError("RUN_NOT_RESUMABLE", `Run "${runId}" cannot be resumed`, runId);
|
|
3315
3959
|
const childRunId = workflowPause.type === "workflow_children" ? workflowPause.children.find(({ childPause }) => pauseMatchesResolution(childPause, resolution))?.childRunId : workflowPause.childRunId;
|
|
3316
3960
|
if (!childRunId) throw new FevexRunError("RUN_NOT_RESUMABLE", "Resolution does not match a paused child run", runId);
|
|
3317
|
-
|
|
3961
|
+
let childRun;
|
|
3962
|
+
try {
|
|
3963
|
+
childRun = await resumeAgent(childRunId, resolution, true, void 0, {
|
|
3964
|
+
admit: false,
|
|
3965
|
+
internal: true
|
|
3966
|
+
});
|
|
3967
|
+
} catch (error) {
|
|
3968
|
+
const parentDeadline = run.deadlineAt === void 0 ? void 0 : Date.parse(run.deadlineAt);
|
|
3969
|
+
const child = await runStore.getRun(childRunId);
|
|
3970
|
+
const childDeadline = child?.deadlineAt === void 0 ? void 0 : Date.parse(child.deadlineAt);
|
|
3971
|
+
if (isTimeoutError(error) && parentDeadline !== void 0 && childDeadline !== void 0 && Math.abs(childDeadline - parentDeadline) <= 5) {
|
|
3972
|
+
const pausedChildIds = workflowPause.type === "workflow_children" ? workflowPause.children.map(({ childRunId }) => childRunId) : [workflowPause.childRunId];
|
|
3973
|
+
for (const pausedChildId of pausedChildIds) if (!await cancelStoredAgent(pausedChildId, "timeout")) throw error;
|
|
3974
|
+
const cancelController = new AbortController();
|
|
3975
|
+
const cancelState = {
|
|
3976
|
+
run,
|
|
3977
|
+
session,
|
|
3978
|
+
request: {
|
|
3979
|
+
input: checkpoint.input,
|
|
3980
|
+
...checkpoint.model === void 0 ? {} : { model: checkpoint.model },
|
|
3981
|
+
...checkpoint.reasoning === void 0 ? {} : { reasoning: checkpoint.reasoning },
|
|
3982
|
+
sessionId: session.id,
|
|
3983
|
+
context: checkpoint.context,
|
|
3984
|
+
limits: checkpoint.limits,
|
|
3985
|
+
signal: cancelController.signal
|
|
3986
|
+
},
|
|
3987
|
+
controller: cancelController,
|
|
3988
|
+
eventSequence: (await runStore.listEvents(runId)).at(-1)?.sequence ?? 0,
|
|
3989
|
+
checkpoint,
|
|
3990
|
+
advancing: false,
|
|
3991
|
+
lease,
|
|
3992
|
+
commitQueue: Promise.resolve()
|
|
3993
|
+
};
|
|
3994
|
+
run.status = "cancelled";
|
|
3995
|
+
run.pause = void 0;
|
|
3996
|
+
run.error = "timeout";
|
|
3997
|
+
run.usage = checkpoint.budget?.usage;
|
|
3998
|
+
const cancelled = createCoordinatorEvent(cancelState, "workflow.run.cancelled", { reason: "timeout" }, "team.run.cancelled");
|
|
3999
|
+
await commit(cancelState, {
|
|
4000
|
+
checkpoint: null,
|
|
4001
|
+
events: [cancelled]
|
|
4002
|
+
});
|
|
4003
|
+
await runStore.releaseLease(runId, ownerId, lease.generation).catch(() => {});
|
|
4004
|
+
return structuredClone(run);
|
|
4005
|
+
}
|
|
4006
|
+
if (isTimeoutError(error)) {
|
|
4007
|
+
const pausedChildIds = workflowPause.type === "workflow_children" ? workflowPause.children.map(({ childRunId }) => childRunId) : [workflowPause.childRunId];
|
|
4008
|
+
for (const pausedChildId of pausedChildIds) if (!await cancelStoredAgent(pausedChildId, "aborted")) throw error;
|
|
4009
|
+
const failController = new AbortController();
|
|
4010
|
+
const failState = {
|
|
4011
|
+
run,
|
|
4012
|
+
session,
|
|
4013
|
+
request: {
|
|
4014
|
+
input: checkpoint.input,
|
|
4015
|
+
...checkpoint.model === void 0 ? {} : { model: checkpoint.model },
|
|
4016
|
+
...checkpoint.reasoning === void 0 ? {} : { reasoning: checkpoint.reasoning },
|
|
4017
|
+
sessionId: session.id,
|
|
4018
|
+
context: checkpoint.context,
|
|
4019
|
+
limits: checkpoint.limits,
|
|
4020
|
+
signal: failController.signal
|
|
4021
|
+
},
|
|
4022
|
+
controller: failController,
|
|
4023
|
+
eventSequence: (await runStore.listEvents(runId)).at(-1)?.sequence ?? 0,
|
|
4024
|
+
checkpoint,
|
|
4025
|
+
advancing: false,
|
|
4026
|
+
lease,
|
|
4027
|
+
commitQueue: Promise.resolve()
|
|
4028
|
+
};
|
|
4029
|
+
run.status = "failed";
|
|
4030
|
+
run.pause = void 0;
|
|
4031
|
+
run.error = toErrorMessage(error);
|
|
4032
|
+
run.usage = checkpoint.budget?.usage;
|
|
4033
|
+
const failed = createCoordinatorEvent(failState, "workflow.run.failed", {
|
|
4034
|
+
error: toErrorMessage(error),
|
|
4035
|
+
...runErrorPayload(error)
|
|
4036
|
+
}, "team.run.failed");
|
|
4037
|
+
await commit(failState, {
|
|
4038
|
+
checkpoint: null,
|
|
4039
|
+
events: [failed]
|
|
4040
|
+
});
|
|
4041
|
+
await runStore.releaseLease(runId, ownerId, lease.generation).catch(() => {});
|
|
4042
|
+
return structuredClone(run);
|
|
4043
|
+
}
|
|
4044
|
+
throw error;
|
|
4045
|
+
}
|
|
3318
4046
|
const controller = new AbortController();
|
|
4047
|
+
const signal = deadlineSignal ? AbortSignal.any([controller.signal, deadlineSignal]) : controller.signal;
|
|
3319
4048
|
const state = {
|
|
3320
4049
|
run,
|
|
3321
4050
|
session,
|
|
@@ -3326,7 +4055,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
3326
4055
|
sessionId: session.id,
|
|
3327
4056
|
context: checkpoint.context,
|
|
3328
4057
|
limits: checkpoint.limits,
|
|
3329
|
-
signal
|
|
4058
|
+
signal
|
|
3330
4059
|
},
|
|
3331
4060
|
controller,
|
|
3332
4061
|
eventSequence: (await runStore.listEvents(runId)).at(-1)?.sequence ?? 0,
|
|
@@ -3339,7 +4068,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
3339
4068
|
run.status = "cancelled";
|
|
3340
4069
|
run.pause = void 0;
|
|
3341
4070
|
run.error = childRun.error ?? "aborted";
|
|
3342
|
-
const reason = childRun.error === "approval_rejected" ? "approval_rejected" : "aborted";
|
|
4071
|
+
const reason = childRun.error === "approval_rejected" ? "approval_rejected" : childRun.error === "timeout" ? "timeout" : "aborted";
|
|
3343
4072
|
const cancelled = createCoordinatorEvent(state, "workflow.run.cancelled", { reason }, "team.run.cancelled");
|
|
3344
4073
|
await commit(state, {
|
|
3345
4074
|
checkpoint: null,
|
|
@@ -3365,35 +4094,104 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
3365
4094
|
throw error;
|
|
3366
4095
|
}
|
|
3367
4096
|
};
|
|
4097
|
+
const resumeWorkflow = async (runId, resolution, recoveryActor, options = {}) => {
|
|
4098
|
+
let release;
|
|
4099
|
+
if (options.admit !== false) {
|
|
4100
|
+
const run = await runStore.getRun(runId);
|
|
4101
|
+
const checkpoint = isDurableRunStore(runStore) ? await runStore.getCheckpoint(runId) : void 0;
|
|
4102
|
+
if (run && checkpoint && (run.status === "paused" || run.status === "running")) release = await admitStart(isTeamRun(run) ? "team" : "workflow", coordinatorName(run), {
|
|
4103
|
+
input: "",
|
|
4104
|
+
...checkpoint.context === void 0 ? {} : { context: checkpoint.context }
|
|
4105
|
+
});
|
|
4106
|
+
}
|
|
4107
|
+
try {
|
|
4108
|
+
return await resumeWorkflowBody(runId, resolution, recoveryActor);
|
|
4109
|
+
} finally {
|
|
4110
|
+
release?.();
|
|
4111
|
+
}
|
|
4112
|
+
};
|
|
3368
4113
|
return {
|
|
3369
4114
|
prepareWorkflowExecution,
|
|
3370
4115
|
executeWorkflow,
|
|
3371
4116
|
nextWorkflowEvent,
|
|
3372
4117
|
drainWorkflowExecution,
|
|
3373
4118
|
resumeWorkflow,
|
|
3374
|
-
cancelWorkflowExecution
|
|
4119
|
+
cancelWorkflowExecution,
|
|
4120
|
+
cancelStoredWorkflow
|
|
3375
4121
|
};
|
|
3376
4122
|
}
|
|
3377
4123
|
//#endregion
|
|
3378
4124
|
//#region src/internal/runtime.ts
|
|
4125
|
+
const DEFAULT_SHUTDOWN_TIMEOUT_MS = 3e4;
|
|
4126
|
+
const SHUTDOWN_CLEANUP_MS = 5e3;
|
|
3379
4127
|
function createRuntime(composition) {
|
|
3380
4128
|
const { models, agents, workflows, teams, tools, contextProviders, memoryStore, runStore, credentialStore, sandbox, policies } = composition;
|
|
3381
4129
|
const core = createRunCore(composition);
|
|
3382
|
-
const { activeRuns, activeWorkflowRuns, activeSessions,
|
|
4130
|
+
const { activeRuns, activeWorkflowRuns, activeSessions, isIdle, waitForIdle, closeAdmission, isClosed, traceExports, runtimeOwner, createEvent, createCoordinatorEvent, notifyObserver, commit, startLease, cancelExecution, releaseWorkflowExecution, commitWorkflow } = core;
|
|
3383
4131
|
const agentEngine = createAgentEngine(composition, core);
|
|
3384
4132
|
const { prepareExecution, executeAgent, nextEvent, drainExecution, resumeAgent, cancelStoredAgent } = agentEngine;
|
|
3385
|
-
const { prepareWorkflowExecution, executeWorkflow, nextWorkflowEvent, drainWorkflowExecution, resumeWorkflow, cancelWorkflowExecution } = createWorkflowEngine(composition, core, agentEngine);
|
|
4133
|
+
const { prepareWorkflowExecution, executeWorkflow, nextWorkflowEvent, drainWorkflowExecution, resumeWorkflow, cancelWorkflowExecution, cancelStoredWorkflow } = createWorkflowEngine(composition, core, agentEngine);
|
|
3386
4134
|
const resumeRun = async (runId, resolution) => {
|
|
4135
|
+
if (isClosed()) throw new FevexRunError("RUNTIME_CLOSED", "Runtime is not accepting new runs", runId);
|
|
3387
4136
|
const run = await runStore.getRun(runId);
|
|
3388
4137
|
if (run && isCoordinatorRun(run)) return resumeWorkflow(runId, resolution);
|
|
3389
4138
|
return resumeAgent(runId, resolution);
|
|
3390
4139
|
};
|
|
3391
4140
|
const recoverRun = async (runId, actor) => {
|
|
4141
|
+
if (isClosed()) throw new FevexRunError("RUNTIME_CLOSED", "Runtime is not accepting new runs", runId);
|
|
3392
4142
|
const run = await runStore.getRun(runId);
|
|
3393
4143
|
if (!run) throw new FevexRunError("RUN_NOT_RECOVERABLE", `Run "${runId}" cannot be recovered`, runId);
|
|
3394
4144
|
if (isCoordinatorRun(run)) return resumeWorkflow(runId, void 0, actor);
|
|
3395
4145
|
return resumeAgent(runId, void 0, false, actor);
|
|
3396
4146
|
};
|
|
4147
|
+
const waitForIdleUntil = async (deadline) => {
|
|
4148
|
+
if (isIdle()) return true;
|
|
4149
|
+
const remaining = deadline - Date.now();
|
|
4150
|
+
if (remaining <= 0) return false;
|
|
4151
|
+
return new Promise((resolve) => {
|
|
4152
|
+
let settled = false;
|
|
4153
|
+
let timer;
|
|
4154
|
+
const finish = (idle) => {
|
|
4155
|
+
if (settled) return;
|
|
4156
|
+
settled = true;
|
|
4157
|
+
if (timer) clearTimeout(timer);
|
|
4158
|
+
resolve(idle);
|
|
4159
|
+
};
|
|
4160
|
+
timer = setTimeout(() => finish(false), remaining);
|
|
4161
|
+
waitForIdle().then(() => finish(true));
|
|
4162
|
+
});
|
|
4163
|
+
};
|
|
4164
|
+
const shutdown = async (options = {}) => {
|
|
4165
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
|
4166
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0) throw new TypeError("Shutdown timeoutMs must be a non-negative safe integer");
|
|
4167
|
+
closeAdmission();
|
|
4168
|
+
const deadline = Date.now() + timeoutMs;
|
|
4169
|
+
let cleanupDeadline = deadline;
|
|
4170
|
+
let timedOut = false;
|
|
4171
|
+
const cancelled = /* @__PURE__ */ new Set();
|
|
4172
|
+
if (!await waitForIdleUntil(deadline)) {
|
|
4173
|
+
timedOut = true;
|
|
4174
|
+
cleanupDeadline = Date.now() + SHUTDOWN_CLEANUP_MS;
|
|
4175
|
+
for (const { run } of activeWorkflowRuns.values()) cancelled.add(run.id);
|
|
4176
|
+
for (const { run } of activeRuns.values()) cancelled.add(run.id);
|
|
4177
|
+
for (const state of [...activeWorkflowRuns.values(), ...activeRuns.values()]) if (!state.request.signal.aborted) state.controller.abort(new DOMException("Run cancelled", "AbortError"));
|
|
4178
|
+
for (const state of [...activeWorkflowRuns.values()]) if (!state.advancing) await cancelWorkflowExecution(state).catch(() => {});
|
|
4179
|
+
for (const state of [...activeRuns.values()]) if (!state.advancing) await cancelExecution(state).catch(() => {});
|
|
4180
|
+
await waitForIdleUntil(cleanupDeadline);
|
|
4181
|
+
}
|
|
4182
|
+
let exportError;
|
|
4183
|
+
try {
|
|
4184
|
+
await traceExports.flush({ timeoutMs: Math.max(1, cleanupDeadline - Date.now()) });
|
|
4185
|
+
} catch (error) {
|
|
4186
|
+
exportError = error;
|
|
4187
|
+
}
|
|
4188
|
+
return {
|
|
4189
|
+
cancelledRunIds: [...cancelled],
|
|
4190
|
+
timedOut,
|
|
4191
|
+
...exportError === void 0 ? {} : { exportError },
|
|
4192
|
+
observability: traceExports.stats()
|
|
4193
|
+
};
|
|
4194
|
+
};
|
|
3397
4195
|
return {
|
|
3398
4196
|
async startAgent(name, request) {
|
|
3399
4197
|
const state = await prepareExecution(name, request);
|
|
@@ -3499,58 +4297,7 @@ function createRuntime(composition) {
|
|
|
3499
4297
|
}
|
|
3500
4298
|
const run = await runStore.getRun(runId);
|
|
3501
4299
|
if (!run || run.status !== "paused" && run.status !== "running") return false;
|
|
3502
|
-
if (
|
|
3503
|
-
const session = await runStore.getSession(run.sessionId);
|
|
3504
|
-
if (!session) return false;
|
|
3505
|
-
if (isCoordinatorRun(run)) {
|
|
3506
|
-
const checkpoint = await runStore.getCheckpoint(runId);
|
|
3507
|
-
if (!checkpoint) return false;
|
|
3508
|
-
const ownerId = `${runtimeOwner}:${crypto.randomUUID()}`;
|
|
3509
|
-
const lease = {
|
|
3510
|
-
generation: 0,
|
|
3511
|
-
runId,
|
|
3512
|
-
ownerId,
|
|
3513
|
-
expiresAt: new Date(Date.now() + LEASE_MS).toISOString()
|
|
3514
|
-
};
|
|
3515
|
-
if (!await runStore.acquireLease(lease)) return false;
|
|
3516
|
-
try {
|
|
3517
|
-
const childRunIds = Object.values(checkpoint.steps).filter((record) => record.type === "agent" && record.status === "running").map(({ childRunId }) => childRunId);
|
|
3518
|
-
for (const childRunId of childRunIds) if (!await cancelStoredAgent(childRunId)) return false;
|
|
3519
|
-
const state = {
|
|
3520
|
-
run,
|
|
3521
|
-
session,
|
|
3522
|
-
request: {
|
|
3523
|
-
input: checkpoint.input,
|
|
3524
|
-
...checkpoint.reasoning === void 0 ? {} : { reasoning: checkpoint.reasoning },
|
|
3525
|
-
sessionId: session.id,
|
|
3526
|
-
context: checkpoint.context,
|
|
3527
|
-
limits: checkpoint.limits,
|
|
3528
|
-
signal: new AbortController().signal
|
|
3529
|
-
},
|
|
3530
|
-
controller: new AbortController(),
|
|
3531
|
-
eventSequence: (await runStore.listEvents(runId)).at(-1)?.sequence ?? 0,
|
|
3532
|
-
checkpoint,
|
|
3533
|
-
advancing: false,
|
|
3534
|
-
lease,
|
|
3535
|
-
commitQueue: Promise.resolve()
|
|
3536
|
-
};
|
|
3537
|
-
run.status = "cancelled";
|
|
3538
|
-
run.pause = void 0;
|
|
3539
|
-
run.error = "aborted";
|
|
3540
|
-
run.usage = checkpoint.budget?.usage;
|
|
3541
|
-
const cancelled = createCoordinatorEvent(state, "workflow.run.cancelled", { reason: "aborted" }, "team.run.cancelled");
|
|
3542
|
-
await commit(state, {
|
|
3543
|
-
checkpoint: null,
|
|
3544
|
-
events: [cancelled]
|
|
3545
|
-
});
|
|
3546
|
-
return true;
|
|
3547
|
-
} catch (error) {
|
|
3548
|
-
if (error instanceof FevexRunError && error.code === "RUN_CONFLICT" && (await runStore.getRun(runId))?.status !== "paused") return false;
|
|
3549
|
-
throw error;
|
|
3550
|
-
} finally {
|
|
3551
|
-
await runStore.releaseLease(runId, ownerId, lease.generation).catch(() => {});
|
|
3552
|
-
}
|
|
3553
|
-
}
|
|
4300
|
+
if (isCoordinatorRun(run)) return cancelStoredWorkflow(runId);
|
|
3554
4301
|
return cancelStoredAgent(runId);
|
|
3555
4302
|
},
|
|
3556
4303
|
resumeRun(runId, resolution) {
|
|
@@ -3559,6 +4306,10 @@ function createRuntime(composition) {
|
|
|
3559
4306
|
recoverRun(runId, options) {
|
|
3560
4307
|
return recoverRun(runId, options.actor);
|
|
3561
4308
|
},
|
|
4309
|
+
async purgeRuns(options) {
|
|
4310
|
+
if (!isRetainableRunStore(runStore)) throw new FevexRunError("RETENTION_UNSUPPORTED", "The configured run store does not support purgeRuns");
|
|
4311
|
+
return runStore.purgeRuns(options);
|
|
4312
|
+
},
|
|
3562
4313
|
async compactSession(sessionId, summary) {
|
|
3563
4314
|
if (typeof summary !== "string" || !summary.trim()) throw new Error("Session summary must be a non-empty string");
|
|
3564
4315
|
if (activeSessions.has(sessionId)) throw new Error(`Session "${sessionId}" has an active run`);
|
|
@@ -3572,13 +4323,13 @@ function createRuntime(composition) {
|
|
|
3572
4323
|
await runStore.saveSession(session);
|
|
3573
4324
|
return structuredClone(session);
|
|
3574
4325
|
},
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
4326
|
+
flushObservability(options) {
|
|
4327
|
+
return traceExports.flush(options);
|
|
4328
|
+
},
|
|
4329
|
+
getObservabilityStats() {
|
|
4330
|
+
return traceExports.stats();
|
|
4331
|
+
},
|
|
4332
|
+
shutdown
|
|
3582
4333
|
};
|
|
3583
4334
|
}
|
|
3584
4335
|
//#endregion
|
|
@@ -3593,4 +4344,4 @@ function createFevex(config) {
|
|
|
3593
4344
|
return createRuntime(createComposition(config));
|
|
3594
4345
|
}
|
|
3595
4346
|
//#endregion
|
|
3596
|
-
export { ChannelError, FEVEX_JSON_SCHEMA_PROFILE_VERSION, FevexConfigurationError, FevexRunError, InMemoryMemoryStore, InMemoryRunStore, IntegrationError, PROVIDER_REASONING_UNSUPPORTED, PROVIDER_SCHEMA_UNSUPPORTED, RunPausedError, SCHEMA_NOT_TRANSPORTABLE, SandboxError, compileFevexJsonSchema, createFevex, createLocalSandbox, createMemoryContextProvider, defineAgent, defineConnection, defineContextProvider, definePolicy, defineSkill, defineTeam, defineTool, defineWorkflow, deliverChannelRun, handleChannelInput, isDurableRunStore, sandboxResultToJson, toToolSpec, validateFevexJsonSchemaProfile, validateListEventsOptions };
|
|
4347
|
+
export { ArtifactError, CatalogedArtifactStore, ChannelError, FEVEX_JSON_SCHEMA_PROFILE_VERSION, FevexConfigurationError, FevexRunError, FileSystemArtifactStore, InMemoryArtifactCatalog, InMemoryArtifactStore, InMemoryMemoryStore, InMemoryRunStore, IntegrationError, PROVIDER_REASONING_UNSUPPORTED, PROVIDER_SCHEMA_UNSUPPORTED, RunPausedError, S3ArtifactStore, SCHEMA_NOT_TRANSPORTABLE, SandboxError, artifactTenantId, assertArtifactId, compileFevexJsonSchema, createFevex, createLocalSandbox, createMemoryContextProvider, defineAgent, defineConnection, defineContextProvider, definePolicy, defineSkill, defineTeam, defineTool, defineWorkflow, deliverChannelRun, handleChannelInput, isDurableRunStore, isRetainableRunStore, sandboxResultToJson, toToolSpec, validateFevexJsonSchemaProfile, validateListEventsOptions, validatePurgeRunsOptions };
|