@noetic-tools/core 5.2.0 → 5.4.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.
- package/README.md +8 -6
- package/dist/adapters/in-memory-subprocess/step-completion.js +1 -1
- package/dist/adapters/openrouter.d.ts +20 -2
- package/dist/adapters/openrouter.js +102 -6
- package/dist/builders/control-flow-builders.d.ts +13 -13
- package/dist/builders/control-flow-builders.js +11 -11
- package/dist/{patterns → builders}/dynamic-workflow.d.ts +1 -1
- package/dist/{patterns → builders}/dynamic-workflow.js +18 -12
- package/dist/builders/every.d.ts +14 -14
- package/dist/builders/every.js +16 -16
- package/dist/builders/layer-provides-builders.d.ts +1 -1
- package/dist/builders/layer-provides-builders.js +1 -1
- package/dist/builders/provide-builder.d.ts +5 -12
- package/dist/builders/provide-builder.js +9 -10
- package/dist/builders/spawn-builder.d.ts +0 -2
- package/dist/builders/spawn-builder.js +2 -3
- package/dist/builders/step-builders.d.ts +66 -55
- package/dist/builders/step-builders.js +118 -111
- package/dist/builders/tool-builder.d.ts +18 -22
- package/dist/builders/tool-builder.js +6 -4
- package/dist/builders/workflow-hydrator.d.ts +3 -3
- package/dist/builders/workflow-hydrator.js +59 -63
- package/dist/builders/workflow-step.d.ts +13 -25
- package/dist/builders/workflow-step.js +13 -18
- package/dist/conditions/conditions.d.ts +2 -2
- package/dist/conditions/conditions.js +1 -1
- package/dist/harness/agent-harness.d.ts +58 -40
- package/dist/harness/agent-harness.js +125 -51
- package/dist/harness/deps/context.d.ts +1 -1
- package/dist/harness/deps/context.js +1 -1
- package/dist/harness/deps/interpreter.d.ts +1 -0
- package/dist/harness/deps/interpreter.js +1 -0
- package/dist/harness/deps/types.d.ts +2 -2
- package/dist/harness/model-call.d.ts +1 -1
- package/dist/harness/model-call.js +45 -23
- package/dist/index.d.ts +23 -35
- package/dist/index.js +10 -26
- package/dist/interpreter/action-deps.d.ts +2 -2
- package/dist/interpreter/action-deps.js +1 -1
- package/dist/interpreter/action-types.d.ts +1 -1
- package/dist/interpreter/collect-tools.js +7 -7
- package/dist/interpreter/context-assembly.d.ts +50 -0
- package/dist/interpreter/context-assembly.js +547 -0
- package/dist/interpreter/execute-action.d.ts +5 -5
- package/dist/interpreter/execute-action.js +337 -208
- package/dist/interpreter/execute-control.d.ts +9 -9
- package/dist/interpreter/execute-control.js +27 -27
- package/dist/interpreter/execute-sub-harness.d.ts +1 -1
- package/dist/interpreter/execute-sub-harness.js +7 -26
- package/dist/interpreter/execute.d.ts +1 -1
- package/dist/interpreter/execute.js +26 -25
- package/dist/interpreter/step-dispatcher.d.ts +1 -1
- package/dist/interpreter/step-dispatcher.js +3 -3
- package/dist/interpreter/structured-output.d.ts +14 -0
- package/dist/interpreter/structured-output.js +40 -0
- package/dist/interpreter/typeguards.d.ts +1 -1
- package/dist/interpreter/typeguards.js +1 -1
- package/dist/observability/genai-attributes.d.ts +20 -0
- package/dist/observability/genai-attributes.js +20 -0
- package/dist/portable/builders.d.ts +3 -3
- package/dist/portable/builders.js +6 -5
- package/dist/portable/context-layers.d.ts +1 -1
- package/dist/portable/context-layers.js +1 -1
- package/dist/runtime/broadcaster-utils.d.ts +1 -1
- package/dist/runtime/broadcaster-utils.js +1 -1
- package/dist/runtime/context-impl.d.ts +8 -10
- package/dist/runtime/context-impl.js +8 -12
- package/dist/runtime/durable/step-ledger.d.ts +2 -2
- package/dist/runtime/durable/step-ledger.js +3 -3
- package/dist/runtime/session-runner.d.ts +12 -1
- package/dist/runtime/session-runner.js +33 -1
- package/dist/runtime/tool-context.js +0 -2
- package/dist/schemas/workflow.d.ts +3 -3
- package/dist/schemas/workflow.js +2 -2
- package/dist/types/checkpoint.d.ts +11 -2
- package/dist/types/checkpoint.js +10 -1
- package/dist/{runtime → util}/detached-handle.js +1 -1
- package/package.json +6 -4
- package/schema/noetic-workflow.schema.json +24 -11
- package/dist/builders/context-option.d.ts +0 -23
- package/dist/builders/context-option.js +0 -22
- package/dist/deprecated.d.ts +0 -30
- package/dist/deprecated.js +0 -28
- package/dist/patterns/interview.d.ts +0 -53
- package/dist/patterns/interview.js +0 -113
- package/dist/patterns/plans.d.ts +0 -44
- package/dist/patterns/plans.js +0 -125
- package/dist/patterns/ralph-wiggum.d.ts +0 -19
- package/dist/patterns/ralph-wiggum.js +0 -37
- package/dist/patterns/react.d.ts +0 -19
- package/dist/patterns/react.js +0 -41
- /package/dist/{runtime → util}/detached-handle.d.ts +0 -0
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Converts a validated `WorkflowDocument` into a live `Step` tree.
|
|
3
3
|
*
|
|
4
|
-
* Each node kind maps to the corresponding builder (`
|
|
4
|
+
* Each node kind maps to the corresponding builder (`callModel`, `inParallel`,
|
|
5
5
|
* `loop`, etc.) so hydrated steps are indistinguishable from programmatic
|
|
6
6
|
* ones — they register in the step registry, carry retry policies, etc.
|
|
7
7
|
*/
|
|
8
8
|
import { frameworkCast, isServerToolSpec, NoeticConfigError } from '@noetic-tools/types';
|
|
9
|
-
import { DetachedHandleImpl } from '../runtime/detached-handle.js';
|
|
10
9
|
import { all, any } from '../until/combinators.js';
|
|
11
10
|
import { until } from '../until/predicates.js';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
11
|
+
import { DetachedHandleImpl } from '../util/detached-handle.js';
|
|
12
|
+
import { conditional, inParallel } from './control-flow-builders.js';
|
|
13
|
+
import { schedule } from './every.js';
|
|
14
14
|
import { loop } from './loop-builder.js';
|
|
15
|
-
import {
|
|
15
|
+
import { withContext } from './provide-builder.js';
|
|
16
16
|
import { spawn } from './spawn-builder.js';
|
|
17
|
-
import { step } from './step-builders.js';
|
|
17
|
+
import { callModel, runCode, step } from './step-builders.js';
|
|
18
18
|
//#endregion
|
|
19
19
|
//#region Until Predicate Hydration
|
|
20
20
|
function hydrateUntilPredicate(pred) {
|
|
@@ -24,9 +24,11 @@ function hydrateUntilPredicate(pred) {
|
|
|
24
24
|
case 'maxCost':
|
|
25
25
|
return until.maxCost(pred.usd);
|
|
26
26
|
case 'maxDuration':
|
|
27
|
-
return until.maxDuration(pred.
|
|
27
|
+
return until.maxDuration(pred.duration);
|
|
28
28
|
case 'noToolCalls':
|
|
29
29
|
return until.noToolCalls();
|
|
30
|
+
case 'never':
|
|
31
|
+
return until.never();
|
|
30
32
|
case 'outputContains':
|
|
31
33
|
return until.outputContains(pred.marker);
|
|
32
34
|
case 'outputEquals':
|
|
@@ -43,7 +45,7 @@ function hydrateUntilPredicate(pred) {
|
|
|
43
45
|
throw new NoeticConfigError({
|
|
44
46
|
code: 'UNKNOWN_UNTIL_PREDICATE',
|
|
45
47
|
message: `Unknown until predicate kind: '${frameworkCast(pred).kind}'.`,
|
|
46
|
-
hint: 'Supported kinds: maxSteps, maxCost, maxDuration, noToolCalls, outputContains, outputEquals, converged, any, all.',
|
|
48
|
+
hint: 'Supported kinds: maxSteps, maxCost, maxDuration, noToolCalls, never, outputContains, outputEquals, converged, any, all.',
|
|
47
49
|
});
|
|
48
50
|
}
|
|
49
51
|
}
|
|
@@ -58,7 +60,7 @@ function hydrateUntilPredicate(pred) {
|
|
|
58
60
|
* kinds combine into one `tools` array; the interpreter partitions them again
|
|
59
61
|
* at execution.
|
|
60
62
|
*/
|
|
61
|
-
function
|
|
63
|
+
function resolveCallModelTools(entries, registry) {
|
|
62
64
|
const toolNames = [];
|
|
63
65
|
const serverSpecs = [];
|
|
64
66
|
for (const entry of entries ?? []) {
|
|
@@ -74,12 +76,12 @@ function resolveLlmTools(entries, registry) {
|
|
|
74
76
|
...serverSpecs,
|
|
75
77
|
];
|
|
76
78
|
}
|
|
77
|
-
function
|
|
78
|
-
if (node.kind !== '
|
|
79
|
+
function hydrateCallModelNode(node, ctx) {
|
|
80
|
+
if (node.kind !== 'callModel') {
|
|
79
81
|
return frameworkCast(undefined);
|
|
80
82
|
}
|
|
81
|
-
const combined =
|
|
82
|
-
return
|
|
83
|
+
const combined = resolveCallModelTools(node.tools, ctx.tools);
|
|
84
|
+
return callModel({
|
|
83
85
|
id: node.id,
|
|
84
86
|
model: node.model ?? 'openai/gpt-4o',
|
|
85
87
|
instructions: node.instructions,
|
|
@@ -89,7 +91,7 @@ function hydrateLlmNode(node, ctx) {
|
|
|
89
91
|
});
|
|
90
92
|
}
|
|
91
93
|
/**
|
|
92
|
-
* Resolve
|
|
94
|
+
* Resolve a `callModel` node's `output` codec reference to a live `OutputCodec`.
|
|
93
95
|
*
|
|
94
96
|
* Hydrated steps are typed `<string, string>` (the JSON boundary erases output
|
|
95
97
|
* types), so the resolved codec is cast to the step's erased output type — the
|
|
@@ -118,8 +120,8 @@ function resolveOutputCodec(ref, nodeId, ctx) {
|
|
|
118
120
|
hint: `Pass output codecs via HydrationContext.uiLibraries, e.g. new Map([['${ref.library}', openUi(myLibrary)]]) from @noetic-tools/openui. Available: ${availableLibraries(ctx)}`,
|
|
119
121
|
});
|
|
120
122
|
}
|
|
121
|
-
function
|
|
122
|
-
if (node.kind !== '
|
|
123
|
+
function hydrateInvokeToolNode(node, ctx) {
|
|
124
|
+
if (node.kind !== 'invokeTool') {
|
|
123
125
|
return frameworkCast(undefined);
|
|
124
126
|
}
|
|
125
127
|
const resolved = ctx.tools.get(node.toolName);
|
|
@@ -132,7 +134,7 @@ function hydrateToolNode(node, ctx) {
|
|
|
132
134
|
].join(', ') || '(none)'}`,
|
|
133
135
|
});
|
|
134
136
|
}
|
|
135
|
-
return frameworkCast(
|
|
137
|
+
return frameworkCast(runCode({
|
|
136
138
|
id: node.id,
|
|
137
139
|
execute: async (_input, execCtx) => {
|
|
138
140
|
const args = node.args ?? {};
|
|
@@ -156,12 +158,6 @@ function hydrateToolNode(node, ctx) {
|
|
|
156
158
|
fs: execCtx.fs,
|
|
157
159
|
shell: execCtx.shell,
|
|
158
160
|
context: layerState,
|
|
159
|
-
// Deprecated alias — the same accessor object, as
|
|
160
|
-
// `buildToolExecutionContext` does. `Tool.execute` takes its second
|
|
161
|
-
// argument as `unknown`, so nothing here is structurally checked
|
|
162
|
-
// against `ToolExecutionContext`; the annotation above is what makes
|
|
163
|
-
// a missing field a compile error instead of a runtime `undefined`.
|
|
164
|
-
memory: layerState,
|
|
165
161
|
assembledView: execCtx.itemLog.items,
|
|
166
162
|
lastStepMeta: execCtx.lastStepMeta,
|
|
167
163
|
};
|
|
@@ -170,13 +166,13 @@ function hydrateToolNode(node, ctx) {
|
|
|
170
166
|
},
|
|
171
167
|
}));
|
|
172
168
|
}
|
|
173
|
-
function
|
|
174
|
-
if (node.kind !== '
|
|
169
|
+
function hydrateRunCodeNode(node, ctx) {
|
|
170
|
+
if (node.kind !== 'runCode') {
|
|
175
171
|
return frameworkCast(undefined);
|
|
176
172
|
}
|
|
177
173
|
const code = node.execute;
|
|
178
174
|
const subprocessRef = node.subprocess;
|
|
179
|
-
return frameworkCast(
|
|
175
|
+
return frameworkCast(runCode({
|
|
180
176
|
id: node.id,
|
|
181
177
|
retry: node.retry,
|
|
182
178
|
execute: async (input, execCtx) => {
|
|
@@ -195,8 +191,8 @@ function hydrateRunNode(node, ctx) {
|
|
|
195
191
|
},
|
|
196
192
|
}));
|
|
197
193
|
}
|
|
198
|
-
function
|
|
199
|
-
if (node.kind !== '
|
|
194
|
+
function hydrateConditionalNode(node, ctx) {
|
|
195
|
+
if (node.kind !== 'conditional') {
|
|
200
196
|
return frameworkCast(undefined);
|
|
201
197
|
}
|
|
202
198
|
const hydratedRoutes = node.routes.map((r) => ({
|
|
@@ -208,7 +204,7 @@ function hydrateBranchNode(node, ctx) {
|
|
|
208
204
|
if (defaultTarget) {
|
|
209
205
|
allTargets.push(defaultTarget);
|
|
210
206
|
}
|
|
211
|
-
return
|
|
207
|
+
return conditional({
|
|
212
208
|
id: node.id,
|
|
213
209
|
route: (input) => {
|
|
214
210
|
const trimmed = input.trim().toLowerCase();
|
|
@@ -222,12 +218,12 @@ function hydrateBranchNode(node, ctx) {
|
|
|
222
218
|
_optimizable: frameworkCast(allTargets),
|
|
223
219
|
});
|
|
224
220
|
}
|
|
225
|
-
function
|
|
226
|
-
if (node.kind !== '
|
|
221
|
+
function hydrateInParallelNode(node, ctx) {
|
|
222
|
+
if (node.kind !== 'inParallel') {
|
|
227
223
|
return frameworkCast(undefined);
|
|
228
224
|
}
|
|
229
|
-
// Static
|
|
230
|
-
// Dynamic
|
|
225
|
+
// Static inParallel: paths are known at hydration time and feed the optimizer.
|
|
226
|
+
// Dynamic inParallel (`each`): paths are produced per inParallel-input at runtime, one
|
|
231
227
|
// child per array item, so they cannot be pre-computed or optimized.
|
|
232
228
|
const dynamic = node.each !== undefined;
|
|
233
229
|
const eachTemplate = node.each;
|
|
@@ -247,7 +243,7 @@ function hydrateForkNode(node, ctx) {
|
|
|
247
243
|
};
|
|
248
244
|
const optimizable = dynamic ? undefined : frameworkCast(staticPaths);
|
|
249
245
|
if (node.mode === 'race') {
|
|
250
|
-
return
|
|
246
|
+
return inParallel({
|
|
251
247
|
id: node.id,
|
|
252
248
|
mode: 'race',
|
|
253
249
|
paths: pathsFactory,
|
|
@@ -257,7 +253,7 @@ function hydrateForkNode(node, ctx) {
|
|
|
257
253
|
}
|
|
258
254
|
const mergeFn = buildMerge(node.merge ?? 'last');
|
|
259
255
|
if (node.mode === 'settle') {
|
|
260
|
-
return
|
|
256
|
+
return inParallel({
|
|
261
257
|
id: node.id,
|
|
262
258
|
mode: 'settle',
|
|
263
259
|
paths: pathsFactory,
|
|
@@ -269,7 +265,7 @@ function hydrateForkNode(node, ctx) {
|
|
|
269
265
|
_optimizable: optimizable,
|
|
270
266
|
});
|
|
271
267
|
}
|
|
272
|
-
return
|
|
268
|
+
return inParallel({
|
|
273
269
|
id: node.id,
|
|
274
270
|
mode: 'all',
|
|
275
271
|
paths: pathsFactory,
|
|
@@ -279,7 +275,7 @@ function hydrateForkNode(node, ctx) {
|
|
|
279
275
|
});
|
|
280
276
|
}
|
|
281
277
|
/**
|
|
282
|
-
* Resolves layer names declared on a `
|
|
278
|
+
* Resolves layer names declared on a `withContext` / `spawn` node against the
|
|
283
279
|
* registry on `HydrationContext.layers`. Returns `[]` when no registry was
|
|
284
280
|
* supplied — the host runs with its harness-default layers in that case.
|
|
285
281
|
*/
|
|
@@ -322,16 +318,16 @@ function hydrateSpawnNode(node, ctx) {
|
|
|
322
318
|
context: resolvedLayers,
|
|
323
319
|
});
|
|
324
320
|
}
|
|
325
|
-
function
|
|
326
|
-
if (node.kind !== '
|
|
321
|
+
function hydrateWithContextNode(node, ctx) {
|
|
322
|
+
if (node.kind !== 'withContext') {
|
|
327
323
|
return frameworkCast(undefined);
|
|
328
324
|
}
|
|
329
|
-
return
|
|
325
|
+
return withContext({
|
|
330
326
|
id: node.id,
|
|
331
327
|
child: hydrateNode(node.child, ctx),
|
|
332
328
|
context: resolveNamedLayers({
|
|
333
329
|
names: node.layers,
|
|
334
|
-
nodeKind: '
|
|
330
|
+
nodeKind: 'withContext',
|
|
335
331
|
nodeId: node.id,
|
|
336
332
|
ctx,
|
|
337
333
|
}),
|
|
@@ -355,7 +351,7 @@ function hydrateSequenceNode(node, ctx) {
|
|
|
355
351
|
return frameworkCast(undefined);
|
|
356
352
|
}
|
|
357
353
|
const children = node.steps.map((s) => hydrateNode(s, ctx));
|
|
358
|
-
return frameworkCast(
|
|
354
|
+
return frameworkCast(runCode({
|
|
359
355
|
id: node.id,
|
|
360
356
|
execute: async (input, execCtx) => {
|
|
361
357
|
let current = input;
|
|
@@ -367,14 +363,14 @@ function hydrateSequenceNode(node, ctx) {
|
|
|
367
363
|
},
|
|
368
364
|
}));
|
|
369
365
|
}
|
|
370
|
-
function
|
|
371
|
-
if (node.kind !== '
|
|
366
|
+
function hydrateScheduleNode(node, ctx) {
|
|
367
|
+
if (node.kind !== 'schedule') {
|
|
372
368
|
return frameworkCast(undefined);
|
|
373
369
|
}
|
|
374
|
-
return
|
|
370
|
+
return schedule({
|
|
375
371
|
id: node.id,
|
|
376
372
|
step: hydrateNode(node.step, ctx),
|
|
377
|
-
|
|
373
|
+
interval: node.interval,
|
|
378
374
|
onError: node.onError,
|
|
379
375
|
});
|
|
380
376
|
}
|
|
@@ -427,7 +423,7 @@ function hydrateSubflowNode(node, ctx) {
|
|
|
427
423
|
}
|
|
428
424
|
return cached;
|
|
429
425
|
};
|
|
430
|
-
return frameworkCast(
|
|
426
|
+
return frameworkCast(runCode({
|
|
431
427
|
id: node.id,
|
|
432
428
|
execute: async (input, execCtx) => {
|
|
433
429
|
const child = resolve();
|
|
@@ -480,16 +476,16 @@ function resolveSubflowDocument(node, ctx) {
|
|
|
480
476
|
//#endregion
|
|
481
477
|
//#region Handler Registry
|
|
482
478
|
const NODE_HYDRATORS = {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
479
|
+
callModel: hydrateCallModelNode,
|
|
480
|
+
invokeTool: hydrateInvokeToolNode,
|
|
481
|
+
runCode: hydrateRunCodeNode,
|
|
482
|
+
conditional: hydrateConditionalNode,
|
|
483
|
+
inParallel: hydrateInParallelNode,
|
|
488
484
|
spawn: hydrateSpawnNode,
|
|
489
|
-
|
|
485
|
+
withContext: hydrateWithContextNode,
|
|
490
486
|
loop: hydrateLoopNode,
|
|
491
487
|
sequence: hydrateSequenceNode,
|
|
492
|
-
|
|
488
|
+
schedule: hydrateScheduleNode,
|
|
493
489
|
subflow: hydrateSubflowNode,
|
|
494
490
|
'claude-code': hydrateSubHarnessNode,
|
|
495
491
|
codex: hydrateSubHarnessNode,
|
|
@@ -537,7 +533,7 @@ function stringifyResult(value) {
|
|
|
537
533
|
//#endregion
|
|
538
534
|
//#region Dynamic Fork Helpers
|
|
539
535
|
/**
|
|
540
|
-
* Parses the
|
|
536
|
+
* Parses the inParallel input (a JSON string) and locates the array to fan out over.
|
|
541
537
|
* When `over` is set, reads that property off the parsed object; otherwise the
|
|
542
538
|
* parsed value itself must be an array.
|
|
543
539
|
*/
|
|
@@ -549,15 +545,15 @@ function selectArray(input, over, nodeId) {
|
|
|
549
545
|
catch {
|
|
550
546
|
throw new NoeticConfigError({
|
|
551
547
|
code: 'INVALID_FORK_INPUT',
|
|
552
|
-
message: `Dynamic
|
|
553
|
-
hint: 'A dynamic
|
|
548
|
+
message: `Dynamic inParallel '${nodeId}' could not parse its input as JSON.`,
|
|
549
|
+
hint: 'A dynamic inParallel (with `each`) expects its input to be a JSON array (or a JSON object when `over` is set).',
|
|
554
550
|
});
|
|
555
551
|
}
|
|
556
552
|
const candidate = over === undefined ? parsed : frameworkCast(parsed)?.[over];
|
|
557
553
|
if (!Array.isArray(candidate)) {
|
|
558
554
|
throw new NoeticConfigError({
|
|
559
555
|
code: 'INVALID_FORK_INPUT',
|
|
560
|
-
message: `Dynamic
|
|
556
|
+
message: `Dynamic inParallel '${nodeId}' did not resolve an array${over ? ` at key '${over}'` : ''}.`,
|
|
561
557
|
hint: over
|
|
562
558
|
? `Ensure the input JSON object has an array at '${over}'.`
|
|
563
559
|
: 'Ensure the input is a JSON array, or set `over` to select an array property.',
|
|
@@ -566,15 +562,15 @@ function selectArray(input, over, nodeId) {
|
|
|
566
562
|
return candidate;
|
|
567
563
|
}
|
|
568
564
|
/**
|
|
569
|
-
* Builds one
|
|
570
|
-
* the body's input (
|
|
565
|
+
* Builds one inParallel path for a single dynamic fan-out item. The item is injected as
|
|
566
|
+
* the body's input (inParallel steps pass the same input to every path), and the
|
|
571
567
|
* template's node ids are suffixed with `-${i}` so each instantiation has
|
|
572
568
|
* unique ids for tracing and step-registry uniqueness.
|
|
573
569
|
*/
|
|
574
570
|
function buildPerItemStep(opts) {
|
|
575
571
|
const { forkId, eachTemplate, item, index, ctx } = opts;
|
|
576
572
|
const hydratedEach = hydrateNode(suffixNodeIds(eachTemplate, `-${index}`), ctx);
|
|
577
|
-
return frameworkCast(
|
|
573
|
+
return frameworkCast(runCode({
|
|
578
574
|
id: `${forkId}-item-${index}`,
|
|
579
575
|
execute: async (_input, execCtx) => {
|
|
580
576
|
const itemInput = JSON.stringify(item);
|
|
@@ -621,7 +617,7 @@ function suffixNodeIds(node, suffix) {
|
|
|
621
617
|
//#endregion
|
|
622
618
|
//#region Run Node Helpers
|
|
623
619
|
/**
|
|
624
|
-
* Resolves the subprocess adapter for a `
|
|
620
|
+
* Resolves the subprocess adapter for a `runCode` node. When the node names an
|
|
625
621
|
* adapter ref, the host must supply a `resolveSubprocess` resolver; otherwise
|
|
626
622
|
* the step falls back to the harness adapter on the execution context.
|
|
627
623
|
*/
|
|
@@ -641,7 +637,7 @@ function resolveSubprocessAdapter(opts) {
|
|
|
641
637
|
return resolved;
|
|
642
638
|
}
|
|
643
639
|
/**
|
|
644
|
-
* Dispatches a `
|
|
640
|
+
* Dispatches a `runCode` node's code string through a subprocess adapter: ships the
|
|
645
641
|
* code plus the JSON-stringified input as a process request (input on stdin),
|
|
646
642
|
* waits for the handle to settle, and returns the captured stdout as the step
|
|
647
643
|
* output. A non-zero exit / failed handle surfaces as a thrown error.
|
|
@@ -1,31 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `
|
|
2
|
+
* `workflow` — runs a `WorkflowDocument` as a single composable step.
|
|
3
3
|
*
|
|
4
4
|
* Lives beside `step-builders.ts` (rather than inside it) because the hydrator
|
|
5
|
-
* imports the base
|
|
6
|
-
*
|
|
5
|
+
* imports the base builders; adding a hydrating builder there would create an
|
|
6
|
+
* import cycle.
|
|
7
7
|
*/
|
|
8
8
|
import type { ContextData, ContextLayer } from '@noetic-tools/context';
|
|
9
|
-
import type { OutputCodec,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export interface StepWorkflowOpts {
|
|
9
|
+
import type { OutputCodec, StepRunCode, SubHarness, SubHarnessKind, SubprocessAdapter, Tool, WorkflowDocument } from '@noetic-tools/types';
|
|
10
|
+
/** @public Options for `workflow`. */
|
|
11
|
+
export interface WorkflowOpts {
|
|
13
12
|
id: string;
|
|
14
13
|
/** Inline workflow document. Mutually exclusive with `ref`. */
|
|
15
14
|
document?: WorkflowDocument;
|
|
16
15
|
/** Named workflow resolved from `workflows`. Mutually exclusive with `document`. */
|
|
17
16
|
ref?: string;
|
|
18
|
-
/** Client tools the document's `
|
|
17
|
+
/** Client tools the document's `callModel`/`invokeTool` nodes may reference by name. Default: none. */
|
|
19
18
|
tools?: Tool[];
|
|
20
|
-
/** Named context layers for `
|
|
19
|
+
/** Named context layers for `withContext`/`spawn` nodes. */
|
|
21
20
|
layers?: ReadonlyMap<string, ContextLayer>;
|
|
22
21
|
/** Named sub-workflows for `subflow` nodes — and for resolving `ref` itself. */
|
|
23
22
|
workflows?: ReadonlyMap<string, WorkflowDocument>;
|
|
24
23
|
/** SubHarness adapters for `claude-code`/`codex`/`opencode`/`pi` nodes. */
|
|
25
24
|
subHarnesses?: ReadonlyMap<SubHarnessKind, SubHarness>;
|
|
26
|
-
/** Output codecs for `
|
|
25
|
+
/** Output codecs for `callModel` nodes' `output` codec references. */
|
|
27
26
|
uiLibraries?: ReadonlyMap<string, OutputCodec>;
|
|
28
|
-
/** Resolver for named subprocess adapters on `
|
|
27
|
+
/** Resolver for named subprocess adapters on `runCode` nodes. */
|
|
29
28
|
resolveSubprocess?: (ref: string) => SubprocessAdapter | undefined;
|
|
30
29
|
/** `'inherit'` (default) runs in the caller's session; `'spawn'` isolates via `spawn()`. */
|
|
31
30
|
isolation?: 'inherit' | 'spawn';
|
|
@@ -40,22 +39,11 @@ export interface StepWorkflowOpts {
|
|
|
40
39
|
* workflow fails with `WORKFLOW_CYCLE` instead of recursing forever.
|
|
41
40
|
*
|
|
42
41
|
* @public
|
|
43
|
-
* @param opts - See `
|
|
44
|
-
* @returns A `
|
|
42
|
+
* @param opts - See `WorkflowOpts`.
|
|
43
|
+
* @returns A `StepRunCode` executing the workflow via the harness on the context.
|
|
45
44
|
* @throws `NoeticConfigError` with code `EMPTY_STEP_ID` if `id` is empty.
|
|
46
45
|
* @throws `NoeticConfigError` with code `INVALID_WORKFLOW_SOURCE` unless exactly one of `document`/`ref` is set.
|
|
47
46
|
* @throws `NoeticConfigError` with code `MISSING_HARNESS_CONTEXT` (at execution) without `ctx.harness`.
|
|
48
47
|
* @throws `NoeticConfigError` with code `UNKNOWN_WORKFLOW_REFERENCE` (at execution) if `ref` names no registered workflow.
|
|
49
48
|
*/
|
|
50
|
-
export declare function
|
|
51
|
-
/** @public The step builder namespace: the base builders plus `workflow`. */
|
|
52
|
-
export declare const step: {
|
|
53
|
-
readonly run: <TContext = Readonly<Record<string, Record<string, unknown>>>, I = unknown, O = unknown>(opts: stepBuilders.StepRunOpts<TContext, I, O>) => StepRun<TContext, I, O>;
|
|
54
|
-
readonly llm: <TContext = Readonly<Record<string, Record<string, unknown>>>, I = unknown, O = unknown>(opts: stepBuilders.StepLLMOpts<TContext, O>) => import("@noetic-tools/types").StepLLM<TContext, I, O>;
|
|
55
|
-
readonly tool: <TContext = Readonly<Record<string, Record<string, unknown>>>, I = unknown, O = unknown>(opts: stepBuilders.StepToolOpts<I, O>) => import("@noetic-tools/types").StepTool<TContext, I, O>;
|
|
56
|
-
readonly claudeCode: <TContext = Readonly<Record<string, Record<string, unknown>>>, I = unknown, O = unknown>(opts: stepBuilders.StepSubHarnessOpts<TContext, O>) => import("@noetic-tools/types").StepSubHarness<TContext, I, O>;
|
|
57
|
-
readonly codex: <TContext = Readonly<Record<string, Record<string, unknown>>>, I = unknown, O = unknown>(opts: stepBuilders.StepSubHarnessOpts<TContext, O>) => import("@noetic-tools/types").StepSubHarness<TContext, I, O>;
|
|
58
|
-
readonly opencode: <TContext = Readonly<Record<string, Record<string, unknown>>>, I = unknown, O = unknown>(opts: stepBuilders.StepSubHarnessOpts<TContext, O>) => import("@noetic-tools/types").StepSubHarness<TContext, I, O>;
|
|
59
|
-
readonly pi: <TContext = Readonly<Record<string, Record<string, unknown>>>, I = unknown, O = unknown>(opts: stepBuilders.StepSubHarnessOpts<TContext, O>) => import("@noetic-tools/types").StepSubHarness<TContext, I, O>;
|
|
60
|
-
readonly workflow: typeof stepWorkflow;
|
|
61
|
-
};
|
|
49
|
+
export declare function workflow(opts: WorkflowOpts): StepRunCode<ContextData, string, string>;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `
|
|
2
|
+
* `workflow` — runs a `WorkflowDocument` as a single composable step.
|
|
3
3
|
*
|
|
4
4
|
* Lives beside `step-builders.ts` (rather than inside it) because the hydrator
|
|
5
|
-
* imports the base
|
|
6
|
-
*
|
|
5
|
+
* imports the base builders; adding a hydrating builder there would create an
|
|
6
|
+
* import cycle.
|
|
7
7
|
*/
|
|
8
8
|
import { frameworkCast, NoeticConfigError } from '@noetic-tools/types';
|
|
9
9
|
import { spawn } from './spawn-builder.js';
|
|
10
|
-
import
|
|
10
|
+
import { runCode } from './step-builders.js';
|
|
11
11
|
import { hydrateWorkflow } from './workflow-hydrator.js';
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region Builder
|
|
@@ -21,25 +21,25 @@ import { hydrateWorkflow } from './workflow-hydrator.js';
|
|
|
21
21
|
* workflow fails with `WORKFLOW_CYCLE` instead of recursing forever.
|
|
22
22
|
*
|
|
23
23
|
* @public
|
|
24
|
-
* @param opts - See `
|
|
25
|
-
* @returns A `
|
|
24
|
+
* @param opts - See `WorkflowOpts`.
|
|
25
|
+
* @returns A `StepRunCode` executing the workflow via the harness on the context.
|
|
26
26
|
* @throws `NoeticConfigError` with code `EMPTY_STEP_ID` if `id` is empty.
|
|
27
27
|
* @throws `NoeticConfigError` with code `INVALID_WORKFLOW_SOURCE` unless exactly one of `document`/`ref` is set.
|
|
28
28
|
* @throws `NoeticConfigError` with code `MISSING_HARNESS_CONTEXT` (at execution) without `ctx.harness`.
|
|
29
29
|
* @throws `NoeticConfigError` with code `UNKNOWN_WORKFLOW_REFERENCE` (at execution) if `ref` names no registered workflow.
|
|
30
30
|
*/
|
|
31
|
-
export function
|
|
31
|
+
export function workflow(opts) {
|
|
32
32
|
if (!opts.id || opts.id.trim() === '') {
|
|
33
33
|
throw new NoeticConfigError({
|
|
34
34
|
code: 'EMPTY_STEP_ID',
|
|
35
|
-
message: '
|
|
36
|
-
hint: 'Pass a unique string as the id field, e.g.
|
|
35
|
+
message: 'workflow() requires a non-empty id.',
|
|
36
|
+
hint: 'Pass a unique string as the id field, e.g. workflow({ id: "verify", ... }).',
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
if ((opts.document === undefined) === (opts.ref === undefined)) {
|
|
40
40
|
throw new NoeticConfigError({
|
|
41
41
|
code: 'INVALID_WORKFLOW_SOURCE',
|
|
42
|
-
message: `
|
|
42
|
+
message: `workflow('${opts.id}') requires exactly one of 'document' (inline) or 'ref' (named).`,
|
|
43
43
|
hint: "Pass document: { version: 1, root: ... } or ref: '<name>' with a workflows registry.",
|
|
44
44
|
});
|
|
45
45
|
}
|
|
@@ -48,14 +48,14 @@ export function stepWorkflow(opts) {
|
|
|
48
48
|
// across sessions re-hydrates when it runs under a different harness
|
|
49
49
|
// instead of routing nested steps through the first one.
|
|
50
50
|
let cached;
|
|
51
|
-
return
|
|
51
|
+
return runCode({
|
|
52
52
|
id: opts.id,
|
|
53
53
|
execute: async (input, ctx) => {
|
|
54
54
|
const harness = ctx.harness;
|
|
55
55
|
if (!harness) {
|
|
56
56
|
throw new NoeticConfigError({
|
|
57
57
|
code: 'MISSING_HARNESS_CONTEXT',
|
|
58
|
-
message: '
|
|
58
|
+
message: 'workflow requires a harness on the execution context.',
|
|
59
59
|
hint: 'Execute this step via AgentHarness.run() or ensure ctx.harness is set.',
|
|
60
60
|
});
|
|
61
61
|
}
|
|
@@ -70,11 +70,6 @@ export function stepWorkflow(opts) {
|
|
|
70
70
|
},
|
|
71
71
|
});
|
|
72
72
|
}
|
|
73
|
-
/** @public The step builder namespace: the base builders plus `workflow`. */
|
|
74
|
-
export const step = {
|
|
75
|
-
...stepBuilders.step,
|
|
76
|
-
workflow: stepWorkflow,
|
|
77
|
-
};
|
|
78
73
|
//#endregion
|
|
79
74
|
//#region Helpers
|
|
80
75
|
function hydrate(opts, executeStep) {
|
|
@@ -82,7 +77,7 @@ function hydrate(opts, executeStep) {
|
|
|
82
77
|
if (!doc) {
|
|
83
78
|
throw new NoeticConfigError({
|
|
84
79
|
code: 'UNKNOWN_WORKFLOW_REFERENCE',
|
|
85
|
-
message: `Workflow '${opts.ref}' referenced by
|
|
80
|
+
message: `Workflow '${opts.ref}' referenced by workflow('${opts.id}') is not registered.`,
|
|
86
81
|
hint: `Pass named workflows via the workflows option. Available: ${[
|
|
87
82
|
...(opts.workflows?.keys() ?? []),
|
|
88
83
|
].join(', ') || '(none)'}.`,
|
|
@@ -35,7 +35,7 @@ export declare function otherwise<TContext = ContextData, I = unknown, O = unkno
|
|
|
35
35
|
*
|
|
36
36
|
* @public
|
|
37
37
|
* @param clauses - Ordered `WhenClause` and optional trailing `OtherwiseClause`.
|
|
38
|
-
* @returns A route function suitable for `
|
|
38
|
+
* @returns A route function suitable for `conditional({ route })`.
|
|
39
39
|
*/
|
|
40
40
|
export declare function semanticRoute<TContext = ContextData, I = unknown, O = unknown>(...clauses: Clause<TContext, I, O>[]): (input: I, ctx: Context) => Promise<Step<TContext, I, O> | null>;
|
|
41
41
|
interface SemanticSwitchSimple<TContext, I, O> {
|
|
@@ -60,7 +60,7 @@ interface SemanticSwitchAdvanced<TContext, I, O> {
|
|
|
60
60
|
*
|
|
61
61
|
* @public
|
|
62
62
|
* @param opts - Simple form with `Record<string, Step>` cases, or advanced form with multi-label cases.
|
|
63
|
-
* @returns A route function suitable for `
|
|
63
|
+
* @returns A route function suitable for `conditional({ route })`.
|
|
64
64
|
*/
|
|
65
65
|
export declare function semanticSwitch<TContext = ContextData, I = unknown, O = unknown>(opts: SemanticSwitchSimple<TContext, I, O>): (input: I, ctx: Context) => Promise<Step<TContext, I, O> | null>;
|
|
66
66
|
/** @public */
|
|
@@ -85,7 +85,7 @@ function isOtherwise(clause) {
|
|
|
85
85
|
*
|
|
86
86
|
* @public
|
|
87
87
|
* @param clauses - Ordered `WhenClause` and optional trailing `OtherwiseClause`.
|
|
88
|
-
* @returns A route function suitable for `
|
|
88
|
+
* @returns A route function suitable for `conditional({ route })`.
|
|
89
89
|
*/
|
|
90
90
|
export function semanticRoute(...clauses) {
|
|
91
91
|
return async (input, ctx) => {
|