@exellix/graph-engine 8.0.0 → 8.1.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 (41) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +11 -12
  3. package/dist/src/compile/authoringDocumentHelpers.d.ts +9 -0
  4. package/dist/src/compile/authoringDocumentHelpers.js +18 -0
  5. package/dist/src/compile/compileExellixExecutablePlan.d.ts +8 -0
  6. package/dist/src/compile/compileExellixExecutablePlan.js +75 -0
  7. package/dist/src/index.d.ts +4 -6
  8. package/dist/src/index.js +2 -4
  9. package/dist/src/runtime/ExellixGraphRuntime.d.ts +11 -37
  10. package/dist/src/runtime/ExellixGraphRuntime.js +78 -173
  11. package/dist/src/runtime/aiTasksStrategyPhases.js +7 -3
  12. package/dist/src/runtime/buildAiTasksRunTaskRequest.d.ts +6 -8
  13. package/dist/src/runtime/buildAiTasksRunTaskRequest.js +2 -7
  14. package/dist/src/runtime/executionMatrixHost.d.ts +6 -3
  15. package/dist/src/runtime/executionMatrixHost.js +6 -4
  16. package/dist/src/runtime/runTaskNodePlan.d.ts +15 -0
  17. package/dist/src/runtime/runTaskNodePlan.js +46 -0
  18. package/dist/src/runtime/studioGraphExecuteRequest.d.ts +5 -30
  19. package/dist/src/runtime/studioGraphExecuteRequest.js +14 -50
  20. package/dist/src/runtime/taskNodeRunTaskPreflight.d.ts +7 -5
  21. package/dist/src/runtime/taskNodeRunTaskPreflight.js +31 -26
  22. package/dist/src/types/refs.d.ts +1 -1
  23. package/dist/testkit/authoringGraphFixtures.d.ts +8 -0
  24. package/dist/testkit/authoringGraphFixtures.js +249 -0
  25. package/dist/testkit/buildExecuteGraphInput.d.ts +3 -3
  26. package/dist/testkit/buildExecuteGraphInput.js +8 -4
  27. package/dist/testkit/flatGraphToAuthoring.d.ts +29 -0
  28. package/dist/testkit/flatGraphToAuthoring.js +281 -0
  29. package/dist/testkit/index.d.ts +3 -0
  30. package/dist/testkit/index.js +3 -0
  31. package/dist/testkit/runTaskNodePlanAssertions.d.ts +34 -0
  32. package/dist/testkit/runTaskNodePlanAssertions.js +32 -0
  33. package/package.json +8 -7
  34. package/dist/src/adapters/compileExellixExecutablePlan.d.ts +0 -8
  35. package/dist/src/adapters/compileExellixExecutablePlan.js +0 -18
  36. package/dist/src/adapters/migrateExellixGraphModelToAuthoring.d.ts +0 -6
  37. package/dist/src/adapters/migrateExellixGraphModelToAuthoring.js +0 -273
  38. package/dist/src/adapters/patchFinalizerPlans.d.ts +0 -7
  39. package/dist/src/adapters/patchFinalizerPlans.js +0 -63
  40. package/dist/src/runtime/graphResponseMigration.d.ts +0 -7
  41. package/dist/src/runtime/graphResponseMigration.js +0 -44
@@ -1,40 +1,15 @@
1
- import type { Graph } from '../types/refs.js';
2
- import type { ExecuteGraphInput, GraphRuntimeObject } from './ExellixGraphRuntime.js';
1
+ import type { ExecuteGraphInput } from './ExellixGraphRuntime.js';
3
2
  /**
4
3
  * Removed from the graphs-studio execute envelope (7.8.3+). No reader, no fallback.
5
- * Default model selection belongs on {@link Graph}.modelConfig only.
4
+ * Default model selection belongs on authoring graph executable profile only.
6
5
  */
7
6
  export declare const STUDIO_GRAPH_EXECUTE_REMOVED_KEYS: readonly ["graphDefaultModel"];
8
7
  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
- };
8
+ /** @deprecated Prefer {@link AuthoringGraphDocument} from `@x12i/graphenix-executable-contracts`. */
9
+ export type StudioGraphExecuteRequest = import('@x12i/graphenix-executable-contracts').StudioGraphExecuteRequest;
34
10
  export declare function getStudioGraphExecuteRemovedKeyViolations(request: unknown): StudioGraphExecuteRemovedKey[];
35
11
  /**
36
12
  * Rejects removed studio execute keys (notably `graphDefaultModel`).
37
- * Hosts must persist model defaults on `graph.modelConfig` and stop sending request-level mirrors.
38
13
  */
39
14
  export declare function assertCanonicalStudioGraphExecuteRequest(request: unknown, context?: {
40
15
  jobId?: string;
@@ -46,6 +21,6 @@ export type BuildGraphExecutionRequestFromStudioExecuteOptions = {
46
21
  };
47
22
  /**
48
23
  * Maps a validated studio execute envelope to {@link ExecuteGraphInput}.
49
- * Does not read or merge removed request-level model defaults — graph.modelConfig is authoritative.
24
+ * Delegates validate + compile to `@x12i/graphenix-execute-envelope`.
50
25
  */
51
26
  export declare function buildGraphExecutionRequestFromStudioExecute(request: StudioGraphExecuteRequest, options?: BuildGraphExecutionRequestFromStudioExecuteOptions): ExecuteGraphInput;
@@ -1,9 +1,9 @@
1
- import { compileExellixExecutablePlan } from '../adapters/compileExellixExecutablePlan.js';
1
+ import { buildGraphExecutionRequestFromStudioExecute as buildAuthoringRequest } from '@x12i/graphenix-execute-envelope';
2
2
  import { ExellixGraphError } from '../errors/ExellixGraphError.js';
3
3
  import { ExellixGraphErrorCode } from '../errors/exellixGraphErrorCodes.js';
4
4
  /**
5
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.
6
+ * Default model selection belongs on authoring graph executable profile only.
7
7
  */
8
8
  export const STUDIO_GRAPH_EXECUTE_REMOVED_KEYS = ['graphDefaultModel'];
9
9
  function isPlainObject(v) {
@@ -16,63 +16,27 @@ export function getStudioGraphExecuteRemovedKeyViolations(request) {
16
16
  }
17
17
  /**
18
18
  * Rejects removed studio execute keys (notably `graphDefaultModel`).
19
- * Hosts must persist model defaults on `graph.modelConfig` and stop sending request-level mirrors.
20
19
  */
21
20
  export function assertCanonicalStudioGraphExecuteRequest(request, context) {
22
21
  const removed = getStudioGraphExecuteRemovedKeyViolations(request);
23
22
  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 });
23
+ 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 the authoring graph executable profile only; request-level graphDefaultModel was removed with no legacy fallback.`, { ...context, removedKeys: removed });
25
24
  }
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 });
25
+ if (!isPlainObject(request) || request.mode !== 'graph' || typeof request.jobId !== 'string') {
26
+ throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, 'Studio graph execute request requires mode graph and jobId.', context);
41
27
  }
42
28
  }
43
29
  /**
44
30
  * Maps a validated studio execute envelope to {@link ExecuteGraphInput}.
45
- * Does not read or merge removed request-level model defaults — graph.modelConfig is authoritative.
31
+ * Delegates validate + compile to `@x12i/graphenix-execute-envelope`.
46
32
  */
47
33
  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
- };
34
+ assertCanonicalStudioGraphExecuteRequest(request, {
35
+ jobId: request.jobId,
36
+ graphId: request.graph?.id,
37
+ });
38
+ return buildAuthoringRequest(request, {
39
+ agentId: options?.agentId,
40
+ jobTypeId: options?.jobTypeId,
41
+ });
78
42
  }
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * Graph-level preflight for `@exellix/ai-tasks` validation and analysis on task nodes.
3
- * Builds the same outbound {@link RunTaskRequest} as `executeNode` (without PRE strategy phases or `runTask`).
3
+ * Builds the same outbound {@link RunTaskRequest} as `executeNode` (without `runTask`).
4
4
  */
5
5
  import type { RunTaskRequest } from '@exellix/ai-tasks';
6
6
  import { type RunTaskInvokeValidationResult, type RunTaskValidationResult, type ValidateRunTaskInvokeParams } from '@exellix/ai-tasks';
7
+ import type { NodeExecutionPlan } from '@x12i/graphenix-executable-contracts';
7
8
  import type { GraphAiModelConfig, Job, TaskNode } from '../types/refs.js';
8
- import type { ExecutionStepOption, RunTaskDiagnostics, SkillKeyResolutionOptions } from '../types/options.js';
9
+ import type { RunTaskDiagnostics, SkillKeyResolutionOptions } from '../types/options.js';
9
10
  export type TaskNodeRunTaskPreflightSkipReason = 'local_skill' | 'finalizer';
10
11
  export type BuildTaskNodeRunTaskRequestResult = {
11
12
  runnable: true;
@@ -29,20 +30,21 @@ export type BuildTaskNodeRunTaskRequestArgs = {
29
30
  graphRunTaskId?: string;
30
31
  /** Host job id on `job.jobId` / `job.id`; defaults from `job` when omitted. */
31
32
  runTaskJobId?: string;
32
- /** Resolved graph model profiles (required for runnable MAIN). */
33
+ /** Frozen per-node plan from compile; when omitted a minimal plan is synthesized and patched from modelConfig. */
34
+ nodePlan?: NodeExecutionPlan;
35
+ /** Resolved graph model profiles (required for runnable MAIN when nodePlan is omitted). */
33
36
  modelConfig: GraphAiModelConfig;
34
37
  runTaskIdentity?: Record<string, unknown>;
35
38
  runTaskExecutionMode?: 'default' | 'trace';
36
39
  runTaskDiagnostics?: RunTaskDiagnostics;
37
40
  jobKnowledgePatch?: Record<string, unknown>;
38
41
  taskKnowledgePatch?: Record<string, unknown>;
39
- graphExecutionPipeline?: ExecutionStepOption[];
40
42
  skillKeyResolution?: SkillKeyResolutionOptions;
41
43
  clearSynthesizedContextPerNode?: boolean;
42
44
  };
43
45
  /**
44
46
  * Materialize the outbound `RunTaskRequest` graph-engine would send for a task node
45
- * (mirrors `executeNode` request assembly; does not run PRE strategy phases or `runTask`).
47
+ * (mirrors `executeNode` request assembly; does not invoke `runTask`).
46
48
  */
47
49
  export declare function buildTaskNodeRunTaskRequest(args: BuildTaskNodeRunTaskRequestArgs): Promise<BuildTaskNodeRunTaskRequestResult>;
48
50
  export type ValidateTaskNodeRunTaskConfigArgs = BuildTaskNodeRunTaskRequestArgs;
@@ -3,7 +3,6 @@ import { toRunTaskModelConfig } from './graphAiModelConfig.js';
3
3
  import { resolveTaskKey } from './resolveTaskKey.js';
4
4
  import { isLocalSkillKey } from './localSkills/index.js';
5
5
  import { buildAiTasksRunTaskRequest, extractRunTaskStrategyOverrides, } from './buildAiTasksRunTaskRequest.js';
6
- import { resolveExecutionPipelineForTaskNode } from './resolveExecutionPipelineForTaskNode.js';
7
6
  import { resolveTaskNodeInputsForRunTask } from './resolveTaskNodeInputs.js';
8
7
  import { assertAiTasksNodeExtensionsValid } from '../inspection/validateAiTasksNodeExtensions.js';
9
8
  import { buildRunTaskMainInput, extractCallerInputsBag } from './resolveGraphEngineMemoryPaths.js';
@@ -11,10 +10,10 @@ import { mirrorStructuredInputOntoExecutionMemory, } from './materializeStructur
11
10
  import { xynthesizedOutboundForNode } from './graphRunExecutionSeed.js';
12
11
  import { buildTaskNodeJobContext } from './buildTaskNodeJobContext.js';
13
12
  import { resolveNarrixForTaskNode } from './resolveNarrixForTaskNode.js';
14
- import { normalizeSynthesizedContextConfig } from './synthesizedContextPipeline.js';
15
- import { buildMainLlmCallForRunTask, buildRunTaskIdentityEnvelope, shouldForwardRunTaskTraceMode, } from './runTaskAugments.js';
13
+ import { buildRunTaskIdentityEnvelope, shouldForwardRunTaskTraceMode, } from './runTaskAugments.js';
16
14
  import { mirrorTaskVariablesOnExecution, readExecutionVariableBuckets, } from './executionVariableBuckets.js';
17
15
  import { newGraphRunTaskId } from './graphRunIdentity.js';
16
+ import { patchNodePlanMainModelFromWire, resolveRunTaskNodePlan } from './runTaskNodePlan.js';
18
17
  function isPlainRecord(v) {
19
18
  return v != null && typeof v === 'object' && !Array.isArray(v);
20
19
  }
@@ -33,7 +32,7 @@ function mergeKnowledgePatchIntoRunTaskMemory(memory, patch) {
33
32
  }
34
33
  /**
35
34
  * Materialize the outbound `RunTaskRequest` graph-engine would send for a task node
36
- * (mirrors `executeNode` request assembly; does not run PRE strategy phases or `runTask`).
35
+ * (mirrors `executeNode` request assembly; does not invoke `runTask`).
37
36
  */
38
37
  export async function buildTaskNodeRunTaskRequest(args) {
39
38
  const node = args.node;
@@ -59,23 +58,22 @@ export async function buildTaskNodeRunTaskRequest(args) {
59
58
  node: node,
60
59
  runtimeTaskVariables: args.taskVariables,
61
60
  });
62
- const executionPipeline = resolveExecutionPipelineForTaskNode({
63
- node,
64
- optionsExecutionPipeline: args.graphExecutionPipeline,
65
- executionExecutionPipeline: execution.executionPipeline,
61
+ const wireModelConfig = toRunTaskModelConfig(args.modelConfig);
62
+ let runTaskNodePlan = resolveRunTaskNodePlan({
63
+ nodePlan: args.nodePlan,
64
+ skillKey,
65
+ nodeId: String(node.id),
66
66
  });
67
- const hasSynthesizedContextPreStep = Array.isArray(executionPipeline) &&
68
- executionPipeline.some((s) => s.phase === 'pre' && s.type === 'synthesized-context');
69
- const preStepConfig = hasSynthesizedContextPreStep && Array.isArray(executionPipeline)
70
- ? normalizeSynthesizedContextConfig(executionPipeline.find((s) => s.phase === 'pre' && s.type === 'synthesized-context')?.config)
71
- : undefined;
72
- const includeContextInPrompt = hasSynthesizedContextPreStep && preStepConfig?.autoEnableContext !== true;
73
- const variableBuckets = readExecutionVariableBuckets(execution);
74
- const jobVariables = variableBuckets.jobVariables;
67
+ if (!args.nodePlan) {
68
+ runTaskNodePlan = patchNodePlanMainModelFromWire(runTaskNodePlan, wireModelConfig);
69
+ }
70
+ const hasSynthesizedContextPreStep = runTaskNodePlan.executionUnits.some((u) => u.unitKind === 'externalPreUtility' || u.unitKind === 'pipelinePhase');
75
71
  const hasExistingSynthesizedContext = execution.synthesizedContext != null;
76
72
  const clearSynthForTask = args.clearSynthesizedContextPerNode === true &&
77
73
  hasSynthesizedContextPreStep &&
78
74
  hasExistingSynthesizedContext;
75
+ const variableBuckets = readExecutionVariableBuckets(execution);
76
+ const jobVariables = variableBuckets.jobVariables;
79
77
  const nodeBindings = resolveTaskNodeInputsForRunTask({ node, execution });
80
78
  const jobContext = buildTaskNodeJobContext({
81
79
  node,
@@ -115,12 +113,21 @@ export async function buildTaskNodeRunTaskRequest(args) {
115
113
  mirrorStructuredInputOntoExecutionMemory(executionForTask, taskInput);
116
114
  const runTaskJobMemory = mergeKnowledgePatchIntoRunTaskMemory(args.jobMemory, args.jobKnowledgePatch);
117
115
  const runTaskTaskMemory = mergeKnowledgePatchIntoRunTaskMemory(args.taskMemory, args.taskKnowledgePatch);
118
- const wireModelConfig = toRunTaskModelConfig(args.modelConfig);
119
- const effectiveLlmCall = buildMainLlmCallForRunTask(wireModelConfig, node.taskConfiguration?.llmCall);
120
116
  const forwardRunTaskTrace = shouldForwardRunTaskTraceMode({
121
117
  runTaskExecutionMode: args.runTaskExecutionMode,
122
118
  });
123
- const metaStrats = node.taskConfiguration?.executionStrategies;
119
+ const metaStrats = runTaskNodePlan.invokeContract?.pipeline &&
120
+ typeof runTaskNodePlan.invokeContract.pipeline === 'object' &&
121
+ Array.isArray(runTaskNodePlan.invokeContract.pipeline.executionStrategies)
122
+ ? runTaskNodePlan.invokeContract.pipeline
123
+ .executionStrategies
124
+ : node.taskConfiguration?.executionStrategies;
125
+ const outputValidationFromPlan = runTaskNodePlan.invokeContract?.validation &&
126
+ typeof runTaskNodePlan.invokeContract.validation === 'object' &&
127
+ 'aiTasksOutputValidation' in runTaskNodePlan.invokeContract.validation
128
+ ? runTaskNodePlan.invokeContract.validation
129
+ .aiTasksOutputValidation
130
+ : undefined;
124
131
  const ov = node.taskConfiguration?.aiTasksOutputValidation;
125
132
  const request = buildAiTasksRunTaskRequest({
126
133
  skillKey,
@@ -138,16 +145,13 @@ export async function buildTaskNodeRunTaskRequest(args) {
138
145
  executionMemory: executionForTask,
139
146
  jobContext,
140
147
  prevNodeId: args.prevNodeId,
141
- executionPipeline: executionPipeline,
142
- includeContextInPrompt: includeContextInPrompt === true ? true : undefined,
143
148
  narrix: narrix ?? undefined,
144
149
  ...extractRunTaskStrategyOverrides(node.taskConfiguration),
145
- outputValidation: ov != null && typeof ov === 'object' && !Array.isArray(ov) && 'schema' in ov
146
- ? ov
147
- : undefined,
150
+ outputValidation: outputValidationFromPlan ??
151
+ (ov != null && typeof ov === 'object' && !Array.isArray(ov) && 'schema' in ov
152
+ ? ov
153
+ : undefined),
148
154
  diagnostics: args.runTaskDiagnostics,
149
- modelConfig: wireModelConfig,
150
- llmCall: effectiveLlmCall,
151
155
  identity: buildRunTaskIdentityEnvelope({
152
156
  base: args.runTaskIdentity,
153
157
  nodeMeta: node.taskConfiguration,
@@ -162,6 +166,7 @@ export async function buildTaskNodeRunTaskRequest(args) {
162
166
  executionStrategies: Array.isArray(metaStrats) ? metaStrats : [],
163
167
  xynthesized: xynthesizedOutboundForNode(execution, String(node.id)),
164
168
  smartInput: node.smartInput,
169
+ nodePlan: runTaskNodePlan,
165
170
  });
166
171
  return { runnable: true, skillKey, request };
167
172
  }
@@ -680,7 +680,7 @@ export interface GraphDocumentMetadata {
680
680
  graphResponse?: GraphResponseContract;
681
681
  /**
682
682
  * Graph execution defaults and labels.
683
- * Planner mode is still selected by `executeGraph({ model, runtime }).runtime.mode` and defaults to `forward`;
683
+ * Planner mode is still selected by `executeGraph({ plan, runtime }).runtime.mode` (or plan.schedulingPolicy) and defaults to `forward`;
684
684
  * output labels in this block shape `stepsResponses`, not `ExecuteGraphResult.finalOutput`.
685
685
  */
686
686
  graphExecution?: GraphExecutionDefaults;
@@ -0,0 +1,8 @@
1
+ import type { AuthoringGraphDocument } from '@x12i/graphenix-executable-contracts';
2
+ /** Single ai-task + select finalizer — mirrors legacy flat `buildAiTaskGraph` tests. */
3
+ export declare function buildAiTaskAuthoringGraph(graphId: string): AuthoringGraphDocument;
4
+ /** deterministic-rule local skill + select finalizer. */
5
+ export declare function buildLocalSkillAuthoringGraph(graphId: string): AuthoringGraphDocument;
6
+ /** Fan-in Q&A graph used by graph.response mapping tests. */
7
+ export declare function buildGraphResponseMappingAuthoringGraph(graphId: string): AuthoringGraphDocument;
8
+ export declare function withAuthoringGraphModelConfig(doc: AuthoringGraphDocument, modelConfig: Record<string, unknown>): AuthoringGraphDocument;
@@ -0,0 +1,249 @@
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
+ function defaultExecutableModelConfig() {
4
+ return {
5
+ version: 'graph-model-config/v1',
6
+ cases: [
7
+ {
8
+ id: 'default',
9
+ modelConfig: {
10
+ preActionModel: { kind: 'profileChoice', key: 'cheap/default' },
11
+ skillModel: { kind: 'profileChoice', key: 'pro/default' },
12
+ postActionModel: { kind: 'profileChoice', key: 'cheap/default' },
13
+ },
14
+ },
15
+ ],
16
+ fallbackPolicy: {
17
+ enabled: true,
18
+ allowedTriggers: [
19
+ 'nodeSlotMissing',
20
+ 'nodeModelUnavailable',
21
+ 'nodeModelUnsupported',
22
+ 'nodeProviderNotConfigured',
23
+ 'nodeModelRateLimited',
24
+ 'nodeModelTransientFailure',
25
+ ],
26
+ maxAttemptsPerSlot: 1,
27
+ },
28
+ };
29
+ }
30
+ function authoringShell(graphId, nodes, edges, graphResponseShape, extra) {
31
+ const taskNodes = nodes.filter((n) => n.kind === TASK_NODE_KIND);
32
+ const finalizer = nodes.find((n) => n.kind === FINALIZER_NODE_KIND);
33
+ return {
34
+ formatVersion: GRAPHENIX_FORMAT_VERSION,
35
+ id: graphId.startsWith('graph:') ? graphId : `graph:${graphId}`,
36
+ revision: '1.0.0',
37
+ name: graphId,
38
+ graph: {
39
+ nodes,
40
+ edges,
41
+ inputs: taskNodes.map((node) => ({
42
+ id: `graph-input:${node.id}`,
43
+ name: 'Input Record',
44
+ type: 'builtin:object',
45
+ target: { nodeId: node.id, portId: 'in:record' },
46
+ contract: { semanticKind: 'record', required: true, schema: { type: 'object' } },
47
+ })),
48
+ outputs: finalizer
49
+ ? [
50
+ {
51
+ id: 'graph-output:final',
52
+ name: 'Final Output',
53
+ type: 'builtin:object',
54
+ source: { nodeId: finalizer.id, portId: 'out:final' },
55
+ contract: { semanticKind: 'final-output', required: true, schema: { type: 'object' } },
56
+ },
57
+ ]
58
+ : [],
59
+ metadata: {
60
+ graphResponse: { shape: graphResponseShape },
61
+ extensions: {
62
+ [EXECUTABLE_PROFILE_NAMESPACE]: {
63
+ profileVersion: EXECUTABLE_PROFILE_VERSION,
64
+ modelConfig: defaultExecutableModelConfig(),
65
+ },
66
+ },
67
+ ...extra,
68
+ },
69
+ },
70
+ types: [],
71
+ };
72
+ }
73
+ /** Single ai-task + select finalizer — mirrors legacy flat `buildAiTaskGraph` tests. */
74
+ export function buildAiTaskAuthoringGraph(graphId) {
75
+ const taskId = 'ask-1';
76
+ const finalId = 'final';
77
+ const taskNode = {
78
+ id: taskId,
79
+ kind: TASK_NODE_KIND,
80
+ inputs: [{ id: 'in:record', direction: 'input', type: 'builtin:object', required: true }],
81
+ outputs: [{ id: 'out:answer', direction: 'output', type: 'builtin:object' }],
82
+ parameters: {
83
+ profile: TASK_NODE_PROFILE,
84
+ nodeType: 'task',
85
+ skillKey: 'professional-answer',
86
+ executionMapping: {
87
+ path: 'answers.ask1',
88
+ mode: 'replace',
89
+ map: { answer: 'output.answer' },
90
+ },
91
+ taskConfiguration: { executionStrategies: [] },
92
+ },
93
+ };
94
+ const finalizerNode = {
95
+ id: finalId,
96
+ kind: FINALIZER_NODE_KIND,
97
+ inputs: [{ id: 'in:answer', direction: 'input', type: 'builtin:object', required: true }],
98
+ outputs: [{ id: 'out:final', direction: 'output', type: 'builtin:object' }],
99
+ parameters: {
100
+ profile: FINALIZER_NODE_PROFILE,
101
+ nodeType: 'finalizer',
102
+ finalizerType: 'select',
103
+ inputs: {
104
+ answer: { type: 'executionMemoryPath', path: 'answers.ask1.answer' },
105
+ },
106
+ config: {
107
+ strategy: 'select',
108
+ selector: { type: 'firstPresent', order: ['answer'] },
109
+ },
110
+ outputMapping: {
111
+ path: 'answers.ask1',
112
+ mode: 'replace',
113
+ map: { answer: 'output.parsed' },
114
+ },
115
+ },
116
+ };
117
+ return authoringShell(graphId, [taskNode, finalizerNode], [{ id: 'edge:ask-final', from: { nodeId: taskId, portId: 'out:answer' }, to: { nodeId: finalId, portId: 'in:answer' } }], { answer: { type: 'outputsMemoryPath', path: 'answers.ask1.answer' } });
118
+ }
119
+ /** deterministic-rule local skill + select finalizer. */
120
+ export function buildLocalSkillAuthoringGraph(graphId) {
121
+ const taskId = 'rule-1';
122
+ const finalId = 'final';
123
+ const taskNode = {
124
+ id: taskId,
125
+ kind: TASK_NODE_KIND,
126
+ inputs: [{ id: 'in:record', direction: 'input', type: 'builtin:object', required: true }],
127
+ outputs: [{ id: 'out:answer', direction: 'output', type: 'builtin:object' }],
128
+ parameters: {
129
+ profile: TASK_NODE_PROFILE,
130
+ nodeType: 'task',
131
+ skillKey: 'deterministic-rule',
132
+ taskConfiguration: {
133
+ rules: [{ id: 'always', condition: { all: [] }, output: { decision: 'go' } }],
134
+ firstMatchWins: true,
135
+ },
136
+ executionMapping: {
137
+ path: 'rule',
138
+ mode: 'replace',
139
+ map: { decision: 'output.output.decision' },
140
+ },
141
+ },
142
+ };
143
+ const finalizerNode = {
144
+ id: finalId,
145
+ kind: FINALIZER_NODE_KIND,
146
+ inputs: [{ id: 'in:answer', direction: 'input', type: 'builtin:object', required: true }],
147
+ outputs: [{ id: 'out:final', direction: 'output', type: 'builtin:object' }],
148
+ parameters: {
149
+ profile: FINALIZER_NODE_PROFILE,
150
+ nodeType: 'finalizer',
151
+ finalizerType: 'select',
152
+ inputs: {
153
+ decision: { type: 'executionMemoryPath', path: 'rule.decision' },
154
+ },
155
+ config: {
156
+ strategy: 'select',
157
+ selector: { type: 'firstPresent', order: ['decision'] },
158
+ },
159
+ outputMapping: {
160
+ path: 'rule',
161
+ mode: 'replace',
162
+ map: { decision: 'output.parsed' },
163
+ },
164
+ },
165
+ };
166
+ return authoringShell(graphId, [taskNode, finalizerNode], [{ id: 'edge:rule-final', from: { nodeId: taskId, portId: 'out:answer' }, to: { nodeId: finalId, portId: 'in:answer' } }], { decision: { type: 'outputsMemoryPath', path: 'rule.decision' } });
167
+ }
168
+ /** Fan-in Q&A graph used by graph.response mapping tests. */
169
+ export function buildGraphResponseMappingAuthoringGraph(graphId) {
170
+ const q1 = 'q1';
171
+ const q2 = 'q2';
172
+ const finalId = 'final';
173
+ const makeTask = (id, path, question) => ({
174
+ id,
175
+ kind: TASK_NODE_KIND,
176
+ inputs: [{ id: 'in:record', direction: 'input', type: 'builtin:object', required: true }],
177
+ outputs: [{ id: 'out:answer', direction: 'output', type: 'builtin:object' }],
178
+ parameters: {
179
+ profile: TASK_NODE_PROFILE,
180
+ nodeType: 'task',
181
+ skillKey: 'professional-answer',
182
+ taskVariable: { question },
183
+ executionMapping: {
184
+ path: `answers.${path}`,
185
+ mode: 'replace',
186
+ map: { shortAnswer: 'output.parsed.shortAnswer' },
187
+ },
188
+ taskConfiguration: { executionStrategies: [] },
189
+ },
190
+ });
191
+ const finalizerNode = {
192
+ id: finalId,
193
+ kind: FINALIZER_NODE_KIND,
194
+ inputs: [
195
+ { id: 'in:q1', direction: 'input', type: 'builtin:object', required: true },
196
+ { id: 'in:q2', direction: 'input', type: 'builtin:object', required: true },
197
+ ],
198
+ outputs: [{ id: 'out:final', direction: 'output', type: 'builtin:object' }],
199
+ parameters: {
200
+ profile: FINALIZER_NODE_PROFILE,
201
+ nodeType: 'finalizer',
202
+ finalizerType: 'aggregate',
203
+ config: {
204
+ sections: [
205
+ { sourceNodeId: q1, sourcePortId: 'out:answer', executionMemoryPath: 'answers.q1' },
206
+ { sourceNodeId: q2, sourcePortId: 'out:answer', executionMemoryPath: 'answers.q2' },
207
+ ],
208
+ },
209
+ },
210
+ };
211
+ const doc = authoringShell(graphId, [makeTask(q1, 'q1', 'Q1?'), makeTask(q2, 'q2', 'Q2?'), finalizerNode], [
212
+ { id: 'edge:q1-final', from: { nodeId: q1, portId: 'out:answer' }, to: { nodeId: finalId, portId: 'in:q1' } },
213
+ { id: 'edge:q2-final', from: { nodeId: q2, portId: 'out:answer' }, to: { nodeId: finalId, portId: 'in:q2' } },
214
+ ], {
215
+ answers: {
216
+ type: 'firstPresent',
217
+ sources: [
218
+ {
219
+ answer: {
220
+ type: 'object',
221
+ properties: {
222
+ shortAnswer: { type: 'outputsMemoryPath', path: 'answers.q1.shortAnswer' },
223
+ },
224
+ },
225
+ },
226
+ {
227
+ answer: {
228
+ type: 'object',
229
+ properties: {
230
+ shortAnswer: { type: 'outputsMemoryPath', path: 'answers.q2.shortAnswer' },
231
+ },
232
+ },
233
+ },
234
+ ],
235
+ },
236
+ });
237
+ return doc;
238
+ }
239
+ export function withAuthoringGraphModelConfig(doc, modelConfig) {
240
+ const next = structuredClone(doc);
241
+ const metadata = next.graph.metadata;
242
+ if (!metadata?.extensions)
243
+ return next;
244
+ const profile = metadata.extensions[EXECUTABLE_PROFILE_NAMESPACE];
245
+ if (profile && typeof profile === 'object') {
246
+ Object.assign(profile, { modelConfig });
247
+ }
248
+ return next;
249
+ }
@@ -1,4 +1,4 @@
1
- import type { Graph, GraphModelObject } from '../src/types/refs.js';
1
+ import type { AuthoringGraphDocument } from '@x12i/graphenix-executable-contracts';
2
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;
3
+ /** Builds `{ plan, runtime }` for tests and hosts with a Graphenix 2.x {@link AuthoringGraphDocument}. */
4
+ export declare function buildExecuteGraphInput(doc: AuthoringGraphDocument | Record<string, unknown>, runtime: GraphRuntimeObject): ExecuteGraphInput;
@@ -1,8 +1,12 @@
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) {
1
+ import { compileExellixExecutablePlan } from '../src/compile/compileExellixExecutablePlan.js';
2
+ import { flatTestGraphToAuthoringDocument, isAuthoringGraphDocument } from './flatGraphToAuthoring.js';
3
+ function normalizeAuthoringDocument(input) {
4
+ return isAuthoringGraphDocument(input) ? input : flatTestGraphToAuthoringDocument(input);
5
+ }
6
+ /** Builds `{ plan, runtime }` for tests and hosts with a Graphenix 2.x {@link AuthoringGraphDocument}. */
7
+ export function buildExecuteGraphInput(doc, runtime) {
4
8
  return {
5
- plan: compileExellixExecutablePlan(model, runtime),
9
+ plan: compileExellixExecutablePlan(normalizeAuthoringDocument(doc), runtime),
6
10
  runtime,
7
11
  };
8
12
  }
@@ -0,0 +1,29 @@
1
+ import type { AuthoringGraphDocument } from '@x12i/graphenix-executable-contracts';
2
+ export declare function isAuthoringGraphDocument(v: unknown): v is AuthoringGraphDocument;
3
+ export declare function defaultExecutableModelConfig(): {
4
+ version: "graph-model-config/v1";
5
+ cases: {
6
+ id: string;
7
+ modelConfig: {
8
+ preActionModel: {
9
+ kind: "profileChoice";
10
+ key: string;
11
+ };
12
+ skillModel: {
13
+ kind: "profileChoice";
14
+ key: string;
15
+ };
16
+ postActionModel: {
17
+ kind: "profileChoice";
18
+ key: string;
19
+ };
20
+ };
21
+ }[];
22
+ fallbackPolicy: {
23
+ enabled: boolean;
24
+ allowedTriggers: readonly ["nodeSlotMissing", "nodeModelUnavailable", "nodeModelUnsupported", "nodeProviderNotConfigured", "nodeModelRateLimited", "nodeModelTransientFailure"];
25
+ maxAttemptsPerSlot: number;
26
+ };
27
+ };
28
+ /** Converts legacy flat exellix test graphs into Graphenix 2.x {@link AuthoringGraphDocument}. */
29
+ export declare function flatTestGraphToAuthoringDocument(flat: Record<string, unknown>): AuthoringGraphDocument;