@noetic-tools/core 0.0.1
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/LICENSE +201 -0
- package/README.md +38 -0
- package/dist/adapters/in-memory-fs-adapter.d.ts +3 -0
- package/dist/adapters/in-memory-fs-adapter.js +245 -0
- package/dist/adapters/in-memory-shell-adapter.d.ts +9 -0
- package/dist/adapters/in-memory-shell-adapter.js +20 -0
- package/dist/adapters/in-memory-subprocess-adapter.d.ts +5 -0
- package/dist/adapters/in-memory-subprocess-adapter.js +126 -0
- package/dist/adapters/local-fs-adapter.d.ts +3 -0
- package/dist/adapters/local-fs-adapter.js +43 -0
- package/dist/adapters/local-shell-adapter.d.ts +51 -0
- package/dist/adapters/local-shell-adapter.js +167 -0
- package/dist/adapters/local-subprocess-adapter.d.ts +16 -0
- package/dist/adapters/local-subprocess-adapter.js +187 -0
- package/dist/adapters/openrouter.d.ts +57 -0
- package/dist/adapters/openrouter.js +248 -0
- package/dist/builders/channel-builder.d.ts +36 -0
- package/dist/builders/channel-builder.js +15 -0
- package/dist/builders/control-flow-builders.d.ts +79 -0
- package/dist/builders/control-flow-builders.js +53 -0
- package/dist/builders/every.d.ts +45 -0
- package/dist/builders/every.js +60 -0
- package/dist/builders/layer-provides-builders.d.ts +32 -0
- package/dist/builders/layer-provides-builders.js +33 -0
- package/dist/builders/loop-builder.d.ts +23 -0
- package/dist/builders/loop-builder.js +46 -0
- package/dist/builders/memory-builder.d.ts +11 -0
- package/dist/builders/memory-builder.js +15 -0
- package/dist/builders/provide-builder.d.ts +20 -0
- package/dist/builders/provide-builder.js +34 -0
- package/dist/builders/spawn-builder.d.ts +20 -0
- package/dist/builders/spawn-builder.js +33 -0
- package/dist/builders/step-builders.d.ts +62 -0
- package/dist/builders/step-builders.js +99 -0
- package/dist/builders/tool-builder.d.ts +52 -0
- package/dist/builders/tool-builder.js +59 -0
- package/dist/conditions/conditions.d.ts +114 -0
- package/dist/conditions/conditions.js +271 -0
- package/dist/conditions/cosine-similarity.d.ts +11 -0
- package/dist/conditions/cosine-similarity.js +28 -0
- package/dist/conditions/index.d.ts +3 -0
- package/dist/conditions/index.js +2 -0
- package/dist/errors/noetic-config-error.d.ts +41 -0
- package/dist/errors/noetic-config-error.js +44 -0
- package/dist/errors/noetic-error.d.ts +18 -0
- package/dist/errors/noetic-error.js +52 -0
- package/dist/index.d.ts +152 -0
- package/dist/index.js +112 -0
- package/dist/internal-test.d.ts +11 -0
- package/dist/internal-test.js +11 -0
- package/dist/interpreter/clone-guard.d.ts +4 -0
- package/dist/interpreter/clone-guard.js +18 -0
- package/dist/interpreter/collect-tools.d.ts +12 -0
- package/dist/interpreter/collect-tools.js +70 -0
- package/dist/interpreter/execute-branch.d.ts +3 -0
- package/dist/interpreter/execute-branch.js +10 -0
- package/dist/interpreter/execute-every.d.ts +15 -0
- package/dist/interpreter/execute-every.js +143 -0
- package/dist/interpreter/execute-fork.d.ts +3 -0
- package/dist/interpreter/execute-fork.js +215 -0
- package/dist/interpreter/execute-llm.d.ts +4 -0
- package/dist/interpreter/execute-llm.js +217 -0
- package/dist/interpreter/execute-loop.d.ts +3 -0
- package/dist/interpreter/execute-loop.js +171 -0
- package/dist/interpreter/execute-provide.d.ts +10 -0
- package/dist/interpreter/execute-provide.js +64 -0
- package/dist/interpreter/execute-run.d.ts +3 -0
- package/dist/interpreter/execute-run.js +39 -0
- package/dist/interpreter/execute-spawn.d.ts +11 -0
- package/dist/interpreter/execute-spawn.js +113 -0
- package/dist/interpreter/execute-tool.d.ts +5 -0
- package/dist/interpreter/execute-tool.js +81 -0
- package/dist/interpreter/execute.d.ts +14 -0
- package/dist/interpreter/execute.js +119 -0
- package/dist/interpreter/framework-cast.d.ts +10 -0
- package/dist/interpreter/framework-cast.js +16 -0
- package/dist/interpreter/message-helpers.d.ts +9 -0
- package/dist/interpreter/message-helpers.js +42 -0
- package/dist/interpreter/typeguards.d.ts +25 -0
- package/dist/interpreter/typeguards.js +50 -0
- package/dist/memory/budget.d.ts +23 -0
- package/dist/memory/budget.js +117 -0
- package/dist/memory/function-call-utils.d.ts +6 -0
- package/dist/memory/function-call-utils.js +25 -0
- package/dist/memory/layer-api.d.ts +14 -0
- package/dist/memory/layer-api.js +91 -0
- package/dist/memory/layer-lifecycle.d.ts +160 -0
- package/dist/memory/layer-lifecycle.js +552 -0
- package/dist/memory/layer-usage.d.ts +15 -0
- package/dist/memory/layer-usage.js +48 -0
- package/dist/memory/layers/durable-task-state.d.ts +79 -0
- package/dist/memory/layers/durable-task-state.js +118 -0
- package/dist/memory/layers/file-reference.d.ts +55 -0
- package/dist/memory/layers/file-reference.js +538 -0
- package/dist/memory/layers/history-window.d.ts +29 -0
- package/dist/memory/layers/history-window.js +114 -0
- package/dist/memory/layers/observational-memory.d.ts +53 -0
- package/dist/memory/layers/observational-memory.js +110 -0
- package/dist/memory/layers/plan.d.ts +55 -0
- package/dist/memory/layers/plan.js +369 -0
- package/dist/memory/layers/static-content.d.ts +27 -0
- package/dist/memory/layers/static-content.js +35 -0
- package/dist/memory/layers/steering.d.ts +51 -0
- package/dist/memory/layers/steering.js +258 -0
- package/dist/memory/layers/tool-memory-layer.d.ts +16 -0
- package/dist/memory/layers/tool-memory-layer.js +43 -0
- package/dist/memory/layers/working-memory.d.ts +44 -0
- package/dist/memory/layers/working-memory.js +92 -0
- package/dist/memory/projector.d.ts +10 -0
- package/dist/memory/projector.js +19 -0
- package/dist/memory/scope.d.ts +3 -0
- package/dist/memory/scope.js +31 -0
- package/dist/memory/strip-unresolved.d.ts +13 -0
- package/dist/memory/strip-unresolved.js +35 -0
- package/dist/observability/genai-attributes.d.ts +11 -0
- package/dist/observability/genai-attributes.js +12 -0
- package/dist/observability/span-impl.d.ts +20 -0
- package/dist/observability/span-impl.js +33 -0
- package/dist/observability/trace-exporter.d.ts +15 -0
- package/dist/observability/trace-exporter.js +26 -0
- package/dist/patterns/flow.d.ts +52 -0
- package/dist/patterns/flow.js +103 -0
- package/dist/patterns/interview.d.ts +53 -0
- package/dist/patterns/interview.js +113 -0
- package/dist/patterns/plans.d.ts +45 -0
- package/dist/patterns/plans.js +125 -0
- package/dist/patterns/ralph-wiggum.d.ts +20 -0
- package/dist/patterns/ralph-wiggum.js +37 -0
- package/dist/patterns/react.d.ts +18 -0
- package/dist/patterns/react.js +39 -0
- package/dist/portable.d.ts +26 -0
- package/dist/portable.js +34 -0
- package/dist/runtime/agent-harness.d.ts +206 -0
- package/dist/runtime/agent-harness.js +1170 -0
- package/dist/runtime/broadcaster-utils.d.ts +30 -0
- package/dist/runtime/broadcaster-utils.js +53 -0
- package/dist/runtime/channel-store.d.ts +18 -0
- package/dist/runtime/channel-store.js +187 -0
- package/dist/runtime/context-impl.d.ts +80 -0
- package/dist/runtime/context-impl.js +140 -0
- package/dist/runtime/cwd-helpers.d.ts +42 -0
- package/dist/runtime/cwd-helpers.js +68 -0
- package/dist/runtime/detached-handle.d.ts +14 -0
- package/dist/runtime/detached-handle.js +56 -0
- package/dist/runtime/event-broadcaster.d.ts +61 -0
- package/dist/runtime/event-broadcaster.js +187 -0
- package/dist/runtime/exec-context-factory.d.ts +25 -0
- package/dist/runtime/exec-context-factory.js +30 -0
- package/dist/runtime/in-memory-storage.d.ts +9 -0
- package/dist/runtime/in-memory-storage.js +33 -0
- package/dist/runtime/item-log-impl.d.ts +11 -0
- package/dist/runtime/item-log-impl.js +25 -0
- package/dist/runtime/message-queue.d.ts +30 -0
- package/dist/runtime/message-queue.js +46 -0
- package/dist/runtime/session-runner.d.ts +77 -0
- package/dist/runtime/session-runner.js +253 -0
- package/dist/runtime/session-streams.d.ts +11 -0
- package/dist/runtime/session-streams.js +204 -0
- package/dist/runtime/tool-memory.d.ts +6 -0
- package/dist/runtime/tool-memory.js +22 -0
- package/dist/schemas/item.d.ts +35 -0
- package/dist/schemas/item.js +155 -0
- package/dist/types/channel.d.ts +29 -0
- package/dist/types/channel.js +1 -0
- package/dist/types/common.d.ts +111 -0
- package/dist/types/common.js +1 -0
- package/dist/types/context.d.ts +93 -0
- package/dist/types/context.js +1 -0
- package/dist/types/detached.d.ts +17 -0
- package/dist/types/detached.js +7 -0
- package/dist/types/embed.d.ts +5 -0
- package/dist/types/embed.js +1 -0
- package/dist/types/error.d.ts +51 -0
- package/dist/types/error.js +1 -0
- package/dist/types/fs-adapter.d.ts +51 -0
- package/dist/types/fs-adapter.js +3 -0
- package/dist/types/harness-result.d.ts +43 -0
- package/dist/types/harness-result.js +2 -0
- package/dist/types/items.d.ts +115 -0
- package/dist/types/items.js +2 -0
- package/dist/types/memory.d.ts +336 -0
- package/dist/types/memory.js +13 -0
- package/dist/types/mutable-context.d.ts +13 -0
- package/dist/types/mutable-context.js +1 -0
- package/dist/types/observability.d.ts +30 -0
- package/dist/types/observability.js +1 -0
- package/dist/types/runtime.d.ts +255 -0
- package/dist/types/runtime.js +1 -0
- package/dist/types/shell-adapter.d.ts +34 -0
- package/dist/types/shell-adapter.js +10 -0
- package/dist/types/steering.d.ts +89 -0
- package/dist/types/steering.js +14 -0
- package/dist/types/step.d.ts +206 -0
- package/dist/types/step.js +1 -0
- package/dist/types/subprocess-adapter.d.ts +41 -0
- package/dist/types/subprocess-adapter.js +1 -0
- package/dist/types/tool-context.d.ts +34 -0
- package/dist/types/tool-context.js +1 -0
- package/dist/unstable.d.ts +28 -0
- package/dist/unstable.js +24 -0
- package/dist/until/combinators.d.ts +17 -0
- package/dist/until/combinators.js +49 -0
- package/dist/until/never.d.ts +9 -0
- package/dist/until/never.js +12 -0
- package/dist/until/predicates.d.ts +43 -0
- package/dist/until/predicates.js +126 -0
- package/package.json +81 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { frameworkCast } from '../interpreter/framework-cast';
|
|
3
|
+
import { isAssistantMessage } from '../interpreter/typeguards';
|
|
4
|
+
import { buildToolExecutionContext } from '../runtime/tool-memory';
|
|
5
|
+
import { SteeringAction } from '../types/steering';
|
|
6
|
+
const EmbeddingsResponseSchema = z.object({
|
|
7
|
+
data: z.array(z.object({
|
|
8
|
+
embedding: z.array(z.number()),
|
|
9
|
+
index: z.number(),
|
|
10
|
+
})),
|
|
11
|
+
});
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region Helpers
|
|
14
|
+
function isTextPart(p) {
|
|
15
|
+
return p.type === 'output_text' || p.type === 'input_text';
|
|
16
|
+
}
|
|
17
|
+
function contentPartsToText(parts) {
|
|
18
|
+
return parts
|
|
19
|
+
.filter(isTextPart)
|
|
20
|
+
.map((p) => p.text)
|
|
21
|
+
.join('');
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region Item → OpenRouter Input Conversion
|
|
25
|
+
/** @internal Extracts system messages from items and returns them as a single instructions string. */
|
|
26
|
+
export function extractSystemInstruction(items) {
|
|
27
|
+
const systemTexts = [];
|
|
28
|
+
const remaining = [];
|
|
29
|
+
for (const item of items) {
|
|
30
|
+
if (item.type === 'message' && 'role' in item && item.role === 'system') {
|
|
31
|
+
const msgItem = item;
|
|
32
|
+
systemTexts.push(contentPartsToText(msgItem.content));
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
remaining.push(item);
|
|
36
|
+
}
|
|
37
|
+
if (systemTexts.length === 0) {
|
|
38
|
+
return {
|
|
39
|
+
instructions: undefined,
|
|
40
|
+
remaining,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
instructions: systemTexts.join('\n\n'),
|
|
45
|
+
remaining,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function itemToInputItem(item) {
|
|
49
|
+
if (item.type === 'message' && 'content' in item && 'role' in item) {
|
|
50
|
+
// All message types (input and output) are converted to EasyInputMessage
|
|
51
|
+
// because the SDK's input union does not accept ResponsesOutputMessage directly.
|
|
52
|
+
return {
|
|
53
|
+
role: item.role,
|
|
54
|
+
content: contentPartsToText(item.content),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (item.type === 'function_call') {
|
|
58
|
+
return {
|
|
59
|
+
type: 'function_call',
|
|
60
|
+
callId: item.callId,
|
|
61
|
+
id: item.id ?? crypto.randomUUID(),
|
|
62
|
+
name: item.name,
|
|
63
|
+
arguments: item.arguments,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (item.type === 'function_call_output') {
|
|
67
|
+
return {
|
|
68
|
+
type: 'function_call_output',
|
|
69
|
+
callId: item.callId,
|
|
70
|
+
output: item.output,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// Reasoning, web_search_call, file_search_call, image_generation_call,
|
|
74
|
+
// server tool outputs — pass through directly for round-tripping
|
|
75
|
+
return frameworkCast(item);
|
|
76
|
+
}
|
|
77
|
+
/** @internal Converts Noetic Items to OpenRouter SDK input format. */
|
|
78
|
+
export function itemsToInput(items) {
|
|
79
|
+
const result = [];
|
|
80
|
+
for (const item of items) {
|
|
81
|
+
const inputItem = itemToInputItem(item);
|
|
82
|
+
if (!inputItem) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
result.push(inputItem);
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region OpenRouter Response → Noetic Item Passthrough
|
|
91
|
+
/**
|
|
92
|
+
* @internal Extracts output items from an SDK response, passing them through directly
|
|
93
|
+
* as Open Responses compliant items. Falls back to creating a message from `outputText`
|
|
94
|
+
* when the output array contains no message items.
|
|
95
|
+
*/
|
|
96
|
+
export function extractOutputItems(response) {
|
|
97
|
+
const items = frameworkCast(response.output);
|
|
98
|
+
const hasMessage = items.some(isAssistantMessage);
|
|
99
|
+
if (hasMessage || !response.outputText) {
|
|
100
|
+
return items;
|
|
101
|
+
}
|
|
102
|
+
// Fallback: no message items but outputText exists
|
|
103
|
+
return [
|
|
104
|
+
frameworkCast({
|
|
105
|
+
id: crypto.randomUUID(),
|
|
106
|
+
type: 'message',
|
|
107
|
+
role: 'assistant',
|
|
108
|
+
status: 'completed',
|
|
109
|
+
content: [
|
|
110
|
+
{
|
|
111
|
+
type: 'output_text',
|
|
112
|
+
text: response.outputText,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
}),
|
|
116
|
+
...items,
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
/** @internal */
|
|
120
|
+
export function extractUsage(usage) {
|
|
121
|
+
if (!usage) {
|
|
122
|
+
return {
|
|
123
|
+
inputTokens: 0,
|
|
124
|
+
outputTokens: 0,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
inputTokens: usage.inputTokens,
|
|
129
|
+
outputTokens: usage.outputTokens,
|
|
130
|
+
cachedTokens: usage.inputTokensDetails?.cachedTokens,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region Tool Conversion
|
|
135
|
+
// We construct the SDK tool shape manually and use frameworkCast to bridge
|
|
136
|
+
// the internal Zod type gap between Noetic's Tool interface and the OpenRouter SDK.
|
|
137
|
+
// This is safe because callModel only uses inputSchema for JSON Schema
|
|
138
|
+
// generation and validation.
|
|
139
|
+
//
|
|
140
|
+
// IMPORTANT: We intentionally omit `execute` from the SDK tool definitions.
|
|
141
|
+
// This prevents the SDK from handling tool calls internally, which would
|
|
142
|
+
// make tool interactions invisible to Noetic's itemLog, token tracking,
|
|
143
|
+
// and observability. Instead, the AgentHarness manages the tool loop.
|
|
144
|
+
/** @internal */
|
|
145
|
+
export function convertTools({ tools }) {
|
|
146
|
+
return tools.map((t) => frameworkCast({
|
|
147
|
+
type: 'function',
|
|
148
|
+
function: {
|
|
149
|
+
name: t.name,
|
|
150
|
+
description: t.description,
|
|
151
|
+
inputSchema: t.input,
|
|
152
|
+
},
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
function isAsyncGenerator(value) {
|
|
156
|
+
if (typeof value !== 'object' || value === null) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
return Symbol.asyncIterator in value;
|
|
160
|
+
}
|
|
161
|
+
async function consumeGenerator(generator) {
|
|
162
|
+
let result;
|
|
163
|
+
while (true) {
|
|
164
|
+
const next = await generator.next();
|
|
165
|
+
if (next.done) {
|
|
166
|
+
result = next.value;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
/** @internal Execute a single tool call with steering checks. */
|
|
173
|
+
export async function executeToolCall(params) {
|
|
174
|
+
const matchedTool = params.tools.find((t) => t.name === params.toolName);
|
|
175
|
+
if (!matchedTool) {
|
|
176
|
+
return {
|
|
177
|
+
output: `Error: unknown tool '${params.toolName}'`,
|
|
178
|
+
error: true,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (params.layers && params.layers.length > 0) {
|
|
182
|
+
const decision = await params.harness.beforeToolCall(params.layers, params.toolName, params.args, params.context);
|
|
183
|
+
if (decision.action === SteeringAction.Deny) {
|
|
184
|
+
return {
|
|
185
|
+
output: `Tool call denied: ${decision.guidance ?? 'steering rule violation'}`,
|
|
186
|
+
error: true,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (decision.action === SteeringAction.Guide) {
|
|
190
|
+
return {
|
|
191
|
+
output: `Tool call redirected: ${decision.guidance}`,
|
|
192
|
+
error: true,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const toolCtx = buildToolExecutionContext(params.context, params.harness);
|
|
197
|
+
try {
|
|
198
|
+
const executionResult = matchedTool.execute(params.args, toolCtx);
|
|
199
|
+
// Handle generator-based tools (like Bash) that return AsyncGenerator
|
|
200
|
+
const result = isAsyncGenerator(executionResult)
|
|
201
|
+
? await consumeGenerator(executionResult)
|
|
202
|
+
: await executionResult;
|
|
203
|
+
return {
|
|
204
|
+
output: typeof result === 'string' ? result : JSON.stringify(result),
|
|
205
|
+
result,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
return {
|
|
210
|
+
output: `Error: ${e instanceof Error ? e.message : String(e)}`,
|
|
211
|
+
error: true,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region Public API
|
|
217
|
+
/**
|
|
218
|
+
* Creates an `EmbedFn` that calls the OpenRouter embeddings API.
|
|
219
|
+
*
|
|
220
|
+
* @public
|
|
221
|
+
* @param apiKey - OpenRouter API key.
|
|
222
|
+
* @param embeddingModel - Model identifier (default: `'openai/text-embedding-3-small'`).
|
|
223
|
+
* @returns An `EmbedFn` that produces embedding vectors for the given texts.
|
|
224
|
+
*/
|
|
225
|
+
export function createOpenRouterEmbed(apiKey, embeddingModel) {
|
|
226
|
+
const model = embeddingModel ?? 'openai/text-embedding-3-small';
|
|
227
|
+
return async (texts) => {
|
|
228
|
+
const response = await fetch('https://openrouter.ai/api/v1/embeddings', {
|
|
229
|
+
method: 'POST',
|
|
230
|
+
headers: {
|
|
231
|
+
Authorization: `Bearer ${apiKey}`,
|
|
232
|
+
'Content-Type': 'application/json',
|
|
233
|
+
},
|
|
234
|
+
body: JSON.stringify({
|
|
235
|
+
model,
|
|
236
|
+
input: texts,
|
|
237
|
+
}),
|
|
238
|
+
});
|
|
239
|
+
if (!response.ok) {
|
|
240
|
+
throw new Error(`OpenRouter embeddings request failed: ${response.status} ${response.statusText}`);
|
|
241
|
+
}
|
|
242
|
+
const json = EmbeddingsResponseSchema.parse(await response.json());
|
|
243
|
+
// Sort by index to preserve input order
|
|
244
|
+
const sorted = json.data.sort((a, b) => a.index - b.index);
|
|
245
|
+
return sorted.map((d) => d.embedding);
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
//#endregion
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ZodType } from 'zod';
|
|
2
|
+
import type { Channel, ExternalChannel } from '../types/channel';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a typed communication channel writable from outside the execution tree.
|
|
5
|
+
*
|
|
6
|
+
* @public
|
|
7
|
+
* @param name - Channel name for identification and debugging.
|
|
8
|
+
* @param opts.schema - Zod schema used to validate every message sent through the channel.
|
|
9
|
+
* @param opts.mode - Delivery semantics: `'value'` (last-write-wins), `'queue'` (FIFO), `'topic'` (pub/sub).
|
|
10
|
+
* @param opts.capacity - Buffer size for queue mode (default: 1000). Ignored for value/topic modes.
|
|
11
|
+
* @param opts.external - Must be `true` to create an externally writable channel.
|
|
12
|
+
* @returns An `ExternalChannel`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function channel<T>(name: string, opts: {
|
|
15
|
+
schema: ZodType<T>;
|
|
16
|
+
mode: 'value' | 'queue' | 'topic';
|
|
17
|
+
capacity?: number;
|
|
18
|
+
external: true;
|
|
19
|
+
}): ExternalChannel<T>;
|
|
20
|
+
/**
|
|
21
|
+
* Creates a typed communication channel for inter-step messaging within an execution tree.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
* @param name - Channel name for identification and debugging.
|
|
25
|
+
* @param opts.schema - Zod schema used to validate every message sent through the channel.
|
|
26
|
+
* @param opts.mode - Delivery semantics: `'value'` (last-write-wins), `'queue'` (FIFO), `'topic'` (pub/sub).
|
|
27
|
+
* @param opts.capacity - Buffer size for queue mode (default: 1000). Ignored for value/topic modes.
|
|
28
|
+
* @param opts.external - Omit or set to `false` for internal-only channels.
|
|
29
|
+
* @returns A `Channel`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function channel<T>(name: string, opts: {
|
|
32
|
+
schema: ZodType<T>;
|
|
33
|
+
mode: 'value' | 'queue' | 'topic';
|
|
34
|
+
capacity?: number;
|
|
35
|
+
external?: false;
|
|
36
|
+
}): Channel<T>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Context } from '../types/context';
|
|
2
|
+
import type { ContextMemory } from '../types/memory';
|
|
3
|
+
import type { SettleResult, Step, StepBranch, StepForkAll, StepForkRace, StepForkSettle } from '../types/step';
|
|
4
|
+
/**
|
|
5
|
+
* Creates a parallel execution step that races paths and returns the first result.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
* @param opts.id - Unique step identifier used in traces and error messages.
|
|
9
|
+
* @param opts.mode - Must be `'race'`: first path to resolve wins, others are cancelled.
|
|
10
|
+
* @param opts.paths - Factory `(input, ctx) => Step[]` generating the parallel paths.
|
|
11
|
+
* @param opts.concurrency - Optional maximum number of paths to run simultaneously.
|
|
12
|
+
* @returns A `StepForkRace` step.
|
|
13
|
+
* @throws `NoeticConfigError` with code `EMPTY_STEP_ID` if `id` is empty.
|
|
14
|
+
*/
|
|
15
|
+
export declare function fork<TMemory = ContextMemory, I = unknown, O = unknown>(opts: {
|
|
16
|
+
id: string;
|
|
17
|
+
mode: 'race';
|
|
18
|
+
paths: (input: I, ctx: Context<TMemory>) => Step<TMemory, I, O>[];
|
|
19
|
+
concurrency?: number;
|
|
20
|
+
_optimizable?: Step<TMemory>[];
|
|
21
|
+
}): StepForkRace<TMemory, I, O>;
|
|
22
|
+
/**
|
|
23
|
+
* Creates a parallel execution step that waits for all paths and merges results.
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
* @param opts.id - Unique step identifier used in traces and error messages.
|
|
27
|
+
* @param opts.mode - Must be `'all'`: waits for every path; fails on any error.
|
|
28
|
+
* @param opts.paths - Factory `(input, ctx) => Step[]` generating the parallel paths.
|
|
29
|
+
* @param opts.merge - Combines all path results into a single output value.
|
|
30
|
+
* @param opts.concurrency - Optional maximum number of paths to run simultaneously.
|
|
31
|
+
* @returns A `StepForkAll` step.
|
|
32
|
+
* @throws `NoeticConfigError` with code `EMPTY_STEP_ID` if `id` is empty.
|
|
33
|
+
* @throws `NoeticConfigError` with code `MISSING_MERGE_FUNCTION` if `merge` is not provided.
|
|
34
|
+
*/
|
|
35
|
+
export declare function fork<TMemory = ContextMemory, I = unknown, O = unknown>(opts: {
|
|
36
|
+
id: string;
|
|
37
|
+
mode: 'all';
|
|
38
|
+
paths: (input: I, ctx: Context<TMemory>) => Step<TMemory, I, O>[];
|
|
39
|
+
merge: (results: O[], ctx: Context<TMemory>) => O;
|
|
40
|
+
concurrency?: number;
|
|
41
|
+
_optimizable?: Step<TMemory>[];
|
|
42
|
+
}): StepForkAll<TMemory, I, O>;
|
|
43
|
+
/**
|
|
44
|
+
* Creates a parallel execution step that waits for all paths, collecting errors as results.
|
|
45
|
+
*
|
|
46
|
+
* @public
|
|
47
|
+
* @param opts.id - Unique step identifier used in traces and error messages.
|
|
48
|
+
* @param opts.mode - Must be `'settle'`: waits for every path; errors are captured, not thrown.
|
|
49
|
+
* @param opts.paths - Factory `(input, ctx) => Step[]` generating the parallel paths.
|
|
50
|
+
* @param opts.merge - Combines settled results (successes and failures) into a single output.
|
|
51
|
+
* @param opts.concurrency - Optional maximum number of paths to run simultaneously.
|
|
52
|
+
* @returns A `StepForkSettle` step.
|
|
53
|
+
* @throws `NoeticConfigError` with code `EMPTY_STEP_ID` if `id` is empty.
|
|
54
|
+
* @throws `NoeticConfigError` with code `MISSING_MERGE_FUNCTION` if `merge` is not provided.
|
|
55
|
+
*/
|
|
56
|
+
export declare function fork<TMemory = ContextMemory, I = unknown, O = unknown>(opts: {
|
|
57
|
+
id: string;
|
|
58
|
+
mode: 'settle';
|
|
59
|
+
paths: (input: I, ctx: Context<TMemory>) => Step<TMemory, I, O>[];
|
|
60
|
+
merge: (results: SettleResult<O>[], ctx: Context<TMemory>) => O;
|
|
61
|
+
concurrency?: number;
|
|
62
|
+
_optimizable?: Step<TMemory>[];
|
|
63
|
+
}): StepForkSettle<TMemory, I, O>;
|
|
64
|
+
/**
|
|
65
|
+
* Creates a conditional routing step that selects the next step at runtime.
|
|
66
|
+
*
|
|
67
|
+
* @public
|
|
68
|
+
* @param opts.id - Unique step identifier used in traces and error messages.
|
|
69
|
+
* @param opts.route - Routing function `(input, ctx) => Step | null`. Returns the next step
|
|
70
|
+
* to execute, or `null` to skip (pass input through as output). May be async.
|
|
71
|
+
* @returns A `StepBranch` step.
|
|
72
|
+
* @throws `NoeticConfigError` with code `EMPTY_STEP_ID` if `id` is empty.
|
|
73
|
+
* @throws `NoeticConfigError` with code `MISSING_ROUTE_FUNCTION` if `route` is not provided.
|
|
74
|
+
*/
|
|
75
|
+
export declare function branch<TMemory = ContextMemory, I = unknown, O = unknown>(opts: {
|
|
76
|
+
id: string;
|
|
77
|
+
route: (input: I, ctx: Context<TMemory>) => Step<TMemory, I, O> | null | Promise<Step<TMemory, I, O> | null>;
|
|
78
|
+
_optimizable?: Step<TMemory>[];
|
|
79
|
+
}): StepBranch<TMemory, I, O>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { NoeticConfigError } from '../errors/noetic-config-error';
|
|
2
|
+
import { frameworkCast } from '../interpreter/framework-cast';
|
|
3
|
+
export function fork(opts) {
|
|
4
|
+
if (!opts.id || opts.id.trim() === '') {
|
|
5
|
+
throw new NoeticConfigError({
|
|
6
|
+
code: 'EMPTY_STEP_ID',
|
|
7
|
+
message: 'fork() requires a non-empty id.',
|
|
8
|
+
hint: 'Pass a unique string as the id field, e.g. fork({ id: "my-fork", ... }).',
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
if ((opts.mode === 'all' || opts.mode === 'settle') && !opts.merge) {
|
|
12
|
+
throw new NoeticConfigError({
|
|
13
|
+
code: 'MISSING_MERGE_FUNCTION',
|
|
14
|
+
message: `fork() mode '${opts.mode}' requires a merge function.`,
|
|
15
|
+
hint: 'Provide a merge function that combines path results into a single output.',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return frameworkCast({
|
|
19
|
+
kind: 'fork',
|
|
20
|
+
...opts,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Creates a conditional routing step that selects the next step at runtime.
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
* @param opts.id - Unique step identifier used in traces and error messages.
|
|
28
|
+
* @param opts.route - Routing function `(input, ctx) => Step | null`. Returns the next step
|
|
29
|
+
* to execute, or `null` to skip (pass input through as output). May be async.
|
|
30
|
+
* @returns A `StepBranch` step.
|
|
31
|
+
* @throws `NoeticConfigError` with code `EMPTY_STEP_ID` if `id` is empty.
|
|
32
|
+
* @throws `NoeticConfigError` with code `MISSING_ROUTE_FUNCTION` if `route` is not provided.
|
|
33
|
+
*/
|
|
34
|
+
export function branch(opts) {
|
|
35
|
+
if (!opts.id || opts.id.trim() === '') {
|
|
36
|
+
throw new NoeticConfigError({
|
|
37
|
+
code: 'EMPTY_STEP_ID',
|
|
38
|
+
message: 'branch() requires a non-empty id.',
|
|
39
|
+
hint: 'Pass a unique string as the id field, e.g. branch({ id: "my-branch", ... }).',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (!opts.route) {
|
|
43
|
+
throw new NoeticConfigError({
|
|
44
|
+
code: 'MISSING_ROUTE_FUNCTION',
|
|
45
|
+
message: 'branch() requires a route function.',
|
|
46
|
+
hint: 'Provide a route function that selects the next step at runtime.',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
kind: 'branch',
|
|
51
|
+
...opts,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Channel } from '../types/channel';
|
|
2
|
+
import type { ContextMemory } from '../types/memory';
|
|
3
|
+
import type { EveryErrorPolicy, Step, StepEvery } from '../types/step';
|
|
4
|
+
/**
|
|
5
|
+
* Configuration options accepted by the `every()` builder, excluding the `kind`
|
|
6
|
+
* discriminant. `every()` schedules a body step on a fixed-interval, optionally
|
|
7
|
+
* woken early by a channel, and runs until the executing context is cancelled.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export interface EveryOptions<TMemory = ContextMemory, I = unknown, O = unknown> {
|
|
12
|
+
/** Unique step identifier used in traces and error messages. */
|
|
13
|
+
id: string;
|
|
14
|
+
/** Body step executed on each iteration. */
|
|
15
|
+
step: Step<TMemory, I, O>;
|
|
16
|
+
/** Park duration between iterations in milliseconds. Must be >= 0. */
|
|
17
|
+
ms: number;
|
|
18
|
+
/** Optional channel that wakes the parking interval when any value arrives. */
|
|
19
|
+
wakeOn?: Channel<unknown>;
|
|
20
|
+
/** Behavior when `step` throws. Defaults to `'continue'`. */
|
|
21
|
+
onError?: EveryErrorPolicy;
|
|
22
|
+
/** Random jitter applied to the park duration in milliseconds. Must be >= 0. Default 0. */
|
|
23
|
+
jitter?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Creates an `every` step that runs a body step on a fixed-interval schedule.
|
|
27
|
+
*
|
|
28
|
+
* The operator runs forever until the executing context is cancelled. After each
|
|
29
|
+
* iteration it parks for `ms ± jitter` milliseconds (or until `wakeOn` receives
|
|
30
|
+
* a message, whichever comes first).
|
|
31
|
+
*
|
|
32
|
+
* @public
|
|
33
|
+
* @param opts.id - Unique step identifier used in traces and error messages.
|
|
34
|
+
* @param opts.step - Body step executed on each iteration.
|
|
35
|
+
* @param opts.ms - Park duration between iterations in milliseconds.
|
|
36
|
+
* @param opts.wakeOn - Optional channel that wakes the parking interval when any value arrives.
|
|
37
|
+
* @param opts.onError - Error policy. `'continue'` (default) records a span event and continues; `'fail'` re-throws.
|
|
38
|
+
* @param opts.jitter - Random jitter in ms applied to the park duration. Default 0.
|
|
39
|
+
* @returns A `StepEvery` step.
|
|
40
|
+
* @throws `NoeticConfigError` with code `EMPTY_STEP_ID` if `id` is empty.
|
|
41
|
+
* @throws `NoeticConfigError` with code `MISSING_BODY_STEP` if `step` is not provided.
|
|
42
|
+
* @throws `NoeticConfigError` with code `INVALID_INTERVAL_MS` if `ms` is negative or not finite.
|
|
43
|
+
* @throws `NoeticConfigError` with code `INVALID_JITTER` if `jitter` is negative or not finite.
|
|
44
|
+
*/
|
|
45
|
+
export declare function every<TMemory = ContextMemory, I = unknown, O = unknown>(opts: EveryOptions<TMemory, I, O>): StepEvery<TMemory, I, O>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { NoeticConfigError } from '../errors/noetic-config-error';
|
|
2
|
+
/**
|
|
3
|
+
* Creates an `every` step that runs a body step on a fixed-interval schedule.
|
|
4
|
+
*
|
|
5
|
+
* The operator runs forever until the executing context is cancelled. After each
|
|
6
|
+
* iteration it parks for `ms ± jitter` milliseconds (or until `wakeOn` receives
|
|
7
|
+
* a message, whichever comes first).
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
* @param opts.id - Unique step identifier used in traces and error messages.
|
|
11
|
+
* @param opts.step - Body step executed on each iteration.
|
|
12
|
+
* @param opts.ms - Park duration between iterations in milliseconds.
|
|
13
|
+
* @param opts.wakeOn - Optional channel that wakes the parking interval when any value arrives.
|
|
14
|
+
* @param opts.onError - Error policy. `'continue'` (default) records a span event and continues; `'fail'` re-throws.
|
|
15
|
+
* @param opts.jitter - Random jitter in ms applied to the park duration. Default 0.
|
|
16
|
+
* @returns A `StepEvery` step.
|
|
17
|
+
* @throws `NoeticConfigError` with code `EMPTY_STEP_ID` if `id` is empty.
|
|
18
|
+
* @throws `NoeticConfigError` with code `MISSING_BODY_STEP` if `step` is not provided.
|
|
19
|
+
* @throws `NoeticConfigError` with code `INVALID_INTERVAL_MS` if `ms` is negative or not finite.
|
|
20
|
+
* @throws `NoeticConfigError` with code `INVALID_JITTER` if `jitter` is negative or not finite.
|
|
21
|
+
*/
|
|
22
|
+
export function every(opts) {
|
|
23
|
+
if (!opts.id || opts.id.trim() === '') {
|
|
24
|
+
throw new NoeticConfigError({
|
|
25
|
+
code: 'EMPTY_STEP_ID',
|
|
26
|
+
message: 'every() requires a non-empty id.',
|
|
27
|
+
hint: 'Pass a unique string as the id field, e.g. every({ id: "my-every", ... }).',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (!opts.step) {
|
|
31
|
+
throw new NoeticConfigError({
|
|
32
|
+
code: 'MISSING_BODY_STEP',
|
|
33
|
+
message: 'every() requires a body step.',
|
|
34
|
+
hint: 'Provide the step to execute on each scheduled iteration.',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (!Number.isFinite(opts.ms) || opts.ms < 0) {
|
|
38
|
+
throw new NoeticConfigError({
|
|
39
|
+
code: 'INVALID_INTERVAL_MS',
|
|
40
|
+
message: `every() requires a non-negative finite ms, got ${opts.ms}.`,
|
|
41
|
+
hint: 'Pass a non-negative number of milliseconds for the park interval.',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (opts.jitter !== undefined && (!Number.isFinite(opts.jitter) || opts.jitter < 0)) {
|
|
45
|
+
throw new NoeticConfigError({
|
|
46
|
+
code: 'INVALID_JITTER',
|
|
47
|
+
message: `every() requires a non-negative finite jitter, got ${opts.jitter}.`,
|
|
48
|
+
hint: 'Pass a non-negative number of milliseconds for jitter, or omit it.',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
kind: 'every',
|
|
53
|
+
id: opts.id,
|
|
54
|
+
step: opts.step,
|
|
55
|
+
ms: opts.ms,
|
|
56
|
+
wakeOn: opts.wakeOn,
|
|
57
|
+
onError: opts.onError ?? 'continue',
|
|
58
|
+
jitter: opts.jitter ?? 0,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ZodType } from 'zod';
|
|
2
|
+
import type { ExecutionContext, LayerDataDecl, LayerFunctionDecl } from '../types/memory';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a read-only data declaration for a memory layer's `provides` map.
|
|
5
|
+
*
|
|
6
|
+
* @public
|
|
7
|
+
* @param opts.read - Function that projects a value from the layer's current state.
|
|
8
|
+
* @returns A `LayerDataDecl` to include in a layer's `provides`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function layerData<T, TState>(opts: {
|
|
11
|
+
read: (state: TState) => T;
|
|
12
|
+
}): LayerDataDecl<T, TState>;
|
|
13
|
+
/**
|
|
14
|
+
* Creates a callable function declaration for a memory layer's `provides` map.
|
|
15
|
+
* Functions are accessible via `ctx.memory['layerId'].fn()` and automatically exposed as LLM tools.
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
* @param opts.description - Human-readable description (used as tool description for LLM).
|
|
19
|
+
* @param opts.input - Zod schema validating input arguments.
|
|
20
|
+
* @param opts.output - Zod schema validating the return value.
|
|
21
|
+
* @param opts.execute - Async function receiving args, current state, and execution context.
|
|
22
|
+
* @returns A `LayerFunctionDecl` to include in a layer's `provides`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function layerFn<TInput, TOutput, TState>(opts: {
|
|
25
|
+
description: string;
|
|
26
|
+
input: ZodType<TInput>;
|
|
27
|
+
output: ZodType<TOutput>;
|
|
28
|
+
execute: (args: TInput, state: TState, ctx: ExecutionContext) => Promise<{
|
|
29
|
+
result: TOutput;
|
|
30
|
+
state?: TState;
|
|
31
|
+
}>;
|
|
32
|
+
}): LayerFunctionDecl<TInput, TOutput, TState>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a read-only data declaration for a memory layer's `provides` map.
|
|
3
|
+
*
|
|
4
|
+
* @public
|
|
5
|
+
* @param opts.read - Function that projects a value from the layer's current state.
|
|
6
|
+
* @returns A `LayerDataDecl` to include in a layer's `provides`.
|
|
7
|
+
*/
|
|
8
|
+
export function layerData(opts) {
|
|
9
|
+
return {
|
|
10
|
+
kind: 'data',
|
|
11
|
+
read: opts.read,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Creates a callable function declaration for a memory layer's `provides` map.
|
|
16
|
+
* Functions are accessible via `ctx.memory['layerId'].fn()` and automatically exposed as LLM tools.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
* @param opts.description - Human-readable description (used as tool description for LLM).
|
|
20
|
+
* @param opts.input - Zod schema validating input arguments.
|
|
21
|
+
* @param opts.output - Zod schema validating the return value.
|
|
22
|
+
* @param opts.execute - Async function receiving args, current state, and execution context.
|
|
23
|
+
* @returns A `LayerFunctionDecl` to include in a layer's `provides`.
|
|
24
|
+
*/
|
|
25
|
+
export function layerFn(opts) {
|
|
26
|
+
return {
|
|
27
|
+
kind: 'function',
|
|
28
|
+
description: opts.description,
|
|
29
|
+
input: opts.input,
|
|
30
|
+
output: opts.output,
|
|
31
|
+
execute: opts.execute,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ContextMemory } from '../types/memory';
|
|
2
|
+
import type { StepLoop } from '../types/step';
|
|
3
|
+
/** @public Configuration options accepted by the `loop()` builder, excluding the `kind` discriminant. */
|
|
4
|
+
export type LoopConfig<TMemory = ContextMemory, I = unknown, O = unknown> = Omit<StepLoop<TMemory, I, O>, 'kind'>;
|
|
5
|
+
/**
|
|
6
|
+
* Creates a loop step that iterates a body step until a termination predicate is satisfied.
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
* @param opts.id - Unique step identifier used in traces and error messages.
|
|
10
|
+
* @param opts.steps - Array of steps to execute sequentially on each iteration.
|
|
11
|
+
* @param opts.until - Termination predicate `(snapshot) => Verdict | Promise<Verdict>` evaluated after each iteration.
|
|
12
|
+
* @param opts.maxIterations - Hard safety cap on iterations (default: 1000).
|
|
13
|
+
* @param opts.maxHistorySize - Maximum entries kept in the snapshot history array.
|
|
14
|
+
* @param opts.inbox - Optional channel for injecting messages into the loop mid-execution.
|
|
15
|
+
* @param opts.parkTimeout - Ms to wait on inbox before the loop parks itself (default: 0).
|
|
16
|
+
* @param opts.prepareNext - Transforms `(output, verdict, ctx)` into the next iteration's input.
|
|
17
|
+
* @param opts.onError - Per-iteration error handler returning `'retry'`, `'skip'`, or `'abort'`.
|
|
18
|
+
* @returns A `StepLoop` step.
|
|
19
|
+
* @throws `NoeticConfigError` with code `EMPTY_STEP_ID` if `id` is empty.
|
|
20
|
+
* @throws `NoeticConfigError` with code `MISSING_LOOP_BODY` if `steps` is empty.
|
|
21
|
+
* @throws `NoeticConfigError` with code `MISSING_UNTIL_PREDICATE` if `until` is not provided.
|
|
22
|
+
*/
|
|
23
|
+
export declare function loop<TMemory = ContextMemory, I = unknown, O = unknown>(opts: LoopConfig<TMemory, I, O>): StepLoop<TMemory, I, O>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { NoeticConfigError } from '../errors/noetic-config-error';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a loop step that iterates a body step until a termination predicate is satisfied.
|
|
4
|
+
*
|
|
5
|
+
* @public
|
|
6
|
+
* @param opts.id - Unique step identifier used in traces and error messages.
|
|
7
|
+
* @param opts.steps - Array of steps to execute sequentially on each iteration.
|
|
8
|
+
* @param opts.until - Termination predicate `(snapshot) => Verdict | Promise<Verdict>` evaluated after each iteration.
|
|
9
|
+
* @param opts.maxIterations - Hard safety cap on iterations (default: 1000).
|
|
10
|
+
* @param opts.maxHistorySize - Maximum entries kept in the snapshot history array.
|
|
11
|
+
* @param opts.inbox - Optional channel for injecting messages into the loop mid-execution.
|
|
12
|
+
* @param opts.parkTimeout - Ms to wait on inbox before the loop parks itself (default: 0).
|
|
13
|
+
* @param opts.prepareNext - Transforms `(output, verdict, ctx)` into the next iteration's input.
|
|
14
|
+
* @param opts.onError - Per-iteration error handler returning `'retry'`, `'skip'`, or `'abort'`.
|
|
15
|
+
* @returns A `StepLoop` step.
|
|
16
|
+
* @throws `NoeticConfigError` with code `EMPTY_STEP_ID` if `id` is empty.
|
|
17
|
+
* @throws `NoeticConfigError` with code `MISSING_LOOP_BODY` if `steps` is empty.
|
|
18
|
+
* @throws `NoeticConfigError` with code `MISSING_UNTIL_PREDICATE` if `until` is not provided.
|
|
19
|
+
*/
|
|
20
|
+
export function loop(opts) {
|
|
21
|
+
if (!opts.id || opts.id.trim() === '') {
|
|
22
|
+
throw new NoeticConfigError({
|
|
23
|
+
code: 'EMPTY_STEP_ID',
|
|
24
|
+
message: 'loop() requires a non-empty id.',
|
|
25
|
+
hint: 'Pass a unique string as the id field, e.g. loop({ id: "my-loop", ... }).',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
if (!Array.isArray(opts.steps) || opts.steps.length === 0) {
|
|
29
|
+
throw new NoeticConfigError({
|
|
30
|
+
code: 'MISSING_LOOP_BODY',
|
|
31
|
+
message: 'loop() requires at least one body step.',
|
|
32
|
+
hint: 'Add at least one step to the steps array.',
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (!opts.until) {
|
|
36
|
+
throw new NoeticConfigError({
|
|
37
|
+
code: 'MISSING_UNTIL_PREDICATE',
|
|
38
|
+
message: 'loop() requires an until predicate.',
|
|
39
|
+
hint: 'Provide an until predicate, e.g. until.maxSteps(10).',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
kind: 'loop',
|
|
44
|
+
...opts,
|
|
45
|
+
};
|
|
46
|
+
}
|