ai-data-workflow-logic 0.1.6 → 0.1.8
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 +8 -8
- package/src/filesystem.ts +125 -19
- package/src/index.ts +33 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-data-workflow-logic",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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.
|
|
15
|
-
"ai-workflow-contract": "0.1.
|
|
16
|
-
"ai-workflow-logic": "0.1.
|
|
17
|
-
"eager-data-flow-contract": "0.1.
|
|
18
|
-
"eager-data-flow-logic": "0.1.
|
|
19
|
-
"flow-step-space-logic": "0.1.
|
|
20
|
-
"work-ctrl-flow-logic": "0.1.
|
|
14
|
+
"ai-data-workflow-contract": "0.1.4",
|
|
15
|
+
"ai-workflow-contract": "0.1.7",
|
|
16
|
+
"ai-workflow-logic": "0.1.7",
|
|
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 =
|
|
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
|
|
135
|
-
const
|
|
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:
|
|
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:
|
|
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
|
|
322
|
-
const
|
|
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:
|
|
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(
|
|
360
|
-
|
|
395
|
+
function validateAIDataDefinition(
|
|
396
|
+
definitionDir: string,
|
|
397
|
+
extensionCodecs: DefinitionStepAssemblyRuntime['extensionCodecs'],
|
|
398
|
+
): void {
|
|
399
|
+
loadAIDataBinding(definitionDir, extensionCodecs);
|
|
361
400
|
}
|
|
362
401
|
|
|
363
|
-
function loadAIDataBinding(
|
|
364
|
-
|
|
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:
|
|
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 = {},
|