@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,81 @@
|
|
|
1
|
+
import { NoeticErrorImpl } from '../errors/noetic-error';
|
|
2
|
+
import { emitFrameworkEvent, getBroadcaster } from '../runtime/broadcaster-utils';
|
|
3
|
+
import { buildToolExecutionContext } from '../runtime/tool-memory';
|
|
4
|
+
import { SteeringAction } from '../types/steering';
|
|
5
|
+
import { frameworkCast } from './framework-cast';
|
|
6
|
+
//#region Helpers
|
|
7
|
+
function isAsyncGenerator(value) {
|
|
8
|
+
if (typeof value !== 'object' || value === null) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
return Symbol.asyncIterator in value;
|
|
12
|
+
}
|
|
13
|
+
async function consumeToolGenerator(params) {
|
|
14
|
+
const broadcaster = getBroadcaster(params.ctx);
|
|
15
|
+
const agentName = params.ctx.harness.config.name;
|
|
16
|
+
while (true) {
|
|
17
|
+
const next = await params.generator.next();
|
|
18
|
+
if (next.done) {
|
|
19
|
+
return next.value;
|
|
20
|
+
}
|
|
21
|
+
emitFrameworkEvent({
|
|
22
|
+
broadcaster,
|
|
23
|
+
agentName,
|
|
24
|
+
eventType: 'tool_progress',
|
|
25
|
+
data: {
|
|
26
|
+
stepId: params.stepId,
|
|
27
|
+
toolName: params.toolName,
|
|
28
|
+
event: next.value,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region Public API
|
|
35
|
+
export async function executeTool(step, input, ctx, harness, layers) {
|
|
36
|
+
const baseCtx = frameworkCast(ctx);
|
|
37
|
+
const args = step.args ? Object.assign({}, input, step.args) : input;
|
|
38
|
+
const parseResult = step.tool.input.safeParse(args);
|
|
39
|
+
if (!parseResult.success) {
|
|
40
|
+
throw new NoeticErrorImpl({
|
|
41
|
+
kind: 'step_failed',
|
|
42
|
+
stepId: step.id,
|
|
43
|
+
cause: new Error(`Tool input validation failed: ${parseResult.error.message}`),
|
|
44
|
+
retriesExhausted: false,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (layers && layers.length > 0) {
|
|
48
|
+
const decision = await harness.beforeToolCall(layers, step.tool.name, parseResult.data, baseCtx);
|
|
49
|
+
if (decision.action !== SteeringAction.Allow) {
|
|
50
|
+
throw new NoeticErrorImpl({
|
|
51
|
+
kind: 'steering_denied',
|
|
52
|
+
guidance: decision.guidance,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const toolCtx = buildToolExecutionContext(baseCtx, harness);
|
|
58
|
+
const result = step.tool.execute(parseResult.data, toolCtx);
|
|
59
|
+
if (isAsyncGenerator(result)) {
|
|
60
|
+
return frameworkCast(await consumeToolGenerator({
|
|
61
|
+
generator: result,
|
|
62
|
+
stepId: step.id,
|
|
63
|
+
toolName: step.tool.name,
|
|
64
|
+
ctx: baseCtx,
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
return frameworkCast(await result);
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
if (e instanceof NoeticErrorImpl) {
|
|
71
|
+
throw e;
|
|
72
|
+
}
|
|
73
|
+
throw new NoeticErrorImpl({
|
|
74
|
+
kind: 'step_failed',
|
|
75
|
+
stepId: step.id,
|
|
76
|
+
cause: e instanceof Error ? e : new Error(String(e)),
|
|
77
|
+
retriesExhausted: false,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Context } from '../types/context';
|
|
2
|
+
import type { ContextMemory } from '../types/memory';
|
|
3
|
+
import type { Step } from '../types/step';
|
|
4
|
+
/**
|
|
5
|
+
* Executes a step within the interpreter, dispatching to the appropriate handler by step kind.
|
|
6
|
+
*
|
|
7
|
+
* @param step - The step to execute.
|
|
8
|
+
* @param input - Input value passed to the step.
|
|
9
|
+
* @param ctx - Execution context carrying state, tokens, and observability.
|
|
10
|
+
* @returns The step's output value.
|
|
11
|
+
* @throws `NoeticError` with kind `step_failed` if max depth is exceeded or an unknown step kind is encountered.
|
|
12
|
+
* @throws `NoeticError` with kind `cancelled` if the context is aborted.
|
|
13
|
+
*/
|
|
14
|
+
export declare function execute<TMemory = ContextMemory, I = unknown, O = unknown>(step: Step<TMemory, I, O>, input: I, ctx: Context<TMemory>): Promise<O>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { NoeticErrorImpl } from '../errors/noetic-error';
|
|
2
|
+
import { emitFrameworkEvent, getBroadcaster, shouldEmit } from '../runtime/broadcaster-utils';
|
|
3
|
+
import { executeBranch } from './execute-branch';
|
|
4
|
+
import { executeEvery } from './execute-every';
|
|
5
|
+
import { executeFork } from './execute-fork';
|
|
6
|
+
import { executeLLM } from './execute-llm';
|
|
7
|
+
import { executeLoop } from './execute-loop';
|
|
8
|
+
import { executeProvide } from './execute-provide';
|
|
9
|
+
import { executeRun } from './execute-run';
|
|
10
|
+
import { executeSpawn } from './execute-spawn';
|
|
11
|
+
import { executeTool } from './execute-tool';
|
|
12
|
+
import { frameworkCast } from './framework-cast';
|
|
13
|
+
import { isMutableContext } from './typeguards';
|
|
14
|
+
const MAX_DEPTH = 64;
|
|
15
|
+
/**
|
|
16
|
+
* Executes a step within the interpreter, dispatching to the appropriate handler by step kind.
|
|
17
|
+
*
|
|
18
|
+
* @param step - The step to execute.
|
|
19
|
+
* @param input - Input value passed to the step.
|
|
20
|
+
* @param ctx - Execution context carrying state, tokens, and observability.
|
|
21
|
+
* @returns The step's output value.
|
|
22
|
+
* @throws `NoeticError` with kind `step_failed` if max depth is exceeded or an unknown step kind is encountered.
|
|
23
|
+
* @throws `NoeticError` with kind `cancelled` if the context is aborted.
|
|
24
|
+
*/
|
|
25
|
+
export async function execute(step, input, ctx) {
|
|
26
|
+
const baseCtx = frameworkCast(ctx);
|
|
27
|
+
// Depth guard — classified as step_failed (not budget_exceeded) because depth
|
|
28
|
+
// is a structural safety limit, not a user-configurable budget field.
|
|
29
|
+
if (ctx.depth >= MAX_DEPTH) {
|
|
30
|
+
throw new NoeticErrorImpl({
|
|
31
|
+
kind: 'step_failed',
|
|
32
|
+
stepId: step.id,
|
|
33
|
+
cause: new Error(`Maximum spawn depth ${MAX_DEPTH} exceeded (depth: ${ctx.depth})`),
|
|
34
|
+
retriesExhausted: true,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// Abort check
|
|
38
|
+
if (ctx.aborted) {
|
|
39
|
+
throw new NoeticErrorImpl({
|
|
40
|
+
kind: 'cancelled',
|
|
41
|
+
reason: ctx.abortReason ?? 'context aborted',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// Increment step count
|
|
45
|
+
if (isMutableContext(baseCtx)) {
|
|
46
|
+
baseCtx.stepCount = (baseCtx.stepCount || 0) + 1;
|
|
47
|
+
}
|
|
48
|
+
// Emit step_started framework event (respects step.emit option)
|
|
49
|
+
const broadcaster = getBroadcaster(baseCtx);
|
|
50
|
+
const agentName = baseCtx.harness.config.name;
|
|
51
|
+
const startedData = {
|
|
52
|
+
stepId: step.id,
|
|
53
|
+
kind: step.kind,
|
|
54
|
+
};
|
|
55
|
+
const emit = step.kind === 'llm' ? step.emit : undefined;
|
|
56
|
+
if (shouldEmit(emit, 'step_started', startedData)) {
|
|
57
|
+
emitFrameworkEvent({
|
|
58
|
+
broadcaster,
|
|
59
|
+
agentName,
|
|
60
|
+
eventType: 'step_started',
|
|
61
|
+
data: startedData,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
let result;
|
|
65
|
+
switch (step.kind) {
|
|
66
|
+
case 'run':
|
|
67
|
+
result = await executeRun(step, input, ctx);
|
|
68
|
+
break;
|
|
69
|
+
case 'llm':
|
|
70
|
+
result = await executeLLM(step, input, ctx, baseCtx.layers);
|
|
71
|
+
break;
|
|
72
|
+
case 'tool':
|
|
73
|
+
result = await executeTool(step, input, ctx, baseCtx.harness);
|
|
74
|
+
break;
|
|
75
|
+
case 'branch':
|
|
76
|
+
result = await executeBranch(step, input, ctx, (s, i, c) => execute(s, i, c));
|
|
77
|
+
break;
|
|
78
|
+
case 'fork':
|
|
79
|
+
result = await executeFork(step, input, ctx, (s, i, c) => execute(s, i, c));
|
|
80
|
+
break;
|
|
81
|
+
case 'spawn':
|
|
82
|
+
result = await executeSpawn(step, input, ctx, (s, i, c) => execute(s, i, c), {
|
|
83
|
+
itemSchemas: baseCtx.itemSchemas,
|
|
84
|
+
});
|
|
85
|
+
break;
|
|
86
|
+
case 'provide':
|
|
87
|
+
result = await executeProvide(step, input, ctx, (s, i, c) => execute(s, i, c));
|
|
88
|
+
break;
|
|
89
|
+
case 'loop':
|
|
90
|
+
result = await executeLoop(step, input, ctx, (s, i, c) => execute(s, i, c));
|
|
91
|
+
break;
|
|
92
|
+
case 'every':
|
|
93
|
+
result = await executeEvery(step, input, ctx, (s, i, c) => execute(s, i, c));
|
|
94
|
+
break;
|
|
95
|
+
default: {
|
|
96
|
+
const _exhaustive = step;
|
|
97
|
+
throw new NoeticErrorImpl({
|
|
98
|
+
kind: 'step_failed',
|
|
99
|
+
stepId: 'unknown',
|
|
100
|
+
cause: new Error('Unknown step kind'),
|
|
101
|
+
retriesExhausted: false,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Emit step_completed framework event (respects step.emit option)
|
|
106
|
+
const completedData = {
|
|
107
|
+
stepId: step.id,
|
|
108
|
+
kind: step.kind,
|
|
109
|
+
};
|
|
110
|
+
if (shouldEmit(emit, 'step_completed', completedData)) {
|
|
111
|
+
emitFrameworkEvent({
|
|
112
|
+
broadcaster,
|
|
113
|
+
agentName,
|
|
114
|
+
eventType: 'step_completed',
|
|
115
|
+
data: completedData,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explicit cast for framework boundaries where TypeScript's type system
|
|
3
|
+
* cannot express the relationship between input and output types.
|
|
4
|
+
*
|
|
5
|
+
* Used for: pass-through returns (branch null route, loop feedback),
|
|
6
|
+
* raw LLM text output, type-erased collections, and cross-library bridges.
|
|
7
|
+
*
|
|
8
|
+
* This is the single approved location for unsafe type coercion in the codebase.
|
|
9
|
+
*/
|
|
10
|
+
export declare function frameworkCast<T>(value: unknown): T;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explicit cast for framework boundaries where TypeScript's type system
|
|
3
|
+
* cannot express the relationship between input and output types.
|
|
4
|
+
*
|
|
5
|
+
* Used for: pass-through returns (branch null route, loop feedback),
|
|
6
|
+
* raw LLM text output, type-erased collections, and cross-library bridges.
|
|
7
|
+
*
|
|
8
|
+
* This is the single approved location for unsafe type coercion in the codebase.
|
|
9
|
+
*/
|
|
10
|
+
// Declared return type differs from the parameter type intentionally —
|
|
11
|
+
// callers specify T to bridge a framework type gap that TypeScript cannot express.
|
|
12
|
+
// The runtime value is returned unchanged (identity function).
|
|
13
|
+
export function frameworkCast(value) {
|
|
14
|
+
// @ts-expect-error — intentional unsafe coercion at framework boundary
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LLMResponse } from '../types/common';
|
|
2
|
+
import type { Context } from '../types/context';
|
|
3
|
+
import type { InputMessageItem, Item } from '../types/items';
|
|
4
|
+
export declare function createMessage(text: string, role: 'user' | 'developer'): InputMessageItem;
|
|
5
|
+
export declare function extractAssistantText(items: ReadonlyArray<Item>): string;
|
|
6
|
+
/** Naive token estimate: ~4 chars per token */
|
|
7
|
+
export declare function estimateTokens(text: string): number;
|
|
8
|
+
/** Accumulate token usage and cost from an LLM response onto the context. */
|
|
9
|
+
export declare function trackUsage(ctx: Context, response: LLMResponse): void;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { isAssistantMessage, isMutableContext, isOutputText } from './typeguards';
|
|
2
|
+
export function createMessage(text, role) {
|
|
3
|
+
return {
|
|
4
|
+
id: crypto.randomUUID(),
|
|
5
|
+
status: 'completed',
|
|
6
|
+
type: 'message',
|
|
7
|
+
role,
|
|
8
|
+
content: [
|
|
9
|
+
{
|
|
10
|
+
type: 'input_text',
|
|
11
|
+
text,
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function extractAssistantText(items) {
|
|
17
|
+
const lastMsg = items.findLast(isAssistantMessage);
|
|
18
|
+
if (!lastMsg) {
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
return (lastMsg.content
|
|
22
|
+
?.filter(isOutputText)
|
|
23
|
+
?.map((c) => c.text)
|
|
24
|
+
?.join('') ?? '');
|
|
25
|
+
}
|
|
26
|
+
/** Naive token estimate: ~4 chars per token */
|
|
27
|
+
export function estimateTokens(text) {
|
|
28
|
+
return Math.ceil(text.length / 4);
|
|
29
|
+
}
|
|
30
|
+
/** Accumulate token usage and cost from an LLM response onto the context. */
|
|
31
|
+
export function trackUsage(ctx, response) {
|
|
32
|
+
if (!isMutableContext(ctx)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
ctx.tokens.input += response.usage.inputTokens;
|
|
36
|
+
ctx.tokens.output += response.usage.outputTokens;
|
|
37
|
+
ctx.tokens.total += response.usage.inputTokens + response.usage.outputTokens;
|
|
38
|
+
ctx.tokens.cached = (ctx.tokens.cached ?? 0) + (response.usage.cachedTokens ?? 0);
|
|
39
|
+
if (response.cost) {
|
|
40
|
+
ctx.cost = ctx.cost + response.cost;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ChannelStore } from '../runtime/channel-store';
|
|
2
|
+
import { ContextImpl } from '../runtime/context-impl';
|
|
3
|
+
import type { Context } from '../types/context';
|
|
4
|
+
import type { FunctionCallItem, Item, MessageItem } from '../types/items';
|
|
5
|
+
import type { ContextMemory } from '../types/memory';
|
|
6
|
+
import type { MutableContext } from '../types/mutable-context';
|
|
7
|
+
export declare function isMutableContext(ctx: Context<ContextMemory>): ctx is MutableContext;
|
|
8
|
+
export declare function isContextImpl(ctx: Context<ContextMemory>): ctx is ContextImpl;
|
|
9
|
+
/**
|
|
10
|
+
* Co-located with `isContextImpl` so the import of `ContextImpl` flows through
|
|
11
|
+
* the same module other interpreters pull it from, sidestepping a circular
|
|
12
|
+
* TDZ when this helper is used by `execute-every.ts`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getContextChannelStore<TMemory>(ctx: Context<TMemory>): ChannelStore | undefined;
|
|
15
|
+
export declare function isAssistantMessage(item: unknown): item is MessageItem;
|
|
16
|
+
export declare function isUserMessage(item: unknown): item is MessageItem;
|
|
17
|
+
export declare function isFunctionCall(item: Item): item is FunctionCallItem;
|
|
18
|
+
export declare function isOutputText(part: {
|
|
19
|
+
type: string;
|
|
20
|
+
}): part is {
|
|
21
|
+
type: 'output_text';
|
|
22
|
+
text: string;
|
|
23
|
+
};
|
|
24
|
+
declare function assertNever(_value: never): never;
|
|
25
|
+
export { assertNever };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ContextImpl } from '../runtime/context-impl';
|
|
2
|
+
export function isMutableContext(ctx) {
|
|
3
|
+
// Check if the context has writable mutable fields (ContextImpl or compatible mock)
|
|
4
|
+
if (ctx instanceof ContextImpl) {
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
// Duck-type check: verify the fields we need to mutate are present and writable
|
|
8
|
+
const desc = Object.getOwnPropertyDescriptor(ctx, 'stepCount');
|
|
9
|
+
return desc !== undefined && desc.writable !== false;
|
|
10
|
+
}
|
|
11
|
+
export function isContextImpl(ctx) {
|
|
12
|
+
return ctx instanceof ContextImpl;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Co-located with `isContextImpl` so the import of `ContextImpl` flows through
|
|
16
|
+
* the same module other interpreters pull it from, sidestepping a circular
|
|
17
|
+
* TDZ when this helper is used by `execute-every.ts`.
|
|
18
|
+
*/
|
|
19
|
+
export function getContextChannelStore(ctx) {
|
|
20
|
+
if (ctx instanceof ContextImpl) {
|
|
21
|
+
return ctx.channelStore;
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
export function isAssistantMessage(item) {
|
|
26
|
+
return (typeof item === 'object' &&
|
|
27
|
+
item !== null &&
|
|
28
|
+
'type' in item &&
|
|
29
|
+
item.type === 'message' &&
|
|
30
|
+
'role' in item &&
|
|
31
|
+
item.role === 'assistant');
|
|
32
|
+
}
|
|
33
|
+
export function isUserMessage(item) {
|
|
34
|
+
return (typeof item === 'object' &&
|
|
35
|
+
item !== null &&
|
|
36
|
+
'type' in item &&
|
|
37
|
+
item.type === 'message' &&
|
|
38
|
+
'role' in item &&
|
|
39
|
+
item.role === 'user');
|
|
40
|
+
}
|
|
41
|
+
export function isFunctionCall(item) {
|
|
42
|
+
return item.type === 'function_call' && 'callId' in item && 'name' in item;
|
|
43
|
+
}
|
|
44
|
+
export function isOutputText(part) {
|
|
45
|
+
return part.type === 'output_text';
|
|
46
|
+
}
|
|
47
|
+
function assertNever(_value) {
|
|
48
|
+
throw new Error('Exhaustive check failed');
|
|
49
|
+
}
|
|
50
|
+
export { assertNever };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Context } from '../types/context';
|
|
2
|
+
import type { MemoryLayer } from '../types/memory';
|
|
3
|
+
export interface BudgetAllocation {
|
|
4
|
+
layerId: string;
|
|
5
|
+
allocated: number;
|
|
6
|
+
}
|
|
7
|
+
interface AllocateBudgetsOpts {
|
|
8
|
+
layers: MemoryLayer[];
|
|
9
|
+
totalBudget: number;
|
|
10
|
+
systemPromptTokens: number;
|
|
11
|
+
responseReserve: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function allocateBudgets({ layers, totalBudget, systemPromptTokens, responseReserve, }: AllocateBudgetsOpts): {
|
|
14
|
+
allocations: BudgetAllocation[];
|
|
15
|
+
historyBudget: number;
|
|
16
|
+
};
|
|
17
|
+
export interface BudgetLimits {
|
|
18
|
+
maxCost?: number;
|
|
19
|
+
maxSteps?: number;
|
|
20
|
+
maxDuration?: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function checkBudget(ctx: Context, limits: BudgetLimits): void;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { NoeticErrorImpl } from '../errors/noetic-error';
|
|
2
|
+
function extractMin(config) {
|
|
3
|
+
if (config && typeof config === 'object' && 'min' in config) {
|
|
4
|
+
return config.min;
|
|
5
|
+
}
|
|
6
|
+
return 0;
|
|
7
|
+
}
|
|
8
|
+
function extractMax(config) {
|
|
9
|
+
if (config === 'auto') {
|
|
10
|
+
return Number.POSITIVE_INFINITY;
|
|
11
|
+
}
|
|
12
|
+
if (typeof config === 'number') {
|
|
13
|
+
return config;
|
|
14
|
+
}
|
|
15
|
+
if (config && typeof config === 'object' && 'min' in config) {
|
|
16
|
+
return config.max;
|
|
17
|
+
}
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
function computeShare({ headroom, layerPool, infiniteCount, finiteTotal, totalMax, allHeadrooms, layerIndex, }) {
|
|
21
|
+
if (!Number.isFinite(headroom)) {
|
|
22
|
+
// All infinite-headroom layers split the pool after finite layers take their share.
|
|
23
|
+
// `infiniteCount >= 1` is guaranteed here since headroom is infinite.
|
|
24
|
+
const finiteUsed = Number.isFinite(totalMax)
|
|
25
|
+
? 0
|
|
26
|
+
: allHeadrooms.reduce((sum, m, j) => Number.isFinite(m) && j !== layerIndex
|
|
27
|
+
? sum + Math.min(m, (m / (finiteTotal || 1)) * layerPool)
|
|
28
|
+
: sum, 0);
|
|
29
|
+
return (layerPool - finiteUsed) / infiniteCount;
|
|
30
|
+
}
|
|
31
|
+
if (Number.isFinite(totalMax)) {
|
|
32
|
+
return (headroom / totalMax) * layerPool;
|
|
33
|
+
}
|
|
34
|
+
// Mix of finite and infinite: finite layers share half the pool proportionally.
|
|
35
|
+
return finiteTotal > 0 ? (headroom / finiteTotal) * (layerPool * 0.5) : 0;
|
|
36
|
+
}
|
|
37
|
+
export function allocateBudgets({ layers, totalBudget, systemPromptTokens, responseReserve, }) {
|
|
38
|
+
const available = totalBudget - responseReserve - systemPromptTokens;
|
|
39
|
+
if (available <= 0) {
|
|
40
|
+
return {
|
|
41
|
+
allocations: layers.map((l) => ({
|
|
42
|
+
layerId: l.id,
|
|
43
|
+
allocated: 0,
|
|
44
|
+
})),
|
|
45
|
+
historyBudget: 0,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// Phase 1: satisfy minimums
|
|
49
|
+
let remaining = available;
|
|
50
|
+
const allocations = [];
|
|
51
|
+
for (const layer of layers) {
|
|
52
|
+
const min = extractMin(layer.budget);
|
|
53
|
+
allocations.push({
|
|
54
|
+
layerId: layer.id,
|
|
55
|
+
allocated: min,
|
|
56
|
+
});
|
|
57
|
+
remaining -= min;
|
|
58
|
+
}
|
|
59
|
+
// Phase 2: distribute 60% of remaining to layers proportionally, 40% to history
|
|
60
|
+
const layerPool = remaining * 0.6;
|
|
61
|
+
const historyBudget = remaining * 0.4;
|
|
62
|
+
// Compute headroom per layer (how much above the minimum each layer can absorb)
|
|
63
|
+
let totalMax = 0;
|
|
64
|
+
const headrooms = [];
|
|
65
|
+
for (let i = 0; i < layers.length; i++) {
|
|
66
|
+
const max = extractMax(layers[i].budget);
|
|
67
|
+
const headroom = Math.max(0, max - allocations[i].allocated);
|
|
68
|
+
headrooms.push(headroom);
|
|
69
|
+
totalMax += headroom;
|
|
70
|
+
}
|
|
71
|
+
if (totalMax > 0) {
|
|
72
|
+
const infiniteCount = headrooms.filter((h) => !Number.isFinite(h)).length;
|
|
73
|
+
const finiteTotal = headrooms.reduce((sum, h) => (Number.isFinite(h) ? sum + h : sum), 0);
|
|
74
|
+
for (let i = 0; i < headrooms.length; i++) {
|
|
75
|
+
const share = computeShare({
|
|
76
|
+
headroom: headrooms[i],
|
|
77
|
+
layerPool,
|
|
78
|
+
infiniteCount,
|
|
79
|
+
finiteTotal,
|
|
80
|
+
totalMax,
|
|
81
|
+
allHeadrooms: headrooms,
|
|
82
|
+
layerIndex: i,
|
|
83
|
+
});
|
|
84
|
+
allocations[i].allocated += Math.min(share, headrooms[i]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
allocations,
|
|
89
|
+
historyBudget: Math.max(0, historyBudget),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export function checkBudget(ctx, limits) {
|
|
93
|
+
if (limits.maxCost !== undefined && ctx.cost > limits.maxCost) {
|
|
94
|
+
throw new NoeticErrorImpl({
|
|
95
|
+
kind: 'budget_exceeded',
|
|
96
|
+
field: 'cost',
|
|
97
|
+
limit: limits.maxCost,
|
|
98
|
+
actual: ctx.cost,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (limits.maxSteps !== undefined && ctx.stepCount > limits.maxSteps) {
|
|
102
|
+
throw new NoeticErrorImpl({
|
|
103
|
+
kind: 'budget_exceeded',
|
|
104
|
+
field: 'steps',
|
|
105
|
+
limit: limits.maxSteps,
|
|
106
|
+
actual: ctx.stepCount,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (limits.maxDuration !== undefined && ctx.elapsed > limits.maxDuration) {
|
|
110
|
+
throw new NoeticErrorImpl({
|
|
111
|
+
kind: 'budget_exceeded',
|
|
112
|
+
field: 'duration',
|
|
113
|
+
limit: limits.maxDuration,
|
|
114
|
+
actual: ctx.elapsed,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Item } from '../types/items';
|
|
2
|
+
/**
|
|
3
|
+
* Finds the first function_call item matching `name`, parses its JSON arguments,
|
|
4
|
+
* and returns the result as a record. Returns null if no match or invalid data.
|
|
5
|
+
*/
|
|
6
|
+
export declare function findFunctionCall(items: ReadonlyArray<Item>, name: string): Record<string, unknown> | null;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { frameworkCast } from '../interpreter/framework-cast';
|
|
2
|
+
import { isFunctionCall } from '../interpreter/typeguards';
|
|
3
|
+
/**
|
|
4
|
+
* Finds the first function_call item matching `name`, parses its JSON arguments,
|
|
5
|
+
* and returns the result as a record. Returns null if no match or invalid data.
|
|
6
|
+
*/
|
|
7
|
+
export function findFunctionCall(items, name) {
|
|
8
|
+
for (const item of items) {
|
|
9
|
+
if (!isFunctionCall(item)) {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
if (item.name !== name) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const raw = JSON.parse(item.arguments);
|
|
17
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
return frameworkCast(raw);
|
|
21
|
+
}
|
|
22
|
+
catch { }
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Tool } from '../types/common';
|
|
2
|
+
import type { Context } from '../types/context';
|
|
3
|
+
import type { ContextMemory, MemoryLayer } from '../types/memory';
|
|
4
|
+
import type { AgentHarnessContract } from '../types/runtime';
|
|
5
|
+
/**
|
|
6
|
+
* Build the `ctx.memory` object — a readonly map of layer ID → resolved handle.
|
|
7
|
+
* Handles use getters for data (live reads) and closures for functions (lazy state).
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildContextMemory(layers: ReadonlyArray<MemoryLayer>, ctx: Context): ContextMemory;
|
|
10
|
+
/**
|
|
11
|
+
* Collect all `provides` function declarations from active layers and convert them to Tools.
|
|
12
|
+
* Tool names are namespaced as `layerId/functionName`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveLayerTools(layers: ReadonlyArray<MemoryLayer>, harness: AgentHarnessContract, ctx: Context): Tool[];
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { frameworkCast } from '../interpreter/framework-cast';
|
|
2
|
+
import { contextToExecCtx } from '../runtime/exec-context-factory';
|
|
3
|
+
function executeLayerFn(params) {
|
|
4
|
+
const { fn, args, layerId, harness, ctx } = params;
|
|
5
|
+
const validated = fn.input.parse(args);
|
|
6
|
+
const state = harness.getLayerState(ctx.id, layerId);
|
|
7
|
+
const execCtx = contextToExecCtx(ctx);
|
|
8
|
+
return fn.execute(validated, state, execCtx).then((outcome) => {
|
|
9
|
+
if (outcome.state !== undefined) {
|
|
10
|
+
harness.setLayerState(ctx.id, layerId, outcome.state);
|
|
11
|
+
}
|
|
12
|
+
return outcome.result;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function buildLayerHandle(layer, ctx) {
|
|
16
|
+
const provides = layer.provides;
|
|
17
|
+
if (!provides) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
const handle = {};
|
|
21
|
+
const harness = ctx.harness;
|
|
22
|
+
const layerId = layer.id;
|
|
23
|
+
for (const [name, decl] of Object.entries(provides)) {
|
|
24
|
+
if (decl.kind === 'data') {
|
|
25
|
+
Object.defineProperty(handle, name, {
|
|
26
|
+
get() {
|
|
27
|
+
const state = harness.getLayerState(ctx.id, layerId);
|
|
28
|
+
return decl.read(state);
|
|
29
|
+
},
|
|
30
|
+
enumerable: true,
|
|
31
|
+
});
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
handle[name] = (args) => executeLayerFn({
|
|
35
|
+
fn: decl,
|
|
36
|
+
args,
|
|
37
|
+
layerId,
|
|
38
|
+
harness,
|
|
39
|
+
ctx,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return handle;
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region Context Memory
|
|
46
|
+
/**
|
|
47
|
+
* Build the `ctx.memory` object — a readonly map of layer ID → resolved handle.
|
|
48
|
+
* Handles use getters for data (live reads) and closures for functions (lazy state).
|
|
49
|
+
*/
|
|
50
|
+
export function buildContextMemory(layers, ctx) {
|
|
51
|
+
const memory = {};
|
|
52
|
+
for (const layer of layers) {
|
|
53
|
+
memory[layer.id] = buildLayerHandle(layer, ctx);
|
|
54
|
+
}
|
|
55
|
+
return frameworkCast(memory);
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region Layer Tools Resolution
|
|
59
|
+
/**
|
|
60
|
+
* Collect all `provides` function declarations from active layers and convert them to Tools.
|
|
61
|
+
* Tool names are namespaced as `layerId/functionName`.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveLayerTools(layers, harness, ctx) {
|
|
64
|
+
const tools = [];
|
|
65
|
+
for (const layer of layers) {
|
|
66
|
+
if (!layer.provides) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
for (const [name, decl] of Object.entries(layer.provides)) {
|
|
70
|
+
if (decl.kind !== 'function') {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const layerId = layer.id;
|
|
74
|
+
tools.push({
|
|
75
|
+
name: `${layerId}/${name}`,
|
|
76
|
+
description: decl.description,
|
|
77
|
+
input: decl.input,
|
|
78
|
+
output: decl.output,
|
|
79
|
+
execute: (args, _toolCtx) => executeLayerFn({
|
|
80
|
+
fn: decl,
|
|
81
|
+
args,
|
|
82
|
+
layerId,
|
|
83
|
+
harness,
|
|
84
|
+
ctx,
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return tools;
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|