@exellix/graph-engine 7.8.1 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/README.md +16 -13
- package/dist/src/adapters/compileExellixExecutablePlan.d.ts +8 -0
- package/dist/src/adapters/compileExellixExecutablePlan.js +18 -0
- package/dist/src/adapters/migrateExellixGraphModelToAuthoring.d.ts +6 -0
- package/dist/src/adapters/migrateExellixGraphModelToAuthoring.js +273 -0
- package/dist/src/adapters/patchFinalizerPlans.d.ts +7 -0
- package/dist/src/adapters/patchFinalizerPlans.js +63 -0
- package/dist/src/errors/exellixGraphErrorCodes.d.ts +5 -0
- package/dist/src/errors/exellixGraphErrorCodes.js +5 -0
- package/dist/src/index.d.ts +9 -2
- package/dist/src/index.js +6 -2
- package/dist/src/integrations/ActivixNodeActivityIntegration.js +7 -0
- package/dist/src/plan/aiModelSelectionWire.d.ts +11 -0
- package/dist/src/plan/aiModelSelectionWire.js +39 -0
- package/dist/src/plan/applyNodePlanInvoke.d.ts +10 -0
- package/dist/src/plan/applyNodePlanInvoke.js +67 -0
- package/dist/src/plan/embeddedGraphToExellixGraph.d.ts +5 -0
- package/dist/src/plan/embeddedGraphToExellixGraph.js +131 -0
- package/dist/src/plan/planDeferredGates.d.ts +16 -0
- package/dist/src/plan/planDeferredGates.js +118 -0
- package/dist/src/plan/planExecuteEntry.d.ts +12 -0
- package/dist/src/plan/planExecuteEntry.js +73 -0
- package/dist/src/plan/planExecutionPipeline.d.ts +11 -0
- package/dist/src/plan/planExecutionPipeline.js +54 -0
- package/dist/src/plan/planModelConfig.d.ts +10 -0
- package/dist/src/plan/planModelConfig.js +46 -0
- package/dist/src/runtime/ExellixGraphRuntime.d.ts +9 -6
- package/dist/src/runtime/ExellixGraphRuntime.js +138 -98
- package/dist/src/runtime/executionMatrixHost.js +2 -1
- package/dist/src/runtime/studioGraphExecuteRequest.d.ts +51 -0
- package/dist/src/runtime/studioGraphExecuteRequest.js +78 -0
- package/dist/testkit/buildExecuteGraphInput.d.ts +4 -0
- package/dist/testkit/buildExecuteGraphInput.js +8 -0
- package/dist/testkit/index.d.ts +1 -0
- package/dist/testkit/index.js +1 -0
- package/dist/testkit/testModelAliasRuntime.js +2 -2
- package/package.json +9 -3
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import { createExecutionTrace, appendExecutionEvent, validateExecutionTrace } from "@x12i/graphenix-trace-format";
|
|
2
|
+
import { preparePlanExecuteEntry, computePlanAudit } from "../plan/planExecuteEntry.js";
|
|
3
|
+
import { embeddedGraphToExellixGraph } from "../plan/embeddedGraphToExellixGraph.js";
|
|
4
|
+
import { graphAiModelConfigFromFinalizerPlan, graphAiModelConfigFromNodePlan } from "../plan/planModelConfig.js";
|
|
5
|
+
import { executionPipelineFromNodePlan } from "../plan/planExecutionPipeline.js";
|
|
6
|
+
import { applyNodePlanInvoke, finalizerPlanForId, nodePlanForId } from "../plan/applyNodePlanInvoke.js";
|
|
7
|
+
import { evaluateDeferredNodeGate, evaluateDeferredEdgeGate, nodeDeferredGate, hasPlanConditionalEdges, evaluatePlanEntryGates, planNeedsRunx, } from "../plan/planDeferredGates.js";
|
|
1
8
|
import { mergeExecutionObject, copyExecutionContextFields, } from "./memory.js";
|
|
2
9
|
import { buildAiTasksRunTaskRequest, extractRunTaskStrategyOverrides, resolveJobTypeId, } from "./buildAiTasksRunTaskRequest.js";
|
|
3
10
|
import { runTaskResponseSucceeded } from "./runTaskResponse.js";
|
|
@@ -16,11 +23,9 @@ import { resolveNarrixForTaskNode } from "./resolveNarrixForTaskNode.js";
|
|
|
16
23
|
import { assertFinalizerRequiredReadsResolvable, validateGraphFinalizer, } from "./finalizers/validateFinalizer.js";
|
|
17
24
|
import { executeDeterministicFinalizer, executeSynthesizeFinalizer } from "./finalizers/executeFinalizer.js";
|
|
18
25
|
import { createFinalizerError } from "./finalizers/errors.js";
|
|
19
|
-
import { assertCanonicalGraphDocument } from "./validateCanonicalGraphDocument.js";
|
|
20
26
|
import { assertCanonicalGraphRuntimeObject } from "./validateCanonicalGraphRuntime.js";
|
|
21
27
|
import { applyGraphResponseDefinition } from "./graphResponseMapping.js";
|
|
22
28
|
import { migrateLegacyGraphResponse } from "./graphResponseMigration.js";
|
|
23
|
-
import { computeGraphDocumentContentSha256 } from "./graphDocumentFingerprint.js";
|
|
24
29
|
import { buildAiTasksObservabilityRecord } from "./aiTasksObservability.js";
|
|
25
30
|
import { buildMainLlmCallForRunTask, buildRunTaskIdentityEnvelope, shouldForwardRunTaskTraceMode, } from "./runTaskAugments.js";
|
|
26
31
|
import { buildRunLog, extractLogxerCorrelationFromMetadata, extractTaskRunLogFromMetadata, resolveRunLogLimits, } from "./buildRunLog.js";
|
|
@@ -33,9 +38,6 @@ import { buildPredicateEvalContextForNode, mirrorTaskVariablesOnExecution, readE
|
|
|
33
38
|
import { mergeExellixGraphRuntimeInvocation } from "./mergeExellixGraphRuntimeInvocation.js";
|
|
34
39
|
import { resolveStepRetryPolicy, runRunTaskWithRetry } from "./stepRetry.js";
|
|
35
40
|
import { isLocalSkillKey, runScopedDataReader, runDeterministicRule, runScopedAnswerWriter, runScopedAnswerAssembler, } from "./localSkills/index.js";
|
|
36
|
-
import { evaluateGraphPredicate } from "./predicates.js";
|
|
37
|
-
import { evaluateStructuredDataFilters } from "./dataFiltersEvaluation.js";
|
|
38
|
-
import { evaluateTaskNodeConditions } from "./taskNodeConditionsEvaluation.js";
|
|
39
41
|
import { resolveModelConfigForNode } from "./resolveModelConfigForNode.js";
|
|
40
42
|
import { toRunTaskModelConfig } from "./graphAiModelConfig.js";
|
|
41
43
|
import { isGraphAiModelConfig } from "./modelConfigSelection.js";
|
|
@@ -963,8 +965,10 @@ export function createExellixGraphRuntime(opts) {
|
|
|
963
965
|
const resMeta = res.metadata ?? res.meta;
|
|
964
966
|
const taskRunLog = [...retryOutcome.syntheticRunLog, ...extractTaskRunLogFromMetadata(resMeta)];
|
|
965
967
|
const logxerCorrelationId = extractLogxerCorrelationFromMetadata(resMeta);
|
|
966
|
-
const
|
|
967
|
-
|
|
968
|
+
const taskErrorMessage = res.error?.message ?? "Task run failed";
|
|
969
|
+
const taskErrorCode = res.error?.code ?? "TASK_RUN_FAILED";
|
|
970
|
+
const err = new Error(taskErrorMessage);
|
|
971
|
+
err.code = taskErrorCode;
|
|
968
972
|
err.skillKey = skillKey;
|
|
969
973
|
err.diagnostics = res.diagnostics;
|
|
970
974
|
err.nodeId = input.node?.id;
|
|
@@ -1153,36 +1157,45 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1153
1157
|
};
|
|
1154
1158
|
}
|
|
1155
1159
|
async function executeGraph(input) {
|
|
1156
|
-
const {
|
|
1157
|
-
const {
|
|
1160
|
+
const { plan: suppliedPlan, runtime } = input;
|
|
1161
|
+
const { plan } = preparePlanExecuteEntry(suppliedPlan, runtime);
|
|
1162
|
+
const materialized = embeddedGraphToExellixGraph(plan);
|
|
1163
|
+
const graph = migrateLegacyGraphResponse(materialized).graph;
|
|
1158
1164
|
const merged = mergeExellixGraphRuntimeInvocation(runtime, opts);
|
|
1159
|
-
const
|
|
1165
|
+
const scheduling = plan.schedulingPolicy;
|
|
1166
|
+
const plannerMode = scheduling?.mode ?? runtime.mode;
|
|
1167
|
+
const plannerGoalNodeId = scheduling?.goalNodeId ?? runtime.goalNodeId;
|
|
1168
|
+
const failFast = scheduling?.failFast ?? merged.failFast;
|
|
1160
1169
|
const debugMode = runtime.debugMode === true;
|
|
1161
1170
|
const eventEmitter = runtime.eventEmitter ?? opts.eventEmitter;
|
|
1162
1171
|
const jobId = assertHostJobId(runtime.jobId);
|
|
1163
1172
|
const graphTaskId = newGraphRunTaskId();
|
|
1164
1173
|
const job = { ...(runtime.job ?? {}), id: jobId, jobId };
|
|
1165
|
-
if (
|
|
1174
|
+
if (plannerMode === "backward" && !plannerGoalNodeId) {
|
|
1166
1175
|
const err = new Error(`BACKWARD_GOAL_REQUIRED: mode=backward requires goalNodeId`);
|
|
1167
1176
|
err.code = "BACKWARD_GOAL_REQUIRED";
|
|
1168
1177
|
throw err;
|
|
1169
1178
|
}
|
|
1170
|
-
const
|
|
1171
|
-
if (resolvedGraphIdRaw == null || resolvedGraphIdRaw === '') {
|
|
1172
|
-
throw new Error('GRAPH_ID_REQUIRED: execution request model must include a non-empty id');
|
|
1173
|
-
}
|
|
1174
|
-
const resolvedGraphId = String(resolvedGraphIdRaw);
|
|
1179
|
+
const resolvedGraphId = String(plan.source.graphId);
|
|
1175
1180
|
const runLogxer = createGraphEngineLogxer({ logging: merged.logging });
|
|
1181
|
+
const executionTrace = createExecutionTrace(plan, runtime);
|
|
1182
|
+
appendExecutionEvent(executionTrace, {
|
|
1183
|
+
id: `evt:${graphTaskId}:graph.started`,
|
|
1184
|
+
ts: new Date().toISOString(),
|
|
1185
|
+
level: "info",
|
|
1186
|
+
type: "graph.started",
|
|
1187
|
+
message: "Graph execution started.",
|
|
1188
|
+
});
|
|
1189
|
+
const planAudit = computePlanAudit(plan);
|
|
1176
1190
|
return runWithAiTasksStackLogging(merged.logging, () => runGraphWithLogContext({ jobId, taskId: graphTaskId, graphId: resolvedGraphId, runId: graphTaskId }, async () => {
|
|
1177
1191
|
bindGraphEngineRunLogxer(runLogxer);
|
|
1178
1192
|
try {
|
|
1179
|
-
assertCanonicalGraphDocument(graph, { jobId, graphId: resolvedGraphId }, { mode: 'execute' });
|
|
1180
1193
|
assertCanonicalGraphRuntimeObject(runtime, { jobId, graphId: resolvedGraphId });
|
|
1181
1194
|
let runxClient = opts.runx;
|
|
1182
|
-
if (
|
|
1195
|
+
if (planNeedsRunx(plan)) {
|
|
1183
1196
|
if (!runxClient) {
|
|
1184
1197
|
if (!opts.runxCreateOptions) {
|
|
1185
|
-
const err = new Error("RUNX_REQUIRED:
|
|
1198
|
+
const err = new Error("RUNX_REQUIRED: executable plan deferred gates require runx. Pass runx or runxCreateOptions on createExellixGraphRuntime.");
|
|
1186
1199
|
err.code = "RUNX_REQUIRED";
|
|
1187
1200
|
throw err;
|
|
1188
1201
|
}
|
|
@@ -1196,10 +1209,6 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1196
1209
|
await runxClient.reload();
|
|
1197
1210
|
}
|
|
1198
1211
|
}
|
|
1199
|
-
const graphAudit = {
|
|
1200
|
-
source: 'model',
|
|
1201
|
-
contentSha256: computeGraphDocumentContentSha256(suppliedGraph),
|
|
1202
|
-
};
|
|
1203
1212
|
const graphDocumentModel = mergeGraphDocumentModel(graph);
|
|
1204
1213
|
const graphExecution = graphDocumentModel.graphExecution;
|
|
1205
1214
|
const nodeResponseKeys = resolveNodeResponseKeys(graphExecution);
|
|
@@ -1213,8 +1222,8 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1213
1222
|
const { finalizer } = validateGraphFinalizer(graph);
|
|
1214
1223
|
const engine = opts.engineFactory.create({
|
|
1215
1224
|
graph,
|
|
1216
|
-
mode:
|
|
1217
|
-
goalNodeId:
|
|
1225
|
+
mode: plannerMode,
|
|
1226
|
+
goalNodeId: plannerGoalNodeId,
|
|
1218
1227
|
dimension: runtime.dimension,
|
|
1219
1228
|
initialState: runtime.initialState,
|
|
1220
1229
|
initialVariables: runtime.initialVariables,
|
|
@@ -1224,19 +1233,15 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1224
1233
|
const errors = [];
|
|
1225
1234
|
const finalizerNodeId = String(finalizer.id);
|
|
1226
1235
|
const jobCorrelation = job?.jobType != null ? { jobType: job.jobType } : undefined;
|
|
1227
|
-
|
|
1228
|
-
const rawEdges = Array.isArray(graph.edges) ? graph.edges : [];
|
|
1229
|
-
const hasConditionalEdges = rawEdges.some((e) => e && typeof e === "object" && e.when != null);
|
|
1236
|
+
const hasConditionalEdges = hasPlanConditionalEdges(plan);
|
|
1230
1237
|
const incomingByTo = new Map();
|
|
1231
1238
|
if (hasConditionalEdges) {
|
|
1232
|
-
for (const
|
|
1233
|
-
|
|
1234
|
-
continue;
|
|
1235
|
-
const to = e.to;
|
|
1239
|
+
for (const edgeRef of plan.topology.edges) {
|
|
1240
|
+
const to = edgeRef.to.nodeId;
|
|
1236
1241
|
if (typeof to !== "string" || to.length === 0)
|
|
1237
1242
|
continue;
|
|
1238
1243
|
const list = incomingByTo.get(to) ?? [];
|
|
1239
|
-
list.push(
|
|
1244
|
+
list.push(edgeRef);
|
|
1240
1245
|
incomingByTo.set(to, list);
|
|
1241
1246
|
}
|
|
1242
1247
|
}
|
|
@@ -1395,50 +1400,67 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1395
1400
|
? currentExecution.input
|
|
1396
1401
|
: {};
|
|
1397
1402
|
}
|
|
1398
|
-
const
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1403
|
+
const entryGateEv = await evaluatePlanEntryGates(plan.deferredGates.entryGates, recordForStructuredDataFilters(), {
|
|
1404
|
+
executionMemory: currentExecution,
|
|
1405
|
+
jobMemory: currentJobMemory,
|
|
1406
|
+
taskMemory: currentTaskMemory,
|
|
1407
|
+
job,
|
|
1408
|
+
}, runxClient);
|
|
1409
|
+
if (!entryGateEv.ok) {
|
|
1410
|
+
const err = new Error("GRAPH_ENTRY_DATA_FILTERS_REJECTED: plan entry gates rejected runtime.executionMemory.input");
|
|
1411
|
+
err.code = ExellixGraphErrorCode.GRAPH_ENTRY_DATA_FILTERS_REJECTED;
|
|
1412
|
+
logGraphEngineErrorCode(ExellixGraphErrorCode.GRAPH_ENTRY_DATA_FILTERS_REJECTED, err.message, {
|
|
1413
|
+
graphId: resolvedGraphId,
|
|
1414
|
+
jobId,
|
|
1415
|
+
taskId: graphTaskId,
|
|
1416
|
+
error: err,
|
|
1417
|
+
});
|
|
1418
|
+
const finalOutput = buildFinalOutputFromGraphResponse();
|
|
1419
|
+
appendExecutionEvent(executionTrace, {
|
|
1420
|
+
id: `evt:${graphTaskId}:graph.failed`,
|
|
1421
|
+
ts: new Date().toISOString(),
|
|
1422
|
+
level: "error",
|
|
1423
|
+
type: "graph.failed",
|
|
1424
|
+
message: err.message,
|
|
1425
|
+
});
|
|
1426
|
+
const result = {
|
|
1427
|
+
jobId,
|
|
1428
|
+
taskId: graphTaskId,
|
|
1429
|
+
graphId: resolvedGraphId,
|
|
1430
|
+
status: "failed",
|
|
1431
|
+
outputsByNodeId,
|
|
1432
|
+
stepsResponses,
|
|
1433
|
+
engineSnapshot: engine.snapshot(),
|
|
1434
|
+
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1435
|
+
errors: [...errors, { error: err }],
|
|
1436
|
+
planAudit,
|
|
1437
|
+
trace: executionTrace,
|
|
1438
|
+
...finalizeGraphPayload("failed"),
|
|
1439
|
+
};
|
|
1440
|
+
const debug = buildDebugTrace();
|
|
1441
|
+
if (debug)
|
|
1442
|
+
result.debug = debug;
|
|
1443
|
+
if (eventEmitter) {
|
|
1444
|
+
eventEmitter.emit(createGraphFailEvent(jobId, resolvedGraphId, graphTaskId, err, {
|
|
1445
|
+
finalMemory: { jobMemory: currentJobMemory, taskMemory: currentTaskMemory, execution: currentExecution, outputsMemory: currentOutputsMemory },
|
|
1446
|
+
...(jobCorrelation ?? {}),
|
|
1447
|
+
}));
|
|
1435
1448
|
}
|
|
1449
|
+
return result;
|
|
1436
1450
|
}
|
|
1437
1451
|
while (true) {
|
|
1438
|
-
const
|
|
1439
|
-
if (
|
|
1452
|
+
const wavePlan = engine.plan();
|
|
1453
|
+
if (wavePlan.status === "completed") {
|
|
1440
1454
|
const graphStatus = errors.length ? "failed" : "completed";
|
|
1441
1455
|
const finalOutput = buildFinalOutputFromGraphResponse();
|
|
1456
|
+
appendExecutionEvent(executionTrace, {
|
|
1457
|
+
id: `evt:${graphTaskId}:graph.${graphStatus}`,
|
|
1458
|
+
ts: new Date().toISOString(),
|
|
1459
|
+
level: graphStatus === "completed" ? "info" : "error",
|
|
1460
|
+
type: graphStatus === "completed" ? "graph.completed" : "graph.failed",
|
|
1461
|
+
message: `Graph execution ${graphStatus}.`,
|
|
1462
|
+
});
|
|
1463
|
+
validateExecutionTrace(executionTrace, plan);
|
|
1442
1464
|
const result = {
|
|
1443
1465
|
jobId,
|
|
1444
1466
|
taskId: graphTaskId,
|
|
@@ -1450,7 +1472,8 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1450
1472
|
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1451
1473
|
...(errors.length === 0 ? { finalizerNodeId, finalizerType: finalizer.finalizerType } : {}),
|
|
1452
1474
|
errors: errors.length ? errors : undefined,
|
|
1453
|
-
|
|
1475
|
+
planAudit,
|
|
1476
|
+
trace: executionTrace,
|
|
1454
1477
|
...finalizeGraphPayload(graphStatus),
|
|
1455
1478
|
};
|
|
1456
1479
|
const debug = buildDebugTrace();
|
|
@@ -1474,8 +1497,8 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1474
1497
|
}
|
|
1475
1498
|
return result;
|
|
1476
1499
|
}
|
|
1477
|
-
if (
|
|
1478
|
-
const err = new Error(`GRAPH_BLOCKED: status=${
|
|
1500
|
+
if (wavePlan.status !== "continue") {
|
|
1501
|
+
const err = new Error(`GRAPH_BLOCKED: status=${wavePlan.status}`);
|
|
1479
1502
|
err.code = "GRAPH_BLOCKED";
|
|
1480
1503
|
const finalOutput = buildFinalOutputFromGraphResponse();
|
|
1481
1504
|
const result = {
|
|
@@ -1488,7 +1511,8 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1488
1511
|
engineSnapshot: engine.snapshot(),
|
|
1489
1512
|
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1490
1513
|
errors: [...errors, { error: err }],
|
|
1491
|
-
|
|
1514
|
+
planAudit,
|
|
1515
|
+
trace: executionTrace,
|
|
1492
1516
|
...finalizeGraphPayload("failed"),
|
|
1493
1517
|
};
|
|
1494
1518
|
const debug = buildDebugTrace();
|
|
@@ -1502,7 +1526,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1502
1526
|
}
|
|
1503
1527
|
return result;
|
|
1504
1528
|
}
|
|
1505
|
-
let runnableNodes =
|
|
1529
|
+
let runnableNodes = wavePlan.nextNodes ?? [];
|
|
1506
1530
|
// Conditional edge filtering: a node with incoming edges that are *all* conditional must
|
|
1507
1531
|
// have at least one incoming edge whose `when` evaluates true to be runnable this round.
|
|
1508
1532
|
// Roots and nodes with at least one unconditional incoming edge always pass through.
|
|
@@ -1519,7 +1543,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1519
1543
|
const incoming = incomingByTo.get(id) ?? [];
|
|
1520
1544
|
if (incoming.length === 0)
|
|
1521
1545
|
return true;
|
|
1522
|
-
const hasUnconditional = incoming.some((e) => e?.
|
|
1546
|
+
const hasUnconditional = incoming.some((e) => !e?.hasDeferredGate);
|
|
1523
1547
|
if (hasUnconditional)
|
|
1524
1548
|
return true;
|
|
1525
1549
|
const planningContext = buildPredicateEvalContextForNode({
|
|
@@ -1529,7 +1553,10 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1529
1553
|
node: n,
|
|
1530
1554
|
runtimeTaskVariables: runtime.taskVariables,
|
|
1531
1555
|
});
|
|
1532
|
-
return incoming.some((
|
|
1556
|
+
return incoming.some((edgeRef) => {
|
|
1557
|
+
const gate = plan.deferredGates.edgeGates[edgeRef.edgeId];
|
|
1558
|
+
return evaluateDeferredEdgeGate(gate, planningContext);
|
|
1559
|
+
});
|
|
1533
1560
|
});
|
|
1534
1561
|
}
|
|
1535
1562
|
const dataFiltersRecord = recordForStructuredDataFilters();
|
|
@@ -1553,7 +1580,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1553
1580
|
node: n,
|
|
1554
1581
|
runtimeTaskVariables: runtime.taskVariables,
|
|
1555
1582
|
});
|
|
1556
|
-
const condEv = await
|
|
1583
|
+
const condEv = await evaluateDeferredNodeGate(nodeDeferredGate(plan, String(n.id)), { ...conditionCtxBase, ...nodePredicateCtx }, dataFiltersRecord, runxClient);
|
|
1557
1584
|
if (!condEv.ok) {
|
|
1558
1585
|
skippedByConditions.push({
|
|
1559
1586
|
node: n,
|
|
@@ -1603,27 +1630,32 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1603
1630
|
context: { model: graph, runtime, graphId: resolvedGraphId, jobId, taskId: graphTaskId, node: node },
|
|
1604
1631
|
});
|
|
1605
1632
|
const nodeTaskMemory = isPlainRecord(currentTaskMemory) ? { ...currentTaskMemory } : currentTaskMemory;
|
|
1606
|
-
const
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
node: node,
|
|
1614
|
-
runtimeTaskVariables: runtime.taskVariables,
|
|
1615
|
-
}),
|
|
1616
|
-
executionInput: dataFiltersRecord,
|
|
1617
|
-
runx: runxClient,
|
|
1618
|
-
graphId: resolvedGraphId,
|
|
1619
|
-
nodeId: typeof node.id === "string" ? node.id : String(node.id),
|
|
1620
|
-
jobId,
|
|
1633
|
+
const nodeIdStr = String(node.id);
|
|
1634
|
+
appendExecutionEvent(executionTrace, {
|
|
1635
|
+
id: `evt:${graphTaskId}:node.started:${nodeIdStr}`,
|
|
1636
|
+
ts: new Date().toISOString(),
|
|
1637
|
+
level: "info",
|
|
1638
|
+
type: "node.started",
|
|
1639
|
+
nodeId: nodeIdStr,
|
|
1621
1640
|
});
|
|
1641
|
+
const nodePlanEntryResolved = nodePlanForId(plan, nodeIdStr);
|
|
1642
|
+
const finalizerPlanEntryResolved = node.type === "finalizer"
|
|
1643
|
+
? finalizerPlanForId(plan, nodeIdStr)
|
|
1644
|
+
: undefined;
|
|
1645
|
+
const taskNodeForExecute = nodePlanEntryResolved && node.type !== "finalizer"
|
|
1646
|
+
? applyNodePlanInvoke(node, nodePlanEntryResolved)
|
|
1647
|
+
: node;
|
|
1648
|
+
const effectiveModelConfig = finalizerPlanEntryResolved != null
|
|
1649
|
+
? graphAiModelConfigFromFinalizerPlan(finalizerPlanEntryResolved)
|
|
1650
|
+
: nodePlanEntryResolved && node.type !== "finalizer"
|
|
1651
|
+
? graphAiModelConfigFromNodePlan(nodePlanEntryResolved)
|
|
1652
|
+
: undefined;
|
|
1653
|
+
const nodeExecutionPipeline = nodePlanEntryResolved != null ? executionPipelineFromNodePlan(nodePlanEntryResolved) : undefined;
|
|
1622
1654
|
const r = await executeNode({
|
|
1623
1655
|
graphId: resolvedGraphId,
|
|
1624
1656
|
graph,
|
|
1625
1657
|
graphRunTaskId: graphTaskId,
|
|
1626
|
-
node,
|
|
1658
|
+
node: taskNodeForExecute,
|
|
1627
1659
|
job,
|
|
1628
1660
|
jobMemory: currentJobMemory,
|
|
1629
1661
|
taskMemory: nodeTaskMemory,
|
|
@@ -1637,7 +1669,7 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1637
1669
|
runTaskIdentity: merged.runTaskIdentity,
|
|
1638
1670
|
runTaskExecutionMode: merged.runTaskExecutionMode,
|
|
1639
1671
|
runTaskDiagnostics: merged.runTaskDiagnostics,
|
|
1640
|
-
graphExecutionPipeline: merged.executionPipeline,
|
|
1672
|
+
graphExecutionPipeline: nodeExecutionPipeline ?? merged.executionPipeline,
|
|
1641
1673
|
skillKeyResolution: merged.skillKeyResolution,
|
|
1642
1674
|
nodeTimeoutMs: merged.nodeTimeoutMs,
|
|
1643
1675
|
clearSynthesizedContextPerNode: merged.clearSynthesizedContextPerNode,
|
|
@@ -1649,6 +1681,13 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1649
1681
|
outputsByNodeId[r.nodeId] = r.output;
|
|
1650
1682
|
appendStepResponse(node, r.output);
|
|
1651
1683
|
engine.commit({ nodeId: r.nodeId, output: r.output });
|
|
1684
|
+
appendExecutionEvent(executionTrace, {
|
|
1685
|
+
id: `evt:${graphTaskId}:node.completed:${nodeIdStr}`,
|
|
1686
|
+
ts: new Date().toISOString(),
|
|
1687
|
+
level: "info",
|
|
1688
|
+
type: "node.completed",
|
|
1689
|
+
nodeId: nodeIdStr,
|
|
1690
|
+
});
|
|
1652
1691
|
const trl = r.taskRunLog;
|
|
1653
1692
|
if (trl?.length)
|
|
1654
1693
|
taskRunLogBuffer.push(...trl);
|
|
@@ -1720,7 +1759,8 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1720
1759
|
engineSnapshot: engine.snapshot(),
|
|
1721
1760
|
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1722
1761
|
errors,
|
|
1723
|
-
|
|
1762
|
+
planAudit,
|
|
1763
|
+
trace: executionTrace,
|
|
1724
1764
|
...finalizeGraphPayload("failed"),
|
|
1725
1765
|
};
|
|
1726
1766
|
const debug = buildDebugTrace();
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* a `{ model, runtime }` request with per-graph `GraphEntryContract` + seeded
|
|
6
6
|
* `runtime.executionMemory` objects built here.
|
|
7
7
|
*/
|
|
8
|
+
import { compileExellixExecutablePlan } from '../adapters/compileExellixExecutablePlan.js';
|
|
8
9
|
import { mergeGraphDocumentModel } from '../types/refs.js';
|
|
9
10
|
import { createExellixGraphRuntime } from './ExellixGraphRuntime.js';
|
|
10
11
|
import { selectByPath, writeByPath } from './pathExpr.js';
|
|
@@ -76,7 +77,7 @@ export function buildExecutionSeedFromGraphEntry(graphEntry, sources = {}) {
|
|
|
76
77
|
/** Builds the canonical graph-engine execution request for matrix-style hosts. */
|
|
77
78
|
export function buildGraphExecutionRequest(input) {
|
|
78
79
|
return {
|
|
79
|
-
|
|
80
|
+
plan: compileExellixExecutablePlan(input.model, input.runtime),
|
|
80
81
|
runtime: input.runtime,
|
|
81
82
|
};
|
|
82
83
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Graph } from '../types/refs.js';
|
|
2
|
+
import type { ExecuteGraphInput, GraphRuntimeObject } from './ExellixGraphRuntime.js';
|
|
3
|
+
/**
|
|
4
|
+
* Removed from the graphs-studio execute envelope (7.8.3+). No reader, no fallback.
|
|
5
|
+
* Default model selection belongs on {@link Graph}.modelConfig only.
|
|
6
|
+
*/
|
|
7
|
+
export declare const STUDIO_GRAPH_EXECUTE_REMOVED_KEYS: readonly ["graphDefaultModel"];
|
|
8
|
+
export type StudioGraphExecuteRemovedKey = (typeof STUDIO_GRAPH_EXECUTE_REMOVED_KEYS)[number];
|
|
9
|
+
/**
|
|
10
|
+
* Graphs-studio / playground graph execute envelope before host adaptation to
|
|
11
|
+
* {@link ExecuteGraphInput}. Credential and simulate-only fields are host concerns;
|
|
12
|
+
* graph-engine validates shape and forbidden keys only.
|
|
13
|
+
*/
|
|
14
|
+
export type StudioGraphExecuteRequest = {
|
|
15
|
+
mode: 'graph';
|
|
16
|
+
graph: Graph;
|
|
17
|
+
jobId: string;
|
|
18
|
+
runtime: Pick<GraphRuntimeObject, 'input' | 'inputs' | 'jobMemory' | 'taskMemory' | 'executionMemory' | 'outputsMemory' | 'variables' | 'jobVariables' | 'taskVariables' | 'nodes'>;
|
|
19
|
+
studioId?: string;
|
|
20
|
+
executionMode?: string;
|
|
21
|
+
runLogMode?: GraphRuntimeObject['runLogMode'];
|
|
22
|
+
maxRunLogEntries?: number;
|
|
23
|
+
maxRunLogDataJsonChars?: number;
|
|
24
|
+
graphExecution?: {
|
|
25
|
+
mode?: GraphRuntimeObject['mode'];
|
|
26
|
+
goalNodeId?: string;
|
|
27
|
+
dimension?: string;
|
|
28
|
+
};
|
|
29
|
+
runTaskDiagnostics?: GraphRuntimeObject['runTaskDiagnostics'];
|
|
30
|
+
exampleIndex?: number;
|
|
31
|
+
validateGraphEntrySeed?: boolean;
|
|
32
|
+
openrouterApiKey?: string;
|
|
33
|
+
};
|
|
34
|
+
export declare function getStudioGraphExecuteRemovedKeyViolations(request: unknown): StudioGraphExecuteRemovedKey[];
|
|
35
|
+
/**
|
|
36
|
+
* Rejects removed studio execute keys (notably `graphDefaultModel`).
|
|
37
|
+
* Hosts must persist model defaults on `graph.modelConfig` and stop sending request-level mirrors.
|
|
38
|
+
*/
|
|
39
|
+
export declare function assertCanonicalStudioGraphExecuteRequest(request: unknown, context?: {
|
|
40
|
+
jobId?: string;
|
|
41
|
+
graphId?: string;
|
|
42
|
+
}): asserts request is StudioGraphExecuteRequest;
|
|
43
|
+
export type BuildGraphExecutionRequestFromStudioExecuteOptions = {
|
|
44
|
+
agentId?: string;
|
|
45
|
+
jobTypeId?: string;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Maps a validated studio execute envelope to {@link ExecuteGraphInput}.
|
|
49
|
+
* Does not read or merge removed request-level model defaults — graph.modelConfig is authoritative.
|
|
50
|
+
*/
|
|
51
|
+
export declare function buildGraphExecutionRequestFromStudioExecute(request: StudioGraphExecuteRequest, options?: BuildGraphExecutionRequestFromStudioExecuteOptions): ExecuteGraphInput;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { compileExellixExecutablePlan } from '../adapters/compileExellixExecutablePlan.js';
|
|
2
|
+
import { ExellixGraphError } from '../errors/ExellixGraphError.js';
|
|
3
|
+
import { ExellixGraphErrorCode } from '../errors/exellixGraphErrorCodes.js';
|
|
4
|
+
/**
|
|
5
|
+
* Removed from the graphs-studio execute envelope (7.8.3+). No reader, no fallback.
|
|
6
|
+
* Default model selection belongs on {@link Graph}.modelConfig only.
|
|
7
|
+
*/
|
|
8
|
+
export const STUDIO_GRAPH_EXECUTE_REMOVED_KEYS = ['graphDefaultModel'];
|
|
9
|
+
function isPlainObject(v) {
|
|
10
|
+
return v != null && typeof v === 'object' && !Array.isArray(v);
|
|
11
|
+
}
|
|
12
|
+
export function getStudioGraphExecuteRemovedKeyViolations(request) {
|
|
13
|
+
if (!isPlainObject(request))
|
|
14
|
+
return [];
|
|
15
|
+
return STUDIO_GRAPH_EXECUTE_REMOVED_KEYS.filter((key) => Object.prototype.hasOwnProperty.call(request, key));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Rejects removed studio execute keys (notably `graphDefaultModel`).
|
|
19
|
+
* Hosts must persist model defaults on `graph.modelConfig` and stop sending request-level mirrors.
|
|
20
|
+
*/
|
|
21
|
+
export function assertCanonicalStudioGraphExecuteRequest(request, context) {
|
|
22
|
+
const removed = getStudioGraphExecuteRemovedKeyViolations(request);
|
|
23
|
+
if (removed.length > 0) {
|
|
24
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, `Studio graph execute request must not include removed field(s): ${removed.join(', ')}. Model defaults belong on graph.modelConfig only; request-level graphDefaultModel was removed with no legacy fallback.`, { ...context, removedKeys: removed });
|
|
25
|
+
}
|
|
26
|
+
if (!isPlainObject(request)) {
|
|
27
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, 'Studio graph execute request must be a plain object.', context);
|
|
28
|
+
}
|
|
29
|
+
if (request.mode !== 'graph') {
|
|
30
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, 'Studio graph execute request mode must be "graph".', context);
|
|
31
|
+
}
|
|
32
|
+
const jobId = typeof request.jobId === 'string' ? request.jobId.trim() : '';
|
|
33
|
+
if (!jobId) {
|
|
34
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, 'Studio graph execute request requires a non-empty jobId.', context);
|
|
35
|
+
}
|
|
36
|
+
if (!isPlainObject(request.graph)) {
|
|
37
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, 'Studio graph execute request requires graph (GraphModelObject).', { ...context, jobId });
|
|
38
|
+
}
|
|
39
|
+
if (!isPlainObject(request.runtime)) {
|
|
40
|
+
throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, 'Studio graph execute request requires runtime.', { ...context, jobId });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Maps a validated studio execute envelope to {@link ExecuteGraphInput}.
|
|
45
|
+
* Does not read or merge removed request-level model defaults — graph.modelConfig is authoritative.
|
|
46
|
+
*/
|
|
47
|
+
export function buildGraphExecutionRequestFromStudioExecute(request, options) {
|
|
48
|
+
assertCanonicalStudioGraphExecuteRequest(request);
|
|
49
|
+
const jobId = request.jobId.trim();
|
|
50
|
+
const graphId = request.graph != null && typeof request.graph === 'object' && typeof request.graph.id === 'string'
|
|
51
|
+
? request.graph.id
|
|
52
|
+
: undefined;
|
|
53
|
+
const runtime = {
|
|
54
|
+
jobId,
|
|
55
|
+
job: {
|
|
56
|
+
id: jobId,
|
|
57
|
+
jobId,
|
|
58
|
+
agentId: options?.agentId ?? 'standalone-agent',
|
|
59
|
+
jobTypeId: options?.jobTypeId ?? 'exellix-graph-job',
|
|
60
|
+
},
|
|
61
|
+
...request.runtime,
|
|
62
|
+
...(request.graphExecution?.mode != null ? { mode: request.graphExecution.mode } : {}),
|
|
63
|
+
...(request.graphExecution?.goalNodeId != null
|
|
64
|
+
? { goalNodeId: request.graphExecution.goalNodeId }
|
|
65
|
+
: {}),
|
|
66
|
+
...(request.graphExecution?.dimension != null ? { dimension: request.graphExecution.dimension } : {}),
|
|
67
|
+
...(request.runLogMode != null ? { runLogMode: request.runLogMode } : {}),
|
|
68
|
+
...(request.maxRunLogEntries != null ? { maxRunLogEntries: request.maxRunLogEntries } : {}),
|
|
69
|
+
...(request.maxRunLogDataJsonChars != null
|
|
70
|
+
? { maxRunLogDataJsonChars: request.maxRunLogDataJsonChars }
|
|
71
|
+
: {}),
|
|
72
|
+
...(request.runTaskDiagnostics != null ? { runTaskDiagnostics: request.runTaskDiagnostics } : {}),
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
plan: compileExellixExecutablePlan(request.graph, runtime),
|
|
76
|
+
runtime,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Graph, GraphModelObject } from '../src/types/refs.js';
|
|
2
|
+
import type { ExecuteGraphInput, GraphRuntimeObject } from '../src/runtime/ExellixGraphRuntime.js';
|
|
3
|
+
/** Builds `{ plan, runtime }` for tests and hosts that still author exellix GraphModelObject JSON. */
|
|
4
|
+
export declare function buildExecuteGraphInput(model: Graph | GraphModelObject, runtime: GraphRuntimeObject): ExecuteGraphInput;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { compileExellixExecutablePlan } from '../src/adapters/compileExellixExecutablePlan.js';
|
|
2
|
+
/** Builds `{ plan, runtime }` for tests and hosts that still author exellix GraphModelObject JSON. */
|
|
3
|
+
export function buildExecuteGraphInput(model, runtime) {
|
|
4
|
+
return {
|
|
5
|
+
plan: compileExellixExecutablePlan(model, runtime),
|
|
6
|
+
runtime,
|
|
7
|
+
};
|
|
8
|
+
}
|
package/dist/testkit/index.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ export { InMemoryGraphLoader } from './inMemoryGraphLoader.js';
|
|
|
2
2
|
export { DepGraphEngineFactory } from './depGraphEngineFactory.js';
|
|
3
3
|
export { RealTasksClient } from './RealTasksClient.js';
|
|
4
4
|
export { createTestExellixGraphRuntime } from './testModelAliasRuntime.js';
|
|
5
|
+
export { buildExecuteGraphInput } from './buildExecuteGraphInput.js';
|
|
5
6
|
export { tryLoadExellixAiTasksRuntimeSubtree, loadExellixGraphRuntimeObjects, } from './exellixRuntimeObjects.js';
|
package/dist/testkit/index.js
CHANGED
|
@@ -2,4 +2,5 @@ export { InMemoryGraphLoader } from './inMemoryGraphLoader.js';
|
|
|
2
2
|
export { DepGraphEngineFactory } from './depGraphEngineFactory.js';
|
|
3
3
|
export { RealTasksClient } from './RealTasksClient.js';
|
|
4
4
|
export { createTestExellixGraphRuntime } from './testModelAliasRuntime.js';
|
|
5
|
+
export { buildExecuteGraphInput } from './buildExecuteGraphInput.js';
|
|
5
6
|
export { tryLoadExellixAiTasksRuntimeSubtree, loadExellixGraphRuntimeObjects, } from './exellixRuntimeObjects.js';
|
|
@@ -9,12 +9,12 @@ export function createTestExellixGraphRuntime(opts) {
|
|
|
9
9
|
const base = input.runtime ?? {};
|
|
10
10
|
const jobId = typeof base.jobId === 'string' && base.jobId.trim() !== ''
|
|
11
11
|
? base.jobId
|
|
12
|
-
: `job-${input.
|
|
12
|
+
: `job-${input.plan.source.graphId}`;
|
|
13
13
|
const job = base.job != null && typeof base.job === 'object'
|
|
14
14
|
? base.job
|
|
15
15
|
: { agentId: 'test-agent', jobType: 'unit-test' };
|
|
16
16
|
return executeGraph({
|
|
17
|
-
|
|
17
|
+
plan: input.plan,
|
|
18
18
|
runtime: { ...base, jobId, job },
|
|
19
19
|
});
|
|
20
20
|
};
|