ai-data-workflow-logic 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-data-workflow-logic",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "private": false,
5
5
  "description": "EagerDataFlow-backed AI Data Workflow RunGraph helpers",
6
6
  "type": "module",
@@ -11,13 +11,13 @@
11
11
  "./filesystem": "./src/filesystem.ts"
12
12
  },
13
13
  "dependencies": {
14
- "ai-data-workflow-contract": "0.1.2",
15
- "ai-workflow-contract": "0.1.5",
16
- "ai-workflow-logic": "0.1.5",
17
- "eager-data-flow-contract": "0.1.2",
18
- "eager-data-flow-logic": "0.1.3",
19
- "flow-step-space-logic": "0.1.0",
20
- "work-ctrl-flow-logic": "0.1.2"
14
+ "ai-data-workflow-contract": "0.1.3",
15
+ "ai-workflow-contract": "0.1.6",
16
+ "ai-workflow-logic": "0.1.6",
17
+ "eager-data-flow-contract": "0.1.3",
18
+ "eager-data-flow-logic": "0.1.4",
19
+ "flow-step-space-logic": "0.1.1",
20
+ "work-ctrl-flow-logic": "0.1.3"
21
21
  },
22
22
  "files": [
23
23
  "src"
package/src/filesystem.ts CHANGED
@@ -20,13 +20,24 @@ import {
20
20
  createFilesystemCodeResolver,
21
21
  materializeEagerDataFlowPlan,
22
22
  } from 'eager-data-flow-logic';
23
- import { assembleDefinitionStepProfileDirectory } from 'flow-step-space-logic/filesystem';
24
- import { FilesystemFlowRunCheckpointStore } from 'ai-workflow-logic/checkpoint-filesystem';
25
23
  import {
24
+ assembleDefinitionStepProfileDirectory,
25
+ createFilesystemDefinitionStepProfileRuntime,
26
+ } from 'flow-step-space-logic/filesystem';
27
+ import {
28
+ createFilesystemFlowRunCheckpointRuntime,
29
+ createFilesystemFlowRunCheckpointStore,
30
+ } from 'ai-workflow-logic/checkpoint-filesystem';
31
+ import {
32
+ createInitialRunStepExtensions,
26
33
  normalizeAIWorkflowProfileDurableState,
27
34
  normalizeFlowClosedObject,
28
35
  normalizeFlowClosedValue,
36
+ type FlowRunProfileAuthorityPort,
37
+ type FlowRunProfileAuthorityRequest,
38
+ type FrozenFlowRunProfileAuthority,
29
39
  } from 'ai-workflow-logic';
40
+ import type { DefinitionStepAssemblyRuntime, DefinitionStepForest } from 'flow-step-space-logic';
30
41
  import {
31
42
  loadMaterializedFlowInstance,
32
43
  materializeFlowInstance,
@@ -46,6 +57,7 @@ export interface MaterializedAIDataWorkflowRuntime {
46
57
  readonly supportRoot: string;
47
58
  readonly workflow: AIDataWorkflowRuntime;
48
59
  readonly importModule: EagerDataFlowModuleImporter;
60
+ readonly extensionCodecs?: DefinitionStepAssemblyRuntime['extensionCodecs'];
49
61
  readonly agentEffects?: AIAgentHostEffectPort;
50
62
  readonly agentTaskProofs?: Readonly<Record<string, FrozenAIAgentTaskBinding>>;
51
63
  readonly checkpointObserver?: (event: {
@@ -82,6 +94,21 @@ export interface ContinueMaterializedAIDataWorkflowInput {
82
94
 
83
95
  export type ContinueMaterializedAIDataWorkflowConfig = Readonly<Record<never, never>>;
84
96
 
97
+ const emptyExtensionCodecs = Object.freeze({ resolve: () => undefined });
98
+
99
+ function checkpointStoreForRuntime(runtime: MaterializedAIDataWorkflowRuntime) {
100
+ const extensionCodecs = runtime.extensionCodecs ?? emptyExtensionCodecs;
101
+ return createFilesystemFlowRunCheckpointStore(
102
+ createFilesystemFlowRunCheckpointRuntime({
103
+ storeRoot: runtime.supportRoot,
104
+ extensionCodecs,
105
+ profileAuthority: createMaterializedAIDataWorkflowProfileAuthority(runtime),
106
+ }),
107
+ {},
108
+ {},
109
+ );
110
+ }
111
+
85
112
  /** Runtime-first canonical Data execution from a frozen instance. */
86
113
  export async function startMaterializedAIDataWorkflow(
87
114
  runtime: MaterializedAIDataWorkflowRuntime,
@@ -90,8 +117,8 @@ export async function startMaterializedAIDataWorkflow(
90
117
  ): Promise<MaterializedAIDataWorkflowResult> {
91
118
  requireAgentTaskBindings(runtime);
92
119
  const instance = ensureAIDataInstance(runtime, input);
93
- const binding = loadAIDataBinding(instance.definitionDir);
94
- const checkpointStore = new FilesystemFlowRunCheckpointStore({ storeRoot: runtime.supportRoot });
120
+ const binding = loadAIDataBinding(instance.definitionDir, runtime.extensionCodecs ?? emptyExtensionCodecs);
121
+ const checkpointStore = checkpointStoreForRuntime(runtime);
95
122
  const checkpointRuntime = { checkpointStore };
96
123
  const existing = await loadAIDataWorkflowCheckpoint(checkpointRuntime, input);
97
124
  if (existing) {
@@ -119,6 +146,7 @@ export async function startMaterializedAIDataWorkflow(
119
146
  controllerSidecars: { status: 'Pending' },
120
147
  nodeSidecars: nodeStatuses(graph),
121
148
  ai: config.durableAI,
149
+ stepExtensions: createInitialRunStepExtensions(profileAuthorityFromBinding(binding)),
122
150
  });
123
151
  return executeAIDataCheckpoint(runtime, binding, v0);
124
152
  }
@@ -131,10 +159,11 @@ export async function continueMaterializedAIDataWorkflow(
131
159
  ): Promise<MaterializedAIDataWorkflowResult> {
132
160
  requireAgentTaskBindings(runtime);
133
161
  const instanceDir = path.join(runtime.supportRoot, 'instances', input.instanceId);
134
- const instance = loadMaterializedFlowInstance(instanceDir, input.instanceId, validateAIDataDefinition);
135
- const binding = loadAIDataBinding(instance.definitionDir);
162
+ const codecs = runtime.extensionCodecs ?? emptyExtensionCodecs;
163
+ const instance = loadMaterializedFlowInstance(instanceDir, input.instanceId, (definitionDir) => validateAIDataDefinition(definitionDir, codecs));
164
+ const binding = loadAIDataBinding(instance.definitionDir, codecs);
136
165
  const checkpoint = await loadAIDataWorkflowCheckpoint(
137
- { checkpointStore: new FilesystemFlowRunCheckpointStore({ storeRoot: runtime.supportRoot }) },
166
+ { checkpointStore: checkpointStoreForRuntime(runtime) },
138
167
  input,
139
168
  );
140
169
  if (!checkpoint) throw new Error(`AI Data Workflow run ${input.runId} does not have a canonical checkpoint.`);
@@ -167,7 +196,7 @@ async function executeAIDataCheckpoint(
167
196
  }
168
197
  const invocationInput = checkpoint.input as Readonly<Record<string, FlowClosedValue>>;
169
198
  const checkpointRuntime = {
170
- checkpointStore: new FilesystemFlowRunCheckpointStore({ storeRoot: runtime.supportRoot }),
199
+ checkpointStore: checkpointStoreForRuntime(runtime),
171
200
  };
172
201
  let transitionTail = Promise.resolve();
173
202
  const transition = <T>(operation: () => Promise<T>): Promise<T> => {
@@ -318,10 +347,11 @@ export async function loadMaterializedAIDataWorkflowRun(
318
347
  input: Pick<StartMaterializedAIDataWorkflowInput, 'instanceId' | 'runId'>,
319
348
  ): Promise<MaterializedAIDataWorkflowResult | undefined> {
320
349
  const instanceDir = path.join(runtime.supportRoot, 'instances', input.instanceId);
321
- const instance = loadMaterializedFlowInstance(instanceDir, input.instanceId, validateAIDataDefinition);
322
- const binding = loadAIDataBinding(instance.definitionDir);
350
+ const codecs = runtime.extensionCodecs ?? emptyExtensionCodecs;
351
+ const instance = loadMaterializedFlowInstance(instanceDir, input.instanceId, (definitionDir) => validateAIDataDefinition(definitionDir, codecs));
352
+ const binding = loadAIDataBinding(instance.definitionDir, codecs);
323
353
  const checkpoint = await loadAIDataWorkflowCheckpoint(
324
- { checkpointStore: new FilesystemFlowRunCheckpointStore({ storeRoot: runtime.supportRoot }) },
354
+ { checkpointStore: checkpointStoreForRuntime(runtime) },
325
355
  input,
326
356
  );
327
357
  if (!checkpoint) return undefined;
@@ -341,7 +371,10 @@ function ensureAIDataInstance(
341
371
  const instancesDir = path.join(runtime.supportRoot, 'instances');
342
372
  const instanceDir = path.join(instancesDir, input.instanceId);
343
373
  if (fs.existsSync(instanceDir)) {
344
- return loadMaterializedFlowInstance(instanceDir, input.instanceId, validateAIDataDefinition);
374
+ return loadMaterializedFlowInstance(instanceDir, input.instanceId, (definitionDir) => validateAIDataDefinition(
375
+ definitionDir,
376
+ runtime.extensionCodecs ?? emptyExtensionCodecs,
377
+ ));
345
378
  }
346
379
  if (!input.bundleDir || !input.source) {
347
380
  if (input.bundleDir || input.source) throw new Error('bundleDir and source identity must be provided together.');
@@ -352,16 +385,29 @@ function ensureAIDataInstance(
352
385
  instancesDir,
353
386
  instanceId: input.instanceId,
354
387
  source: input.source,
355
- validateDefinition: validateAIDataDefinition,
388
+ validateDefinition: (definitionDir) => validateAIDataDefinition(
389
+ definitionDir,
390
+ runtime.extensionCodecs ?? emptyExtensionCodecs,
391
+ ),
356
392
  });
357
393
  }
358
394
 
359
- function validateAIDataDefinition(definitionDir: string): void {
360
- loadAIDataBinding(definitionDir);
395
+ function validateAIDataDefinition(
396
+ definitionDir: string,
397
+ extensionCodecs: DefinitionStepAssemblyRuntime['extensionCodecs'],
398
+ ): void {
399
+ loadAIDataBinding(definitionDir, extensionCodecs);
361
400
  }
362
401
 
363
- function loadAIDataBinding(definitionDir: string) {
364
- const assembled = assembleDefinitionStepProfileDirectory(definitionDir, 'AIDataWorkflow');
402
+ function loadAIDataBinding(
403
+ definitionDir: string,
404
+ extensionCodecs: DefinitionStepAssemblyRuntime['extensionCodecs'],
405
+ ) {
406
+ const assembled = assembleDefinitionStepProfileDirectory(
407
+ createFilesystemDefinitionStepProfileRuntime({ rootDir: definitionDir, extensionCodecs }),
408
+ {},
409
+ { profileRoot: 'AIDataWorkflow' },
410
+ );
365
411
  if (assembled.diagnostics.length > 0) {
366
412
  throw new Error(`AIDataWorkflow StepSpace is invalid: ${assembled.diagnostics.map((item) => item.code).join(', ')}`);
367
413
  }
@@ -376,11 +422,71 @@ function loadAIDataBinding(definitionDir: string) {
376
422
  : loaded.binding;
377
423
  }
378
424
 
425
+ function profileAuthorityFromBinding(
426
+ binding: ReturnType<typeof loadAIDataBinding>,
427
+ ): FrozenFlowRunProfileAuthority {
428
+ const forest = binding.definition.definitionStepForest as DefinitionStepForest | undefined;
429
+ const definitionStepIds = forest?.stepOrder ?? binding.definition.declarationOrder;
430
+ return Object.freeze({
431
+ profileKind: 'AIDataWorkflow' as const,
432
+ definitionStepIds: Object.freeze([...definitionStepIds]),
433
+ binding: durableBinding(binding) as unknown as import('ai-workflow-contract').FlowClosedObject,
434
+ extensionsByStepId: Object.freeze(Object.fromEntries((forest?.stepOrder ?? []).flatMap((stepId) => {
435
+ const extensions = Object.fromEntries(Object.entries(forest!.steps[stepId].extensions).map(([kind, extension]) => [kind, Object.freeze({
436
+ ...(extension.schemaRef === undefined ? {} : { schemaRef: extension.schemaRef }),
437
+ initialValue: extension.value as import('ai-workflow-contract').FlowClosedValue,
438
+ })]));
439
+ return Object.keys(extensions).length === 0 ? [] : [[stepId, Object.freeze(extensions)]];
440
+ }))),
441
+ });
442
+ }
443
+
444
+ export function createMaterializedAIDataWorkflowProfileAuthority(
445
+ runtime: Pick<MaterializedAIDataWorkflowRuntime, 'supportRoot' | 'extensionCodecs'>,
446
+ ): FlowRunProfileAuthorityPort {
447
+ const extensionCodecs = runtime.extensionCodecs ?? emptyExtensionCodecs;
448
+ return Object.freeze({
449
+ load(request: FlowRunProfileAuthorityRequest) {
450
+ if (request.profileKind !== 'AIDataWorkflow') {
451
+ throw new Error(`AI Data profile authority cannot load ${request.profileKind}.`);
452
+ }
453
+ const instanceDir = path.join(runtime.supportRoot, 'instances', request.instanceId);
454
+ const instance = loadMaterializedFlowInstance(instanceDir, request.instanceId, (definitionDir) => {
455
+ validateAIDataDefinition(definitionDir, extensionCodecs);
456
+ });
457
+ if (canonicalClosed(normalizeFlowClosedValue(instance.descriptor.definition, 'profileAuthority.instanceDefinition'))
458
+ !== canonicalClosed(normalizeFlowClosedValue(request.definition, 'profileAuthority.requestDefinition'))) {
459
+ throw new Error('AI Data profile authority definition identity differs from the checkpoint.');
460
+ }
461
+ return profileAuthorityFromBinding(loadAIDataBinding(instance.definitionDir, extensionCodecs));
462
+ },
463
+ });
464
+ }
465
+
466
+ const compareCodeUnits = (left: string, right: string): number => left < right ? -1 : left > right ? 1 : 0;
467
+
468
+ function canonicalClosed(value: import('ai-workflow-contract').FlowClosedValue): string {
469
+ if (value === null || typeof value !== 'object') return JSON.stringify(value);
470
+ if (Array.isArray(value)) return `[${value.map(canonicalClosed).join(',')}]`;
471
+ const record = value as import('ai-workflow-contract').FlowClosedObject;
472
+ return `{${Object.keys(record).sort(compareCodeUnits).map((key) => (
473
+ `${JSON.stringify(key)}:${canonicalClosed(record[key])}`
474
+ )).join(',')}}`;
475
+ }
476
+
477
+ function durableBinding(binding: ReturnType<typeof loadAIDataBinding>) {
478
+ const {
479
+ baseUri: _baseUri,
480
+ definitionStepForest: _definitionStepForest,
481
+ ...definition
482
+ } = binding.definition;
483
+ return { ...binding, definition };
484
+ }
485
+
379
486
  function durableGraph(graph: AIDataWorkflowRunGraph): AIDataWorkflowRunGraph {
380
- const { baseUri: _baseUri, ...definition } = graph.binding.definition;
381
487
  return {
382
488
  ...graph,
383
- binding: { ...graph.binding, definition },
489
+ binding: durableBinding(graph.binding as ReturnType<typeof loadAIDataBinding>),
384
490
  };
385
491
  }
386
492
 
package/src/index.ts CHANGED
@@ -38,6 +38,7 @@ import type {
38
38
  FlowRunCheckpointCommit,
39
39
  FlowRunCheckpointKey,
40
40
  FlowRunCheckpointRuntime,
41
+ FlowRunStepExtensions,
41
42
  FlowRunProfileState,
42
43
  } from 'ai-workflow-contract';
43
44
  import type {
@@ -48,7 +49,8 @@ import type {
48
49
  EagerDataFlowValue,
49
50
  LoadEagerDataFlowOptions,
50
51
  } from 'eager-data-flow-contract';
51
- import { loadEagerDataFlowProfileSources } from 'eager-data-flow-logic';
52
+ import { loadEagerDataFlowProfileSources, loadEagerDataFlowProfileSourcesWithRuntime } from 'eager-data-flow-logic';
53
+ import { createDefinitionStepSourceRuntime, type DefinitionStepAssemblyRuntime } from 'flow-step-space-logic';
52
54
  import {
53
55
  createAIWorkflowRuntimeContext,
54
56
  commitFlowRunCheckpoint,
@@ -70,6 +72,7 @@ export interface CreateAIDataWorkflowCheckpointInput {
70
72
  readonly controllerSidecars: FlowClosedObject;
71
73
  readonly nodeSidecars: FlowClosedObject;
72
74
  readonly ai: AIWorkflowProfileDurableState;
75
+ readonly stepExtensions?: FlowRunStepExtensions;
73
76
  }
74
77
 
75
78
  /** Creates the first complete Data checkpoint as one runtime-owned transition. */
@@ -91,6 +94,7 @@ export function createAIDataWorkflowCheckpoint(
91
94
  output: input.output,
92
95
  controllerSidecars: input.controllerSidecars,
93
96
  nodeSidecars: input.nodeSidecars,
97
+ ...(input.stepExtensions === undefined ? {} : { stepExtensions: input.stepExtensions }),
94
98
  profile: { kind: 'AIDataWorkflow', runGraph: input.graph, ai: input.ai },
95
99
  },
96
100
  });
@@ -194,6 +198,34 @@ export interface AIDataWorkflowSourceLoadResult {
194
198
  readonly diagnostics: readonly EagerDataFlowDiagnostic[];
195
199
  }
196
200
 
201
+ export interface AIDataWorkflowSourceLoadRuntime {
202
+ readonly extensionCodecs: DefinitionStepAssemblyRuntime['extensionCodecs'];
203
+ }
204
+
205
+ export interface AIDataWorkflowSourceLoadInput {
206
+ readonly sources: EagerDataFlowSourceCollection;
207
+ }
208
+
209
+ export type AIDataWorkflowSourceLoadConfig = LoadEagerDataFlowOptions;
210
+
211
+ /** Processor form for profile admission with runtime-owned extension codecs. */
212
+ export function loadAIDataWorkflow(
213
+ runtime: AIDataWorkflowSourceLoadRuntime,
214
+ input: AIDataWorkflowSourceLoadInput,
215
+ config: AIDataWorkflowSourceLoadConfig = {},
216
+ ): AIDataWorkflowSourceLoadResult {
217
+ const loaded = loadEagerDataFlowProfileSourcesWithRuntime(
218
+ createDefinitionStepSourceRuntime(input.sources, runtime.extensionCodecs),
219
+ input.sources,
220
+ { ...config, profileRoot: 'AIDataWorkflow' },
221
+ );
222
+ if (!loaded.plan || loaded.diagnostics.length > 0) return { diagnostics: loaded.diagnostics };
223
+ return {
224
+ binding: defineEagerBackedAIDataWorkflow(loaded.plan),
225
+ diagnostics: loaded.diagnostics,
226
+ };
227
+ }
228
+
197
229
  export function loadAIDataWorkflowSources(
198
230
  sources: EagerDataFlowSourceCollection,
199
231
  options: LoadEagerDataFlowOptions = {},