ai-workflow-logic 0.1.2 → 0.1.3
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 +3 -4
- package/src/agent-resources.ts +203 -6
- package/src/run-freeze.ts +25 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-workflow-logic",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "AI workflow domain projections and pure runtime helpers with an explicit Halfcode filesystem adapter",
|
|
6
6
|
"type": "module",
|
|
@@ -12,14 +12,13 @@
|
|
|
12
12
|
"./run-freeze": "./src/run-freeze.ts"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"ai-workflow-contract": "0.1.
|
|
15
|
+
"ai-workflow-contract": "0.1.3",
|
|
16
16
|
"halfcode-compiler.xnl": "0.2.2"
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
19
|
"src"
|
|
20
20
|
],
|
|
21
21
|
"publishConfig": {
|
|
22
|
-
"access": "public"
|
|
23
|
-
"registry": "https://registry.npmjs.com"
|
|
22
|
+
"access": "public"
|
|
24
23
|
}
|
|
25
24
|
}
|
package/src/agent-resources.ts
CHANGED
|
@@ -2,10 +2,13 @@ import type {
|
|
|
2
2
|
AIAgentDefinitionProjection,
|
|
3
3
|
AIAgentMessageProjection,
|
|
4
4
|
AIAgentMessageRole,
|
|
5
|
+
AIWorkflowEffectPolicyProjection,
|
|
5
6
|
AIWorkflowAgentResourceProjection,
|
|
6
7
|
AIWorkflowAgentResourceRelation,
|
|
7
8
|
AIWorkflowAgentTaskRef,
|
|
8
9
|
AIWorkflowDefinitionResourceKind,
|
|
10
|
+
AIWorkflowMaterialValueProjection,
|
|
11
|
+
AIWorkflowMessageSchemaProjection,
|
|
9
12
|
AIWorkflowResourceProjectionDiagnostic,
|
|
10
13
|
AIWorkflowResourceRegistryView,
|
|
11
14
|
AIWorkflowTypedResourceRef,
|
|
@@ -114,7 +117,7 @@ function projectMaterialPort(
|
|
|
114
117
|
diagnostics.push(diagnostic('AI_MATERIAL_PORT_CARDINALITY_INVALID', resource.documentUri, 'MaterialPort cardinality must be one or many.'));
|
|
115
118
|
valid = false;
|
|
116
119
|
}
|
|
117
|
-
const schema =
|
|
120
|
+
const schema = projectOptionalSchemaSubdomain(
|
|
118
121
|
resource,
|
|
119
122
|
'SchemaRef',
|
|
120
123
|
resources,
|
|
@@ -147,12 +150,12 @@ function projectAgentDefinition(
|
|
|
147
150
|
const messages = messagesNode
|
|
148
151
|
? projectMessages(resource, messagesNode, resources, edges, diagnostics)
|
|
149
152
|
: [];
|
|
150
|
-
const inputSchema =
|
|
151
|
-
const outputSchema =
|
|
153
|
+
const inputSchema = projectOptionalSchemaSubdomain(resource, 'InputSchemaRef', resources, 'agent-input-schema', edges, diagnostics);
|
|
154
|
+
const outputSchema = projectOptionalSchemaSubdomain(resource, 'OutputSchemaRef', resources, 'agent-output-schema', edges, diagnostics);
|
|
152
155
|
const tools = toolRefsNode
|
|
153
156
|
? projectTypedRefList(resource, toolRefsNode, 'ToolRef', resources, 'agent-tool', edges, diagnostics)
|
|
154
157
|
: [];
|
|
155
|
-
const effectPolicy =
|
|
158
|
+
const effectPolicy = projectOptionalEffectPolicySubdomain(resource, 'EffectPolicyRef', resources, 'agent-effect-policy', edges, diagnostics);
|
|
156
159
|
const materialPorts = materialPortRefsNode
|
|
157
160
|
? projectTypedRefList(resource, materialPortRefsNode, 'MaterialPortRef', resources, 'agent-material-port', edges, diagnostics, 'MaterialPort')
|
|
158
161
|
: [];
|
|
@@ -209,7 +212,7 @@ function projectMessages(
|
|
|
209
212
|
if (prompt) edges.push(edge(resource.resourceId, prompt.resource.resourceId, 'agent-message-prompt'));
|
|
210
213
|
const schemaNode = value.subdomains.SchemaRef;
|
|
211
214
|
const schema = schemaNode?.tag === 'SchemaRef'
|
|
212
|
-
?
|
|
215
|
+
? projectMessageSchemaRefNode(schemaNode, resources, `${location}#SchemaRef`, diagnostics)
|
|
213
216
|
: undefined;
|
|
214
217
|
if (schemaNode && schemaNode.tag !== 'SchemaRef') diagnostics.push(diagnostic('AI_RESOURCE_SUBDOMAIN_TAG_MISMATCH', `${location}#SchemaRef`, `Subdomain SchemaRef must contain a SchemaRef node, got '${schemaNode.tag}'.`));
|
|
215
218
|
if (schema) edges.push(edge(resource.resourceId, schema.resource.resourceId, 'agent-message-schema'));
|
|
@@ -274,6 +277,69 @@ function projectOptionalTypedSubdomain(
|
|
|
274
277
|
return typedRef;
|
|
275
278
|
}
|
|
276
279
|
|
|
280
|
+
function projectOptionalSchemaSubdomain(
|
|
281
|
+
owner: ResourceRecord,
|
|
282
|
+
name: string,
|
|
283
|
+
resources: ReadonlyMap<string, ResourceRecord>,
|
|
284
|
+
relation: AIWorkflowAgentResourceRelation,
|
|
285
|
+
edges: ResourceDependencyEdge[],
|
|
286
|
+
diagnostics: AIWorkflowResourceProjectionDiagnostic[],
|
|
287
|
+
): AIWorkflowMessageSchemaProjection | undefined {
|
|
288
|
+
const node = owner.node.subdomains[name];
|
|
289
|
+
if (!node) return undefined;
|
|
290
|
+
if (node.tag !== name) {
|
|
291
|
+
diagnostics.push(diagnostic('AI_RESOURCE_SUBDOMAIN_TAG_MISMATCH', `${owner.documentUri}#${name}`, `Subdomain ${name} must contain a ${name} node, got '${node.tag}'.`));
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
const schema = projectMessageSchemaRefNode(node, resources, `${owner.documentUri}#${name}`, diagnostics);
|
|
295
|
+
if (schema) edges.push(edge(owner.resourceId, schema.resource.resourceId, relation));
|
|
296
|
+
return schema;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function projectOptionalEffectPolicySubdomain(
|
|
300
|
+
owner: ResourceRecord,
|
|
301
|
+
name: string,
|
|
302
|
+
resources: ReadonlyMap<string, ResourceRecord>,
|
|
303
|
+
relation: AIWorkflowAgentResourceRelation,
|
|
304
|
+
edges: ResourceDependencyEdge[],
|
|
305
|
+
diagnostics: AIWorkflowResourceProjectionDiagnostic[],
|
|
306
|
+
): AIWorkflowEffectPolicyProjection | undefined {
|
|
307
|
+
const node = owner.node.subdomains[name];
|
|
308
|
+
if (!node) return undefined;
|
|
309
|
+
if (node.tag !== name) {
|
|
310
|
+
diagnostics.push(diagnostic('AI_RESOURCE_SUBDOMAIN_TAG_MISMATCH', `${owner.documentUri}#${name}`, `Subdomain ${name} must contain a ${name} node, got '${node.tag}'.`));
|
|
311
|
+
return undefined;
|
|
312
|
+
}
|
|
313
|
+
const typedRef = projectTypedRefNode(node, resources, `${owner.documentUri}#${name}`, diagnostics, 'EffectPolicy');
|
|
314
|
+
if (!typedRef) return undefined;
|
|
315
|
+
const supportedProperties = new Set(['lifecycle', 'description', 'toolMode']);
|
|
316
|
+
const unsupportedProperties = Reflect.ownKeys(typedRef.resource.node.properties)
|
|
317
|
+
.filter((key) => typeof key !== 'string' || !supportedProperties.has(key))
|
|
318
|
+
.map((key) => typeof key === 'string' ? key : key.toString())
|
|
319
|
+
.sort(compareCodeUnits);
|
|
320
|
+
if (unsupportedProperties.length > 0) {
|
|
321
|
+
for (const property of unsupportedProperties) {
|
|
322
|
+
diagnostics.push(diagnostic(
|
|
323
|
+
'AI_EFFECT_POLICY_PROPERTY_UNSUPPORTED',
|
|
324
|
+
`${typedRef.resource.documentUri}#${property}`,
|
|
325
|
+
`EffectPolicy property '${property}' is not part of the closed executable policy contract.`,
|
|
326
|
+
));
|
|
327
|
+
}
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
const toolMode = ownResourceValue(typedRef.resource.node.properties, 'toolMode');
|
|
331
|
+
if (toolMode !== 'declared-only' && toolMode !== 'none') {
|
|
332
|
+
diagnostics.push(diagnostic(
|
|
333
|
+
'AI_EFFECT_POLICY_TOOL_MODE_INVALID',
|
|
334
|
+
typedRef.resource.documentUri,
|
|
335
|
+
"EffectPolicy toolMode must be 'declared-only' or 'none'.",
|
|
336
|
+
));
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
edges.push(edge(owner.resourceId, typedRef.resource.resourceId, relation));
|
|
340
|
+
return Object.freeze({ ...typedRef, kind: 'EffectPolicy', toolMode });
|
|
341
|
+
}
|
|
342
|
+
|
|
277
343
|
function projectMaterialBinding(
|
|
278
344
|
resource: ResourceRecord,
|
|
279
345
|
resources: ReadonlyMap<string, ResourceRecord>,
|
|
@@ -287,7 +353,10 @@ function projectMaterialBinding(
|
|
|
287
353
|
const materialNode = requiredSubdomain(resource, 'MaterialRef', diagnostics);
|
|
288
354
|
const task = taskNode ? projectTaskRef(taskNode, resources, `${resource.documentUri}#AgentTaskRef`, diagnostics) : undefined;
|
|
289
355
|
const portRef = portNode ? projectTypedRefNode(portNode, resources, `${resource.documentUri}#PortRef`, diagnostics, 'MaterialPort') : undefined;
|
|
290
|
-
const
|
|
356
|
+
const materialRef = materialNode ? projectTypedRefNode(materialNode, resources, `${resource.documentUri}#MaterialRef`, diagnostics) : undefined;
|
|
357
|
+
const material = materialRef
|
|
358
|
+
? projectMaterialValue(materialRef, diagnostics)
|
|
359
|
+
: undefined;
|
|
291
360
|
if (portRef) edges.push(edge(resource.resourceId, portRef.resource.resourceId, 'material-binding-port'));
|
|
292
361
|
if (material) edges.push(edge(resource.resourceId, material.resource.resourceId, 'material-binding-material'));
|
|
293
362
|
const port = portRef ? portsById.get(portRef.resource.resourceId) : undefined;
|
|
@@ -303,6 +372,134 @@ function projectMaterialBinding(
|
|
|
303
372
|
});
|
|
304
373
|
}
|
|
305
374
|
|
|
375
|
+
function projectMessageSchemaRefNode(
|
|
376
|
+
node: ResourceNode,
|
|
377
|
+
resources: ReadonlyMap<string, ResourceRecord>,
|
|
378
|
+
location: string,
|
|
379
|
+
diagnostics: AIWorkflowResourceProjectionDiagnostic[],
|
|
380
|
+
): AIWorkflowMessageSchemaProjection | undefined {
|
|
381
|
+
const typedRef = projectTypedRefNode(node, resources, location, diagnostics, 'MessageSchema');
|
|
382
|
+
if (!typedRef) return undefined;
|
|
383
|
+
const schemaValue = ownResourceValue(typedRef.resource.node.properties, 'schema');
|
|
384
|
+
if (!isResourceValueMap(schemaValue)) {
|
|
385
|
+
diagnostics.push(diagnostic(
|
|
386
|
+
'AI_MESSAGE_SCHEMA_VALUE_INVALID',
|
|
387
|
+
typedRef.resource.documentUri,
|
|
388
|
+
'MessageSchema must declare one explicit schema map.',
|
|
389
|
+
));
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
const schema = cloneResourceValue(schemaValue, typedRef.resource.documentUri, diagnostics, 'AI_MESSAGE_SCHEMA_VALUE_INVALID');
|
|
393
|
+
if (!isResourceValueMap(schema)) return undefined;
|
|
394
|
+
return Object.freeze({ ...typedRef, kind: 'MessageSchema', schema });
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function projectMaterialValue(
|
|
398
|
+
typedRef: AIWorkflowTypedResourceRef,
|
|
399
|
+
diagnostics: AIWorkflowResourceProjectionDiagnostic[],
|
|
400
|
+
): AIWorkflowMaterialValueProjection | undefined {
|
|
401
|
+
const value = ownResourceValue(typedRef.resource.node.properties, 'value');
|
|
402
|
+
if (value === undefined) {
|
|
403
|
+
diagnostics.push(diagnostic(
|
|
404
|
+
'AI_MATERIAL_VALUE_MISSING',
|
|
405
|
+
typedRef.resource.documentUri,
|
|
406
|
+
`Material '${typedRef.resource.resourceId}' must declare one explicit value.`,
|
|
407
|
+
));
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
const cloned = cloneResourceValue(value, typedRef.resource.documentUri, diagnostics, 'AI_MATERIAL_VALUE_INVALID');
|
|
411
|
+
return cloned === undefined ? undefined : Object.freeze({ ...typedRef, value: cloned });
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function ownResourceValue(
|
|
415
|
+
record: Readonly<Record<string, ResourceValue>>,
|
|
416
|
+
field: string,
|
|
417
|
+
): ResourceValue | undefined {
|
|
418
|
+
const descriptor = Object.getOwnPropertyDescriptor(record, field);
|
|
419
|
+
return descriptor && descriptor.enumerable && 'value' in descriptor
|
|
420
|
+
? descriptor.value
|
|
421
|
+
: undefined;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function isResourceValueMap(value: ResourceValue | undefined): value is Readonly<Record<string, ResourceValue>> {
|
|
425
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value) && !isResourceNode(value);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function cloneResourceValue(
|
|
429
|
+
value: ResourceValue,
|
|
430
|
+
location: string,
|
|
431
|
+
diagnostics: AIWorkflowResourceProjectionDiagnostic[],
|
|
432
|
+
code: string,
|
|
433
|
+
ancestors: WeakSet<object> = new WeakSet(),
|
|
434
|
+
): ResourceValue | undefined {
|
|
435
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;
|
|
436
|
+
if (typeof value === 'number') {
|
|
437
|
+
if (Number.isFinite(value)) return value;
|
|
438
|
+
diagnostics.push(diagnostic(code, location, 'Executable resource values may contain only finite numbers.'));
|
|
439
|
+
return undefined;
|
|
440
|
+
}
|
|
441
|
+
if (ancestors.has(value)) {
|
|
442
|
+
diagnostics.push(diagnostic(code, location, 'Executable resource values must not contain cycles.'));
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
ancestors.add(value);
|
|
446
|
+
if (Array.isArray(value)) {
|
|
447
|
+
const keys = Reflect.ownKeys(value);
|
|
448
|
+
const expected = new Set([...value.keys()].map(String).concat('length'));
|
|
449
|
+
if (keys.some((key) => typeof key !== 'string' || !expected.has(key))) {
|
|
450
|
+
diagnostics.push(diagnostic(code, location, 'Executable resource arrays must be dense plain-data arrays.'));
|
|
451
|
+
ancestors.delete(value);
|
|
452
|
+
return undefined;
|
|
453
|
+
}
|
|
454
|
+
const output: ResourceValue[] = [];
|
|
455
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
456
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
457
|
+
if (!descriptor || !descriptor.enumerable || !('value' in descriptor)) {
|
|
458
|
+
diagnostics.push(diagnostic(code, location, 'Executable resource arrays must be dense plain-data arrays.'));
|
|
459
|
+
ancestors.delete(value);
|
|
460
|
+
return undefined;
|
|
461
|
+
}
|
|
462
|
+
const item = cloneResourceValue(descriptor.value as ResourceValue, location, diagnostics, code, ancestors);
|
|
463
|
+
if (item === undefined) {
|
|
464
|
+
ancestors.delete(value);
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
output.push(item);
|
|
468
|
+
}
|
|
469
|
+
ancestors.delete(value);
|
|
470
|
+
return Object.freeze(output);
|
|
471
|
+
}
|
|
472
|
+
const prototype = Object.getPrototypeOf(value);
|
|
473
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
474
|
+
diagnostics.push(diagnostic(code, location, 'Executable resource maps must be plain-data objects.'));
|
|
475
|
+
ancestors.delete(value);
|
|
476
|
+
return undefined;
|
|
477
|
+
}
|
|
478
|
+
const output: Record<string, ResourceValue> = Object.create(null) as Record<string, ResourceValue>;
|
|
479
|
+
const keys = Reflect.ownKeys(value);
|
|
480
|
+
if (keys.some((key) => typeof key !== 'string')) {
|
|
481
|
+
diagnostics.push(diagnostic(code, location, 'Executable resource maps may contain only string keys.'));
|
|
482
|
+
ancestors.delete(value);
|
|
483
|
+
return undefined;
|
|
484
|
+
}
|
|
485
|
+
for (const key of (keys as string[]).sort(compareCodeUnits)) {
|
|
486
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
487
|
+
if (!descriptor || !descriptor.enumerable || !('value' in descriptor)) {
|
|
488
|
+
diagnostics.push(diagnostic(code, location, 'Executable resource maps must contain only enumerable data properties.'));
|
|
489
|
+
ancestors.delete(value);
|
|
490
|
+
return undefined;
|
|
491
|
+
}
|
|
492
|
+
const item = cloneResourceValue(descriptor.value as ResourceValue, location, diagnostics, code, ancestors);
|
|
493
|
+
if (item === undefined) {
|
|
494
|
+
ancestors.delete(value);
|
|
495
|
+
return undefined;
|
|
496
|
+
}
|
|
497
|
+
output[key] = item;
|
|
498
|
+
}
|
|
499
|
+
ancestors.delete(value);
|
|
500
|
+
return Object.freeze(output);
|
|
501
|
+
}
|
|
502
|
+
|
|
306
503
|
function projectTaskRef(
|
|
307
504
|
node: ResourceNode,
|
|
308
505
|
resources: ReadonlyMap<string, ResourceRecord>,
|
package/src/run-freeze.ts
CHANGED
|
@@ -154,18 +154,38 @@ function semanticResourceFingerprint(
|
|
|
154
154
|
id: message.id,
|
|
155
155
|
role: message.role,
|
|
156
156
|
prompt: [message.prompt.kind, message.prompt.resource.resourceId],
|
|
157
|
-
schema: message.schema ?
|
|
157
|
+
schema: message.schema ? {
|
|
158
|
+
kind: message.schema.kind,
|
|
159
|
+
resourceId: message.schema.resource.resourceId,
|
|
160
|
+
value: message.schema.schema,
|
|
161
|
+
} : null,
|
|
158
162
|
})),
|
|
159
|
-
inputSchema: agent.inputSchema ?
|
|
160
|
-
|
|
163
|
+
inputSchema: agent.inputSchema ? {
|
|
164
|
+
kind: agent.inputSchema.kind,
|
|
165
|
+
resourceId: agent.inputSchema.resource.resourceId,
|
|
166
|
+
value: agent.inputSchema.schema,
|
|
167
|
+
} : null,
|
|
168
|
+
outputSchema: agent.outputSchema ? {
|
|
169
|
+
kind: agent.outputSchema.kind,
|
|
170
|
+
resourceId: agent.outputSchema.resource.resourceId,
|
|
171
|
+
value: agent.outputSchema.schema,
|
|
172
|
+
} : null,
|
|
161
173
|
tools: agent.tools.map((ref) => [ref.kind, ref.resource.resourceId]),
|
|
162
|
-
effectPolicy: agent.effectPolicy ?
|
|
174
|
+
effectPolicy: agent.effectPolicy ? {
|
|
175
|
+
kind: agent.effectPolicy.kind,
|
|
176
|
+
resourceId: agent.effectPolicy.resource.resourceId,
|
|
177
|
+
toolMode: agent.effectPolicy.toolMode,
|
|
178
|
+
} : null,
|
|
163
179
|
materialPorts: agent.materialPorts.map((ref) => [ref.kind, ref.resource.resourceId]),
|
|
164
180
|
},
|
|
165
181
|
bindings: bindings.map((binding) => ({
|
|
166
182
|
resourceId: binding.resource.resourceId,
|
|
167
183
|
port: [binding.port.resource.resourceId, binding.port.materialKind, binding.port.required, binding.port.cardinality],
|
|
168
|
-
material:
|
|
184
|
+
material: {
|
|
185
|
+
kind: binding.material.kind,
|
|
186
|
+
resourceId: binding.material.resource.resourceId,
|
|
187
|
+
value: binding.material.value,
|
|
188
|
+
},
|
|
169
189
|
})),
|
|
170
190
|
closure: closure.map((resource) => ({
|
|
171
191
|
resourceId: resource.resourceId,
|