ai-data-workflow-logic 0.1.5 → 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 +8 -7
- package/src/filesystem.ts +132 -23
- 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.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "EagerDataFlow-backed AI Data Workflow RunGraph helpers",
|
|
6
6
|
"type": "module",
|
|
@@ -11,12 +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
|
-
"
|
|
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"
|
|
20
21
|
},
|
|
21
22
|
"files": [
|
|
22
23
|
"src"
|
package/src/filesystem.ts
CHANGED
|
@@ -20,12 +20,24 @@ import {
|
|
|
20
20
|
createFilesystemCodeResolver,
|
|
21
21
|
materializeEagerDataFlowPlan,
|
|
22
22
|
} from 'eager-data-flow-logic';
|
|
23
|
-
import { FilesystemFlowRunCheckpointStore } from 'ai-workflow-logic/checkpoint-filesystem';
|
|
24
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,
|
|
25
33
|
normalizeAIWorkflowProfileDurableState,
|
|
26
34
|
normalizeFlowClosedObject,
|
|
27
35
|
normalizeFlowClosedValue,
|
|
36
|
+
type FlowRunProfileAuthorityPort,
|
|
37
|
+
type FlowRunProfileAuthorityRequest,
|
|
38
|
+
type FrozenFlowRunProfileAuthority,
|
|
28
39
|
} from 'ai-workflow-logic';
|
|
40
|
+
import type { DefinitionStepAssemblyRuntime, DefinitionStepForest } from 'flow-step-space-logic';
|
|
29
41
|
import {
|
|
30
42
|
loadMaterializedFlowInstance,
|
|
31
43
|
materializeFlowInstance,
|
|
@@ -45,6 +57,7 @@ export interface MaterializedAIDataWorkflowRuntime {
|
|
|
45
57
|
readonly supportRoot: string;
|
|
46
58
|
readonly workflow: AIDataWorkflowRuntime;
|
|
47
59
|
readonly importModule: EagerDataFlowModuleImporter;
|
|
60
|
+
readonly extensionCodecs?: DefinitionStepAssemblyRuntime['extensionCodecs'];
|
|
48
61
|
readonly agentEffects?: AIAgentHostEffectPort;
|
|
49
62
|
readonly agentTaskProofs?: Readonly<Record<string, FrozenAIAgentTaskBinding>>;
|
|
50
63
|
readonly checkpointObserver?: (event: {
|
|
@@ -81,6 +94,21 @@ export interface ContinueMaterializedAIDataWorkflowInput {
|
|
|
81
94
|
|
|
82
95
|
export type ContinueMaterializedAIDataWorkflowConfig = Readonly<Record<never, never>>;
|
|
83
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
|
+
|
|
84
112
|
/** Runtime-first canonical Data execution from a frozen instance. */
|
|
85
113
|
export async function startMaterializedAIDataWorkflow(
|
|
86
114
|
runtime: MaterializedAIDataWorkflowRuntime,
|
|
@@ -89,8 +117,8 @@ export async function startMaterializedAIDataWorkflow(
|
|
|
89
117
|
): Promise<MaterializedAIDataWorkflowResult> {
|
|
90
118
|
requireAgentTaskBindings(runtime);
|
|
91
119
|
const instance = ensureAIDataInstance(runtime, input);
|
|
92
|
-
const binding = loadAIDataBinding(instance.definitionDir);
|
|
93
|
-
const checkpointStore =
|
|
120
|
+
const binding = loadAIDataBinding(instance.definitionDir, runtime.extensionCodecs ?? emptyExtensionCodecs);
|
|
121
|
+
const checkpointStore = checkpointStoreForRuntime(runtime);
|
|
94
122
|
const checkpointRuntime = { checkpointStore };
|
|
95
123
|
const existing = await loadAIDataWorkflowCheckpoint(checkpointRuntime, input);
|
|
96
124
|
if (existing) {
|
|
@@ -118,6 +146,7 @@ export async function startMaterializedAIDataWorkflow(
|
|
|
118
146
|
controllerSidecars: { status: 'Pending' },
|
|
119
147
|
nodeSidecars: nodeStatuses(graph),
|
|
120
148
|
ai: config.durableAI,
|
|
149
|
+
stepExtensions: createInitialRunStepExtensions(profileAuthorityFromBinding(binding)),
|
|
121
150
|
});
|
|
122
151
|
return executeAIDataCheckpoint(runtime, binding, v0);
|
|
123
152
|
}
|
|
@@ -130,10 +159,11 @@ export async function continueMaterializedAIDataWorkflow(
|
|
|
130
159
|
): Promise<MaterializedAIDataWorkflowResult> {
|
|
131
160
|
requireAgentTaskBindings(runtime);
|
|
132
161
|
const instanceDir = path.join(runtime.supportRoot, 'instances', input.instanceId);
|
|
133
|
-
const
|
|
134
|
-
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);
|
|
135
165
|
const checkpoint = await loadAIDataWorkflowCheckpoint(
|
|
136
|
-
{ checkpointStore:
|
|
166
|
+
{ checkpointStore: checkpointStoreForRuntime(runtime) },
|
|
137
167
|
input,
|
|
138
168
|
);
|
|
139
169
|
if (!checkpoint) throw new Error(`AI Data Workflow run ${input.runId} does not have a canonical checkpoint.`);
|
|
@@ -166,7 +196,7 @@ async function executeAIDataCheckpoint(
|
|
|
166
196
|
}
|
|
167
197
|
const invocationInput = checkpoint.input as Readonly<Record<string, FlowClosedValue>>;
|
|
168
198
|
const checkpointRuntime = {
|
|
169
|
-
checkpointStore:
|
|
199
|
+
checkpointStore: checkpointStoreForRuntime(runtime),
|
|
170
200
|
};
|
|
171
201
|
let transitionTail = Promise.resolve();
|
|
172
202
|
const transition = <T>(operation: () => Promise<T>): Promise<T> => {
|
|
@@ -317,10 +347,11 @@ export async function loadMaterializedAIDataWorkflowRun(
|
|
|
317
347
|
input: Pick<StartMaterializedAIDataWorkflowInput, 'instanceId' | 'runId'>,
|
|
318
348
|
): Promise<MaterializedAIDataWorkflowResult | undefined> {
|
|
319
349
|
const instanceDir = path.join(runtime.supportRoot, 'instances', input.instanceId);
|
|
320
|
-
const
|
|
321
|
-
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);
|
|
322
353
|
const checkpoint = await loadAIDataWorkflowCheckpoint(
|
|
323
|
-
{ checkpointStore:
|
|
354
|
+
{ checkpointStore: checkpointStoreForRuntime(runtime) },
|
|
324
355
|
input,
|
|
325
356
|
);
|
|
326
357
|
if (!checkpoint) return undefined;
|
|
@@ -340,7 +371,10 @@ function ensureAIDataInstance(
|
|
|
340
371
|
const instancesDir = path.join(runtime.supportRoot, 'instances');
|
|
341
372
|
const instanceDir = path.join(instancesDir, input.instanceId);
|
|
342
373
|
if (fs.existsSync(instanceDir)) {
|
|
343
|
-
return loadMaterializedFlowInstance(instanceDir, input.instanceId, validateAIDataDefinition
|
|
374
|
+
return loadMaterializedFlowInstance(instanceDir, input.instanceId, (definitionDir) => validateAIDataDefinition(
|
|
375
|
+
definitionDir,
|
|
376
|
+
runtime.extensionCodecs ?? emptyExtensionCodecs,
|
|
377
|
+
));
|
|
344
378
|
}
|
|
345
379
|
if (!input.bundleDir || !input.source) {
|
|
346
380
|
if (input.bundleDir || input.source) throw new Error('bundleDir and source identity must be provided together.');
|
|
@@ -351,33 +385,108 @@ function ensureAIDataInstance(
|
|
|
351
385
|
instancesDir,
|
|
352
386
|
instanceId: input.instanceId,
|
|
353
387
|
source: input.source,
|
|
354
|
-
validateDefinition: validateAIDataDefinition
|
|
388
|
+
validateDefinition: (definitionDir) => validateAIDataDefinition(
|
|
389
|
+
definitionDir,
|
|
390
|
+
runtime.extensionCodecs ?? emptyExtensionCodecs,
|
|
391
|
+
),
|
|
355
392
|
});
|
|
356
393
|
}
|
|
357
394
|
|
|
358
|
-
function validateAIDataDefinition(
|
|
359
|
-
|
|
395
|
+
function validateAIDataDefinition(
|
|
396
|
+
definitionDir: string,
|
|
397
|
+
extensionCodecs: DefinitionStepAssemblyRuntime['extensionCodecs'],
|
|
398
|
+
): void {
|
|
399
|
+
loadAIDataBinding(definitionDir, extensionCodecs);
|
|
360
400
|
}
|
|
361
401
|
|
|
362
|
-
function loadAIDataBinding(
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
402
|
+
function loadAIDataBinding(
|
|
403
|
+
definitionDir: string,
|
|
404
|
+
extensionCodecs: DefinitionStepAssemblyRuntime['extensionCodecs'],
|
|
405
|
+
) {
|
|
406
|
+
const assembled = assembleDefinitionStepProfileDirectory(
|
|
407
|
+
createFilesystemDefinitionStepProfileRuntime({ rootDir: definitionDir, extensionCodecs }),
|
|
408
|
+
{},
|
|
409
|
+
{ profileRoot: 'AIDataWorkflow' },
|
|
368
410
|
);
|
|
411
|
+
if (assembled.diagnostics.length > 0) {
|
|
412
|
+
throw new Error(`AIDataWorkflow StepSpace is invalid: ${assembled.diagnostics.map((item) => item.code).join(', ')}`);
|
|
413
|
+
}
|
|
414
|
+
const sources = Object.fromEntries((assembled.sources as readonly ({ readonly ref: string; readonly content: string } | { readonly name: string; readonly content: string })[])
|
|
415
|
+
.map((source) => ['name' in source ? source.name : source.ref, source.content]));
|
|
369
416
|
const loaded = loadAIDataWorkflowSources(sources, { baseUri: definitionDir });
|
|
370
417
|
if (!loaded.binding || loaded.diagnostics.length > 0) {
|
|
371
418
|
throw new Error(`AIDataWorkflow definition is invalid: ${loaded.diagnostics.map((item) => item.code).join(', ')}`);
|
|
372
419
|
}
|
|
373
|
-
return
|
|
420
|
+
return assembled.forest
|
|
421
|
+
? { ...loaded.binding, definition: { ...loaded.binding.definition, definitionStepForest: assembled.forest } }
|
|
422
|
+
: loaded.binding;
|
|
423
|
+
}
|
|
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 };
|
|
374
484
|
}
|
|
375
485
|
|
|
376
486
|
function durableGraph(graph: AIDataWorkflowRunGraph): AIDataWorkflowRunGraph {
|
|
377
|
-
const { baseUri: _baseUri, ...definition } = graph.binding.definition;
|
|
378
487
|
return {
|
|
379
488
|
...graph,
|
|
380
|
-
binding:
|
|
489
|
+
binding: durableBinding(graph.binding as ReturnType<typeof loadAIDataBinding>),
|
|
381
490
|
};
|
|
382
491
|
}
|
|
383
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 = {},
|