@noetic-tools/types 2.1.0 → 2.2.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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/schemas/workflow.d.ts +297 -0
- package/dist/schemas/workflow.js +401 -0
- package/dist/types/context.d.ts +17 -0
- package/dist/types/runtime.d.ts +10 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
export * from './errors/noetic-config-error.js';
|
|
6
6
|
export * from './errors/noetic-error.js';
|
|
7
7
|
export * from './schemas/item.js';
|
|
8
|
+
export * from './schemas/workflow.js';
|
|
8
9
|
export * from './types/channel.js';
|
|
9
10
|
export * from './types/common.js';
|
|
10
11
|
export * from './types/context.js';
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
export * from './errors/noetic-config-error.js';
|
|
6
6
|
export * from './errors/noetic-error.js';
|
|
7
7
|
export * from './schemas/item.js';
|
|
8
|
+
export * from './schemas/workflow.js';
|
|
8
9
|
export * from './types/channel.js';
|
|
9
10
|
export * from './types/common.js';
|
|
10
11
|
export * from './types/context.js';
|
|
@@ -0,0 +1,297 @@
|
|
|
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
|
+
import type { SubHarnessKind } from '../types/sub-harness.js';
|
|
13
|
+
interface UntilAnyPredicate {
|
|
14
|
+
kind: 'any';
|
|
15
|
+
predicates: UntilPredicate[];
|
|
16
|
+
}
|
|
17
|
+
interface UntilAllPredicate {
|
|
18
|
+
kind: 'all';
|
|
19
|
+
predicates: UntilPredicate[];
|
|
20
|
+
}
|
|
21
|
+
/** @public Discriminated union of named until predicates usable in JSON workflows. */
|
|
22
|
+
export type UntilPredicate = {
|
|
23
|
+
kind: 'maxSteps';
|
|
24
|
+
n: number;
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'maxCost';
|
|
27
|
+
usd: number;
|
|
28
|
+
} | {
|
|
29
|
+
kind: 'maxDuration';
|
|
30
|
+
ms: number;
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'noToolCalls';
|
|
33
|
+
} | {
|
|
34
|
+
kind: 'outputContains';
|
|
35
|
+
marker: string;
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'outputEquals';
|
|
38
|
+
sentinel: string;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'converged';
|
|
41
|
+
threshold?: number;
|
|
42
|
+
} | UntilAnyPredicate | UntilAllPredicate;
|
|
43
|
+
/** @public Zod schema validating a named `until` predicate (including `any`/`all` combinators). */
|
|
44
|
+
export declare const UntilPredicateSchema: z.ZodType<UntilPredicate>;
|
|
45
|
+
/** @public ESM literal enum for merge strategies. */
|
|
46
|
+
export declare const MergeStrategy: {
|
|
47
|
+
readonly Last: 'last';
|
|
48
|
+
readonly First: 'first';
|
|
49
|
+
readonly Concat: 'concat';
|
|
50
|
+
};
|
|
51
|
+
/** @public Named merge strategy for fork nodes. */
|
|
52
|
+
export type MergeStrategy = (typeof MergeStrategy)[keyof typeof MergeStrategy];
|
|
53
|
+
/** @public Zod schema for the named merge strategy used by `fork` nodes. */
|
|
54
|
+
export declare const MergeStrategySchema: z.ZodEnum<{
|
|
55
|
+
concat: "concat";
|
|
56
|
+
first: "first";
|
|
57
|
+
last: "last";
|
|
58
|
+
}>;
|
|
59
|
+
declare const ModelParamsSchema: z.ZodObject<{
|
|
60
|
+
temperature: z.ZodOptional<z.ZodNumber>;
|
|
61
|
+
topP: z.ZodOptional<z.ZodNumber>;
|
|
62
|
+
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
63
|
+
stopSequences: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
64
|
+
}, z.core.$strip>;
|
|
65
|
+
/**
|
|
66
|
+
* A uniform tool entry on an `llm` node. Every entry is an object keyed by
|
|
67
|
+
* `type`:
|
|
68
|
+
* - A CLIENT tool is `{ type: "<registered-tool-name>" }`. The hydrator
|
|
69
|
+
* resolves `type` against the tool registry; `parameters` (if present) is
|
|
70
|
+
* ignored — client tools receive their call args from the model at runtime.
|
|
71
|
+
* - A SERVER tool is `{ type: "openrouter:web_search" | "openrouter:web_fetch",
|
|
72
|
+
* parameters?: {...} }`. The provider executes it; `parameters` keys are
|
|
73
|
+
* camelCase (e.g. `maxResults`, `searchContextSize`) — the SDK re-serialises
|
|
74
|
+
* them and silently drops unknown keys.
|
|
75
|
+
*
|
|
76
|
+
* Server vs client is decided by the `type` value (a reserved `openrouter:*`
|
|
77
|
+
* server-tool literal vs an arbitrary tool name), not by the entry's shape.
|
|
78
|
+
*/
|
|
79
|
+
declare const LlmToolEntrySchema: z.ZodObject<{
|
|
80
|
+
type: z.ZodString;
|
|
81
|
+
parameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
82
|
+
}, z.core.$strip>;
|
|
83
|
+
/** Retry policy for a `run` node, mirroring `RetryPolicy` in `@noetic-tools/types`. */
|
|
84
|
+
declare const RetryPolicySchema: z.ZodObject<{
|
|
85
|
+
maxAttempts: z.ZodNumber;
|
|
86
|
+
backoff: z.ZodEnum<{
|
|
87
|
+
exponential: "exponential";
|
|
88
|
+
fixed: "fixed";
|
|
89
|
+
linear: "linear";
|
|
90
|
+
}>;
|
|
91
|
+
initialDelay: z.ZodNumber;
|
|
92
|
+
maxDelay: z.ZodOptional<z.ZodNumber>;
|
|
93
|
+
}, z.core.$strip>;
|
|
94
|
+
declare const HarnessSettingsSchema: z.ZodObject<{
|
|
95
|
+
model: z.ZodOptional<z.ZodString>;
|
|
96
|
+
permissionMode: z.ZodOptional<z.ZodEnum<{
|
|
97
|
+
acceptEdits: "acceptEdits";
|
|
98
|
+
bypassPermissions: "bypassPermissions";
|
|
99
|
+
default: "default";
|
|
100
|
+
plan: "plan";
|
|
101
|
+
}>>;
|
|
102
|
+
maxTurns: z.ZodOptional<z.ZodNumber>;
|
|
103
|
+
allowedTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
104
|
+
extra: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
105
|
+
}, z.core.$strip>;
|
|
106
|
+
declare const HarnessSessionPolicySchema: z.ZodObject<{
|
|
107
|
+
reuse: z.ZodOptional<z.ZodString>;
|
|
108
|
+
onComplete: z.ZodOptional<z.ZodEnum<{
|
|
109
|
+
destroy: "destroy";
|
|
110
|
+
detach: "detach";
|
|
111
|
+
stop: "stop";
|
|
112
|
+
}>>;
|
|
113
|
+
}, z.core.$strip>;
|
|
114
|
+
interface WorkflowNodeBase {
|
|
115
|
+
id: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* A reference to a streaming `OutputCodec` for an `llm` node's structured
|
|
119
|
+
* output. The hydrator resolves `library` from `HydrationContext.uiLibraries`
|
|
120
|
+
* to a live codec (e.g. `openUi(myLibrary)` from `@noetic-tools/openui`), the
|
|
121
|
+
* same registry-resolution pattern sub-harness nodes use for adapters. Kept a
|
|
122
|
+
* *reference* because a codec is a runtime object, not JSON-expressible.
|
|
123
|
+
*/
|
|
124
|
+
export interface OutputCodecRef {
|
|
125
|
+
/** The codec dialect. Only `'openui'` is defined today. */
|
|
126
|
+
codec: 'openui';
|
|
127
|
+
/** Registry key resolved from `HydrationContext.uiLibraries`. */
|
|
128
|
+
library: string;
|
|
129
|
+
}
|
|
130
|
+
export interface LlmWorkflowNode extends WorkflowNodeBase {
|
|
131
|
+
kind: 'llm';
|
|
132
|
+
model?: string;
|
|
133
|
+
instructions: string;
|
|
134
|
+
/**
|
|
135
|
+
* Tool entries. Every entry is an object `{ type, parameters? }`. A CLIENT
|
|
136
|
+
* tool is `{ type: "<registered-tool-name>" }` (resolved from the registry);
|
|
137
|
+
* a SERVER tool is `{ type: "openrouter:web_search" | "openrouter:web_fetch",
|
|
138
|
+
* parameters?: {...} }` (executed by the provider). Client vs server is
|
|
139
|
+
* decided by the `type` value.
|
|
140
|
+
*/
|
|
141
|
+
tools?: z.infer<typeof LlmToolEntrySchema>[];
|
|
142
|
+
params?: z.infer<typeof ModelParamsSchema>;
|
|
143
|
+
/** Streaming output-codec reference, resolved from `HydrationContext.uiLibraries`. */
|
|
144
|
+
output?: OutputCodecRef;
|
|
145
|
+
}
|
|
146
|
+
export interface ToolWorkflowNode extends WorkflowNodeBase {
|
|
147
|
+
kind: 'tool';
|
|
148
|
+
toolName: string;
|
|
149
|
+
args?: Record<string, unknown>;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* A node that runs a serialised code body. Unlike the programmatic `step.run`
|
|
153
|
+
* (which carries a closure), the JSON form carries `execute` as a code STRING.
|
|
154
|
+
* The code is never eval'd in-process (Cloudflare Workers forbid eval); it is
|
|
155
|
+
* dispatched through a subprocess adapter (`ctx.subprocess`, or a named adapter
|
|
156
|
+
* resolved by ref). Execution therefore requires a subprocess adapter capable
|
|
157
|
+
* of running the code and returning its stdout.
|
|
158
|
+
*/
|
|
159
|
+
export interface RunWorkflowNode extends WorkflowNodeBase {
|
|
160
|
+
kind: 'run';
|
|
161
|
+
/** Source code executed in the subprocess. Receives the step input on stdin. */
|
|
162
|
+
execute: string;
|
|
163
|
+
/** Optional retry policy applied to the step. */
|
|
164
|
+
retry?: z.infer<typeof RetryPolicySchema>;
|
|
165
|
+
/** Named subprocess adapter ref; defaults to `ctx.subprocess` when omitted. */
|
|
166
|
+
subprocess?: string;
|
|
167
|
+
}
|
|
168
|
+
export interface BranchRoute {
|
|
169
|
+
match: string;
|
|
170
|
+
target: WorkflowNode;
|
|
171
|
+
}
|
|
172
|
+
export interface BranchWorkflowNode extends WorkflowNodeBase {
|
|
173
|
+
kind: 'branch';
|
|
174
|
+
routes: BranchRoute[];
|
|
175
|
+
default?: WorkflowNode;
|
|
176
|
+
}
|
|
177
|
+
export interface ForkWorkflowNode extends WorkflowNodeBase {
|
|
178
|
+
kind: 'fork';
|
|
179
|
+
mode: 'race' | 'all' | 'settle';
|
|
180
|
+
/**
|
|
181
|
+
* Static fan-out: one child per entry. Mutually exclusive with `each` —
|
|
182
|
+
* supply exactly one.
|
|
183
|
+
*/
|
|
184
|
+
paths?: WorkflowNode[];
|
|
185
|
+
/**
|
|
186
|
+
* Dynamic fan-out: instantiate this body template once per item of a
|
|
187
|
+
* runtime-produced array (data-dependent N). Mutually exclusive with `paths`.
|
|
188
|
+
*/
|
|
189
|
+
each?: WorkflowNode;
|
|
190
|
+
/**
|
|
191
|
+
* Selector key into the fork input (parsed as JSON) locating the array to
|
|
192
|
+
* fan out over. When omitted, the input string itself is parsed as a JSON
|
|
193
|
+
* array. Only meaningful with `each`.
|
|
194
|
+
*/
|
|
195
|
+
over?: string;
|
|
196
|
+
merge?: MergeStrategy;
|
|
197
|
+
concurrency?: number;
|
|
198
|
+
}
|
|
199
|
+
export interface SpawnWorkflowNode extends WorkflowNodeBase {
|
|
200
|
+
kind: 'spawn';
|
|
201
|
+
child: WorkflowNode;
|
|
202
|
+
timeout?: number;
|
|
203
|
+
/**
|
|
204
|
+
* Memory layers the child runs with, by registered name (resolved from
|
|
205
|
+
* `HydrationContext.layers`, like `provide`). Omit to inherit the parent's
|
|
206
|
+
* layers — the default spawn behaviour. Naming layers here REPLACES the
|
|
207
|
+
* inherited set for the child, so list every layer the child needs.
|
|
208
|
+
*/
|
|
209
|
+
layers?: string[];
|
|
210
|
+
}
|
|
211
|
+
export interface ProvideWorkflowNode extends WorkflowNodeBase {
|
|
212
|
+
kind: 'provide';
|
|
213
|
+
child: WorkflowNode;
|
|
214
|
+
layers: string[];
|
|
215
|
+
}
|
|
216
|
+
export interface LoopWorkflowNode extends WorkflowNodeBase {
|
|
217
|
+
kind: 'loop';
|
|
218
|
+
body: WorkflowNode;
|
|
219
|
+
until: UntilPredicate;
|
|
220
|
+
maxIterations?: number;
|
|
221
|
+
}
|
|
222
|
+
export interface SequenceWorkflowNode extends WorkflowNodeBase {
|
|
223
|
+
kind: 'sequence';
|
|
224
|
+
steps: WorkflowNode[];
|
|
225
|
+
}
|
|
226
|
+
export interface EveryWorkflowNode extends WorkflowNodeBase {
|
|
227
|
+
kind: 'every';
|
|
228
|
+
step: WorkflowNode;
|
|
229
|
+
ms: number;
|
|
230
|
+
onError?: 'continue' | 'fail';
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* A node that delegates a turn to a coding-agent harness. `kind` is the harness
|
|
234
|
+
* id (e.g. `claude-code`); the hydrator resolves the matching adapter from the
|
|
235
|
+
* workflow's harness registry.
|
|
236
|
+
*/
|
|
237
|
+
export interface SubHarnessWorkflowNode extends WorkflowNodeBase {
|
|
238
|
+
kind: SubHarnessKind;
|
|
239
|
+
prompt: string;
|
|
240
|
+
instructions?: string;
|
|
241
|
+
settings?: z.infer<typeof HarnessSettingsSchema>;
|
|
242
|
+
session?: z.infer<typeof HarnessSessionPolicySchema>;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* A node that runs another workflow document as a sub-step. The document is
|
|
246
|
+
* either inline or a named reference resolved from `HydrationContext.workflows`
|
|
247
|
+
* at execution time. Supply exactly one of `document` / `ref`.
|
|
248
|
+
*/
|
|
249
|
+
export interface SubflowWorkflowNode extends WorkflowNodeBase {
|
|
250
|
+
kind: 'subflow';
|
|
251
|
+
/** Inline sub-workflow. Mutually exclusive with `ref`. */
|
|
252
|
+
document?: WorkflowDocument;
|
|
253
|
+
/** Named sub-workflow resolved from `HydrationContext.workflows`. Mutually exclusive with `document`. */
|
|
254
|
+
ref?: string;
|
|
255
|
+
/** Literal input passed to the sub-workflow; defaults to the node's runtime input. */
|
|
256
|
+
input?: string;
|
|
257
|
+
}
|
|
258
|
+
/** @public Discriminated union of all JSON-serialisable workflow node kinds. */
|
|
259
|
+
export type WorkflowNode = LlmWorkflowNode | ToolWorkflowNode | RunWorkflowNode | BranchWorkflowNode | ForkWorkflowNode | SpawnWorkflowNode | ProvideWorkflowNode | LoopWorkflowNode | SequenceWorkflowNode | EveryWorkflowNode | SubflowWorkflowNode | SubHarnessWorkflowNode;
|
|
260
|
+
/** @public Zod schema validating a single `WorkflowNode` (any JSON-safe kind). */
|
|
261
|
+
export declare const WorkflowNodeSchema: z.ZodType<WorkflowNode>;
|
|
262
|
+
/** @public Top-level envelope for a JSON workflow definition. */
|
|
263
|
+
export interface WorkflowDocument {
|
|
264
|
+
version: 1;
|
|
265
|
+
root: WorkflowNode;
|
|
266
|
+
}
|
|
267
|
+
/** @public Zod schema validating a complete `WorkflowDocument`. */
|
|
268
|
+
export declare const WorkflowDocumentSchema: z.ZodType<WorkflowDocument>;
|
|
269
|
+
/** Validates a candidate workflow document. Throws `ZodError` on invalid input. */
|
|
270
|
+
export declare function validateWorkflow(input: unknown): WorkflowDocument;
|
|
271
|
+
/** Walks a workflow tree depth-first, yielding each node. */
|
|
272
|
+
export declare function walkWorkflow(node: WorkflowNode): Iterable<WorkflowNode>;
|
|
273
|
+
/** A flattened, JSON-safe view of a workflow tree: its nodes and parent→child edges. */
|
|
274
|
+
export interface WorkflowGraph {
|
|
275
|
+
nodes: Array<{
|
|
276
|
+
id: string;
|
|
277
|
+
kind: WorkflowNode['kind'];
|
|
278
|
+
}>;
|
|
279
|
+
edges: Array<{
|
|
280
|
+
from: string;
|
|
281
|
+
to: string;
|
|
282
|
+
}>;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Flattens a workflow tree into a node + edge list — the static "potential
|
|
286
|
+
* paths" of the DAG, suitable for attaching to a trace span so observers can
|
|
287
|
+
* reconstruct the declared graph independent of which branches actually ran.
|
|
288
|
+
* `subflow` refs are leaves: their target documents resolve at hydration time.
|
|
289
|
+
*/
|
|
290
|
+
export declare function workflowGraph(root: WorkflowNode): WorkflowGraph;
|
|
291
|
+
/**
|
|
292
|
+
* Returns the maximum depth of a workflow tree.
|
|
293
|
+
* Leaf nodes (`llm`, `tool`) have depth 0. Structural nodes add +1.
|
|
294
|
+
* A `subflow` ref counts as a leaf — depth does not see through named refs.
|
|
295
|
+
*/
|
|
296
|
+
export declare function workflowDepth(node: WorkflowNode): number;
|
|
297
|
+
export {};
|
|
@@ -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
|
package/dist/types/context.d.ts
CHANGED
|
@@ -177,6 +177,11 @@ export interface ContextHarness {
|
|
|
177
177
|
disposeLayers(layers: MemoryLayer[], ctx: Context): Promise<void>;
|
|
178
178
|
checkpoint(ctx: Context): Promise<void>;
|
|
179
179
|
restore(executionId: string, opts?: RestoreContextOptions): Promise<Context | null>;
|
|
180
|
+
/**
|
|
181
|
+
* Cancel an execution: abort `ctx` and every live descendant context, then
|
|
182
|
+
* run memory-layer teardown (`onComplete` with `outcome: 'aborted'`, then
|
|
183
|
+
* `dispose`) bottom-up. A no-op on an already-cancelled context.
|
|
184
|
+
*/
|
|
180
185
|
cancel(ctx: Context, reason?: string): Promise<void>;
|
|
181
186
|
/** Trace exporter spans are flushed to. Defaults to a no-op exporter. */
|
|
182
187
|
readonly traceExporter: TraceExporter;
|
|
@@ -242,6 +247,18 @@ export interface Context<TMemory = ContextMemory, TState = unknown> {
|
|
|
242
247
|
readonly completionValue: unknown;
|
|
243
248
|
readonly aborted: boolean;
|
|
244
249
|
readonly abortReason?: string;
|
|
250
|
+
/**
|
|
251
|
+
* Cancel this context and everything running beneath it. The first call wins
|
|
252
|
+
* (later calls are no-ops, so `abortReason` is stable), and it cascades
|
|
253
|
+
* *down* the execution tree: every live fork path and spawn child is aborted
|
|
254
|
+
* too. It never travels up — aborting a child leaves the parent running.
|
|
255
|
+
*
|
|
256
|
+
* Aborting rejects operations blocked on the context (channel `recv` waiters,
|
|
257
|
+
* parked back-pressure senders) with `cancelled`, cuts short the in-flight
|
|
258
|
+
* model call or sub-harness turn, and makes the next step boundary throw
|
|
259
|
+
* `cancelled`. Memory-layer teardown is NOT run — use
|
|
260
|
+
* `harness.cancel(ctx, reason)` for that.
|
|
261
|
+
*/
|
|
245
262
|
abort(reason?: string): void;
|
|
246
263
|
}
|
|
247
264
|
export {};
|
package/dist/types/runtime.d.ts
CHANGED
|
@@ -251,6 +251,16 @@ export interface AgentHarnessContract<TParams extends Record<string, unknown> =
|
|
|
251
251
|
* decorated context is silently replaced by a bare one on resume.
|
|
252
252
|
*/
|
|
253
253
|
restore(executionId: string, opts?: RestoreContextOptions): Promise<Context | null>;
|
|
254
|
+
/**
|
|
255
|
+
* Cancel an execution. Aborts `ctx` and — cascading down the execution tree —
|
|
256
|
+
* every live fork path and spawn child, then runs memory-layer teardown
|
|
257
|
+
* (`onComplete` with `outcome: 'aborted'`, then `dispose`) bottom-up.
|
|
258
|
+
*
|
|
259
|
+
* Blocked channel operations reject with `cancelled`, the in-flight model
|
|
260
|
+
* call and sub-harness turn are cut short, and the next step boundary throws
|
|
261
|
+
* `cancelled`. Idempotent: cancelling an already-cancelled context is a
|
|
262
|
+
* no-op. Use `ctx.abort()` when you want the signal without layer teardown.
|
|
263
|
+
*/
|
|
254
264
|
cancel(ctx: Context, reason?: string): Promise<void>;
|
|
255
265
|
/** Trace exporter spans are flushed to. Defaults to a no-op exporter. */
|
|
256
266
|
readonly traceExporter: TraceExporter;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noetic-tools/types",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Foundational type contracts and primitives shared across the Noetic framework (items, LLM config, adapters, steering, errors).",
|
|
6
6
|
"license": "Apache-2.0",
|