@exellix/graph-engine 7.8.2 → 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.
Files changed (37) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +16 -13
  3. package/dist/src/adapters/compileExellixExecutablePlan.d.ts +8 -0
  4. package/dist/src/adapters/compileExellixExecutablePlan.js +18 -0
  5. package/dist/src/adapters/migrateExellixGraphModelToAuthoring.d.ts +6 -0
  6. package/dist/src/adapters/migrateExellixGraphModelToAuthoring.js +273 -0
  7. package/dist/src/adapters/patchFinalizerPlans.d.ts +7 -0
  8. package/dist/src/adapters/patchFinalizerPlans.js +63 -0
  9. package/dist/src/errors/exellixGraphErrorCodes.d.ts +5 -0
  10. package/dist/src/errors/exellixGraphErrorCodes.js +5 -0
  11. package/dist/src/index.d.ts +9 -2
  12. package/dist/src/index.js +6 -2
  13. package/dist/src/plan/aiModelSelectionWire.d.ts +11 -0
  14. package/dist/src/plan/aiModelSelectionWire.js +39 -0
  15. package/dist/src/plan/applyNodePlanInvoke.d.ts +10 -0
  16. package/dist/src/plan/applyNodePlanInvoke.js +67 -0
  17. package/dist/src/plan/embeddedGraphToExellixGraph.d.ts +5 -0
  18. package/dist/src/plan/embeddedGraphToExellixGraph.js +131 -0
  19. package/dist/src/plan/planDeferredGates.d.ts +16 -0
  20. package/dist/src/plan/planDeferredGates.js +118 -0
  21. package/dist/src/plan/planExecuteEntry.d.ts +12 -0
  22. package/dist/src/plan/planExecuteEntry.js +73 -0
  23. package/dist/src/plan/planExecutionPipeline.d.ts +11 -0
  24. package/dist/src/plan/planExecutionPipeline.js +54 -0
  25. package/dist/src/plan/planModelConfig.d.ts +10 -0
  26. package/dist/src/plan/planModelConfig.js +46 -0
  27. package/dist/src/runtime/ExellixGraphRuntime.d.ts +9 -6
  28. package/dist/src/runtime/ExellixGraphRuntime.js +134 -96
  29. package/dist/src/runtime/executionMatrixHost.js +2 -1
  30. package/dist/src/runtime/studioGraphExecuteRequest.d.ts +51 -0
  31. package/dist/src/runtime/studioGraphExecuteRequest.js +78 -0
  32. package/dist/testkit/buildExecuteGraphInput.d.ts +4 -0
  33. package/dist/testkit/buildExecuteGraphInput.js +8 -0
  34. package/dist/testkit/index.d.ts +1 -0
  35. package/dist/testkit/index.js +1 -0
  36. package/dist/testkit/testModelAliasRuntime.js +2 -2
  37. package/package.json +7 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 8.0.0
4
+
5
+ ### Breaking
6
+
7
+ - **`executeGraph({ plan, runtime })` only** — removed `ExecuteGraphInput.model`. Hosts compile with `compileExellixExecutablePlan` or `@x12i/graphenix-plan-compiler` before invoke.
8
+ - **`ExecuteGraphResult.graphAudit` → `planAudit`** (`{ planHash, source }`).
9
+ - Execute-time model case selection and `assertCanonicalGraphDocument` removed from the run path; frozen `plan.nodePlans` / `plan.finalizerPlans` drive model config and invoke contracts.
10
+
11
+ ### Added
12
+
13
+ - **`@x12i/graphenix-*` integration:** plan validation, deferred gates, trace events (`ExecuteGraphResult.trace`), and exports for `ExecutableGraphPlanV2`.
14
+ - **`compileExellixExecutablePlan`**, **`migrateExellixGraphModelToAuthoring`** — host compile helpers for legacy exellix `GraphModelObject`.
15
+ - **`buildAuthoringStudioGraphExecutionRequest`** re-export from `@x12i/graphenix-execute-envelope`.
16
+
17
+ See [`BREAKING-CHANGES.md`](BREAKING-CHANGES.md) §8.0.0.
18
+
19
+ ## 7.8.3
20
+
21
+ ### Breaking
22
+
23
+ - **Studio graph execute envelope:** `graphDefaultModel` removed with **no legacy reader**. Request-level model defaults duplicated `graph.modelConfig` and were never applied by graph-engine. Hosts (graphs-studio) must persist defaults on **`graph.modelConfig` only** and stop sending `graphDefaultModel`.
24
+ - **Validation:** `assertCanonicalStudioGraphExecuteRequest` and `buildGraphExecutionRequestFromStudioExecute` reject the removed key (`NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST`).
25
+
26
+ ### Added
27
+
28
+ - **Documentation:** [`formats-documentations/graph-format-boundaries.md`](formats-documentations/graph-format-boundaries.md) — four-layer model (graph / runtime / studio envelope / studio DB), field mapping from playground payloads, and explicit list of what is not on `GraphModelObject`.
29
+
3
30
  ## 7.8.2
4
31
 
5
32
  ### Fixed
package/README.md CHANGED
@@ -4,7 +4,7 @@ A minimal, focused SDK for executing graphs in the exellix ecosystem.
4
4
 
5
5
  ## What this package does and does not
6
6
 
7
- **In scope — what this package does:** On each **`createExellixGraphRuntime(...).executeGraph({ model, runtime })`** invocation, it runs **exactly one graph run** (validate modelplan → nodes → finalizer) until that run **completes or fails**, then returns the result. There is **no** batching, queueing, or multi-job orchestration inside this package — each call is one logical run. You supply the static **`model: GraphModelObject`**, planner (`GraphEngineFactory`), tasks client, and dynamic **`runtime: GraphRuntimeObject`** with the mandatory host `jobId`, `job` envelope, active `input`, memory, variables, and per-run options. The engine generates a fresh **`taskId`** (UUID) per invocation and sends it on **every** `runTask` request together with **`jobId`**. That is the **entire** product role of graph-engine.
7
+ **In scope — what this package does:** On each **`createExellixGraphRuntime(...).executeGraph({ plan, runtime })`** invocation, it runs **exactly one graph run** (validate planschedule waves → nodes → finalizer) until that run **completes or fails**, then returns the result. There is **no** batching, queueing, or multi-job orchestration inside this package — each call is one logical run. You supply a compiled v2 **`plan: ExecutableGraphPlanV2`** (from `@x12i/graphenix-plan-compiler` or {@link compileExellixExecutablePlan}), planner (`GraphEngineFactory`), tasks client, and dynamic **`runtime: GraphRuntimeObject`** with the mandatory host `jobId`, `job` envelope, active `input`, memory, variables, and per-run options. The engine generates a fresh **`taskId`** (UUID) per invocation and sends it on **every** `runTask` request together with **`jobId`**. That is the **entire** product role of graph-engine.
8
8
 
9
9
  **Out of scope — what this package does not do:** It does **not** schedule work, own **execution matrices**, manage **claims** or **rows**, persist **job** lifecycle, implement **retry/requeue policy**, or **track** runs across tenants or sessions. Optional helpers and docs for matrix *hosts* only help **build arguments** for the same single-run API; they do **not** move orchestration into this package. Integrations (e.g. Activix graph-run events) emit data **for that call** when you wire an `eventEmitter` — they do not make graph-engine a workflow or matrix service.
10
10
 
@@ -12,8 +12,8 @@ A minimal, focused SDK for executing graphs in the exellix ecosystem.
12
12
 
13
13
  `exellix-graph-engine` does exactly this loop:
14
14
 
15
- 1. **Validate graph model:** The caller supplies **`model: GraphModelObject`** directly in the execution request. The effective correlation **`graphId`** on the result and in telemetry is **`model.id`**.
16
- 2. `plan = graphenix.plan(...)`
15
+ 1. **Validate executable plan:** The caller supplies **`plan: ExecutableGraphPlanV2`** (compiled upstream). The effective correlation **`graphId`** on the result and in telemetry is **`plan.source.graphId`**.
16
+ 2. `executeGraph({ plan, runtime })`
17
17
  3. For each runnable node:
18
18
  - Map node → `skillKey` (strict rules)
19
19
  - If `skillKey` is a **local skill** (`scoped-data-reader`, `deterministic-rule`, `scoped-answer-writer`, `scoped-answer-assembler`), run it in-process (no `ai-tasks` call); otherwise call `ai-tasks.runTask(...)`
@@ -97,7 +97,7 @@ Lower-level helpers (`validateRunTaskConfig`, `validateRunTaskInvoke`, `analyzeE
97
97
  ## Quick Start
98
98
 
99
99
  ```typescript
100
- import { createExellixGraphRuntime } from '@exellix/graph-engine';
100
+ import { compileExellixExecutablePlan, createExellixGraphRuntime } from '@exellix/graph-engine';
101
101
 
102
102
  const runtime = createExellixGraphRuntime({
103
103
  graphLoader: myGraphLoader,
@@ -121,15 +121,18 @@ const graphModel = {
121
121
  // …
122
122
  };
123
123
 
124
+ const graphRuntime = {
125
+ jobId: 'job-123',
126
+ job: { agentId: 'agent-1', input: {} },
127
+ input: { question: 'Analyze this record' },
128
+ };
129
+
130
+ const plan = compileExellixExecutablePlan(graphModel, graphRuntime);
131
+
124
132
  // Host correlation id (required). Engine sets `job.id` / `job.jobId` from it and generates `result.taskId`.
125
133
  const result = await runtime.executeGraph({
126
- model: graphModel,
127
- runtime: {
128
- jobId: 'job-123',
129
- job: { agentId: 'agent-1', input: {} },
130
- input: { question: 'Analyze this record' },
131
- // no modelConfig or aliasConfig on runtime (removed in 7.7)
132
- },
134
+ plan,
135
+ runtime: graphRuntime,
133
136
  });
134
137
 
135
138
  // Canonical business output + per-run ids:
@@ -138,11 +141,11 @@ console.log(result.finalOutput, result.jobId, result.taskId);
138
141
 
139
142
  ## Public API
140
143
 
141
- ### `runtime.executeGraph({ model, runtime })`
144
+ ### `runtime.executeGraph({ plan, runtime })`
142
145
 
143
146
  Execute a complete graph through the **single canonical client API**: `createExellixGraphRuntime(...)`. The runtime owns local-skill interception, conditional edge filtering, optional `eventEmitter`, optional `debugMode`, and produces one `ExecuteGraphResult` shape.
144
147
 
145
- Every call requires a static **`model: GraphModelObject`** and a dynamic **`runtime: GraphRuntimeObject`**. `runtime.jobId` is mandatory and non-empty. The engine also generates a **`taskId`** (UUID) per invocation. Together they form the **identity** forwarded to **`@exellix/ai-tasks`** (`runTask({ jobId, taskId, … })`), graph/node **`eventEmitter`** payloads, structured **`runLog`**, and Activix **`runContext`** / record metadata.
148
+ Every call requires a compiled v2 **`plan: ExecutableGraphPlanV2`** and a dynamic **`runtime: GraphRuntimeObject`**. Hosts compile authoring graphs with `@x12i/graphenix-plan-compiler` (or {@link compileExellixExecutablePlan} for legacy exellix `GraphModelObject`) before invoke. `runtime.jobId` is mandatory and non-empty. The engine also generates a **`taskId`** (UUID) per invocation. Together they form the **identity** forwarded to **`@exellix/ai-tasks`** (`runTask({ jobId, taskId, … })`), graph/node **`eventEmitter`** payloads, structured **`runLog`**, and Activix **`runContext`** / record metadata.
146
149
 
147
150
  ```typescript
148
151
  import { createExellixGraphRuntime } from '@exellix/graph-engine';
@@ -0,0 +1,8 @@
1
+ import type { CompileExecutablePlanV2Options, ExecutableGraphPlanV2 } from '@x12i/graphenix-executable-contracts';
2
+ import type { Graph, GraphModelObject } from '../types/refs.js';
3
+ import type { GraphRuntimeObject } from '../runtime/ExellixGraphRuntime.js';
4
+ export type CompileExellixExecutablePlanOptions = CompileExecutablePlanV2Options;
5
+ /**
6
+ * Host/test helper: exellix graph model + runtime → validated v2 executable plan.
7
+ */
8
+ export declare function compileExellixExecutablePlan(model: Graph | GraphModelObject, runtime: GraphRuntimeObject, options?: CompileExellixExecutablePlanOptions): ExecutableGraphPlanV2;
@@ -0,0 +1,18 @@
1
+ import { compileExecutablePlanV2 } from '@x12i/graphenix-plan-compiler';
2
+ import { validateExecutablePlan } from '@x12i/graphenix-plan-format';
3
+ import { migrateExellixGraphModelToAuthoring } from './migrateExellixGraphModelToAuthoring.js';
4
+ import { patchFinalizerPlansOnExecutablePlan } from './patchFinalizerPlans.js';
5
+ /**
6
+ * Host/test helper: exellix graph model + runtime → validated v2 executable plan.
7
+ */
8
+ export function compileExellixExecutablePlan(model, runtime, options) {
9
+ const authoring = migrateExellixGraphModelToAuthoring(model);
10
+ const compiled = compileExecutablePlanV2(authoring, runtime, options);
11
+ const plan = patchFinalizerPlansOnExecutablePlan(compiled, runtime);
12
+ const validation = validateExecutablePlan(plan);
13
+ if (!validation.valid) {
14
+ const summary = validation.errors.map((e) => `${e.path}: ${e.message}`).join('; ');
15
+ throw new Error(`compileExellixExecutablePlan: invalid plan: ${summary}`);
16
+ }
17
+ return plan;
18
+ }
@@ -0,0 +1,6 @@
1
+ import { type AuthoringGraphDocument } from '@x12i/graphenix-executable-contracts';
2
+ import type { Graph, GraphModelObject } from '../types/refs.js';
3
+ /**
4
+ * Converts exellix {@link GraphModelObject} to graphenix {@link AuthoringGraphDocument} for plan compilation.
5
+ */
6
+ export declare function migrateExellixGraphModelToAuthoring(model: Graph | GraphModelObject): AuthoringGraphDocument;
@@ -0,0 +1,273 @@
1
+ import { GRAPHENIX_FORMAT_VERSION } from '@x12i/graphenix-core';
2
+ import { EXECUTABLE_PROFILE_NAMESPACE, EXECUTABLE_PROFILE_VERSION, FINALIZER_NODE_KIND, FINALIZER_NODE_PROFILE, TASK_NODE_KIND, TASK_NODE_PROFILE, } from '@x12i/graphenix-executable-contracts';
3
+ import { EXELLIX_STRUCTURED_DATA_FILTERS_V1 } from '../types/refs.js';
4
+ import { DEFAULT_GRAPH_AI_MODEL_PROFILE_CONFIG } from '../runtime/graphAiModelConfig.js';
5
+ import { migrateLegacyGraphResponse } from '../runtime/graphResponseMigration.js';
6
+ function isPlainRecord(v) {
7
+ return v != null && typeof v === 'object' && !Array.isArray(v);
8
+ }
9
+ function aliasToProfileChoice(value) {
10
+ const trimmed = value.trim();
11
+ if (trimmed.includes('/'))
12
+ return { kind: 'profileChoice', key: trimmed };
13
+ const shortcuts = {
14
+ cheap: 'cheap/default',
15
+ balanced: 'vol/default',
16
+ deep: 'deep/openai_deep',
17
+ pro: 'pro/default',
18
+ };
19
+ const key = shortcuts[trimmed] ?? `${trimmed}/default`;
20
+ return { kind: 'profileChoice', key };
21
+ }
22
+ function exellixModelConfigToExecutable(modelConfig) {
23
+ if (!modelConfig?.cases?.length)
24
+ return undefined;
25
+ const cases = modelConfig.cases.map((c, index) => {
26
+ const mc = c.modelConfig;
27
+ const modelConfigOut = {};
28
+ if (mc.preActionModel != null && String(mc.preActionModel).trim() !== '') {
29
+ modelConfigOut.preActionModel = aliasToProfileChoice(String(mc.preActionModel));
30
+ }
31
+ if (mc.skillModel != null && String(mc.skillModel).trim() !== '') {
32
+ modelConfigOut.skillModel = aliasToProfileChoice(String(mc.skillModel));
33
+ }
34
+ if (mc.postActionModel != null && String(mc.postActionModel).trim() !== '') {
35
+ modelConfigOut.postActionModel = aliasToProfileChoice(String(mc.postActionModel));
36
+ }
37
+ return {
38
+ id: c.when == null ? 'default' : `case-${index}`,
39
+ ...(c.when != null ? { when: c.when } : {}),
40
+ modelConfig: modelConfigOut,
41
+ };
42
+ });
43
+ const hasPartialCase = cases.some((c) => !c.modelConfig.preActionModel ||
44
+ !c.modelConfig.skillModel ||
45
+ !c.modelConfig.postActionModel);
46
+ return {
47
+ version: 'graph-model-config/v1',
48
+ cases,
49
+ ...(hasPartialCase ? { inherit: true } : {}),
50
+ };
51
+ }
52
+ const DEFAULT_EXECUTABLE_FALLBACK_POLICY = {
53
+ enabled: true,
54
+ allowedTriggers: [
55
+ 'nodeSlotMissing',
56
+ 'nodeModelUnavailable',
57
+ 'nodeModelUnsupported',
58
+ 'nodeProviderNotConfigured',
59
+ 'nodeModelRateLimited',
60
+ 'nodeModelTransientFailure',
61
+ ],
62
+ maxAttemptsPerSlot: 1,
63
+ };
64
+ function defaultExecutableModelConfig() {
65
+ const mc = DEFAULT_GRAPH_AI_MODEL_PROFILE_CONFIG;
66
+ return {
67
+ version: 'graph-model-config/v1',
68
+ cases: [
69
+ {
70
+ id: 'default',
71
+ modelConfig: {
72
+ preActionModel: aliasToProfileChoice(mc.preActionModel),
73
+ skillModel: aliasToProfileChoice(mc.skillModel),
74
+ postActionModel: aliasToProfileChoice(mc.postActionModel),
75
+ },
76
+ },
77
+ ],
78
+ fallbackPolicy: DEFAULT_EXECUTABLE_FALLBACK_POLICY,
79
+ };
80
+ }
81
+ function isStructuredExellixDataFilters(value) {
82
+ return (isPlainRecord(value) &&
83
+ value.version === EXELLIX_STRUCTURED_DATA_FILTERS_V1 &&
84
+ isPlainRecord(value.when));
85
+ }
86
+ function exellixGraphEntryToAuthoring(graphEntry) {
87
+ if (!isPlainRecord(graphEntry))
88
+ return graphEntry;
89
+ const next = structuredClone(graphEntry);
90
+ const dataFilters = next.dataFilters;
91
+ if (isStructuredExellixDataFilters(dataFilters)) {
92
+ next.dataFilters = [dataFilters.when];
93
+ }
94
+ return next;
95
+ }
96
+ /** Authoring validation requires jsonConditions as an array; exellix uses a CaseCondition object. */
97
+ function exellixConditionsToAuthoring(conditions) {
98
+ if (Array.isArray(conditions)) {
99
+ return { runWhen: conditions };
100
+ }
101
+ const runWhenEntry = {};
102
+ if (conditions.jsonConditions != null) {
103
+ runWhenEntry.jsonConditions = structuredClone(conditions.jsonConditions);
104
+ }
105
+ if (conditions.jsConditionFunction != null) {
106
+ runWhenEntry.jsConditionFunction = structuredClone(conditions.jsConditionFunction);
107
+ }
108
+ if (conditions.aiCondition != null) {
109
+ runWhenEntry.aiCondition = structuredClone(conditions.aiCondition);
110
+ }
111
+ const out = {};
112
+ if (Object.keys(runWhenEntry).length > 0) {
113
+ out.runWhen = [runWhenEntry];
114
+ }
115
+ if (conditions.dataFilters != null) {
116
+ out.dataFilters = structuredClone(conditions.dataFilters);
117
+ }
118
+ if (conditions.narratives != null) {
119
+ out.narratives = structuredClone(conditions.narratives);
120
+ }
121
+ return out;
122
+ }
123
+ function defaultPorts(nodeId) {
124
+ return {
125
+ in: `in:${nodeId}`,
126
+ out: `out:${nodeId}`,
127
+ };
128
+ }
129
+ function exellixTaskNodeToExecutable(node) {
130
+ const ports = defaultPorts(String(node.id));
131
+ const params = {
132
+ profile: TASK_NODE_PROFILE,
133
+ nodeType: 'task',
134
+ skillKey: node.skillKey,
135
+ };
136
+ if (node.taskConfiguration != null)
137
+ params.taskConfiguration = structuredClone(node.taskConfiguration);
138
+ if (node.taskVariable != null)
139
+ params.taskVariable = structuredClone(node.taskVariable);
140
+ if (node.inputsConfig != null)
141
+ params.inputsConfig = structuredClone(node.inputsConfig);
142
+ if (node.executionMapping != null)
143
+ params.executionMapping = structuredClone(node.executionMapping);
144
+ if (node.conditions != null)
145
+ params.conditions = exellixConditionsToAuthoring(node.conditions);
146
+ if (Array.isArray(node.taskKnowledge)) {
147
+ params.taskKnowledge = exellixKnowledgeRefsToAuthoring(node.taskKnowledge);
148
+ }
149
+ if (node.variables != null)
150
+ params.variables = structuredClone(node.variables);
151
+ if (node.metadata != null)
152
+ params.metadata = structuredClone(node.metadata);
153
+ if (node.executionPipeline != null)
154
+ params.executionPipeline = structuredClone(node.executionPipeline);
155
+ if (node.jobContextMapping != null)
156
+ params.jobContextMapping = structuredClone(node.jobContextMapping);
157
+ if (node.scope != null)
158
+ params.scope = structuredClone(node.scope);
159
+ if (node.smartInput != null)
160
+ params.smartInput = exellixSmartInputToAuthoring(node.smartInput);
161
+ if (node.outputValidation != null)
162
+ params.outputValidation = structuredClone(node.outputValidation);
163
+ const nodeModelConfig = node.taskConfiguration?.modelConfig;
164
+ if (nodeModelConfig != null) {
165
+ const converted = exellixModelConfigToExecutable(nodeModelConfig);
166
+ if (converted) {
167
+ params.taskConfiguration = {
168
+ ...(isPlainRecord(params.taskConfiguration) ? params.taskConfiguration : {}),
169
+ modelConfig: converted,
170
+ };
171
+ }
172
+ }
173
+ return {
174
+ id: String(node.id),
175
+ kind: TASK_NODE_KIND,
176
+ inputs: [{ id: ports.in, direction: 'input', type: 'builtin:object', required: false }],
177
+ outputs: [{ id: ports.out, direction: 'output', type: 'builtin:object' }],
178
+ parameters: params,
179
+ };
180
+ }
181
+ function exellixSmartInputToAuthoring(smartInput) {
182
+ if (!isPlainRecord(smartInput))
183
+ return structuredClone(smartInput);
184
+ const next = structuredClone(smartInput);
185
+ if (Array.isArray(next.paths)) {
186
+ next.paths = next.paths.map((entry) => typeof entry === 'string' ? { path: entry } : entry);
187
+ }
188
+ return next;
189
+ }
190
+ function exellixKnowledgeRefsToAuthoring(refs) {
191
+ return refs.map((ref) => ({ id: ref }));
192
+ }
193
+ function exellixFinalizerToExecutable(node) {
194
+ const n = node;
195
+ const ports = defaultPorts(String(node.id));
196
+ return {
197
+ id: String(node.id),
198
+ kind: FINALIZER_NODE_KIND,
199
+ inputs: [{ id: ports.in, direction: 'input', type: 'builtin:object', required: false }],
200
+ outputs: [{ id: ports.out, direction: 'output', type: 'builtin:object' }],
201
+ parameters: {
202
+ profile: FINALIZER_NODE_PROFILE,
203
+ nodeType: 'finalizer',
204
+ finalizerType: n.finalizerType,
205
+ config: isPlainRecord(n.config) ? structuredClone(n.config) : {},
206
+ ...(n.inputs != null ? { inputs: structuredClone(n.inputs) } : {}),
207
+ ...(n.outputMapping != null ? { outputMapping: structuredClone(n.outputMapping) } : {}),
208
+ },
209
+ };
210
+ }
211
+ /**
212
+ * Converts exellix {@link GraphModelObject} to graphenix {@link AuthoringGraphDocument} for plan compilation.
213
+ */
214
+ export function migrateExellixGraphModelToAuthoring(model) {
215
+ const legacyMigrated = migrateLegacyGraphResponse(structuredClone(model));
216
+ const source = legacyMigrated.graph;
217
+ const executableModelConfig = exellixModelConfigToExecutable(source.modelConfig) ?? defaultExecutableModelConfig();
218
+ const metadata = isPlainRecord(source.metadata) ? structuredClone(source.metadata) : {};
219
+ const extensions = isPlainRecord(metadata.extensions) ? { ...metadata.extensions } : {};
220
+ extensions[EXECUTABLE_PROFILE_NAMESPACE] = {
221
+ ...(isPlainRecord(extensions[EXECUTABLE_PROFILE_NAMESPACE])
222
+ ? extensions[EXECUTABLE_PROFILE_NAMESPACE]
223
+ : {}),
224
+ profileVersion: EXECUTABLE_PROFILE_VERSION,
225
+ modelConfig: executableModelConfig,
226
+ };
227
+ metadata.extensions = extensions;
228
+ if (metadata.graphEntry != null) {
229
+ metadata.graphEntry = exellixGraphEntryToAuthoring(metadata.graphEntry);
230
+ }
231
+ if (Array.isArray(source.jobKnowledge)) {
232
+ metadata.jobKnowledge = exellixKnowledgeRefsToAuthoring(source.jobKnowledge);
233
+ }
234
+ const nodes = (source.nodes ?? []).map((node) => {
235
+ const type = node.type ?? 'task';
236
+ if (type === 'finalizer')
237
+ return exellixFinalizerToExecutable(node);
238
+ return exellixTaskNodeToExecutable(node);
239
+ });
240
+ const portByNode = new Map();
241
+ for (const n of nodes)
242
+ portByNode.set(n.id, defaultPorts(n.id));
243
+ const edges = (source.edges ?? []).map((e, index) => {
244
+ const fromPorts = portByNode.get(e.from) ?? defaultPorts(e.from);
245
+ const toPorts = portByNode.get(e.to) ?? defaultPorts(e.to);
246
+ return {
247
+ id: `edge:${index}:${e.from}->${e.to}`,
248
+ from: { nodeId: e.from, portId: fromPorts.out },
249
+ to: { nodeId: e.to, portId: toPorts.in },
250
+ ...(e.when != null ? { when: e.when } : {}),
251
+ };
252
+ });
253
+ const graphResponse = isPlainRecord(source.response)
254
+ ? { shape: source.response.shape, ...source.response }
255
+ : undefined;
256
+ return {
257
+ formatVersion: GRAPHENIX_FORMAT_VERSION,
258
+ id: String(source.id),
259
+ revision: typeof source.version === 'string' ? source.version : '1.0.0',
260
+ name: typeof metadata.name === 'string' ? metadata.name : String(source.id),
261
+ graph: {
262
+ nodes,
263
+ edges,
264
+ inputs: [],
265
+ outputs: [],
266
+ metadata: {
267
+ ...metadata,
268
+ ...(graphResponse ? { graphResponse } : {}),
269
+ graphEntry: metadata.graphEntry,
270
+ },
271
+ },
272
+ };
273
+ }
@@ -0,0 +1,7 @@
1
+ import type { ExecutableGraphPlanV2 } from '@x12i/graphenix-executable-contracts';
2
+ import type { GraphRuntimeObject } from '../runtime/ExellixGraphRuntime.js';
3
+ /**
4
+ * `@x12i/graphenix-plan-compiler` v1.0.0 builds AI finalizer plans without resolved
5
+ * `finalizerModel` slots. Rebuild finalizer plans using published compiler helpers.
6
+ */
7
+ export declare function patchFinalizerPlansOnExecutablePlan(plan: ExecutableGraphPlanV2, runtime: GraphRuntimeObject): ExecutableGraphPlanV2;
@@ -0,0 +1,63 @@
1
+ import { getFinalizerNodeConfig, isExecutableFinalizerNode, } from '@x12i/graphenix-executable-contracts';
2
+ import { buildFinalizerPlans, buildDeterministicCaseContext, resolveGraphModelConfig, } from '@x12i/graphenix-plan-compiler';
3
+ const AI_FINALIZER_TYPES = new Set(['synthesize']);
4
+ /**
5
+ * `@x12i/graphenix-plan-compiler` v1.0.0 builds AI finalizer plans without resolved
6
+ * `finalizerModel` slots. Rebuild finalizer plans using published compiler helpers.
7
+ */
8
+ export function patchFinalizerPlansOnExecutablePlan(plan, runtime) {
9
+ if (plan.normalizedGraph.mode !== 'embedded')
10
+ return plan;
11
+ const normalized = plan.normalizedGraph.graph;
12
+ const aiFinalizers = normalized.graph.nodes.filter(isExecutableFinalizerNode).filter((node) => {
13
+ const config = getFinalizerNodeConfig(node);
14
+ return config != null && AI_FINALIZER_TYPES.has(String(config.finalizerType));
15
+ });
16
+ if (aiFinalizers.length === 0)
17
+ return plan;
18
+ const context = buildDeterministicCaseContext(normalized, runtime);
19
+ const graphResolution = resolveGraphModelConfig(normalized, context);
20
+ const triplet = graphResolution.triplet;
21
+ if (triplet.preActionModel == null && triplet.skillModel == null && triplet.postActionModel == null) {
22
+ return plan;
23
+ }
24
+ const slotMeta = {
25
+ source: 'graphDefault',
26
+ inherited: true,
27
+ graphCaseId: graphResolution.caseId,
28
+ };
29
+ const resolvedFinalizerSlots = {};
30
+ for (const node of aiFinalizers) {
31
+ const finalizerSelection = triplet.postActionModel ?? triplet.skillModel ?? triplet.preActionModel;
32
+ if (finalizerSelection == null)
33
+ continue;
34
+ resolvedFinalizerSlots[node.id] = {
35
+ selection: finalizerSelection,
36
+ ...slotMeta,
37
+ };
38
+ }
39
+ const finalizerPlans = buildFinalizerPlans(normalized, resolvedFinalizerSlots);
40
+ for (const node of aiFinalizers) {
41
+ const entry = finalizerPlans[node.id];
42
+ if (!entry)
43
+ continue;
44
+ entry.modelSlots = {
45
+ ...(triplet.preActionModel != null
46
+ ? { preActionModel: { selection: triplet.preActionModel, ...slotMeta } }
47
+ : {}),
48
+ ...(triplet.skillModel != null
49
+ ? { skillModel: { selection: triplet.skillModel, ...slotMeta } }
50
+ : {}),
51
+ ...(triplet.postActionModel != null
52
+ ? { postActionModel: { selection: triplet.postActionModel, ...slotMeta } }
53
+ : {}),
54
+ ...(entry.modelSlots?.finalizerModel != null
55
+ ? { finalizerModel: entry.modelSlots.finalizerModel }
56
+ : {}),
57
+ };
58
+ }
59
+ return {
60
+ ...plan,
61
+ finalizerPlans,
62
+ };
63
+ }
@@ -13,6 +13,11 @@ export declare enum ExellixGraphErrorCode {
13
13
  INVALID_GRAPH = "INVALID_GRAPH",
14
14
  /** Graph JSON has forbidden top-level keys; document metadata must be under `metadata` only. */
15
15
  NON_CANONICAL_GRAPH_DOCUMENT = "NON_CANONICAL_GRAPH_DOCUMENT",
16
+ /**
17
+ * Graphs-studio execute envelope carries a removed or forbidden key (e.g. request-level `graphDefaultModel`).
18
+ * Model defaults belong on `graph.modelConfig` only.
19
+ */
20
+ NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST = "NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST",
16
21
  /** Runtime object carries forbidden model / llmCall overrides (graph document only since 7.7). */
17
22
  NON_CANONICAL_GRAPH_RUNTIME = "NON_CANONICAL_GRAPH_RUNTIME",
18
23
  /**
@@ -14,6 +14,11 @@ export var ExellixGraphErrorCode;
14
14
  ExellixGraphErrorCode["INVALID_GRAPH"] = "INVALID_GRAPH";
15
15
  /** Graph JSON has forbidden top-level keys; document metadata must be under `metadata` only. */
16
16
  ExellixGraphErrorCode["NON_CANONICAL_GRAPH_DOCUMENT"] = "NON_CANONICAL_GRAPH_DOCUMENT";
17
+ /**
18
+ * Graphs-studio execute envelope carries a removed or forbidden key (e.g. request-level `graphDefaultModel`).
19
+ * Model defaults belong on `graph.modelConfig` only.
20
+ */
21
+ ExellixGraphErrorCode["NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST"] = "NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST";
17
22
  /** Runtime object carries forbidden model / llmCall overrides (graph document only since 7.7). */
18
23
  ExellixGraphErrorCode["NON_CANONICAL_GRAPH_RUNTIME"] = "NON_CANONICAL_GRAPH_RUNTIME";
19
24
  /**
@@ -9,8 +9,8 @@
9
9
  * 5. Repeat until done/fail
10
10
  *
11
11
  * Single canonical entry point: {@link createExellixGraphRuntime} returns
12
- * `{ executeGraph({ model, runtime }: GraphExecutionRequest) }`. The legacy functional `executeGraph`
13
- * and the `ExellixGraphClient` class were removed in 5.0.
12
+ * `{ executeGraph({ plan, runtime }: GraphExecutionRequest) }`. Hosts compile upstream via
13
+ * {@link compileExellixExecutablePlan} or `@x12i/graphenix-plan-compiler`.
14
14
  */
15
15
  export type { HostExecuteGraphRunOptions, MainReadinessPolicy, ExecutionStepOption, StepRetryPolicy, ActivixNodeActivityExellixConfig, SkillKeyResolutionOptions, RunTaskRequest as ExellixGraphRunTaskRequest, RunTaskResponse as ExellixGraphRunTaskResponse, } from './types/options.js';
16
16
  export type { AiTaskProfileMetadata, AiTaskProfileWebScoping, AiTaskProfileInputSynthesis, } from './types/aiTaskProfile.js';
@@ -83,11 +83,18 @@ export type { AssertCanonicalGraphDocumentOptions, CanonicalGraphDocumentValidat
83
83
  export { assertCanonicalGraphRuntimeObject } from './runtime/validateCanonicalGraphRuntime.js';
84
84
  export { GRAPH_ENTRY_STUDIO_ONLY_KEYS, GRAPH_METADATA_STUDIO_ONLY_KEYS, stripGraphModelStudioFields, primaryRuntimeInputFromStudioDocument, getGraphEntryStudioOnlyKeyViolations, getGraphMetadataStudioOnlyKeyViolations, getGraphEntryEmptyInputPathViolations, } from './runtime/graphModelStudioSeparation.js';
85
85
  export type { GraphStudioDocument } from './runtime/graphModelStudioSeparation.js';
86
+ export { STUDIO_GRAPH_EXECUTE_REMOVED_KEYS, getStudioGraphExecuteRemovedKeyViolations, assertCanonicalStudioGraphExecuteRequest, buildGraphExecutionRequestFromStudioExecute, } from './runtime/studioGraphExecuteRequest.js';
87
+ export type { StudioGraphExecuteRemovedKey, StudioGraphExecuteRequest, BuildGraphExecutionRequestFromStudioExecuteOptions, } from './runtime/studioGraphExecuteRequest.js';
86
88
  export { computeGraphDocumentContentSha256, stableStringifyGraphDocument, } from './runtime/graphDocumentFingerprint.js';
87
89
  export { migrateLegacyGraphResponse, migrateLegacyGraphResponseDefinition, } from './runtime/graphResponseMigration.js';
88
90
  export type { MigrateGraphResponseResult } from './runtime/graphResponseMigration.js';
89
91
  export { applyAiTaskProfileWebScopingToNarrix, mapAiTaskProfileQuestionsToWebScopeQuestions, } from './runtime/applyAiTaskProfileWebScopingToNarrix.js';
90
92
  export { createExellixGraphRuntime } from './runtime/ExellixGraphRuntime.js';
93
+ export type { ExecutableGraphPlanV2, NodeExecutionPlan, ExecutionUnitPlanV2, } from '@x12i/graphenix-executable-contracts';
94
+ export { compileExellixExecutablePlan } from './adapters/compileExellixExecutablePlan.js';
95
+ export type { CompileExellixExecutablePlanOptions } from './adapters/compileExellixExecutablePlan.js';
96
+ export { migrateExellixGraphModelToAuthoring } from './adapters/migrateExellixGraphModelToAuthoring.js';
97
+ export { buildGraphExecutionRequestFromStudioExecute as buildAuthoringStudioGraphExecutionRequest } from '@x12i/graphenix-execute-envelope';
91
98
  export type { RunTaskRequest, RunTaskResponse, TasksClientLike, GraphLoader as RuntimeGraphLoader, ExecuteGraphInput, GraphExecutionRequest, GraphRuntimeObject, GraphKnowledgeResolver, GraphKnowledgeResolverContext, ExecuteGraphResult, ExellixGraphRuntimeOptions, } from './runtime/ExellixGraphRuntime.js';
92
99
  export type { PlanStatus, GraphPlan, GraphEngine, GraphEngineFactory, } from './runtime/GraphEngine.js';
93
100
  export { InMemoryGraphLoader, DepGraphEngineFactory } from '../testkit/index.js';
package/dist/src/index.js CHANGED
@@ -9,8 +9,8 @@
9
9
  * 5. Repeat until done/fail
10
10
  *
11
11
  * Single canonical entry point: {@link createExellixGraphRuntime} returns
12
- * `{ executeGraph({ model, runtime }: GraphExecutionRequest) }`. The legacy functional `executeGraph`
13
- * and the `ExellixGraphClient` class were removed in 5.0.
12
+ * `{ executeGraph({ plan, runtime }: GraphExecutionRequest) }`. Hosts compile upstream via
13
+ * {@link compileExellixExecutablePlan} or `@x12i/graphenix-plan-compiler`.
14
14
  */
15
15
  export { mergeGraphDocumentModel, EXELLIX_GRAPH_MODEL_VARIABLE_KEY, EXELLIX_STRUCTURED_DATA_FILTERS_V1, } from './types/refs.js';
16
16
  export { getTaskConfiguration } from './types/taskNodeConfiguration.js';
@@ -61,11 +61,15 @@ export { buildExellixGraphRuntimeObjects, setRuntimeObjectsLastJobId, summarizeR
61
61
  export { assertCanonicalGraphDocument, assertCanonicalTaskNode, getCanonicalGraphDocumentViolations, CANONICAL_GRAPH_TOP_LEVEL_KEYS, } from './runtime/validateCanonicalGraphDocument.js';
62
62
  export { assertCanonicalGraphRuntimeObject } from './runtime/validateCanonicalGraphRuntime.js';
63
63
  export { GRAPH_ENTRY_STUDIO_ONLY_KEYS, GRAPH_METADATA_STUDIO_ONLY_KEYS, stripGraphModelStudioFields, primaryRuntimeInputFromStudioDocument, getGraphEntryStudioOnlyKeyViolations, getGraphMetadataStudioOnlyKeyViolations, getGraphEntryEmptyInputPathViolations, } from './runtime/graphModelStudioSeparation.js';
64
+ export { STUDIO_GRAPH_EXECUTE_REMOVED_KEYS, getStudioGraphExecuteRemovedKeyViolations, assertCanonicalStudioGraphExecuteRequest, buildGraphExecutionRequestFromStudioExecute, } from './runtime/studioGraphExecuteRequest.js';
64
65
  export { computeGraphDocumentContentSha256, stableStringifyGraphDocument, } from './runtime/graphDocumentFingerprint.js';
65
66
  export { migrateLegacyGraphResponse, migrateLegacyGraphResponseDefinition, } from './runtime/graphResponseMigration.js';
66
67
  export { applyAiTaskProfileWebScopingToNarrix, mapAiTaskProfileQuestionsToWebScopeQuestions, } from './runtime/applyAiTaskProfileWebScopingToNarrix.js';
67
68
  // New runtime with injection seam
68
69
  export { createExellixGraphRuntime } from './runtime/ExellixGraphRuntime.js';
70
+ export { compileExellixExecutablePlan } from './adapters/compileExellixExecutablePlan.js';
71
+ export { migrateExellixGraphModelToAuthoring } from './adapters/migrateExellixGraphModelToAuthoring.js';
72
+ export { buildGraphExecutionRequestFromStudioExecute as buildAuthoringStudioGraphExecutionRequest } from '@x12i/graphenix-execute-envelope';
69
73
  // Testkit (in-memory loader, dep engine, recording client — sample NARRIX tasks: `@exellix/graph-engine/testkit`)
70
74
  export { InMemoryGraphLoader, DepGraphEngineFactory } from '../testkit/index.js';
71
75
  // Graph loaders
@@ -0,0 +1,11 @@
1
+ import type { AiModelSelection } from '@x12i/graphenix-executable-contracts';
2
+ import type { ResolvedInvocationSnapshot } from '@x12i/graphenix-executable-contracts';
3
+ /**
4
+ * Maps plan {@link AiModelSelection} to ai-tasks 8.4+ wire profile strings.
5
+ */
6
+ export declare function aiModelSelectionToWireString(selection: AiModelSelection | undefined): string | undefined;
7
+ export declare function resolvedSnapshotToWireString(snapshot: ResolvedInvocationSnapshot | undefined): string | undefined;
8
+ export declare function unitModelWireString(args: {
9
+ modelSelection?: AiModelSelection;
10
+ resolvedInvocationSnapshot?: ResolvedInvocationSnapshot;
11
+ }): string | undefined;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Maps plan {@link AiModelSelection} to ai-tasks 8.4+ wire profile strings.
3
+ */
4
+ export function aiModelSelectionToWireString(selection) {
5
+ if (selection == null)
6
+ return undefined;
7
+ if (selection.kind === 'profileChoice')
8
+ return selection.key.trim();
9
+ if (selection.kind === 'profile') {
10
+ const profile = selection.profile.trim();
11
+ const choice = typeof selection.choice === 'string' ? selection.choice.trim() : '';
12
+ return choice ? `${profile}/${choice}` : profile;
13
+ }
14
+ if (selection.kind === 'model') {
15
+ const modelId = typeof selection.modelId === 'string'
16
+ ? selection.modelId
17
+ : typeof selection.model === 'string'
18
+ ? selection.model
19
+ : '';
20
+ return `${selection.provider}/${modelId}`.replace(/\/+/g, '/');
21
+ }
22
+ return undefined;
23
+ }
24
+ export function resolvedSnapshotToWireString(snapshot) {
25
+ if (snapshot == null)
26
+ return undefined;
27
+ if (snapshot.purpose === 'strictReplay' || snapshot.purpose === 'debugReplay') {
28
+ return `${snapshot.provider}/${snapshot.modelId}`;
29
+ }
30
+ if (snapshot.profileChoiceKey)
31
+ return snapshot.profileChoiceKey;
32
+ return undefined;
33
+ }
34
+ export function unitModelWireString(args) {
35
+ const fromSnapshot = resolvedSnapshotToWireString(args.resolvedInvocationSnapshot);
36
+ if (fromSnapshot)
37
+ return fromSnapshot;
38
+ return aiModelSelectionToWireString(args.modelSelection);
39
+ }
@@ -0,0 +1,10 @@
1
+ import type { FinalizerExecutionPlan, NodeExecutionPlan } from '@x12i/graphenix-executable-contracts';
2
+ import type { TaskNode } from '../types/refs.js';
3
+ /** Merge frozen plan invoke contract onto exellix task node before execution. */
4
+ export declare function applyNodePlanInvoke(node: TaskNode, nodePlan: NodeExecutionPlan): TaskNode;
5
+ export declare function finalizerPlanForId(plan: {
6
+ finalizerPlans: Record<string, FinalizerExecutionPlan>;
7
+ }, nodeId: string): FinalizerExecutionPlan | undefined;
8
+ export declare function nodePlanForId(plan: {
9
+ nodePlans: Record<string, NodeExecutionPlan>;
10
+ }, nodeId: string): NodeExecutionPlan | undefined;