@noetic-tools/core 5.3.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -6
- package/dist/adapters/in-memory-subprocess/step-completion.js +1 -1
- package/dist/adapters/openrouter.d.ts +12 -1
- package/dist/adapters/openrouter.js +79 -5
- package/dist/builders/control-flow-builders.d.ts +13 -13
- package/dist/builders/control-flow-builders.js +11 -11
- package/dist/{patterns → builders}/dynamic-workflow.d.ts +1 -1
- package/dist/{patterns → builders}/dynamic-workflow.js +18 -12
- package/dist/builders/every.d.ts +14 -14
- package/dist/builders/every.js +16 -16
- package/dist/builders/layer-provides-builders.d.ts +1 -1
- package/dist/builders/layer-provides-builders.js +1 -1
- package/dist/builders/provide-builder.d.ts +5 -12
- package/dist/builders/provide-builder.js +9 -10
- package/dist/builders/spawn-builder.d.ts +0 -2
- package/dist/builders/spawn-builder.js +2 -3
- package/dist/builders/step-builders.d.ts +66 -55
- package/dist/builders/step-builders.js +118 -111
- package/dist/builders/tool-builder.d.ts +18 -22
- package/dist/builders/tool-builder.js +6 -4
- package/dist/builders/workflow-hydrator.d.ts +3 -3
- package/dist/builders/workflow-hydrator.js +59 -63
- package/dist/builders/workflow-step.d.ts +13 -25
- package/dist/builders/workflow-step.js +13 -18
- package/dist/conditions/conditions.d.ts +2 -2
- package/dist/conditions/conditions.js +1 -1
- package/dist/harness/agent-harness.d.ts +54 -40
- package/dist/harness/agent-harness.js +44 -33
- package/dist/harness/deps/types.d.ts +1 -1
- package/dist/harness/model-call.d.ts +1 -1
- package/dist/harness/model-call.js +23 -18
- package/dist/index.d.ts +21 -33
- package/dist/index.js +10 -26
- package/dist/interpreter/action-types.d.ts +1 -1
- package/dist/interpreter/collect-tools.js +7 -7
- package/dist/interpreter/execute-action.d.ts +5 -5
- package/dist/interpreter/execute-action.js +284 -218
- package/dist/interpreter/execute-control.d.ts +9 -9
- package/dist/interpreter/execute-control.js +27 -27
- package/dist/interpreter/execute-sub-harness.d.ts +1 -1
- package/dist/interpreter/execute-sub-harness.js +7 -26
- package/dist/interpreter/execute.d.ts +1 -1
- package/dist/interpreter/execute.js +26 -25
- package/dist/interpreter/step-dispatcher.d.ts +1 -1
- package/dist/interpreter/step-dispatcher.js +3 -3
- package/dist/interpreter/structured-output.d.ts +14 -0
- package/dist/interpreter/structured-output.js +40 -0
- package/dist/interpreter/typeguards.d.ts +1 -1
- package/dist/interpreter/typeguards.js +1 -1
- package/dist/portable/builders.d.ts +3 -3
- package/dist/portable/builders.js +6 -5
- package/dist/portable/context-layers.d.ts +1 -1
- package/dist/portable/context-layers.js +1 -1
- package/dist/runtime/broadcaster-utils.d.ts +1 -1
- package/dist/runtime/broadcaster-utils.js +1 -1
- package/dist/runtime/context-impl.d.ts +8 -10
- package/dist/runtime/context-impl.js +8 -12
- package/dist/runtime/durable/step-ledger.d.ts +2 -2
- package/dist/runtime/durable/step-ledger.js +3 -3
- package/dist/runtime/session-runner.d.ts +12 -1
- package/dist/runtime/session-runner.js +33 -1
- package/dist/runtime/tool-context.js +0 -2
- package/dist/schemas/workflow.d.ts +3 -3
- package/dist/schemas/workflow.js +2 -2
- package/dist/types/checkpoint.d.ts +11 -2
- package/dist/types/checkpoint.js +10 -1
- package/dist/{runtime → util}/detached-handle.js +1 -1
- package/package.json +6 -4
- package/schema/noetic-workflow.schema.json +24 -11
- package/dist/builders/context-option.d.ts +0 -23
- package/dist/builders/context-option.js +0 -22
- package/dist/deprecated.d.ts +0 -30
- package/dist/deprecated.js +0 -28
- package/dist/patterns/interview.d.ts +0 -53
- package/dist/patterns/interview.js +0 -113
- package/dist/patterns/plans.d.ts +0 -44
- package/dist/patterns/plans.js +0 -125
- package/dist/patterns/ralph-wiggum.d.ts +0 -19
- package/dist/patterns/ralph-wiggum.js +0 -37
- package/dist/patterns/react.d.ts +0 -19
- package/dist/patterns/react.js +0 -41
- /package/dist/{runtime → util}/detached-handle.d.ts +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Action step handlers: run, llm, spawn, provide, tool.
|
|
3
3
|
*/
|
|
4
|
-
import { createMessage, estimateTokens, extractAssistantText, frameworkCast, isNoeticError, isOutputCodec, NoeticConfigError, NoeticErrorImpl, } from '@noetic-tools/types';
|
|
5
|
-
import { ZodError } from 'zod';
|
|
4
|
+
import { createMessage, estimateTokens, extractAssistantText, frameworkCast, isNoeticError, isOutputCodec, NoeticConfigError, NoeticErrorImpl, validateSchema, } from '@noetic-tools/types';
|
|
6
5
|
import { emitToolUi } from '../runtime/tool-ui.js';
|
|
7
6
|
import { allocateBudgets, assembleView, buildToolExecutionContext, ContextImpl, commitLayerUsage, computeLayerUsage, contextToExecCtx, createContextCacheStore, DEFAULT_PROJECTION, defaultItemSchemaRegistry, emitFrameworkEvent, getBroadcaster, lineageKey, noteCacheOutcome, resolveCacheConfig, resolveLayerTools, returnLayers, shouldEmit, snapshotCwdState, spawnLayers, } from './action-deps.js';
|
|
8
7
|
import { isServerToolSpec, SteeringAction } from './action-types.js';
|
|
@@ -10,9 +9,10 @@ import { cloneWithGuard } from './clone-guard.js';
|
|
|
10
9
|
import { collectAllTools, deduplicateTools } from './collect-tools.js';
|
|
11
10
|
import { prepareBandedView, stampAnchoringAttributes } from './context-assembly.js';
|
|
12
11
|
import { trackUsage } from './message-helpers.js';
|
|
12
|
+
import { parseStructuredOutput } from './structured-output.js';
|
|
13
13
|
import { getContextChannelStore, isContextImpl, isFunctionCall, isMutableContext, } from './typeguards.js';
|
|
14
14
|
//#region run
|
|
15
|
-
export async function
|
|
15
|
+
export async function executeRunCode(step, input, ctx) {
|
|
16
16
|
const retry = step.retry;
|
|
17
17
|
const maxAttempts = retry?.maxAttempts ?? 1;
|
|
18
18
|
let lastError = new Error('No attempts executed');
|
|
@@ -174,7 +174,7 @@ async function runInputPipeline({ ctx, layers, input, }) {
|
|
|
174
174
|
* it is kept off `outputSchema` (which drives JSON-schema formatting).
|
|
175
175
|
*/
|
|
176
176
|
function resolveOutputMode(output, baseInstructions) {
|
|
177
|
-
// A generic type predicate does not reliably narrow a `
|
|
177
|
+
// A generic type predicate does not reliably narrow a `StandardSchemaV1 |
|
|
178
178
|
// OutputCodec<O>` union through a free type parameter, so bridge the two
|
|
179
179
|
// branches with the sanctioned framework cast — `isOutputCodec` guarantees
|
|
180
180
|
// the discriminant at runtime.
|
|
@@ -223,46 +223,237 @@ function finalizeCodecOutput(codec, lastText, ctx, emit) {
|
|
|
223
223
|
});
|
|
224
224
|
return session.finish(lastText);
|
|
225
225
|
}
|
|
226
|
-
/** JSON-parse + Zod-validate the assistant text, raising `llm_parse_error`. */
|
|
227
|
-
function parseSchemaOutput(schema, lastText, stepId) {
|
|
228
|
-
try {
|
|
229
|
-
return schema.parse(JSON.parse(lastText));
|
|
230
|
-
}
|
|
231
|
-
catch (e) {
|
|
232
|
-
if (e instanceof SyntaxError || e instanceof ZodError) {
|
|
233
|
-
throw new NoeticErrorImpl({
|
|
234
|
-
kind: 'llm_parse_error',
|
|
235
|
-
stepId,
|
|
236
|
-
raw: lastText,
|
|
237
|
-
schema,
|
|
238
|
-
zodError: e instanceof ZodError
|
|
239
|
-
? e
|
|
240
|
-
: new ZodError([
|
|
241
|
-
{
|
|
242
|
-
code: 'custom',
|
|
243
|
-
message: `Invalid JSON: ${e.message}`,
|
|
244
|
-
path: [],
|
|
245
|
-
},
|
|
246
|
-
]),
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
throw e;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
226
|
/**
|
|
253
227
|
* Turn the finished assistant text into the step's typed output: a streaming
|
|
254
|
-
* codec, a
|
|
228
|
+
* codec, a Standard Schema, or raw text passthrough.
|
|
255
229
|
*/
|
|
256
|
-
function finalizeStepOutput(params) {
|
|
230
|
+
async function finalizeStepOutput(params) {
|
|
257
231
|
if (params.outputCodec) {
|
|
258
232
|
return finalizeCodecOutput(params.outputCodec, params.lastText, params.ctx, params.emit);
|
|
259
233
|
}
|
|
260
234
|
if (params.outputSchema) {
|
|
261
|
-
return
|
|
235
|
+
return parseStructuredOutput({
|
|
236
|
+
schema: params.outputSchema,
|
|
237
|
+
rawText: params.lastText,
|
|
238
|
+
stepId: params.stepId,
|
|
239
|
+
});
|
|
262
240
|
}
|
|
263
241
|
return frameworkCast(params.lastText);
|
|
264
242
|
}
|
|
265
|
-
|
|
243
|
+
/**
|
|
244
|
+
* `step.tools` is heterogeneous: client `Tool`s plus inline OpenRouter
|
|
245
|
+
* server-tool specs (web search/fetch). Partition them — client tools flow
|
|
246
|
+
* through the unified-tool / allowedToolNames / steering machinery as before;
|
|
247
|
+
* server-tool specs bypass it entirely and are stamped onto the model request
|
|
248
|
+
* (`_serverTools`) for the model caller to wrap via the SDK's `serverTool()`.
|
|
249
|
+
* `undefined` (unrestricted) is preserved as-is; only a present array is
|
|
250
|
+
* partitioned so the `[] = opt-out` semantics of step.tools stay intact.
|
|
251
|
+
*/
|
|
252
|
+
function partitionStepTools(resolvedStepTools) {
|
|
253
|
+
if (resolvedStepTools === undefined) {
|
|
254
|
+
return {
|
|
255
|
+
clientStepTools: undefined,
|
|
256
|
+
serverToolSpecs: [],
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const clientStepTools = [];
|
|
260
|
+
const serverToolSpecs = [];
|
|
261
|
+
for (const entry of resolvedStepTools) {
|
|
262
|
+
if (isServerToolSpec(entry)) {
|
|
263
|
+
serverToolSpecs.push(entry);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
clientStepTools.push(entry);
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
clientStepTools,
|
|
270
|
+
serverToolSpecs,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Append user input — through layer pipeline if layers exist, otherwise direct.
|
|
275
|
+
* The append pipeline may request a context re-render (e.g. a layer that
|
|
276
|
+
* expanded an input reference into new content); applied after recall.
|
|
277
|
+
*/
|
|
278
|
+
async function appendUserInput(params) {
|
|
279
|
+
const { ctx, layers, input } = params;
|
|
280
|
+
if (typeof input !== 'string' || input.length === 0) {
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
283
|
+
if (layers !== undefined && layers.length > 0) {
|
|
284
|
+
return await runInputPipeline({
|
|
285
|
+
ctx,
|
|
286
|
+
layers,
|
|
287
|
+
input,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
ctx.itemLog.append(createMessage(input, 'user'));
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Splits the context budget across layers via `allocateBudgets`. An empty map
|
|
295
|
+
* when the step runs without layers.
|
|
296
|
+
*/
|
|
297
|
+
function computeBudgetMap(params) {
|
|
298
|
+
const { layers, policy, systemPromptTokens } = params;
|
|
299
|
+
if (layers === undefined || layers.length === 0) {
|
|
300
|
+
return new Map();
|
|
301
|
+
}
|
|
302
|
+
const { allocations } = allocateBudgets({
|
|
303
|
+
layers,
|
|
304
|
+
totalBudget: policy.tokenBudget,
|
|
305
|
+
systemPromptTokens,
|
|
306
|
+
responseReserve: policy.responseReserve,
|
|
307
|
+
});
|
|
308
|
+
return new Map(allocations.map((a) => [
|
|
309
|
+
a.layerId,
|
|
310
|
+
a.allocated,
|
|
311
|
+
]));
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Recall once per LLM step: atomic layers run in the hot path; eventual layers
|
|
315
|
+
* are served from cache. Results drive both the assembled context window and
|
|
316
|
+
* the per-layer usage breakdown (ctx.lastLayerUsage). Recall fires before the
|
|
317
|
+
* steering retry loop because retries replay the same context. Re-render
|
|
318
|
+
* requests collected from the input-append pipeline are applied on top.
|
|
319
|
+
*/
|
|
320
|
+
async function gatherRecallResults(params) {
|
|
321
|
+
const { ctx, layers, recallQuery, budgetMap, slotOf, rerenderRequests } = params;
|
|
322
|
+
if (layers === undefined || layers.length === 0) {
|
|
323
|
+
return emptyRecall;
|
|
324
|
+
}
|
|
325
|
+
const atomic = await ctx.harness.recallLayersAtomic(layers, recallQuery, ctx, budgetMap);
|
|
326
|
+
const eventual = await ctx.harness.recallLayersEventual(layers, recallQuery, ctx, budgetMap);
|
|
327
|
+
let recallResults = [
|
|
328
|
+
...atomic,
|
|
329
|
+
...eventual,
|
|
330
|
+
].sort((a, b) => (slotOf.get(a.layerId) ?? 0) - (slotOf.get(b.layerId) ?? 0));
|
|
331
|
+
if (rerenderRequests.length > 0) {
|
|
332
|
+
const rerendered = await ctx.harness.executeRerender(rerenderRequests, layers, ctx, budgetMap, recallQuery);
|
|
333
|
+
recallResults = mergeRecallResults([
|
|
334
|
+
...recallResults,
|
|
335
|
+
], rerendered, slotOf);
|
|
336
|
+
}
|
|
337
|
+
return recallResults;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Builds the items the model sees for one attempt: system messages stay at the
|
|
341
|
+
* front, the banded anchor/live/delta output rides between them and history,
|
|
342
|
+
* and steering guidance from a prior retry lands at the very tail. The
|
|
343
|
+
* projector enforces the token budget (drops highest-slot layer output, then
|
|
344
|
+
* oldest history).
|
|
345
|
+
*/
|
|
346
|
+
async function assembleTurnItems(params) {
|
|
347
|
+
const { ctx, layers, banded, steeringTail, viewPolicy } = params;
|
|
348
|
+
const rawHistoryItems = ctx.itemLog.items;
|
|
349
|
+
if (layers === undefined || layers.length === 0) {
|
|
350
|
+
return rawHistoryItems;
|
|
351
|
+
}
|
|
352
|
+
const projectedHistoryItems = await ctx.harness.projectHistory(layers, rawHistoryItems, ctx);
|
|
353
|
+
const systemItems = [];
|
|
354
|
+
const nonSystemHistory = [];
|
|
355
|
+
const tailIds = new Set(steeringTail.map((i) => i.id));
|
|
356
|
+
for (const item of projectedHistoryItems) {
|
|
357
|
+
if (item.type === 'message' && item.role === 'system') {
|
|
358
|
+
systemItems.push(item);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
// Steering guidance is re-placed at the tail; skip it here so the retry
|
|
362
|
+
// does not see the same correction twice.
|
|
363
|
+
if ('id' in item && typeof item.id === 'string' && tailIds.has(item.id)) {
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
nonSystemHistory.push(item);
|
|
367
|
+
}
|
|
368
|
+
return assembleView({
|
|
369
|
+
systemPromptItems: systemItems,
|
|
370
|
+
layerOutputItems: banded.anchorItems,
|
|
371
|
+
historyItems: nonSystemHistory,
|
|
372
|
+
liveLayerItems: banded.liveItems,
|
|
373
|
+
deltaItems: banded.deltaItems,
|
|
374
|
+
tailItems: steeringTail,
|
|
375
|
+
policy: viewPolicy,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Builds the callModel request for one attempt. Cancellation reaches INSIDE
|
|
380
|
+
* the model call, not just the step boundary: the caller's abort signal stops
|
|
381
|
+
* the stream and the tool-round loop mid flight, so `ctx.abort()` /
|
|
382
|
+
* `harness.cancel()` don't wait out a long generation before taking effect
|
|
383
|
+
* (spec 09, Cancellation item 2).
|
|
384
|
+
*/
|
|
385
|
+
function buildModelRequest(params) {
|
|
386
|
+
const { step, ctx, layers } = params;
|
|
387
|
+
const _serverTools = params.serverToolSpecs.length > 0 ? params.serverToolSpecs : undefined;
|
|
388
|
+
const signal = isContextImpl(ctx) ? ctx.abortSignal : undefined;
|
|
389
|
+
if (params.resolvedTools) {
|
|
390
|
+
return {
|
|
391
|
+
model: params.resolvedModel,
|
|
392
|
+
items: params.assembledItems,
|
|
393
|
+
instructions: params.resolvedInstructions,
|
|
394
|
+
tools: params.resolvedTools,
|
|
395
|
+
params: step.params,
|
|
396
|
+
outputSchema: params.outputSchema,
|
|
397
|
+
outputJsonSchema: params.step.outputJsonSchema,
|
|
398
|
+
emit: step.emit,
|
|
399
|
+
_serverTools,
|
|
400
|
+
ctx,
|
|
401
|
+
layers,
|
|
402
|
+
allowedToolNames: params.allowedToolNames,
|
|
403
|
+
nodeId: step.id,
|
|
404
|
+
parentSpan: ctx.span,
|
|
405
|
+
signal,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
model: params.resolvedModel,
|
|
410
|
+
items: params.assembledItems,
|
|
411
|
+
instructions: params.resolvedInstructions,
|
|
412
|
+
params: step.params,
|
|
413
|
+
outputSchema: params.outputSchema,
|
|
414
|
+
outputJsonSchema: params.step.outputJsonSchema,
|
|
415
|
+
emit: step.emit,
|
|
416
|
+
_serverTools,
|
|
417
|
+
nodeId: step.id,
|
|
418
|
+
parentSpan: ctx.span,
|
|
419
|
+
signal,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Records a finished model response on the context: appends response items to
|
|
424
|
+
* the log, publishes `lastStepMeta`, and commits the per-layer usage breakdown
|
|
425
|
+
* (what the model was actually shown, pinned replays included — raw recall
|
|
426
|
+
* output would report content it never saw).
|
|
427
|
+
*/
|
|
428
|
+
function recordTurnOutcome(params) {
|
|
429
|
+
const { ctx, response, banded } = params;
|
|
430
|
+
const toolCalls = [];
|
|
431
|
+
for (const item of response.items) {
|
|
432
|
+
ctx.itemLog.append(item);
|
|
433
|
+
if (isFunctionCall(item)) {
|
|
434
|
+
toolCalls.push(item);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const meta = {
|
|
438
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
439
|
+
usage: response.usage,
|
|
440
|
+
cost: response.cost,
|
|
441
|
+
responseItems: response.items,
|
|
442
|
+
};
|
|
443
|
+
if (isMutableContext(ctx)) {
|
|
444
|
+
ctx.lastStepMeta = meta;
|
|
445
|
+
}
|
|
446
|
+
commitLayerUsage(ctx, computeLayerUsage({
|
|
447
|
+
ctx,
|
|
448
|
+
modelId: params.resolvedModel,
|
|
449
|
+
instructions: params.resolvedInstructions,
|
|
450
|
+
tools: params.resolvedTools,
|
|
451
|
+
recallResults: banded.servedPerLayer,
|
|
452
|
+
serveInfo: banded.serveInfo,
|
|
453
|
+
epoch: banded.epoch,
|
|
454
|
+
}));
|
|
455
|
+
}
|
|
456
|
+
export async function executeCallModel(step, input, ctx, layers) {
|
|
266
457
|
const baseCtx = frameworkCast(ctx);
|
|
267
458
|
const hasLayers = layers !== undefined && layers.length > 0;
|
|
268
459
|
// Resolve lazy step fields up-front. Function-form getters run once per
|
|
@@ -272,49 +463,19 @@ export async function executeLLM(step, input, ctx, layers) {
|
|
|
272
463
|
if (!resolvedModel || resolvedModel.trim() === '') {
|
|
273
464
|
throw new NoeticConfigError({
|
|
274
465
|
code: 'MISSING_MODEL',
|
|
275
|
-
message: `
|
|
466
|
+
message: `callModel(${JSON.stringify(step.id)}) resolved model to an empty string.`,
|
|
276
467
|
hint: "Ensure `model` (or your `(ctx) => string` getter) returns a non-empty identifier, e.g. 'anthropic/claude-sonnet-4-20250514'.",
|
|
277
468
|
});
|
|
278
469
|
}
|
|
279
470
|
const baseInstructions = await resolveLazy(step.instructions, ctx);
|
|
280
471
|
const { outputCodec, outputSchema, resolvedInstructions } = resolveOutputMode(step.output, baseInstructions);
|
|
281
472
|
const resolvedStepTools = await resolveLazy(step.tools, ctx);
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
// partitioned so the `[] = opt-out` semantics of step.tools stay intact.
|
|
289
|
-
let clientStepTools;
|
|
290
|
-
const serverToolSpecs = [];
|
|
291
|
-
if (resolvedStepTools !== undefined) {
|
|
292
|
-
clientStepTools = [];
|
|
293
|
-
for (const entry of resolvedStepTools) {
|
|
294
|
-
if (isServerToolSpec(entry)) {
|
|
295
|
-
serverToolSpecs.push(entry);
|
|
296
|
-
}
|
|
297
|
-
else {
|
|
298
|
-
clientStepTools.push(entry);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
// Append user input — through layer pipeline if layers exist, otherwise direct.
|
|
303
|
-
// The append pipeline may request a context re-render (e.g. a layer that
|
|
304
|
-
// expanded an input reference into new content); applied after recall below.
|
|
305
|
-
let rerenderRequests = [];
|
|
306
|
-
if (typeof input === 'string' && input.length > 0) {
|
|
307
|
-
if (hasLayers) {
|
|
308
|
-
rerenderRequests = await runInputPipeline({
|
|
309
|
-
ctx: baseCtx,
|
|
310
|
-
layers,
|
|
311
|
-
input,
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
baseCtx.itemLog.append(createMessage(input, 'user'));
|
|
316
|
-
}
|
|
317
|
-
}
|
|
473
|
+
const { clientStepTools, serverToolSpecs } = partitionStepTools(resolvedStepTools);
|
|
474
|
+
const rerenderRequests = await appendUserInput({
|
|
475
|
+
ctx: baseCtx,
|
|
476
|
+
layers,
|
|
477
|
+
input,
|
|
478
|
+
});
|
|
318
479
|
const { tools: resolvedTools, allowedToolNames } = resolveToolsAndRestrictions(clientStepTools, layers, baseCtx);
|
|
319
480
|
// Resolve the projection policy (step override > harness default > fallback)
|
|
320
481
|
// and split the context budget across layers via allocateBudgets.
|
|
@@ -327,44 +488,24 @@ export async function executeLLM(step, input, ctx, layers) {
|
|
|
327
488
|
...policy,
|
|
328
489
|
tokenBudget: Math.max(0, policy.tokenBudget - systemPromptTokens),
|
|
329
490
|
};
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
systemPromptTokens,
|
|
336
|
-
responseReserve: policy.responseReserve,
|
|
337
|
-
});
|
|
338
|
-
budgetMap = new Map(allocations.map((a) => [
|
|
339
|
-
a.layerId,
|
|
340
|
-
a.allocated,
|
|
341
|
-
]));
|
|
342
|
-
}
|
|
343
|
-
// Recall once per LLM step: atomic layers run in the hot path; eventual layers
|
|
344
|
-
// are served from cache. Results drive both the assembled context window and
|
|
345
|
-
// the per-layer usage breakdown (ctx.lastLayerUsage). Recall fires before the
|
|
346
|
-
// steering retry loop because retries replay the same context.
|
|
491
|
+
const budgetMap = computeBudgetMap({
|
|
492
|
+
layers,
|
|
493
|
+
policy,
|
|
494
|
+
systemPromptTokens,
|
|
495
|
+
});
|
|
347
496
|
const recallQuery = typeof input === 'string' ? input : '';
|
|
348
497
|
const slotOf = new Map((layers ?? []).map((l) => [
|
|
349
498
|
l.id,
|
|
350
499
|
l.slot,
|
|
351
500
|
]));
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
// Apply re-render requests collected from the input-append pipeline.
|
|
361
|
-
if (rerenderRequests.length > 0) {
|
|
362
|
-
const rerendered = await baseCtx.harness.executeRerender(rerenderRequests, layers, baseCtx, budgetMap, recallQuery);
|
|
363
|
-
recallResults = mergeRecallResults([
|
|
364
|
-
...recallResults,
|
|
365
|
-
], rerendered, slotOf);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
501
|
+
const recallResults = await gatherRecallResults({
|
|
502
|
+
ctx: baseCtx,
|
|
503
|
+
layers,
|
|
504
|
+
recallQuery,
|
|
505
|
+
budgetMap,
|
|
506
|
+
slotOf,
|
|
507
|
+
rerenderRequests,
|
|
508
|
+
});
|
|
368
509
|
// Split recall output into the cache-stable anchor band and the live band,
|
|
369
510
|
// replaying pinned anchors and gathering any changes into one supersede
|
|
370
511
|
// message. Runs once, outside the retry loop, for the same reason recall does:
|
|
@@ -391,82 +532,25 @@ export async function executeLLM(step, input, ctx, layers) {
|
|
|
391
532
|
let steeringTail = [];
|
|
392
533
|
let cacheJudged = false;
|
|
393
534
|
while (retries <= MAX_STEERING_RETRIES) {
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
nonSystemHistory.push(item);
|
|
416
|
-
}
|
|
417
|
-
assembledItems = assembleView({
|
|
418
|
-
systemPromptItems: systemItems,
|
|
419
|
-
layerOutputItems: banded.anchorItems,
|
|
420
|
-
historyItems: nonSystemHistory,
|
|
421
|
-
liveLayerItems: banded.liveItems,
|
|
422
|
-
deltaItems: banded.deltaItems,
|
|
423
|
-
tailItems: steeringTail,
|
|
424
|
-
policy: viewPolicy,
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
else {
|
|
428
|
-
assembledItems =
|
|
429
|
-
projectedHistoryItems === rawHistoryItems
|
|
430
|
-
? rawHistoryItems
|
|
431
|
-
: [
|
|
432
|
-
...projectedHistoryItems,
|
|
433
|
-
];
|
|
434
|
-
}
|
|
435
|
-
const _serverTools = serverToolSpecs.length > 0 ? serverToolSpecs : undefined;
|
|
436
|
-
// Cancellation reaches INSIDE the model call, not just the step boundary:
|
|
437
|
-
// the caller's abort signal stops the stream and the tool-round loop mid
|
|
438
|
-
// flight, so `ctx.abort()` / `harness.cancel()` don't wait out a long
|
|
439
|
-
// generation before taking effect (spec 09, Cancellation item 2).
|
|
440
|
-
const signal = isContextImpl(baseCtx) ? baseCtx.abortSignal : undefined;
|
|
441
|
-
const request = resolvedTools
|
|
442
|
-
? {
|
|
443
|
-
model: resolvedModel,
|
|
444
|
-
items: assembledItems,
|
|
445
|
-
instructions: resolvedInstructions,
|
|
446
|
-
tools: resolvedTools,
|
|
447
|
-
params: step.params,
|
|
448
|
-
outputSchema: outputSchema,
|
|
449
|
-
emit: step.emit,
|
|
450
|
-
_serverTools,
|
|
451
|
-
ctx: baseCtx,
|
|
452
|
-
layers,
|
|
453
|
-
allowedToolNames,
|
|
454
|
-
nodeId: step.id,
|
|
455
|
-
parentSpan: baseCtx.span,
|
|
456
|
-
signal,
|
|
457
|
-
}
|
|
458
|
-
: {
|
|
459
|
-
model: resolvedModel,
|
|
460
|
-
items: assembledItems,
|
|
461
|
-
instructions: resolvedInstructions,
|
|
462
|
-
params: step.params,
|
|
463
|
-
outputSchema: outputSchema,
|
|
464
|
-
emit: step.emit,
|
|
465
|
-
_serverTools,
|
|
466
|
-
nodeId: step.id,
|
|
467
|
-
parentSpan: baseCtx.span,
|
|
468
|
-
signal,
|
|
469
|
-
};
|
|
535
|
+
const assembledItems = await assembleTurnItems({
|
|
536
|
+
ctx: baseCtx,
|
|
537
|
+
layers,
|
|
538
|
+
banded,
|
|
539
|
+
steeringTail,
|
|
540
|
+
viewPolicy,
|
|
541
|
+
});
|
|
542
|
+
const request = buildModelRequest({
|
|
543
|
+
step,
|
|
544
|
+
ctx: baseCtx,
|
|
545
|
+
layers,
|
|
546
|
+
resolvedModel,
|
|
547
|
+
resolvedInstructions,
|
|
548
|
+
resolvedTools,
|
|
549
|
+
allowedToolNames,
|
|
550
|
+
outputSchema,
|
|
551
|
+
serverToolSpecs,
|
|
552
|
+
assembledItems,
|
|
553
|
+
});
|
|
470
554
|
const response = await baseCtx.harness.callModel(request);
|
|
471
555
|
// Track tokens/cost for EVERY model call, including responses steering
|
|
472
556
|
// subsequently rejects (Deny throw / Guide retry below) — the spend is
|
|
@@ -522,33 +606,14 @@ export async function executeLLM(step, input, ctx, layers) {
|
|
|
522
606
|
continue;
|
|
523
607
|
}
|
|
524
608
|
}
|
|
525
|
-
|
|
526
|
-
for (const item of response.items) {
|
|
527
|
-
baseCtx.itemLog.append(item);
|
|
528
|
-
if (isFunctionCall(item)) {
|
|
529
|
-
toolCalls.push(item);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
const meta = {
|
|
533
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
534
|
-
usage: response.usage,
|
|
535
|
-
cost: response.cost,
|
|
536
|
-
responseItems: response.items,
|
|
537
|
-
};
|
|
538
|
-
if (isMutableContext(baseCtx)) {
|
|
539
|
-
baseCtx.lastStepMeta = meta;
|
|
540
|
-
}
|
|
541
|
-
commitLayerUsage(baseCtx, computeLayerUsage({
|
|
609
|
+
recordTurnOutcome({
|
|
542
610
|
ctx: baseCtx,
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
serveInfo: banded.serveInfo,
|
|
550
|
-
epoch: banded.epoch,
|
|
551
|
-
}));
|
|
611
|
+
response,
|
|
612
|
+
resolvedModel,
|
|
613
|
+
resolvedInstructions,
|
|
614
|
+
resolvedTools,
|
|
615
|
+
banded,
|
|
616
|
+
});
|
|
552
617
|
if (hasLayers) {
|
|
553
618
|
await baseCtx.harness.storeLayers(layers, response, baseCtx);
|
|
554
619
|
}
|
|
@@ -642,7 +707,7 @@ export async function executeSpawn(step, input, ctx, executeStep, opts) {
|
|
|
642
707
|
// Build unified tool set for child from its step tree + layers, plus the
|
|
643
708
|
// parent's unified tools. Sub-agents that read tools dynamically (via
|
|
644
709
|
// `ctx.unifiedTools`) otherwise lose the harness tool pool at the spawn
|
|
645
|
-
// boundary — a child whose `
|
|
710
|
+
// boundary — a child whose `callModel` resolves `tools` from context would
|
|
646
711
|
// see nothing. Inheriting the parent's tools keeps the harness toolset
|
|
647
712
|
// available across spawns; the child's own step/layer tools take precedence
|
|
648
713
|
// on name collision (dedup keeps the first occurrence), and children that
|
|
@@ -737,7 +802,7 @@ function mergeLayers(existing, provided) {
|
|
|
737
802
|
* Unlike spawn, provide does not create a new itemLog or clone state.
|
|
738
803
|
* Events flow through to the parent in real-time.
|
|
739
804
|
*/
|
|
740
|
-
export async function
|
|
805
|
+
export async function executeWithContext(step, input, ctx, executeStep) {
|
|
741
806
|
const baseCtx = frameworkCast(ctx);
|
|
742
807
|
const previousLayers = baseCtx.layers;
|
|
743
808
|
const previousUnifiedTools = baseCtx.unifiedTools;
|
|
@@ -792,7 +857,7 @@ async function consumeToolGenerator(params) {
|
|
|
792
857
|
event: next.value,
|
|
793
858
|
},
|
|
794
859
|
});
|
|
795
|
-
// A direct `
|
|
860
|
+
// A direct `invokeTool` has no model call id — the step id keys its region.
|
|
796
861
|
emitToolUi({
|
|
797
862
|
ctx: params.ctx,
|
|
798
863
|
tool: params.tool,
|
|
@@ -803,20 +868,21 @@ async function consumeToolGenerator(params) {
|
|
|
803
868
|
});
|
|
804
869
|
}
|
|
805
870
|
}
|
|
806
|
-
export async function
|
|
871
|
+
export async function executeInvokeTool(step, input, ctx, harness, layers) {
|
|
807
872
|
const baseCtx = frameworkCast(ctx);
|
|
808
873
|
const args = step.args ? Object.assign({}, input, step.args) : input;
|
|
809
|
-
const parseResult = step.tool.input
|
|
874
|
+
const parseResult = await validateSchema(step.tool.input, args);
|
|
810
875
|
if (!parseResult.success) {
|
|
811
876
|
throw new NoeticErrorImpl({
|
|
812
877
|
kind: 'step_failed',
|
|
813
878
|
stepId: step.id,
|
|
814
|
-
cause: new Error(`Tool input validation failed: ${parseResult.
|
|
879
|
+
cause: new Error(`Tool input validation failed: ${parseResult.zodError.message}`),
|
|
815
880
|
retriesExhausted: false,
|
|
816
881
|
});
|
|
817
882
|
}
|
|
883
|
+
const parsedArgs = parseResult.value;
|
|
818
884
|
if (layers && layers.length > 0) {
|
|
819
|
-
const decision = await harness.beforeToolCall(layers, step.tool.name,
|
|
885
|
+
const decision = await harness.beforeToolCall(layers, step.tool.name, parsedArgs, baseCtx);
|
|
820
886
|
if (decision.action !== SteeringAction.Allow) {
|
|
821
887
|
throw new NoeticErrorImpl({
|
|
822
888
|
kind: 'steering_denied',
|
|
@@ -829,18 +895,18 @@ export async function executeTool(step, input, ctx, harness, layers) {
|
|
|
829
895
|
tool: step.tool,
|
|
830
896
|
callId: step.id,
|
|
831
897
|
phase: 'call',
|
|
832
|
-
args:
|
|
898
|
+
args: parsedArgs,
|
|
833
899
|
});
|
|
834
900
|
try {
|
|
835
901
|
const toolCtx = buildToolExecutionContext(baseCtx, harness);
|
|
836
|
-
const execResult = step.tool.execute(
|
|
902
|
+
const execResult = step.tool.execute(parsedArgs, toolCtx);
|
|
837
903
|
const result = isAsyncGenerator(execResult)
|
|
838
904
|
? await consumeToolGenerator({
|
|
839
905
|
generator: execResult,
|
|
840
906
|
stepId: step.id,
|
|
841
907
|
tool: step.tool,
|
|
842
908
|
ctx: baseCtx,
|
|
843
|
-
args:
|
|
909
|
+
args: parsedArgs,
|
|
844
910
|
})
|
|
845
911
|
: await execResult;
|
|
846
912
|
emitToolUi({
|
|
@@ -848,7 +914,7 @@ export async function executeTool(step, input, ctx, harness, layers) {
|
|
|
848
914
|
tool: step.tool,
|
|
849
915
|
callId: step.id,
|
|
850
916
|
phase: 'result',
|
|
851
|
-
args:
|
|
917
|
+
args: parsedArgs,
|
|
852
918
|
output: result,
|
|
853
919
|
});
|
|
854
920
|
return frameworkCast(result);
|
|
@@ -859,7 +925,7 @@ export async function executeTool(step, input, ctx, harness, layers) {
|
|
|
859
925
|
tool: step.tool,
|
|
860
926
|
callId: step.id,
|
|
861
927
|
phase: 'error',
|
|
862
|
-
args:
|
|
928
|
+
args: parsedArgs,
|
|
863
929
|
error: e,
|
|
864
930
|
});
|
|
865
931
|
if (e instanceof NoeticErrorImpl) {
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Control-flow step handlers:
|
|
2
|
+
* Control-flow step handlers: conditional, inParallel, loop, schedule.
|
|
3
3
|
*/
|
|
4
|
-
import type { Context, ExecuteStepFn,
|
|
4
|
+
import type { Context, ExecuteStepFn, StepConditional, StepInParallel, StepLoop, StepSchedule } from '@noetic-tools/types';
|
|
5
5
|
import type { ItemSchemaRegistry, LayerStateStore } from './action-deps.js';
|
|
6
|
-
export declare function
|
|
6
|
+
export declare function executeConditional<TContext, I, O>(step: StepConditional<TContext, I, O>, input: I, ctx: Context<TContext>, executeStep: ExecuteStepFn): Promise<O>;
|
|
7
7
|
/** Layer-boundary wiring supplied by the interpreter (see `buildSpawnOpts`). */
|
|
8
8
|
export interface ExecuteForkOpts {
|
|
9
9
|
layerStore?: LayerStateStore;
|
|
10
10
|
itemSchemas?: ItemSchemaRegistry;
|
|
11
11
|
}
|
|
12
|
-
export declare function
|
|
12
|
+
export declare function executeInParallel<TContext, I, O>(step: StepInParallel<TContext, I, O>, input: I, ctx: Context<TContext>, rawExecuteStep: ExecuteStepFn, opts?: ExecuteForkOpts): Promise<O>;
|
|
13
13
|
export declare function executeLoop<TContext, I, O>(step: StepLoop<TContext, I, O>, input: I, ctx: Context<TContext>, executeStep: ExecuteStepFn): Promise<O>;
|
|
14
14
|
/**
|
|
15
|
-
* Executes
|
|
16
|
-
* woken early by `
|
|
15
|
+
* Executes a `schedule` step: runs the body step forever, paced by `ms ± jitter`,
|
|
16
|
+
* woken early by `inbox`. Throws `cancelled` when the context is aborted.
|
|
17
17
|
*
|
|
18
18
|
* On body throw, behavior depends on `onError`:
|
|
19
|
-
* - `'continue'` (default): emit `
|
|
19
|
+
* - `'continue'` (default): emit `schedule.iteration.error` span event and proceed
|
|
20
20
|
* to the park step as if no error occurred.
|
|
21
21
|
* - `'fail'`: re-throw, terminating the operator.
|
|
22
22
|
*
|
|
23
|
-
* The body's output is discarded — `
|
|
23
|
+
* The body's output is discarded — `schedule` runs forever and does not accumulate
|
|
24
24
|
* iteration outputs. Only ever returns by throwing on cancellation or `fail`.
|
|
25
25
|
*/
|
|
26
|
-
export declare function
|
|
26
|
+
export declare function executeSchedule<TContext, I, O>(step: StepSchedule<TContext, I, O>, input: I, ctx: Context<TContext>, executeStep: ExecuteStepFn): Promise<O>;
|