@noetic-tools/types 2.1.1 → 2.3.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/dist/deprecated.d.ts +39 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/schemas/item.d.ts +1 -1
- package/dist/schemas/item.js +1 -1
- package/dist/schemas/workflow.d.ts +297 -0
- package/dist/schemas/workflow.js +401 -0
- package/dist/types/{memory.d.ts → context-layer.d.ts} +50 -50
- package/dist/types/{memory.js → context-layer.js} +1 -1
- package/dist/types/{memory-context.d.ts → context-scope.d.ts} +8 -8
- package/dist/types/context-scope.js +1 -0
- package/dist/types/context.d.ts +40 -28
- package/dist/types/harness-result.d.ts +1 -1
- package/dist/types/items.d.ts +3 -3
- package/dist/types/observability.d.ts +2 -2
- package/dist/types/output-codec.d.ts +1 -1
- package/dist/types/output-codec.js +1 -1
- package/dist/types/runtime.d.ts +28 -24
- package/dist/types/steering.d.ts +2 -2
- package/dist/types/step.d.ts +44 -44
- package/dist/types/sub-harness.d.ts +1 -1
- package/dist/types/sub-harness.js +1 -1
- package/dist/types/tool-context.d.ts +10 -3
- package/dist/types/tool.d.ts +8 -8
- package/dist/util/message-helpers.d.ts +2 -2
- package/dist/util/message-helpers.js +2 -2
- package/package.json +3 -3
- /package/dist/{types/memory-context.js → deprecated.js} +0 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schemas for JSON-serialisable workflow definitions.
|
|
3
|
+
*
|
|
4
|
+
* A `WorkflowDocument` is a portable, JSON-safe representation of a noetic
|
|
5
|
+
* step tree. It covers every step kind. The `run` node carries its body as a
|
|
6
|
+
* code STRING (not an in-process closure) dispatched through a subprocess
|
|
7
|
+
* adapter, keeping the document JSON-safe. The hydrator in
|
|
8
|
+
* `@noetic-tools/core` (`builders/workflow-hydrator.ts`) converts a validated
|
|
9
|
+
* document back into live `Step` objects via the existing builders.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region Until Predicate Schemas
|
|
14
|
+
const MaxStepsPredicateSchema = z.object({
|
|
15
|
+
kind: z.literal('maxSteps'),
|
|
16
|
+
n: z.number().int().positive(),
|
|
17
|
+
});
|
|
18
|
+
const MaxCostPredicateSchema = z.object({
|
|
19
|
+
kind: z.literal('maxCost'),
|
|
20
|
+
usd: z.number().positive(),
|
|
21
|
+
});
|
|
22
|
+
const MaxDurationPredicateSchema = z.object({
|
|
23
|
+
kind: z.literal('maxDuration'),
|
|
24
|
+
ms: z.number().positive(),
|
|
25
|
+
});
|
|
26
|
+
const NoToolCallsPredicateSchema = z.object({
|
|
27
|
+
kind: z.literal('noToolCalls'),
|
|
28
|
+
});
|
|
29
|
+
const OutputContainsPredicateSchema = z.object({
|
|
30
|
+
kind: z.literal('outputContains'),
|
|
31
|
+
marker: z.string().min(1),
|
|
32
|
+
});
|
|
33
|
+
const OutputEqualsPredicateSchema = z.object({
|
|
34
|
+
kind: z.literal('outputEquals'),
|
|
35
|
+
sentinel: z.string().min(1),
|
|
36
|
+
});
|
|
37
|
+
const ConvergedPredicateSchema = z.object({
|
|
38
|
+
kind: z.literal('converged'),
|
|
39
|
+
threshold: z.number().min(0).max(1).optional(),
|
|
40
|
+
});
|
|
41
|
+
const UntilPredicateRef = z.lazy(() => UntilPredicateSchema);
|
|
42
|
+
const AnyPredicateSchema = z.object({
|
|
43
|
+
kind: z.literal('any'),
|
|
44
|
+
predicates: z.array(UntilPredicateRef).min(1),
|
|
45
|
+
});
|
|
46
|
+
const AllPredicateSchema = z.object({
|
|
47
|
+
kind: z.literal('all'),
|
|
48
|
+
predicates: z.array(UntilPredicateRef).min(1),
|
|
49
|
+
});
|
|
50
|
+
/** @public Zod schema validating a named `until` predicate (including `any`/`all` combinators). */
|
|
51
|
+
export const UntilPredicateSchema = z
|
|
52
|
+
.union([
|
|
53
|
+
MaxStepsPredicateSchema,
|
|
54
|
+
MaxCostPredicateSchema,
|
|
55
|
+
MaxDurationPredicateSchema,
|
|
56
|
+
NoToolCallsPredicateSchema,
|
|
57
|
+
OutputContainsPredicateSchema,
|
|
58
|
+
OutputEqualsPredicateSchema,
|
|
59
|
+
ConvergedPredicateSchema,
|
|
60
|
+
AnyPredicateSchema,
|
|
61
|
+
AllPredicateSchema,
|
|
62
|
+
])
|
|
63
|
+
.meta({
|
|
64
|
+
id: 'UntilPredicate',
|
|
65
|
+
title: 'UntilPredicate',
|
|
66
|
+
});
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region Merge Strategy
|
|
69
|
+
/** @public ESM literal enum for merge strategies. */
|
|
70
|
+
export const MergeStrategy = {
|
|
71
|
+
Last: 'last',
|
|
72
|
+
First: 'first',
|
|
73
|
+
Concat: 'concat',
|
|
74
|
+
};
|
|
75
|
+
/** @public Zod schema for the named merge strategy used by `fork` nodes. */
|
|
76
|
+
export const MergeStrategySchema = z.enum([
|
|
77
|
+
'last',
|
|
78
|
+
'first',
|
|
79
|
+
'concat',
|
|
80
|
+
]);
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region Model Params
|
|
83
|
+
const ModelParamsSchema = z.object({
|
|
84
|
+
temperature: z.number().min(0).optional(),
|
|
85
|
+
topP: z.number().min(0).max(1).optional(),
|
|
86
|
+
maxTokens: z.number().int().positive().optional(),
|
|
87
|
+
stopSequences: z.array(z.string()).optional(),
|
|
88
|
+
});
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region Tool Entries
|
|
91
|
+
/**
|
|
92
|
+
* A uniform tool entry on an `llm` node. Every entry is an object keyed by
|
|
93
|
+
* `type`:
|
|
94
|
+
* - A CLIENT tool is `{ type: "<registered-tool-name>" }`. The hydrator
|
|
95
|
+
* resolves `type` against the tool registry; `parameters` (if present) is
|
|
96
|
+
* ignored — client tools receive their call args from the model at runtime.
|
|
97
|
+
* - A SERVER tool is `{ type: "openrouter:web_search" | "openrouter:web_fetch",
|
|
98
|
+
* parameters?: {...} }`. The provider executes it; `parameters` keys are
|
|
99
|
+
* camelCase (e.g. `maxResults`, `searchContextSize`) — the SDK re-serialises
|
|
100
|
+
* them and silently drops unknown keys.
|
|
101
|
+
*
|
|
102
|
+
* Server vs client is decided by the `type` value (a reserved `openrouter:*`
|
|
103
|
+
* server-tool literal vs an arbitrary tool name), not by the entry's shape.
|
|
104
|
+
*/
|
|
105
|
+
const LlmToolEntrySchema = z.object({
|
|
106
|
+
type: z.string().min(1),
|
|
107
|
+
parameters: z.record(z.string(), z.unknown()).optional(),
|
|
108
|
+
});
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region Retry Policy
|
|
111
|
+
/** Retry policy for a `run` node, mirroring `RetryPolicy` in `@noetic-tools/types`. */
|
|
112
|
+
const RetryPolicySchema = z.object({
|
|
113
|
+
maxAttempts: z.number().int().positive(),
|
|
114
|
+
backoff: z.enum([
|
|
115
|
+
'fixed',
|
|
116
|
+
'linear',
|
|
117
|
+
'exponential',
|
|
118
|
+
]),
|
|
119
|
+
initialDelay: z.number().nonnegative(),
|
|
120
|
+
maxDelay: z.number().nonnegative().optional(),
|
|
121
|
+
});
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region SubHarness Settings
|
|
124
|
+
const HarnessSettingsSchema = z.object({
|
|
125
|
+
model: z.string().optional(),
|
|
126
|
+
permissionMode: z
|
|
127
|
+
.enum([
|
|
128
|
+
'default',
|
|
129
|
+
'plan',
|
|
130
|
+
'acceptEdits',
|
|
131
|
+
'bypassPermissions',
|
|
132
|
+
])
|
|
133
|
+
.optional(),
|
|
134
|
+
maxTurns: z.number().int().positive().optional(),
|
|
135
|
+
allowedTools: z.array(z.string()).optional(),
|
|
136
|
+
extra: z.record(z.string(), z.unknown()).optional(),
|
|
137
|
+
});
|
|
138
|
+
const HarnessSessionPolicySchema = z.object({
|
|
139
|
+
reuse: z.string().min(1).optional(),
|
|
140
|
+
onComplete: z
|
|
141
|
+
.enum([
|
|
142
|
+
'stop',
|
|
143
|
+
'detach',
|
|
144
|
+
'destroy',
|
|
145
|
+
])
|
|
146
|
+
.optional(),
|
|
147
|
+
});
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region Workflow Node Schema
|
|
150
|
+
const WorkflowNodeRef = z.lazy(() => WorkflowNodeSchema);
|
|
151
|
+
const SHARED_FIELDS = {
|
|
152
|
+
id: z.string().min(1),
|
|
153
|
+
};
|
|
154
|
+
const OutputCodecRefSchema = z.object({
|
|
155
|
+
codec: z.literal('openui'),
|
|
156
|
+
library: z.string().min(1),
|
|
157
|
+
});
|
|
158
|
+
const LlmNodeSchema = z.object({
|
|
159
|
+
kind: z.literal('llm'),
|
|
160
|
+
...SHARED_FIELDS,
|
|
161
|
+
model: z.string().optional(),
|
|
162
|
+
instructions: z.string(),
|
|
163
|
+
tools: z.array(LlmToolEntrySchema).optional(),
|
|
164
|
+
params: ModelParamsSchema.optional(),
|
|
165
|
+
output: OutputCodecRefSchema.optional(),
|
|
166
|
+
});
|
|
167
|
+
const ToolNodeSchema = z.object({
|
|
168
|
+
kind: z.literal('tool'),
|
|
169
|
+
...SHARED_FIELDS,
|
|
170
|
+
toolName: z.string().min(1),
|
|
171
|
+
args: z.record(z.string(), z.unknown()).optional(),
|
|
172
|
+
});
|
|
173
|
+
const RunNodeSchema = z.object({
|
|
174
|
+
kind: z.literal('run'),
|
|
175
|
+
...SHARED_FIELDS,
|
|
176
|
+
execute: z.string().min(1),
|
|
177
|
+
retry: RetryPolicySchema.optional(),
|
|
178
|
+
subprocess: z.string().min(1).optional(),
|
|
179
|
+
});
|
|
180
|
+
const BranchRouteSchema = z.object({
|
|
181
|
+
match: z.string().min(1),
|
|
182
|
+
target: z.lazy(() => WorkflowNodeSchema),
|
|
183
|
+
});
|
|
184
|
+
const BranchNodeSchema = z.object({
|
|
185
|
+
kind: z.literal('branch'),
|
|
186
|
+
...SHARED_FIELDS,
|
|
187
|
+
routes: z.array(BranchRouteSchema).min(1),
|
|
188
|
+
default: WorkflowNodeRef.optional(),
|
|
189
|
+
});
|
|
190
|
+
const ForkNodeSchema = z
|
|
191
|
+
.object({
|
|
192
|
+
kind: z.literal('fork'),
|
|
193
|
+
...SHARED_FIELDS,
|
|
194
|
+
mode: z.enum([
|
|
195
|
+
'race',
|
|
196
|
+
'all',
|
|
197
|
+
'settle',
|
|
198
|
+
]),
|
|
199
|
+
paths: z.array(WorkflowNodeRef).min(1).optional(),
|
|
200
|
+
each: WorkflowNodeRef.optional(),
|
|
201
|
+
over: z.string().min(1).optional(),
|
|
202
|
+
merge: MergeStrategySchema.optional(),
|
|
203
|
+
concurrency: z.number().int().positive().optional(),
|
|
204
|
+
})
|
|
205
|
+
.refine((node) => (node.paths === undefined) !== (node.each === undefined), {
|
|
206
|
+
message: "fork node requires exactly one of 'paths' (static) or 'each' (dynamic).",
|
|
207
|
+
});
|
|
208
|
+
const SpawnNodeSchema = z.object({
|
|
209
|
+
kind: z.literal('spawn'),
|
|
210
|
+
...SHARED_FIELDS,
|
|
211
|
+
child: WorkflowNodeRef,
|
|
212
|
+
timeout: z.number().positive().optional(),
|
|
213
|
+
layers: z.array(z.string().min(1)).min(1).optional(),
|
|
214
|
+
});
|
|
215
|
+
const ProvideNodeSchema = z.object({
|
|
216
|
+
kind: z.literal('provide'),
|
|
217
|
+
...SHARED_FIELDS,
|
|
218
|
+
child: WorkflowNodeRef,
|
|
219
|
+
layers: z.array(z.string().min(1)).min(1),
|
|
220
|
+
});
|
|
221
|
+
const LoopNodeSchema = z.object({
|
|
222
|
+
kind: z.literal('loop'),
|
|
223
|
+
...SHARED_FIELDS,
|
|
224
|
+
body: WorkflowNodeRef,
|
|
225
|
+
until: UntilPredicateSchema,
|
|
226
|
+
maxIterations: z.number().int().positive().optional(),
|
|
227
|
+
});
|
|
228
|
+
const SequenceNodeSchema = z.object({
|
|
229
|
+
kind: z.literal('sequence'),
|
|
230
|
+
...SHARED_FIELDS,
|
|
231
|
+
steps: z.array(WorkflowNodeRef).min(1),
|
|
232
|
+
});
|
|
233
|
+
const EveryNodeSchema = z.object({
|
|
234
|
+
kind: z.literal('every'),
|
|
235
|
+
...SHARED_FIELDS,
|
|
236
|
+
step: WorkflowNodeRef,
|
|
237
|
+
ms: z.number().nonnegative(),
|
|
238
|
+
onError: z
|
|
239
|
+
.enum([
|
|
240
|
+
'continue',
|
|
241
|
+
'fail',
|
|
242
|
+
])
|
|
243
|
+
.optional(),
|
|
244
|
+
});
|
|
245
|
+
const WorkflowDocumentRef = z.lazy(() => WorkflowDocumentSchema);
|
|
246
|
+
const SubflowNodeSchema = z
|
|
247
|
+
.object({
|
|
248
|
+
kind: z.literal('subflow'),
|
|
249
|
+
...SHARED_FIELDS,
|
|
250
|
+
document: WorkflowDocumentRef.optional(),
|
|
251
|
+
ref: z.string().min(1).optional(),
|
|
252
|
+
input: z.string().optional(),
|
|
253
|
+
})
|
|
254
|
+
.refine((node) => (node.document === undefined) !== (node.ref === undefined), {
|
|
255
|
+
message: "subflow node requires exactly one of 'document' (inline) or 'ref' (named).",
|
|
256
|
+
});
|
|
257
|
+
/** Builds the schema for a single harness node kind (`claude-code`, `codex`, …). */
|
|
258
|
+
function subHarnessNodeSchema(kind) {
|
|
259
|
+
return z.object({
|
|
260
|
+
kind: z.literal(kind),
|
|
261
|
+
...SHARED_FIELDS,
|
|
262
|
+
prompt: z.string().min(1),
|
|
263
|
+
instructions: z.string().optional(),
|
|
264
|
+
settings: HarnessSettingsSchema.optional(),
|
|
265
|
+
session: HarnessSessionPolicySchema.optional(),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
const ClaudeCodeNodeSchema = subHarnessNodeSchema('claude-code');
|
|
269
|
+
const CodexNodeSchema = subHarnessNodeSchema('codex');
|
|
270
|
+
const OpencodeNodeSchema = subHarnessNodeSchema('opencode');
|
|
271
|
+
const PiNodeSchema = subHarnessNodeSchema('pi');
|
|
272
|
+
/** @public Zod schema validating a single `WorkflowNode` (any JSON-safe kind). */
|
|
273
|
+
export const WorkflowNodeSchema = z
|
|
274
|
+
.discriminatedUnion('kind', [
|
|
275
|
+
LlmNodeSchema,
|
|
276
|
+
ToolNodeSchema,
|
|
277
|
+
RunNodeSchema,
|
|
278
|
+
BranchNodeSchema,
|
|
279
|
+
ForkNodeSchema,
|
|
280
|
+
SpawnNodeSchema,
|
|
281
|
+
ProvideNodeSchema,
|
|
282
|
+
LoopNodeSchema,
|
|
283
|
+
SequenceNodeSchema,
|
|
284
|
+
EveryNodeSchema,
|
|
285
|
+
SubflowNodeSchema,
|
|
286
|
+
ClaudeCodeNodeSchema,
|
|
287
|
+
CodexNodeSchema,
|
|
288
|
+
OpencodeNodeSchema,
|
|
289
|
+
PiNodeSchema,
|
|
290
|
+
])
|
|
291
|
+
.meta({
|
|
292
|
+
id: 'WorkflowNode',
|
|
293
|
+
title: 'WorkflowNode',
|
|
294
|
+
});
|
|
295
|
+
/** @public Zod schema validating a complete `WorkflowDocument`. */
|
|
296
|
+
export const WorkflowDocumentSchema = z
|
|
297
|
+
.object({
|
|
298
|
+
version: z.literal(1),
|
|
299
|
+
root: WorkflowNodeSchema,
|
|
300
|
+
})
|
|
301
|
+
.meta({
|
|
302
|
+
id: 'WorkflowDocument',
|
|
303
|
+
title: 'WorkflowDocument',
|
|
304
|
+
description: 'A portable, JSON-safe representation of a Noetic step tree.',
|
|
305
|
+
});
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region Helpers
|
|
308
|
+
/** Returns the direct child nodes of a workflow node. Leaf nodes return an empty array. */
|
|
309
|
+
function childNodes(node) {
|
|
310
|
+
switch (node.kind) {
|
|
311
|
+
case 'sequence':
|
|
312
|
+
return node.steps;
|
|
313
|
+
case 'fork':
|
|
314
|
+
if (node.each) {
|
|
315
|
+
return [
|
|
316
|
+
node.each,
|
|
317
|
+
];
|
|
318
|
+
}
|
|
319
|
+
return node.paths ?? [];
|
|
320
|
+
case 'spawn':
|
|
321
|
+
case 'provide':
|
|
322
|
+
return [
|
|
323
|
+
node.child,
|
|
324
|
+
];
|
|
325
|
+
case 'loop':
|
|
326
|
+
return [
|
|
327
|
+
node.body,
|
|
328
|
+
];
|
|
329
|
+
case 'every':
|
|
330
|
+
return [
|
|
331
|
+
node.step,
|
|
332
|
+
];
|
|
333
|
+
case 'branch': {
|
|
334
|
+
const children = node.routes.map((r) => r.target);
|
|
335
|
+
if (node.default) {
|
|
336
|
+
children.push(node.default);
|
|
337
|
+
}
|
|
338
|
+
return children;
|
|
339
|
+
}
|
|
340
|
+
case 'subflow':
|
|
341
|
+
// A named ref is a static leaf: the target document is only known at
|
|
342
|
+
// hydration time, so walk/graph/depth cannot see through it.
|
|
343
|
+
return node.document
|
|
344
|
+
? [
|
|
345
|
+
node.document.root,
|
|
346
|
+
]
|
|
347
|
+
: [];
|
|
348
|
+
default:
|
|
349
|
+
return [];
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/** Validates a candidate workflow document. Throws `ZodError` on invalid input. */
|
|
353
|
+
export function validateWorkflow(input) {
|
|
354
|
+
return WorkflowDocumentSchema.parse(input);
|
|
355
|
+
}
|
|
356
|
+
/** Walks a workflow tree depth-first, yielding each node. */
|
|
357
|
+
export function* walkWorkflow(node) {
|
|
358
|
+
yield node;
|
|
359
|
+
for (const child of childNodes(node)) {
|
|
360
|
+
yield* walkWorkflow(child);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Flattens a workflow tree into a node + edge list — the static "potential
|
|
365
|
+
* paths" of the DAG, suitable for attaching to a trace span so observers can
|
|
366
|
+
* reconstruct the declared graph independent of which branches actually ran.
|
|
367
|
+
* `subflow` refs are leaves: their target documents resolve at hydration time.
|
|
368
|
+
*/
|
|
369
|
+
export function workflowGraph(root) {
|
|
370
|
+
const nodes = [];
|
|
371
|
+
const edges = [];
|
|
372
|
+
for (const node of walkWorkflow(root)) {
|
|
373
|
+
nodes.push({
|
|
374
|
+
id: node.id,
|
|
375
|
+
kind: node.kind,
|
|
376
|
+
});
|
|
377
|
+
for (const child of childNodes(node)) {
|
|
378
|
+
edges.push({
|
|
379
|
+
from: node.id,
|
|
380
|
+
to: child.id,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
nodes,
|
|
386
|
+
edges,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Returns the maximum depth of a workflow tree.
|
|
391
|
+
* Leaf nodes (`llm`, `tool`) have depth 0. Structural nodes add +1.
|
|
392
|
+
* A `subflow` ref counts as a leaf — depth does not see through named refs.
|
|
393
|
+
*/
|
|
394
|
+
export function workflowDepth(node) {
|
|
395
|
+
const children = childNodes(node);
|
|
396
|
+
if (children.length === 0) {
|
|
397
|
+
return 0;
|
|
398
|
+
}
|
|
399
|
+
return 1 + Math.max(0, ...children.map(workflowDepth));
|
|
400
|
+
}
|
|
401
|
+
//#endregion
|