@fevex/core 0.1.0-alpha.5 → 0.1.0-alpha.7
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 +100 -9
- package/dist/agents/index.d.mts +1 -1
- package/dist/artifacts/index.d.mts +1 -1
- package/dist/channels/index.d.mts +1 -1
- package/dist/evals/index.d.mts +4 -4
- package/dist/{fevex-B5NxFKcV.d.mts → fevex-CVMLnZsk.d.mts} +41 -12
- package/dist/http/index.d.mts +4 -4
- package/dist/http/index.mjs +2 -0
- package/dist/{index-Dsulk-n6.d.mts → index-1cHr7KB_.d.mts} +3 -3
- package/dist/{index-CKQCNvJz.d.mts → index-5p3q2yIp.d.mts} +2 -2
- package/dist/index-B0AdFbgr.d.mts +116 -0
- package/dist/{index--_LdSEuF.d.mts → index-BTD1c4Ys.d.mts} +3 -3
- package/dist/{index-BPwkgdo3.d.mts → index-BgKFd9yw.d.mts} +1 -1
- package/dist/{index-Cb3AB98n.d.mts → index-CbACw6xZ.d.mts} +2 -2
- package/dist/index-CpLDt6Rw.d.mts +118 -0
- package/dist/{index-Br-zPxZH.d.mts → index-CseuTR-F.d.mts} +2 -2
- package/dist/{index-CGmHXV31.d.mts → index-CzAi2Y_Z.d.mts} +13 -6
- package/dist/{index-BFnWUu7j.d.mts → index-Df84VWmN.d.mts} +1 -1
- package/dist/index.d.mts +13 -13
- package/dist/index.mjs +569 -78
- package/dist/knowledge/index.d.mts +2 -2
- package/dist/knowledge/index.mjs +236 -36
- 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/runtime/index.d.mts +2 -2
- package/dist/sandbox/index.d.mts +1 -1
- package/dist/teams/index.d.mts +1 -1
- package/dist/testing/index.d.mts +6 -6
- package/dist/tools/index.d.mts +2 -2
- package/dist/{types-BDVUoAU6.d.mts → types-B8laW1hM.d.mts} +2 -0
- package/dist/workflows/index.d.mts +1 -1
- package/package.json +1 -1
- package/dist/index-D6lnI92z.d.mts +0 -80
- package/dist/index-aayvtf0J.d.mts +0 -70
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { defineAgent } from "./agents/index.mjs";
|
|
|
2
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";
|
|
3
3
|
import { n as RunPausedError, t as FevexRunError } from "./run-error-Cr17wPGH.mjs";
|
|
4
4
|
import { ChannelError, deliverChannelRun, handleChannelInput } from "./channels/index.mjs";
|
|
5
|
-
import { InMemoryMemoryStore, createMemoryContextProvider, defineContextProvider, defineSkill } from "./knowledge/index.mjs";
|
|
5
|
+
import { InMemoryMemoryStore, createAgentsMdContextProvider, createMemoryContextProvider, defineContextProvider, defineSkill, isProgressiveSkillProvider, loadSkillFile, loadSkillFiles } from "./knowledge/index.mjs";
|
|
6
6
|
import { n as teamAsWorkflow, t as defineTeam } from "./teams-Ctz676G4.mjs";
|
|
7
7
|
import { InMemoryRunStore, isDurableRunStore, isRetainableRunStore, validateListEventsOptions, validatePurgeRunsOptions } from "./runtime/index.mjs";
|
|
8
8
|
import { FEVEX_JSON_SCHEMA_PROFILE_VERSION, IntegrationError, compileFevexJsonSchema, defineConnection, defineTool, toToolSpec, validateFevexJsonSchemaProfile } from "./tools/index.mjs";
|
|
@@ -332,6 +332,18 @@ function createComposition(config) {
|
|
|
332
332
|
"error.message"
|
|
333
333
|
].includes(kind)) && (content.redact === void 0 || typeof content.redact === "function"), "INVALID_CONFIG", "Fevex config \"observability.content\" is invalid");
|
|
334
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
|
+
}
|
|
335
347
|
}
|
|
336
348
|
const models = /* @__PURE__ */ new Map();
|
|
337
349
|
const agents = /* @__PURE__ */ new Map();
|
|
@@ -624,7 +636,8 @@ function createComposition(config) {
|
|
|
624
636
|
...config.observability.content ? { content: {
|
|
625
637
|
include: [...config.observability.content.include],
|
|
626
638
|
...config.observability.content.redact ? { redact: config.observability.content.redact } : {}
|
|
627
|
-
} } : {}
|
|
639
|
+
} } : {},
|
|
640
|
+
...config.observability.exportQueue ? { exportQueue: { ...config.observability.exportQueue } } : {}
|
|
628
641
|
}
|
|
629
642
|
};
|
|
630
643
|
}
|
|
@@ -636,6 +649,8 @@ const LEASE_MS = 3e4;
|
|
|
636
649
|
const LEASE_RENEW_MS = 1e4;
|
|
637
650
|
/** Reserved name of the internal elicitation tool exposed to the model. */
|
|
638
651
|
const ELICIT_TOOL_NAME = "fevex__elicit";
|
|
652
|
+
/** Reserved name of the internal progressive skill loader exposed to the model. */
|
|
653
|
+
const LOAD_SKILL_TOOL_NAME = "fevex__load_skill";
|
|
639
654
|
//#endregion
|
|
640
655
|
//#region src/internal/run-helpers.ts
|
|
641
656
|
function redact(message, secrets) {
|
|
@@ -890,8 +905,9 @@ function addWorkflowBudget(first, second) {
|
|
|
890
905
|
*/
|
|
891
906
|
function createAgentEngine(composition, core) {
|
|
892
907
|
const { models, agents, tools, contextProviders, memoryStore, runStore, credentialStore, sandbox, artifacts, policies } = composition;
|
|
893
|
-
const { activeRuns, activeSessions, runtimeOwner, admitStart, markInternalRun, resolveModel, resolveCheckpointModel, notifyObserver, createEvent, emitEvent, releaseExecution, commit, startLease, cancelExecution, authorize } = core;
|
|
894
|
-
const prepareExecutionBody = async (name, request, internal = false) => {
|
|
908
|
+
const { activeRuns, activeSessions, runtimeOwner, isClosed, admitStart, markInternalRun, resolveModel, resolveCheckpointModel, notifyObserver, createEvent, emitEvent, releaseExecution, commit, startLease, cancelExecution, authorize } = core;
|
|
909
|
+
const prepareExecutionBody = async (name, request, internal = false, correlation = {}) => {
|
|
910
|
+
if (!internal && isClosed()) throw new FevexRunError("RUNTIME_CLOSED", "Runtime is not accepting new runs");
|
|
895
911
|
const agent = agents.get(name);
|
|
896
912
|
if (!agent) throw new FevexRunError("AGENT_NOT_FOUND", `Agent "${name}" is not registered`);
|
|
897
913
|
const { modelName, model } = resolveModel(agent, request.model);
|
|
@@ -920,6 +936,8 @@ function createAgentEngine(composition, core) {
|
|
|
920
936
|
agentName: name,
|
|
921
937
|
modelName,
|
|
922
938
|
...model.metadata === void 0 ? {} : { modelMetadata: structuredClone(model.metadata) },
|
|
939
|
+
...correlation.parentRunId === void 0 ? {} : { parentRunId: correlation.parentRunId },
|
|
940
|
+
...correlation.rootRunId === void 0 ? {} : { rootRunId: correlation.rootRunId },
|
|
923
941
|
status: "running",
|
|
924
942
|
revision: 0,
|
|
925
943
|
createdAt: now,
|
|
@@ -1010,7 +1028,10 @@ function createAgentEngine(composition, core) {
|
|
|
1010
1028
|
const prepareExecution = async (name, request, options = {}) => {
|
|
1011
1029
|
const release = options.admit === false ? void 0 : await admitStart("agent", name, request);
|
|
1012
1030
|
try {
|
|
1013
|
-
return await prepareExecutionBody(name, request, options.admit === false
|
|
1031
|
+
return await prepareExecutionBody(name, request, options.admit === false, {
|
|
1032
|
+
...options.parentRunId === void 0 ? {} : { parentRunId: options.parentRunId },
|
|
1033
|
+
...options.rootRunId === void 0 ? {} : { rootRunId: options.rootRunId }
|
|
1034
|
+
});
|
|
1014
1035
|
} finally {
|
|
1015
1036
|
release?.();
|
|
1016
1037
|
}
|
|
@@ -1160,26 +1181,50 @@ function createAgentEngine(composition, core) {
|
|
|
1160
1181
|
description: remote.description
|
|
1161
1182
|
} : tool, inputSchema);
|
|
1162
1183
|
}));
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1165
|
-
|
|
1184
|
+
const progressiveSkills = (agent.skills ?? []).map((skillName) => contextProviders.get(skillName)).filter((provider) => provider !== void 0 && isProgressiveSkillProvider(provider));
|
|
1185
|
+
const skillTool = progressiveSkills.length === 0 ? void 0 : {
|
|
1186
|
+
name: LOAD_SKILL_TOOL_NAME,
|
|
1187
|
+
description: `Load the full instructions for one of this agent's skills. Available skills: ${progressiveSkills.map((skill) => skill.name).join(", ")}. Pass a resource path from the loaded skill to read one resource.`,
|
|
1166
1188
|
inputSchema: {
|
|
1167
1189
|
type: "object",
|
|
1168
1190
|
properties: {
|
|
1169
|
-
|
|
1191
|
+
name: {
|
|
1170
1192
|
type: "string",
|
|
1171
|
-
|
|
1172
|
-
},
|
|
1173
|
-
responseSchema: {
|
|
1174
|
-
type: "object",
|
|
1175
|
-
description: "JSON Schema for the expected response. Each property should include a short title for the form label and may include a description for field help."
|
|
1193
|
+
enum: progressiveSkills.map((skill) => skill.name)
|
|
1176
1194
|
},
|
|
1177
|
-
|
|
1195
|
+
resource: {
|
|
1196
|
+
type: "string",
|
|
1197
|
+
description: "Optional resource path returned by a previous load."
|
|
1198
|
+
}
|
|
1178
1199
|
},
|
|
1179
|
-
required: ["
|
|
1200
|
+
required: ["name"],
|
|
1180
1201
|
additionalProperties: false
|
|
1181
1202
|
}
|
|
1182
|
-
}
|
|
1203
|
+
};
|
|
1204
|
+
const allTools = [
|
|
1205
|
+
...agentTools,
|
|
1206
|
+
...skillTool === void 0 ? [] : [skillTool],
|
|
1207
|
+
...currentElicitationMode === "pause" ? [{
|
|
1208
|
+
name: ELICIT_TOOL_NAME,
|
|
1209
|
+
description: "Request external information required to continue this run. Use prompt for explanatory text shown outside the form. Put short field labels in responseSchema property titles and optional field help in property descriptions.",
|
|
1210
|
+
inputSchema: {
|
|
1211
|
+
type: "object",
|
|
1212
|
+
properties: {
|
|
1213
|
+
prompt: {
|
|
1214
|
+
type: "string",
|
|
1215
|
+
description: "Explanatory text for the user; do not duplicate field labels here."
|
|
1216
|
+
},
|
|
1217
|
+
responseSchema: {
|
|
1218
|
+
type: "object",
|
|
1219
|
+
description: "JSON Schema for the expected response. Each property should include a short title for the form label and may include a description for field help."
|
|
1220
|
+
},
|
|
1221
|
+
expiresAt: { type: "string" }
|
|
1222
|
+
},
|
|
1223
|
+
required: ["prompt", "responseSchema"],
|
|
1224
|
+
additionalProperties: false
|
|
1225
|
+
}
|
|
1226
|
+
}] : []
|
|
1227
|
+
];
|
|
1183
1228
|
const effectiveLimits = state.checkpoint?.limits ?? combineLimits(agent.limits, request.limits);
|
|
1184
1229
|
const maxSteps = effectiveLimits?.maxSteps ?? 8;
|
|
1185
1230
|
const maxToolCalls = effectiveLimits?.maxToolCalls ?? 16;
|
|
@@ -1268,6 +1313,57 @@ function createAgentEngine(composition, core) {
|
|
|
1268
1313
|
assertContinuationBudget(name, "maxInputTokens", "inputTokens", effectiveLimits?.maxInputTokens, usage);
|
|
1269
1314
|
assertContinuationBudget(name, "maxOutputTokens", "outputTokens", effectiveLimits?.maxOutputTokens, usage);
|
|
1270
1315
|
if (result.toolCalls.length > maxToolCalls - toolCallCount) throw new Error(`Agent "${name}" exceeded maxToolCalls limit of ${maxToolCalls}`);
|
|
1316
|
+
const loadSkillCall = progressiveSkills.length === 0 ? void 0 : result.toolCalls.find((call) => call.name === LOAD_SKILL_TOOL_NAME);
|
|
1317
|
+
if (loadSkillCall) {
|
|
1318
|
+
if (result.toolCalls.length !== 1) throw new Error(`${LOAD_SKILL_TOOL_NAME} must be the only tool call in a model turn`);
|
|
1319
|
+
if (!loadSkillCall.id?.trim()) throw new Error("Tool call id cannot be empty");
|
|
1320
|
+
if (seenToolCallIds.has(loadSkillCall.id)) throw new Error(`Tool call id "${loadSkillCall.id}" is duplicated in run "${state.run.id}"`);
|
|
1321
|
+
if (toolCallCount + 1 > maxToolCalls) throw new Error(`Agent "${name}" exceeded maxToolCalls limit of ${maxToolCalls}`);
|
|
1322
|
+
const rawInput = toJsonValue(loadSkillCall.input, `Input for tool "${LOAD_SKILL_TOOL_NAME}" must be JSON-serializable`);
|
|
1323
|
+
const skillInput = readSkillInput(rawInput);
|
|
1324
|
+
const skill = progressiveSkills.find((item) => item.name === skillInput.name);
|
|
1325
|
+
if (!skill) throw new Error(`Skill "${skillInput.name}" is not available to agent "${name}"`);
|
|
1326
|
+
seenToolCallIds.add(loadSkillCall.id);
|
|
1327
|
+
messages.push({
|
|
1328
|
+
role: "assistant",
|
|
1329
|
+
content: result.output === void 0 ? "" : serializeValue(result.output, `Model output for agent "${name}" must be serializable`),
|
|
1330
|
+
toolCalls: [{
|
|
1331
|
+
...loadSkillCall,
|
|
1332
|
+
input: rawInput
|
|
1333
|
+
}]
|
|
1334
|
+
});
|
|
1335
|
+
yield await emit("tool.started", {
|
|
1336
|
+
step,
|
|
1337
|
+
toolCallId: loadSkillCall.id,
|
|
1338
|
+
toolName: LOAD_SKILL_TOOL_NAME
|
|
1339
|
+
});
|
|
1340
|
+
let blocks;
|
|
1341
|
+
try {
|
|
1342
|
+
blocks = await abortable(() => skill.load(knowledgeContext(state, inputContent), skillInput.resource), request.signal);
|
|
1343
|
+
} catch (error) {
|
|
1344
|
+
yield await emit("tool.failed", {
|
|
1345
|
+
step,
|
|
1346
|
+
toolCallId: loadSkillCall.id,
|
|
1347
|
+
toolName: LOAD_SKILL_TOOL_NAME,
|
|
1348
|
+
error: toErrorMessage(error)
|
|
1349
|
+
});
|
|
1350
|
+
throw new Error(`Skill provider "${skill.name}" failed: ${toErrorMessage(error)}`, { cause: error });
|
|
1351
|
+
}
|
|
1352
|
+
messages.push({
|
|
1353
|
+
role: "tool",
|
|
1354
|
+
name: LOAD_SKILL_TOOL_NAME,
|
|
1355
|
+
toolCallId: loadSkillCall.id,
|
|
1356
|
+
content: blocks.filter((block) => block.content.trim()).map((block) => `[Skill: ${skill.name}/${block.id}]\n${block.content}`).join("\n\n")
|
|
1357
|
+
});
|
|
1358
|
+
yield await emit("tool.completed", {
|
|
1359
|
+
step,
|
|
1360
|
+
toolCallId: loadSkillCall.id,
|
|
1361
|
+
toolName: LOAD_SKILL_TOOL_NAME
|
|
1362
|
+
});
|
|
1363
|
+
toolCallCount += 1;
|
|
1364
|
+
step += 1;
|
|
1365
|
+
continue;
|
|
1366
|
+
}
|
|
1271
1367
|
const elicitationCall = result.toolCalls.find(isElicitationToolCall);
|
|
1272
1368
|
if (elicitationCall) {
|
|
1273
1369
|
if (!isDurableRunStore(runStore)) throw new FevexRunError("DURABLE_STORE_REQUIRED", "Elicitation requires a DurableRunStore", state.run.id);
|
|
@@ -2017,6 +2113,18 @@ function createAgentEngine(composition, core) {
|
|
|
2017
2113
|
cancelStoredAgent
|
|
2018
2114
|
};
|
|
2019
2115
|
}
|
|
2116
|
+
function readSkillInput(input) {
|
|
2117
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) throw new TypeError(`${LOAD_SKILL_TOOL_NAME} input must be an object`);
|
|
2118
|
+
const record = input;
|
|
2119
|
+
const name = record.name;
|
|
2120
|
+
const resource = record.resource;
|
|
2121
|
+
if (typeof name !== "string" || !name.trim()) throw new TypeError(`${LOAD_SKILL_TOOL_NAME} name must be a non-empty string`);
|
|
2122
|
+
if (resource !== void 0 && (typeof resource !== "string" || !resource.trim())) throw new TypeError(`${LOAD_SKILL_TOOL_NAME} resource must be a non-empty string`);
|
|
2123
|
+
return {
|
|
2124
|
+
name: name.trim(),
|
|
2125
|
+
...resource === void 0 ? {} : { resource: resource.trim() }
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2020
2128
|
function scopedArtifacts(store, options) {
|
|
2021
2129
|
const scopedContext = (value) => {
|
|
2022
2130
|
const contextTenant = options.request.context?.attributes?.tenantId;
|
|
@@ -2053,16 +2161,31 @@ function scopedArtifacts(store, options) {
|
|
|
2053
2161
|
const TERMINAL_TYPES = /* @__PURE__ */ new Set([
|
|
2054
2162
|
"run.completed",
|
|
2055
2163
|
"run.failed",
|
|
2056
|
-
"run.cancelled"
|
|
2164
|
+
"run.cancelled",
|
|
2165
|
+
"workflow.run.completed",
|
|
2166
|
+
"workflow.run.failed",
|
|
2167
|
+
"workflow.run.cancelled",
|
|
2168
|
+
"team.run.completed",
|
|
2169
|
+
"team.run.failed",
|
|
2170
|
+
"team.run.cancelled"
|
|
2057
2171
|
]);
|
|
2058
2172
|
const SENSITIVE_KEY = /api[-_]?key|authorization|cookie|credential|password|secret|token/i;
|
|
2173
|
+
function isTraceTerminalEvent(event) {
|
|
2174
|
+
return TERMINAL_TYPES.has(event.type);
|
|
2175
|
+
}
|
|
2059
2176
|
function durationMs(startedAt, endedAt) {
|
|
2060
2177
|
return Math.max(0, Date.parse(endedAt) - Date.parse(startedAt));
|
|
2061
2178
|
}
|
|
2062
2179
|
function traceStatus(event) {
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2180
|
+
switch (event.type) {
|
|
2181
|
+
case "run.completed":
|
|
2182
|
+
case "workflow.run.completed":
|
|
2183
|
+
case "team.run.completed": return "completed";
|
|
2184
|
+
case "run.cancelled":
|
|
2185
|
+
case "workflow.run.cancelled":
|
|
2186
|
+
case "team.run.cancelled": return "cancelled";
|
|
2187
|
+
default: return "failed";
|
|
2188
|
+
}
|
|
2066
2189
|
}
|
|
2067
2190
|
function redactKeys(value) {
|
|
2068
2191
|
if (Array.isArray(value)) return value.map(redactKeys);
|
|
@@ -2169,20 +2292,74 @@ function annotation(event) {
|
|
|
2169
2292
|
toolName: event.payload.toolName
|
|
2170
2293
|
}
|
|
2171
2294
|
};
|
|
2295
|
+
case "workflow.run.paused":
|
|
2296
|
+
case "team.run.paused": {
|
|
2297
|
+
const stepId = event.type === "workflow.run.paused" ? event.payload.stepId : event.payload.delegationId;
|
|
2298
|
+
return {
|
|
2299
|
+
name: event.type,
|
|
2300
|
+
timestamp: event.timestamp,
|
|
2301
|
+
attributes: {
|
|
2302
|
+
stepId,
|
|
2303
|
+
reason: event.payload.reason,
|
|
2304
|
+
...event.payload.childRunId === void 0 ? {} : { childRunId: event.payload.childRunId }
|
|
2305
|
+
}
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
case "workflow.run.resumed":
|
|
2309
|
+
case "team.run.resumed": return {
|
|
2310
|
+
name: event.type,
|
|
2311
|
+
timestamp: event.timestamp
|
|
2312
|
+
};
|
|
2313
|
+
case "team.handoff.created": return {
|
|
2314
|
+
name: event.type,
|
|
2315
|
+
timestamp: event.timestamp,
|
|
2316
|
+
attributes: {
|
|
2317
|
+
delegationId: event.payload.delegationId,
|
|
2318
|
+
from: event.payload.from,
|
|
2319
|
+
to: event.payload.to,
|
|
2320
|
+
reason: event.payload.reason
|
|
2321
|
+
}
|
|
2322
|
+
};
|
|
2172
2323
|
default: return;
|
|
2173
2324
|
}
|
|
2174
2325
|
}
|
|
2175
|
-
function
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
if (
|
|
2326
|
+
function terminalContent(options, terminal) {
|
|
2327
|
+
if (terminal.type === "run.completed" || terminal.type === "workflow.run.completed" || terminal.type === "team.run.completed") {
|
|
2328
|
+
const output = content(options, "run.output", terminal.payload.output);
|
|
2329
|
+
return output ? [output] : [];
|
|
2330
|
+
}
|
|
2331
|
+
if (terminal.type === "run.failed" || terminal.type === "workflow.run.failed" || terminal.type === "team.run.failed") {
|
|
2332
|
+
const error = content(options, "error.message", terminal.payload.error);
|
|
2333
|
+
return error ? [error] : [];
|
|
2334
|
+
}
|
|
2335
|
+
return [];
|
|
2336
|
+
}
|
|
2337
|
+
function sortedEvents(rawEvents) {
|
|
2338
|
+
return [...rawEvents].sort((left, right) => left.sequence - right.sequence);
|
|
2339
|
+
}
|
|
2340
|
+
function terminalEvent(runId, events) {
|
|
2341
|
+
const terminal = [...events].reverse().find((event) => isTraceTerminalEvent(event));
|
|
2342
|
+
if (!terminal) throw new TypeError(`Run "${runId}" has no terminal event`);
|
|
2343
|
+
return terminal;
|
|
2344
|
+
}
|
|
2345
|
+
function startEvent(events) {
|
|
2346
|
+
const started = events.find((event) => event.type === "run.started" || event.type === "workflow.run.started" || event.type === "team.run.started") ?? events[0];
|
|
2347
|
+
if (!started) throw new TypeError("Run has no events");
|
|
2348
|
+
return started;
|
|
2349
|
+
}
|
|
2350
|
+
function traceKind(run) {
|
|
2351
|
+
if (isTeamRun(run)) return "team";
|
|
2352
|
+
return run.kind === "workflow" ? "workflow" : "agent";
|
|
2353
|
+
}
|
|
2354
|
+
function buildAgentTrace(run, events, modelRegistryName, model, options) {
|
|
2355
|
+
const terminal = terminalEvent(run.id, events);
|
|
2356
|
+
const started = startEvent(events);
|
|
2181
2357
|
const status = traceStatus(terminal);
|
|
2182
2358
|
const annotations = [];
|
|
2183
2359
|
const spans = [];
|
|
2184
2360
|
const openModels = /* @__PURE__ */ new Map();
|
|
2185
2361
|
const openTools = /* @__PURE__ */ new Map();
|
|
2362
|
+
const modelSpanByStep = /* @__PURE__ */ new Map();
|
|
2186
2363
|
const previousUsage = {};
|
|
2187
2364
|
const closeModel = (step, endedAt, spanStatus) => {
|
|
2188
2365
|
const open = openModels.get(step);
|
|
@@ -2213,6 +2390,7 @@ function buildRunTrace(run, rawEvents, modelRegistryName, model, options) {
|
|
|
2213
2390
|
const spanCost = costFor(options, modelRegistryName, model, usage);
|
|
2214
2391
|
if (spanCost) span.cost = spanCost;
|
|
2215
2392
|
spans.push(span);
|
|
2393
|
+
modelSpanByStep.set(step, span.id);
|
|
2216
2394
|
};
|
|
2217
2395
|
const findOpenTool = (toolCallId) => {
|
|
2218
2396
|
const entries = [...openTools.entries()];
|
|
@@ -2232,7 +2410,7 @@ function buildRunTrace(run, rawEvents, modelRegistryName, model, options) {
|
|
|
2232
2410
|
const errorContent = error ? content(options, "error.message", error) : void 0;
|
|
2233
2411
|
spans.push({
|
|
2234
2412
|
id: event.id,
|
|
2235
|
-
parentId: run.id,
|
|
2413
|
+
parentId: modelSpanByStep.get(event.payload.step) ?? run.id,
|
|
2236
2414
|
kind: "tool",
|
|
2237
2415
|
name: event.payload.toolName,
|
|
2238
2416
|
startedAt: event.timestamp,
|
|
@@ -2297,19 +2475,15 @@ function buildRunTrace(run, rawEvents, modelRegistryName, model, options) {
|
|
|
2297
2475
|
for (const open of [...openTools.values()]) if (open.event.type === "tool.started") closeTool(open.event.payload.toolCallId, terminal.timestamp, status);
|
|
2298
2476
|
const finalUsage = run.usage ?? previousUsage;
|
|
2299
2477
|
const totalCost = costFor(options, modelRegistryName, model, finalUsage);
|
|
2300
|
-
const traceContent =
|
|
2301
|
-
if (terminal.type === "run.completed") {
|
|
2302
|
-
const output = content(options, "run.output", terminal.payload.output);
|
|
2303
|
-
if (output) traceContent.push(output);
|
|
2304
|
-
} else if (terminal.type === "run.failed") {
|
|
2305
|
-
const error = content(options, "error.message", terminal.payload.error);
|
|
2306
|
-
if (error) traceContent.push(error);
|
|
2307
|
-
}
|
|
2478
|
+
const traceContent = terminalContent(options, terminal);
|
|
2308
2479
|
return {
|
|
2309
2480
|
schemaVersion: "1",
|
|
2481
|
+
kind: "agent",
|
|
2310
2482
|
traceId: run.id,
|
|
2311
2483
|
runId: run.id,
|
|
2312
2484
|
sessionId: run.sessionId,
|
|
2485
|
+
...run.parentRunId === void 0 ? {} : { parentRunId: run.parentRunId },
|
|
2486
|
+
rootRunId: run.rootRunId ?? run.parentRunId ?? run.id,
|
|
2313
2487
|
agentName: run.agentName,
|
|
2314
2488
|
modelRegistryName,
|
|
2315
2489
|
...model ? { model: { ...model } } : {},
|
|
@@ -2329,6 +2503,249 @@ function buildRunTrace(run, rawEvents, modelRegistryName, model, options) {
|
|
|
2329
2503
|
...traceContent.length ? { content: traceContent } : {}
|
|
2330
2504
|
};
|
|
2331
2505
|
}
|
|
2506
|
+
function buildCoordinatorTrace(run, events, options) {
|
|
2507
|
+
const terminal = terminalEvent(run.id, events);
|
|
2508
|
+
const started = startEvent(events);
|
|
2509
|
+
const status = traceStatus(terminal);
|
|
2510
|
+
const annotations = [];
|
|
2511
|
+
const spans = [];
|
|
2512
|
+
const openSteps = /* @__PURE__ */ new Map();
|
|
2513
|
+
const stepKey = (scope, stepId) => `${scope}:${stepId}`;
|
|
2514
|
+
const modelCallCount = events.filter(({ type }) => type === "model.completed").length;
|
|
2515
|
+
const toolCallCount = events.filter(({ type }) => type === "tool.completed" || type === "tool.failed").length;
|
|
2516
|
+
const openStep = (key, event, name, attributes) => {
|
|
2517
|
+
openSteps.set(key, {
|
|
2518
|
+
event,
|
|
2519
|
+
name,
|
|
2520
|
+
attributes
|
|
2521
|
+
});
|
|
2522
|
+
};
|
|
2523
|
+
const closeStep = (key, endedAt, spanStatus, error) => {
|
|
2524
|
+
const open = openSteps.get(key);
|
|
2525
|
+
if (!open) return;
|
|
2526
|
+
openSteps.delete(key);
|
|
2527
|
+
const errorContent = error ? content(options, "error.message", error) : void 0;
|
|
2528
|
+
spans.push({
|
|
2529
|
+
id: open.event.id,
|
|
2530
|
+
parentId: run.id,
|
|
2531
|
+
kind: "step",
|
|
2532
|
+
name: open.name,
|
|
2533
|
+
startedAt: open.event.timestamp,
|
|
2534
|
+
endedAt,
|
|
2535
|
+
durationMs: durationMs(open.event.timestamp, endedAt),
|
|
2536
|
+
status: spanStatus,
|
|
2537
|
+
attributes: open.attributes,
|
|
2538
|
+
annotations: [],
|
|
2539
|
+
...errorContent ? { content: [errorContent] } : {}
|
|
2540
|
+
});
|
|
2541
|
+
};
|
|
2542
|
+
for (const event of events) {
|
|
2543
|
+
const item = annotation(event);
|
|
2544
|
+
if (item) annotations.push(item);
|
|
2545
|
+
switch (event.type) {
|
|
2546
|
+
case "workflow.step.started":
|
|
2547
|
+
openStep(stepKey("workflow", event.payload.stepId), event, event.payload.stepId, {
|
|
2548
|
+
stepId: event.payload.stepId,
|
|
2549
|
+
stepKind: event.payload.kind,
|
|
2550
|
+
...event.payload.agentName === void 0 ? {} : { agentName: event.payload.agentName },
|
|
2551
|
+
...event.payload.childRunId === void 0 ? {} : { childRunId: event.payload.childRunId }
|
|
2552
|
+
});
|
|
2553
|
+
break;
|
|
2554
|
+
case "workflow.step.completed":
|
|
2555
|
+
closeStep(stepKey("workflow", event.payload.stepId), event.timestamp, "completed");
|
|
2556
|
+
break;
|
|
2557
|
+
case "workflow.step.failed":
|
|
2558
|
+
closeStep(stepKey("workflow", event.payload.stepId), event.timestamp, "failed", event.payload.error);
|
|
2559
|
+
break;
|
|
2560
|
+
case "workflow.wait.started":
|
|
2561
|
+
openStep(stepKey("workflow", event.payload.stepId), event, event.payload.stepId, {
|
|
2562
|
+
stepId: event.payload.stepId,
|
|
2563
|
+
stepKind: "wait",
|
|
2564
|
+
waitKind: event.payload.kind,
|
|
2565
|
+
...event.payload.resumeAt === void 0 ? {} : { resumeAt: event.payload.resumeAt },
|
|
2566
|
+
...event.payload.eventName === void 0 ? {} : { eventName: event.payload.eventName }
|
|
2567
|
+
});
|
|
2568
|
+
break;
|
|
2569
|
+
case "workflow.wait.completed":
|
|
2570
|
+
closeStep(stepKey("workflow", event.payload.stepId), event.timestamp, "completed");
|
|
2571
|
+
break;
|
|
2572
|
+
case "team.agent.assigned":
|
|
2573
|
+
openStep(stepKey("team", event.payload.delegationId), event, event.payload.delegationId, {
|
|
2574
|
+
stepId: event.payload.delegationId,
|
|
2575
|
+
stepKind: "agent",
|
|
2576
|
+
agentName: event.payload.agentName,
|
|
2577
|
+
action: event.payload.action,
|
|
2578
|
+
...event.payload.expectedOutput === void 0 ? {} : { expectedOutput: event.payload.expectedOutput },
|
|
2579
|
+
...event.payload.constraints === void 0 ? {} : { constraints: event.payload.constraints },
|
|
2580
|
+
...event.payload.childRunId === void 0 ? {} : { childRunId: event.payload.childRunId }
|
|
2581
|
+
});
|
|
2582
|
+
break;
|
|
2583
|
+
case "team.task.completed":
|
|
2584
|
+
closeStep(stepKey("team", event.payload.delegationId), event.timestamp, "completed");
|
|
2585
|
+
break;
|
|
2586
|
+
case "team.task.failed":
|
|
2587
|
+
closeStep(stepKey("team", event.payload.delegationId), event.timestamp, "failed", event.payload.error);
|
|
2588
|
+
break;
|
|
2589
|
+
case "team.merge.started":
|
|
2590
|
+
openStep(stepKey("team", event.payload.stepId), event, event.payload.stepId, {
|
|
2591
|
+
stepId: event.payload.stepId,
|
|
2592
|
+
stepKind: "merge",
|
|
2593
|
+
width: event.payload.width
|
|
2594
|
+
});
|
|
2595
|
+
break;
|
|
2596
|
+
case "team.merge.completed":
|
|
2597
|
+
closeStep(stepKey("team", event.payload.stepId), event.timestamp, "completed");
|
|
2598
|
+
break;
|
|
2599
|
+
case "team.merge.failed":
|
|
2600
|
+
closeStep(stepKey("team", event.payload.stepId), event.timestamp, "failed", event.payload.error);
|
|
2601
|
+
break;
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
for (const key of [...openSteps.keys()]) closeStep(key, terminal.timestamp, status);
|
|
2605
|
+
const traceContent = terminalContent(options, terminal);
|
|
2606
|
+
return {
|
|
2607
|
+
schemaVersion: "1",
|
|
2608
|
+
kind: traceKind(run),
|
|
2609
|
+
traceId: run.id,
|
|
2610
|
+
runId: run.id,
|
|
2611
|
+
sessionId: run.sessionId,
|
|
2612
|
+
...run.parentRunId === void 0 ? {} : { parentRunId: run.parentRunId },
|
|
2613
|
+
rootRunId: run.rootRunId ?? run.parentRunId ?? run.id,
|
|
2614
|
+
agentName: coordinatorName(run),
|
|
2615
|
+
startedAt: started.timestamp,
|
|
2616
|
+
endedAt: terminal.timestamp,
|
|
2617
|
+
durationMs: durationMs(started.timestamp, terminal.timestamp),
|
|
2618
|
+
status,
|
|
2619
|
+
attributes: {
|
|
2620
|
+
eventCount: events.length,
|
|
2621
|
+
stepCount: spans.filter(({ kind }) => kind === "step").length,
|
|
2622
|
+
modelCallCount,
|
|
2623
|
+
toolCallCount
|
|
2624
|
+
},
|
|
2625
|
+
annotations,
|
|
2626
|
+
spans,
|
|
2627
|
+
...run.usage && Object.keys(run.usage).length ? { usage: { ...run.usage } } : {},
|
|
2628
|
+
...traceContent.length ? { content: traceContent } : {}
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
function buildRunTrace(run, rawEvents, modelRegistryName, model, options) {
|
|
2632
|
+
const events = sortedEvents(rawEvents);
|
|
2633
|
+
if (isCoordinatorRun(run)) return buildCoordinatorTrace(run, events, options);
|
|
2634
|
+
if (!modelRegistryName) throw new TypeError(`Run "${run.id}" has no model registry name`);
|
|
2635
|
+
return buildAgentTrace(run, events, modelRegistryName, model, options);
|
|
2636
|
+
}
|
|
2637
|
+
//#endregion
|
|
2638
|
+
//#region src/internal/trace-export.ts
|
|
2639
|
+
const DEFAULT_CONCURRENCY = 4;
|
|
2640
|
+
const DEFAULT_MAX_QUEUED = 100;
|
|
2641
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
2642
|
+
const DEFAULT_MAX_FAILURES = 100;
|
|
2643
|
+
const DEDUPE_WINDOW = 1e4;
|
|
2644
|
+
function withTimeout(promise, timeoutMs, message) {
|
|
2645
|
+
return new Promise((resolve, reject) => {
|
|
2646
|
+
const timer = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
2647
|
+
promise.then((value) => {
|
|
2648
|
+
clearTimeout(timer);
|
|
2649
|
+
resolve(value);
|
|
2650
|
+
}, (error) => {
|
|
2651
|
+
clearTimeout(timer);
|
|
2652
|
+
reject(error);
|
|
2653
|
+
});
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
function withDefault(value, fallback) {
|
|
2657
|
+
return value ?? fallback;
|
|
2658
|
+
}
|
|
2659
|
+
/**
|
|
2660
|
+
* Bounded, deduplicated trace export queue. Jobs include building the trace
|
|
2661
|
+
* from the store, so a slow store or exporter cannot grow memory without limit.
|
|
2662
|
+
*/
|
|
2663
|
+
function createTraceExportQueue(options = {}) {
|
|
2664
|
+
const concurrency = withDefault(options.concurrency, DEFAULT_CONCURRENCY);
|
|
2665
|
+
const maxQueued = withDefault(options.maxQueued, DEFAULT_MAX_QUEUED);
|
|
2666
|
+
const timeoutMs = withDefault(options.timeoutMs, DEFAULT_TIMEOUT_MS);
|
|
2667
|
+
const maxFailures = withDefault(options.maxFailures, DEFAULT_MAX_FAILURES);
|
|
2668
|
+
const queue = [];
|
|
2669
|
+
const pending = /* @__PURE__ */ new Set();
|
|
2670
|
+
const exportedRuns = /* @__PURE__ */ new Set();
|
|
2671
|
+
const failures = [];
|
|
2672
|
+
let active = 0;
|
|
2673
|
+
let exported = 0;
|
|
2674
|
+
let failed = 0;
|
|
2675
|
+
let dropped = 0;
|
|
2676
|
+
let unreported = 0;
|
|
2677
|
+
const rememberRun = (runId) => {
|
|
2678
|
+
if (exportedRuns.has(runId)) return false;
|
|
2679
|
+
if (exportedRuns.size >= DEDUPE_WINDOW) {
|
|
2680
|
+
const oldest = exportedRuns.values().next().value;
|
|
2681
|
+
if (oldest !== void 0) exportedRuns.delete(oldest);
|
|
2682
|
+
}
|
|
2683
|
+
exportedRuns.add(runId);
|
|
2684
|
+
return true;
|
|
2685
|
+
};
|
|
2686
|
+
const recordFailure = (error) => {
|
|
2687
|
+
failed += 1;
|
|
2688
|
+
unreported += 1;
|
|
2689
|
+
failures.push(error);
|
|
2690
|
+
if (failures.length > maxFailures) failures.shift();
|
|
2691
|
+
};
|
|
2692
|
+
const startNext = () => {
|
|
2693
|
+
while (active < concurrency && queue.length) {
|
|
2694
|
+
const task = queue.shift();
|
|
2695
|
+
active += 1;
|
|
2696
|
+
let job;
|
|
2697
|
+
job = Promise.resolve().then(task).then(() => {
|
|
2698
|
+
exported += 1;
|
|
2699
|
+
}, (error) => {
|
|
2700
|
+
recordFailure(error);
|
|
2701
|
+
}).finally(() => {
|
|
2702
|
+
active -= 1;
|
|
2703
|
+
pending.delete(job);
|
|
2704
|
+
startNext();
|
|
2705
|
+
});
|
|
2706
|
+
pending.add(job);
|
|
2707
|
+
}
|
|
2708
|
+
};
|
|
2709
|
+
return {
|
|
2710
|
+
timeoutMs,
|
|
2711
|
+
enqueue(runId, task) {
|
|
2712
|
+
if (!rememberRun(runId)) return;
|
|
2713
|
+
if (queue.length >= maxQueued) {
|
|
2714
|
+
dropped += 1;
|
|
2715
|
+
recordFailure(/* @__PURE__ */ new Error(`Observability export queue is full; trace for run "${runId}" was dropped`));
|
|
2716
|
+
return;
|
|
2717
|
+
}
|
|
2718
|
+
queue.push(task);
|
|
2719
|
+
startNext();
|
|
2720
|
+
},
|
|
2721
|
+
async flush(flushOptions) {
|
|
2722
|
+
const timeout = flushOptions?.timeoutMs;
|
|
2723
|
+
if (timeout !== void 0 && (typeof timeout !== "number" || !Number.isSafeInteger(timeout) || timeout <= 0)) throw new Error("Observability flush timeoutMs must be a positive safe integer");
|
|
2724
|
+
const drain = async () => {
|
|
2725
|
+
while (pending.size) await Promise.all([...pending]);
|
|
2726
|
+
};
|
|
2727
|
+
if (timeout === void 0) await drain();
|
|
2728
|
+
else await withTimeout(drain(), timeout, `Observability flush timed out after ${timeout}ms`);
|
|
2729
|
+
if (unreported === 0) return;
|
|
2730
|
+
const errors = [...failures];
|
|
2731
|
+
const omitted = unreported - failures.length;
|
|
2732
|
+
if (omitted > 0) errors.push(/* @__PURE__ */ new Error(`${omitted} additional export failures were not retained`));
|
|
2733
|
+
failures.length = 0;
|
|
2734
|
+
unreported = 0;
|
|
2735
|
+
throw new AggregateError(errors, `Observability export failed (${failed} failure(s), ${dropped} dropped trace(s))`);
|
|
2736
|
+
},
|
|
2737
|
+
stats() {
|
|
2738
|
+
return {
|
|
2739
|
+
exported,
|
|
2740
|
+
failed,
|
|
2741
|
+
dropped,
|
|
2742
|
+
queued: queue.length,
|
|
2743
|
+
active,
|
|
2744
|
+
retainedFailures: failures.length
|
|
2745
|
+
};
|
|
2746
|
+
}
|
|
2747
|
+
};
|
|
2748
|
+
}
|
|
2332
2749
|
//#endregion
|
|
2333
2750
|
//#region src/internal/run-core.ts
|
|
2334
2751
|
/**
|
|
@@ -2347,11 +2764,25 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
|
|
|
2347
2764
|
const activeSessions = /* @__PURE__ */ new Set();
|
|
2348
2765
|
const internalRuns = /* @__PURE__ */ new Set();
|
|
2349
2766
|
const pendingTenantStarts = /* @__PURE__ */ new Map();
|
|
2767
|
+
const idleWaiters = /* @__PURE__ */ new Set();
|
|
2350
2768
|
let pendingStarts = 0;
|
|
2351
|
-
|
|
2352
|
-
const
|
|
2353
|
-
const exportedRuns = /* @__PURE__ */ new Set();
|
|
2769
|
+
let closed = false;
|
|
2770
|
+
const traceExports = createTraceExportQueue(observability?.exportQueue);
|
|
2354
2771
|
const runtimeOwner = crypto.randomUUID();
|
|
2772
|
+
const isIdle = () => activeRuns.size === 0 && activeWorkflowRuns.size === 0;
|
|
2773
|
+
const notifyIdle = () => {
|
|
2774
|
+
if (!isIdle()) return;
|
|
2775
|
+
for (const resolve of idleWaiters) resolve();
|
|
2776
|
+
idleWaiters.clear();
|
|
2777
|
+
};
|
|
2778
|
+
const waitForIdle = () => {
|
|
2779
|
+
if (isIdle()) return Promise.resolve();
|
|
2780
|
+
return new Promise((resolve) => idleWaiters.add(resolve));
|
|
2781
|
+
};
|
|
2782
|
+
const closeAdmission = () => {
|
|
2783
|
+
closed = true;
|
|
2784
|
+
};
|
|
2785
|
+
const isClosed = () => closed;
|
|
2355
2786
|
const unboundedEvents = /* @__PURE__ */ new Set([
|
|
2356
2787
|
"run.started",
|
|
2357
2788
|
"run.recovered",
|
|
@@ -2403,6 +2834,7 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
|
|
|
2403
2834
|
return count;
|
|
2404
2835
|
};
|
|
2405
2836
|
const admitStart = async (kind, name, request) => {
|
|
2837
|
+
if (closed) throw new FevexRunError("RUNTIME_CLOSED", "Runtime is not accepting new runs");
|
|
2406
2838
|
if (!admission) return () => {};
|
|
2407
2839
|
const running = activeTopLevelRuns() + pendingStarts;
|
|
2408
2840
|
const tenant = tenantOf(request.context);
|
|
@@ -2459,35 +2891,30 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
|
|
|
2459
2891
|
}
|
|
2460
2892
|
};
|
|
2461
2893
|
const scheduleTraceExport = (event) => {
|
|
2462
|
-
if (!observability || !
|
|
2463
|
-
|
|
2464
|
-
"run.failed",
|
|
2465
|
-
"run.cancelled"
|
|
2466
|
-
].includes(event.type) || exportedRuns.has(event.runId)) return;
|
|
2467
|
-
exportedRuns.add(event.runId);
|
|
2468
|
-
let task;
|
|
2469
|
-
task = (async () => {
|
|
2894
|
+
if (!observability || !isTraceTerminalEvent(event)) return;
|
|
2895
|
+
traceExports.enqueue(event.runId, async () => {
|
|
2470
2896
|
const run = await runStore.getRun(event.runId);
|
|
2471
2897
|
if (!run) throw new Error(`Run "${event.runId}" does not exist`);
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
let
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2898
|
+
const coordinator = isCoordinatorRun(run);
|
|
2899
|
+
let modelRegistryName;
|
|
2900
|
+
let modelMetadata;
|
|
2901
|
+
if (!coordinator) {
|
|
2902
|
+
const agentRun = run;
|
|
2903
|
+
modelRegistryName = agentRun.modelName;
|
|
2904
|
+
modelMetadata = agentRun.modelMetadata;
|
|
2905
|
+
if (modelRegistryName === void 0) {
|
|
2906
|
+
const agent = agents.get(agentRun.agentName);
|
|
2907
|
+
if (!agent) throw new Error(`Agent "${agentRun.agentName}" is not registered`);
|
|
2908
|
+
const checkpoint = isDurableRunStore(runStore) ? await runStore.getCheckpoint(agentRun.id) : void 0;
|
|
2909
|
+
const resolved = resolveModel(agent, checkpoint?.modelName);
|
|
2910
|
+
modelRegistryName = resolved.modelName;
|
|
2911
|
+
modelMetadata = resolved.model.metadata;
|
|
2912
|
+
}
|
|
2483
2913
|
}
|
|
2484
|
-
const trace = buildRunTrace(
|
|
2485
|
-
const failures = (await Promise.allSettled(observability.exporters.map((exporter) => Promise.resolve().then(() => exporter.export(trace))))).filter((result) => result.status === "rejected").map(({ reason }) => reason);
|
|
2914
|
+
const trace = buildRunTrace(run, await runStore.listEvents(run.id), modelRegistryName, modelMetadata, observability);
|
|
2915
|
+
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);
|
|
2486
2916
|
if (failures.length) throw new AggregateError(failures, "Trace export failed");
|
|
2487
|
-
})
|
|
2488
|
-
exportFailures.push(error);
|
|
2489
|
-
}).finally(() => pendingExports.delete(task));
|
|
2490
|
-
pendingExports.add(task);
|
|
2917
|
+
});
|
|
2491
2918
|
};
|
|
2492
2919
|
const notifyObserver = (event) => {
|
|
2493
2920
|
scheduleTraceExport(event);
|
|
@@ -2526,6 +2953,7 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
|
|
|
2526
2953
|
state.leaseTimer = void 0;
|
|
2527
2954
|
state.leaseExpiryTimer = void 0;
|
|
2528
2955
|
if (state.lease && isDurableRunStore(runStore)) await runStore.releaseLease(state.run.id, state.lease.ownerId, state.lease.generation).catch(() => {});
|
|
2956
|
+
notifyIdle();
|
|
2529
2957
|
};
|
|
2530
2958
|
const commit = async (state, options = {}) => {
|
|
2531
2959
|
if (!isDurableRunStore(runStore)) throw new FevexRunError("DURABLE_STORE_REQUIRED", "This run requires a DurableRunStore", state.run.id);
|
|
@@ -2615,6 +3043,7 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
|
|
|
2615
3043
|
state.leaseTimer = void 0;
|
|
2616
3044
|
state.leaseExpiryTimer = void 0;
|
|
2617
3045
|
if (state.lease && isDurableRunStore(runStore)) await runStore.releaseLease(state.run.id, state.lease.ownerId, state.lease.generation).catch(() => {});
|
|
3046
|
+
notifyIdle();
|
|
2618
3047
|
};
|
|
2619
3048
|
const commitWorkflow = async (state, update, events = []) => {
|
|
2620
3049
|
state.commitQueue = state.commitQueue.then(async () => {
|
|
@@ -2648,8 +3077,11 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
|
|
|
2648
3077
|
activeRuns,
|
|
2649
3078
|
activeWorkflowRuns,
|
|
2650
3079
|
activeSessions,
|
|
2651
|
-
|
|
2652
|
-
|
|
3080
|
+
isIdle,
|
|
3081
|
+
waitForIdle,
|
|
3082
|
+
closeAdmission,
|
|
3083
|
+
isClosed,
|
|
3084
|
+
traceExports,
|
|
2653
3085
|
runtimeOwner,
|
|
2654
3086
|
admitStart,
|
|
2655
3087
|
markInternalRun,
|
|
@@ -2680,7 +3112,7 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
|
|
|
2680
3112
|
*/
|
|
2681
3113
|
function createWorkflowEngine(composition, core, agentEngine) {
|
|
2682
3114
|
const { models, agents, workflows, teams, runStore } = composition;
|
|
2683
|
-
const { activeRuns, activeWorkflowRuns, activeSessions, runtimeOwner, admitStart, createEvent, createCoordinatorEvent, notifyObserver, commit, startLease, releaseWorkflowExecution, commitWorkflow } = core;
|
|
3115
|
+
const { activeRuns, activeWorkflowRuns, activeSessions, runtimeOwner, isClosed, admitStart, createEvent, createCoordinatorEvent, notifyObserver, commit, startLease, releaseWorkflowExecution, commitWorkflow } = core;
|
|
2684
3116
|
const { prepareExecution, executeAgent, nextEvent, resumeAgent, cancelStoredAgent } = agentEngine;
|
|
2685
3117
|
const cancelWorkflowExecution = async (state, reason = cancellationReason(state.request.signal)) => {
|
|
2686
3118
|
if (state.leaseLost) {
|
|
@@ -2772,6 +3204,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
2772
3204
|
}
|
|
2773
3205
|
};
|
|
2774
3206
|
const prepareWorkflowExecutionBody = async (name, request, kind = "workflow") => {
|
|
3207
|
+
if (isClosed()) throw new FevexRunError("RUNTIME_CLOSED", "Runtime is not accepting new runs");
|
|
2775
3208
|
const workflow = workflows.get(name);
|
|
2776
3209
|
const team = kind === "team" ? teams.get(name) : void 0;
|
|
2777
3210
|
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`);
|
|
@@ -3072,7 +3505,11 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
3072
3505
|
context: mergeExecutionContext(state.request.context, request.context),
|
|
3073
3506
|
limits: combineLimits(request.limits, remainingWorkflowLimits(state.checkpoint.limits, state.checkpoint.budget)),
|
|
3074
3507
|
signal: childSignal
|
|
3075
|
-
}, {
|
|
3508
|
+
}, {
|
|
3509
|
+
admit: false,
|
|
3510
|
+
parentRunId: state.run.id,
|
|
3511
|
+
rootRunId: state.run.rootRunId ?? state.run.id
|
|
3512
|
+
});
|
|
3076
3513
|
const action = options?.metadata?.teamAction === "handoff" ? "handoff" : "delegate";
|
|
3077
3514
|
const lifecycleEvents = [];
|
|
3078
3515
|
if (isTeamRun(state.run) && action === "handoff") lifecycleEvents.push(createEvent(state, "team.handoff.created", {
|
|
@@ -3084,11 +3521,13 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
3084
3521
|
const started = createCoordinatorEvent(state, "workflow.step.started", {
|
|
3085
3522
|
stepId,
|
|
3086
3523
|
kind: "agent",
|
|
3087
|
-
agentName
|
|
3524
|
+
agentName,
|
|
3525
|
+
childRunId: childState.run.id
|
|
3088
3526
|
}, "team.agent.assigned", {
|
|
3089
3527
|
delegationId: stepId,
|
|
3090
3528
|
agentName,
|
|
3091
3529
|
action,
|
|
3530
|
+
childRunId: childState.run.id,
|
|
3092
3531
|
...typeof options?.metadata?.expectedOutput === "string" ? { expectedOutput: options.metadata.expectedOutput } : {},
|
|
3093
3532
|
...Array.isArray(options?.metadata?.constraints) ? { constraints: options.metadata.constraints } : {}
|
|
3094
3533
|
});
|
|
@@ -3772,24 +4211,76 @@ function createWorkflowEngine(composition, core, agentEngine) {
|
|
|
3772
4211
|
}
|
|
3773
4212
|
//#endregion
|
|
3774
4213
|
//#region src/internal/runtime.ts
|
|
4214
|
+
const DEFAULT_SHUTDOWN_TIMEOUT_MS = 3e4;
|
|
4215
|
+
const SHUTDOWN_CLEANUP_MS = 5e3;
|
|
3775
4216
|
function createRuntime(composition) {
|
|
3776
4217
|
const { models, agents, workflows, teams, tools, contextProviders, memoryStore, runStore, credentialStore, sandbox, policies } = composition;
|
|
3777
4218
|
const core = createRunCore(composition);
|
|
3778
|
-
const { activeRuns, activeWorkflowRuns, activeSessions,
|
|
4219
|
+
const { activeRuns, activeWorkflowRuns, activeSessions, isIdle, waitForIdle, closeAdmission, isClosed, traceExports, runtimeOwner, createEvent, createCoordinatorEvent, notifyObserver, commit, startLease, cancelExecution, releaseWorkflowExecution, commitWorkflow } = core;
|
|
3779
4220
|
const agentEngine = createAgentEngine(composition, core);
|
|
3780
4221
|
const { prepareExecution, executeAgent, nextEvent, drainExecution, resumeAgent, cancelStoredAgent } = agentEngine;
|
|
3781
4222
|
const { prepareWorkflowExecution, executeWorkflow, nextWorkflowEvent, drainWorkflowExecution, resumeWorkflow, cancelWorkflowExecution, cancelStoredWorkflow } = createWorkflowEngine(composition, core, agentEngine);
|
|
3782
4223
|
const resumeRun = async (runId, resolution) => {
|
|
4224
|
+
if (isClosed()) throw new FevexRunError("RUNTIME_CLOSED", "Runtime is not accepting new runs", runId);
|
|
3783
4225
|
const run = await runStore.getRun(runId);
|
|
3784
4226
|
if (run && isCoordinatorRun(run)) return resumeWorkflow(runId, resolution);
|
|
3785
4227
|
return resumeAgent(runId, resolution);
|
|
3786
4228
|
};
|
|
3787
4229
|
const recoverRun = async (runId, actor) => {
|
|
4230
|
+
if (isClosed()) throw new FevexRunError("RUNTIME_CLOSED", "Runtime is not accepting new runs", runId);
|
|
3788
4231
|
const run = await runStore.getRun(runId);
|
|
3789
4232
|
if (!run) throw new FevexRunError("RUN_NOT_RECOVERABLE", `Run "${runId}" cannot be recovered`, runId);
|
|
3790
4233
|
if (isCoordinatorRun(run)) return resumeWorkflow(runId, void 0, actor);
|
|
3791
4234
|
return resumeAgent(runId, void 0, false, actor);
|
|
3792
4235
|
};
|
|
4236
|
+
const waitForIdleUntil = async (deadline) => {
|
|
4237
|
+
if (isIdle()) return true;
|
|
4238
|
+
const remaining = deadline - Date.now();
|
|
4239
|
+
if (remaining <= 0) return false;
|
|
4240
|
+
return new Promise((resolve) => {
|
|
4241
|
+
let settled = false;
|
|
4242
|
+
let timer;
|
|
4243
|
+
const finish = (idle) => {
|
|
4244
|
+
if (settled) return;
|
|
4245
|
+
settled = true;
|
|
4246
|
+
if (timer) clearTimeout(timer);
|
|
4247
|
+
resolve(idle);
|
|
4248
|
+
};
|
|
4249
|
+
timer = setTimeout(() => finish(false), remaining);
|
|
4250
|
+
waitForIdle().then(() => finish(true));
|
|
4251
|
+
});
|
|
4252
|
+
};
|
|
4253
|
+
const shutdown = async (options = {}) => {
|
|
4254
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
|
4255
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0) throw new TypeError("Shutdown timeoutMs must be a non-negative safe integer");
|
|
4256
|
+
closeAdmission();
|
|
4257
|
+
const deadline = Date.now() + timeoutMs;
|
|
4258
|
+
let cleanupDeadline = deadline;
|
|
4259
|
+
let timedOut = false;
|
|
4260
|
+
const cancelled = /* @__PURE__ */ new Set();
|
|
4261
|
+
if (!await waitForIdleUntil(deadline)) {
|
|
4262
|
+
timedOut = true;
|
|
4263
|
+
cleanupDeadline = Date.now() + SHUTDOWN_CLEANUP_MS;
|
|
4264
|
+
for (const { run } of activeWorkflowRuns.values()) cancelled.add(run.id);
|
|
4265
|
+
for (const { run } of activeRuns.values()) cancelled.add(run.id);
|
|
4266
|
+
for (const state of [...activeWorkflowRuns.values(), ...activeRuns.values()]) if (!state.request.signal.aborted) state.controller.abort(new DOMException("Run cancelled", "AbortError"));
|
|
4267
|
+
for (const state of [...activeWorkflowRuns.values()]) if (!state.advancing) await cancelWorkflowExecution(state).catch(() => {});
|
|
4268
|
+
for (const state of [...activeRuns.values()]) if (!state.advancing) await cancelExecution(state).catch(() => {});
|
|
4269
|
+
await waitForIdleUntil(cleanupDeadline);
|
|
4270
|
+
}
|
|
4271
|
+
let exportError;
|
|
4272
|
+
try {
|
|
4273
|
+
await traceExports.flush({ timeoutMs: Math.max(1, cleanupDeadline - Date.now()) });
|
|
4274
|
+
} catch (error) {
|
|
4275
|
+
exportError = error;
|
|
4276
|
+
}
|
|
4277
|
+
return {
|
|
4278
|
+
cancelledRunIds: [...cancelled],
|
|
4279
|
+
timedOut,
|
|
4280
|
+
...exportError === void 0 ? {} : { exportError },
|
|
4281
|
+
observability: traceExports.stats()
|
|
4282
|
+
};
|
|
4283
|
+
};
|
|
3793
4284
|
return {
|
|
3794
4285
|
async startAgent(name, request) {
|
|
3795
4286
|
const state = await prepareExecution(name, request);
|
|
@@ -3921,13 +4412,13 @@ function createRuntime(composition) {
|
|
|
3921
4412
|
await runStore.saveSession(session);
|
|
3922
4413
|
return structuredClone(session);
|
|
3923
4414
|
},
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
4415
|
+
flushObservability(options) {
|
|
4416
|
+
return traceExports.flush(options);
|
|
4417
|
+
},
|
|
4418
|
+
getObservabilityStats() {
|
|
4419
|
+
return traceExports.stats();
|
|
4420
|
+
},
|
|
4421
|
+
shutdown
|
|
3931
4422
|
};
|
|
3932
4423
|
}
|
|
3933
4424
|
//#endregion
|
|
@@ -3942,4 +4433,4 @@ function createFevex(config) {
|
|
|
3942
4433
|
return createRuntime(createComposition(config));
|
|
3943
4434
|
}
|
|
3944
4435
|
//#endregion
|
|
3945
|
-
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 };
|
|
4436
|
+
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, createAgentsMdContextProvider, createFevex, createLocalSandbox, createMemoryContextProvider, defineAgent, defineConnection, defineContextProvider, definePolicy, defineSkill, defineTeam, defineTool, defineWorkflow, deliverChannelRun, handleChannelInput, isDurableRunStore, isProgressiveSkillProvider, isRetainableRunStore, loadSkillFile, loadSkillFiles, sandboxResultToJson, toToolSpec, validateFevexJsonSchemaProfile, validateListEventsOptions, validatePurgeRunsOptions };
|