@noetic-tools/core 5.4.1 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/openrouter.js +1 -1
- package/dist/builders/tool-builder.d.ts +5 -1
- package/dist/builders/tool-builder.js +2 -0
- package/dist/harness/agent-harness.d.ts +3 -1
- package/dist/harness/agent-harness.js +11 -3
- package/dist/harness/model-call.js +3 -1
- package/dist/interpreter/execute-acp-agent.js +1 -1
- package/dist/interpreter/execute-action.js +46 -21
- package/dist/runtime/session-runner.js +4 -0
- package/package.json +3 -3
|
@@ -308,7 +308,7 @@ export async function executeToolCall(params) {
|
|
|
308
308
|
};
|
|
309
309
|
}
|
|
310
310
|
if (params.layers && params.layers.length > 0) {
|
|
311
|
-
const decision = await params.harness.beforeToolCall(params.layers, params.toolName, params.args, params.context);
|
|
311
|
+
const decision = await params.harness.beforeToolCall(params.layers, params.toolName, params.args, params.context, params.callId);
|
|
312
312
|
if (decision.action === SteeringAction.Deny) {
|
|
313
313
|
return {
|
|
314
314
|
output: `Tool call denied: ${decision.guidance ?? 'steering rule violation'}`,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FunctionCallItem, FunctionCallOutputItem, InferSchemaOutput, InputSchemaConfig, Item, ItemSchemaExtensions, StandardSchemaV1, Tool, ToolContextDeclaration, ToolExecutionContext, ToolResultExtensionItem, ToolUiDeclaration } from '@noetic-tools/types';
|
|
1
|
+
import type { FunctionCallItem, FunctionCallOutputItem, InferSchemaOutput, InputSchemaConfig, Item, ItemSchemaExtensions, StandardSchemaV1, Tool, ToolAcpDeclaration, ToolContextDeclaration, ToolExecutionContext, ToolResultExtensionItem, ToolUiDeclaration } from '@noetic-tools/types';
|
|
2
2
|
type ToolConfig<I extends StandardSchemaV1, O extends StandardSchemaV1> = {
|
|
3
3
|
name: string;
|
|
4
4
|
description: string;
|
|
@@ -19,6 +19,8 @@ type ToolConfig<I extends StandardSchemaV1, O extends StandardSchemaV1> = {
|
|
|
19
19
|
context?: ToolContextDeclaration;
|
|
20
20
|
/** Optional UI declaration — the runtime emits the rendered fragments at call/progress/result points. */
|
|
21
21
|
ui?: ToolUiDeclaration<I, O>;
|
|
22
|
+
/** Optional ACP presentation — how the call renders in an ACP client when the harness is served as an agent. */
|
|
23
|
+
acp?: ToolAcpDeclaration<I>;
|
|
22
24
|
} & InputSchemaConfig<I>;
|
|
23
25
|
type GeneratorToolConfig<I extends StandardSchemaV1, E extends StandardSchemaV1, O extends StandardSchemaV1> = {
|
|
24
26
|
name: string;
|
|
@@ -41,6 +43,8 @@ type GeneratorToolConfig<I extends StandardSchemaV1, E extends StandardSchemaV1,
|
|
|
41
43
|
context?: ToolContextDeclaration;
|
|
42
44
|
/** Optional UI declaration — the runtime emits the rendered fragments at call/progress/result points. */
|
|
43
45
|
ui?: ToolUiDeclaration<I, O, InferSchemaOutput<E>>;
|
|
46
|
+
/** Optional ACP presentation — how the call renders in an ACP client when the harness is served as an agent. */
|
|
47
|
+
acp?: ToolAcpDeclaration<I>;
|
|
44
48
|
} & InputSchemaConfig<I>;
|
|
45
49
|
/**
|
|
46
50
|
* Creates a typed Tool with Standard Schema inference for input and output.
|
|
@@ -39,6 +39,7 @@ export function tool(config) {
|
|
|
39
39
|
needsApproval: config.needsApproval,
|
|
40
40
|
context: config.context,
|
|
41
41
|
ui: config.ui,
|
|
42
|
+
acp: config.acp,
|
|
42
43
|
};
|
|
43
44
|
}
|
|
44
45
|
/**
|
|
@@ -61,6 +62,7 @@ export function toolWithGenerator(config) {
|
|
|
61
62
|
needsApproval: config.needsApproval,
|
|
62
63
|
context: config.context,
|
|
63
64
|
ui: config.ui,
|
|
65
|
+
acp: config.acp,
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
68
|
//#endregion
|
|
@@ -271,6 +271,8 @@ export declare class AgentHarness<TParams extends Record<string, unknown> = Reco
|
|
|
271
271
|
threadId?: string;
|
|
272
272
|
resourceId?: string;
|
|
273
273
|
contextLayers?: ContextLayer[];
|
|
274
|
+
/** Additional layers appended AFTER the effective set — never replaces it. */
|
|
275
|
+
extraContextLayers?: ContextLayer[];
|
|
274
276
|
/**
|
|
275
277
|
* Initial cwd for the new context. When set, takes precedence over both
|
|
276
278
|
* the parent snapshot and the harness root cwd — used by worktree
|
|
@@ -393,7 +395,7 @@ export declare class AgentHarness<TParams extends Record<string, unknown> = Reco
|
|
|
393
395
|
createSpan(name: string, parent: Span | null): Span;
|
|
394
396
|
getLayerState<T>(executionId: string, layerId: string): T | undefined;
|
|
395
397
|
setLayerState<T>(executionId: string, layerId: string, state: T): void;
|
|
396
|
-
beforeToolCall(layers: ContextLayer[], toolName: string, toolArgs: unknown, ctx: Context): Promise<SteeringDecision>;
|
|
398
|
+
beforeToolCall(layers: ContextLayer[], toolName: string, toolArgs: unknown, ctx: Context, callId?: string): Promise<SteeringDecision>;
|
|
397
399
|
afterModelCall(layers: ContextLayer[], response: LLMResponse, ctx: Context): Promise<SteeringDecision>;
|
|
398
400
|
projectHistory(layers: ContextLayer[], items: ReadonlyArray<Item>, ctx: Context): Promise<ReadonlyArray<Item>>;
|
|
399
401
|
runAppendPipeline(layers: ContextLayer[], items: Item[], ctx: Context): Promise<{
|
|
@@ -439,6 +439,7 @@ export class AgentHarness {
|
|
|
439
439
|
resourceId: perTurnOptions.resourceId,
|
|
440
440
|
state: perTurnOptions.state,
|
|
441
441
|
contextLayers: perTurnOptions.contextLayers,
|
|
442
|
+
extraContextLayers: perTurnOptions.extraContextLayers,
|
|
442
443
|
_broadcaster: session.runner.broadcaster,
|
|
443
444
|
});
|
|
444
445
|
const ext = frameworkCast(ctx);
|
|
@@ -596,8 +597,14 @@ export class AgentHarness {
|
|
|
596
597
|
const resolved = opts ?? {};
|
|
597
598
|
// `ContextImpl` takes the layers as `layers`, so the option spelling is
|
|
598
599
|
// dropped from `rest` rather than spread through under the wrong name.
|
|
599
|
-
const { contextLayers, cwdInit, ...rest } = resolved;
|
|
600
|
-
const
|
|
600
|
+
const { contextLayers, extraContextLayers, cwdInit, ...rest } = resolved;
|
|
601
|
+
const baseLayers = contextLayers ?? this._contextLayers;
|
|
602
|
+
const effectiveLayers = extraContextLayers && extraContextLayers.length > 0
|
|
603
|
+
? [
|
|
604
|
+
...(baseLayers ?? []),
|
|
605
|
+
...extraContextLayers,
|
|
606
|
+
]
|
|
607
|
+
: baseLayers;
|
|
601
608
|
const itemSchemas = buildItemSchemaRegistry({
|
|
602
609
|
base: this.itemSchemas,
|
|
603
610
|
layers: effectiveLayers,
|
|
@@ -948,7 +955,7 @@ export class AgentHarness {
|
|
|
948
955
|
setLayerState(executionId, layerId, state) {
|
|
949
956
|
this.layerStateStore.set(executionId, layerId, state);
|
|
950
957
|
}
|
|
951
|
-
async beforeToolCall(layers, toolName, toolArgs, ctx) {
|
|
958
|
+
async beforeToolCall(layers, toolName, toolArgs, ctx, callId) {
|
|
952
959
|
const hasHook = layers.some((l) => l.hooks.beforeToolCall);
|
|
953
960
|
if (!hasHook) {
|
|
954
961
|
return {
|
|
@@ -959,6 +966,7 @@ export class AgentHarness {
|
|
|
959
966
|
layers,
|
|
960
967
|
toolName,
|
|
961
968
|
toolArgs,
|
|
969
|
+
callId,
|
|
962
970
|
ctx: this.toExecCtx(ctx),
|
|
963
971
|
store: this.layerStateStore,
|
|
964
972
|
});
|
|
@@ -749,7 +749,8 @@ export class AgentHarnessModelCaller {
|
|
|
749
749
|
params.emitIfAllowed('tool_call_completed', {
|
|
750
750
|
name: fc.name,
|
|
751
751
|
callId: fc.callId,
|
|
752
|
-
error:
|
|
752
|
+
error: toolResult.error === true,
|
|
753
|
+
output: toolResult.output,
|
|
753
754
|
});
|
|
754
755
|
}
|
|
755
756
|
recordMalformedToolCall(params) {
|
|
@@ -776,6 +777,7 @@ export class AgentHarnessModelCaller {
|
|
|
776
777
|
name: fc.name,
|
|
777
778
|
callId: fc.callId,
|
|
778
779
|
error: true,
|
|
780
|
+
output: errorOutput,
|
|
779
781
|
});
|
|
780
782
|
}
|
|
781
783
|
handleToolRoundLimit(params) {
|
|
@@ -132,7 +132,7 @@ function buildSteerer(ctx, layers) {
|
|
|
132
132
|
return undefined;
|
|
133
133
|
}
|
|
134
134
|
return async (request) => {
|
|
135
|
-
const decision = await ctx.harness.beforeToolCall(layers, request.toolCall.title ?? request.toolCall.toolCallId, request.toolCall.rawInput, ctx);
|
|
135
|
+
const decision = await ctx.harness.beforeToolCall(layers, request.toolCall.title ?? request.toolCall.toolCallId, request.toolCall.rawInput, ctx, request.toolCall.toolCallId);
|
|
136
136
|
if (decision.action === SteeringAction.Allow) {
|
|
137
137
|
return undefined;
|
|
138
138
|
}
|
|
@@ -386,26 +386,11 @@ function buildModelRequest(params) {
|
|
|
386
386
|
const { step, ctx, layers } = params;
|
|
387
387
|
const _serverTools = params.serverToolSpecs.length > 0 ? params.serverToolSpecs : undefined;
|
|
388
388
|
const signal = isContextImpl(ctx) ? ctx.abortSignal : undefined;
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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 {
|
|
389
|
+
// One construction for both shapes: the tool fields vary, but `ctx` (which
|
|
390
|
+
// carries the broadcaster — without it a tool-less call streams NOTHING to
|
|
391
|
+
// getFullStream/getTextStream) and `layers` must be attached either way.
|
|
392
|
+
// The two hand-maintained branches this replaces had drifted exactly there.
|
|
393
|
+
const base = {
|
|
409
394
|
model: params.resolvedModel,
|
|
410
395
|
items: params.assembledItems,
|
|
411
396
|
instructions: params.resolvedInstructions,
|
|
@@ -414,10 +399,20 @@ function buildModelRequest(params) {
|
|
|
414
399
|
outputJsonSchema: params.step.outputJsonSchema,
|
|
415
400
|
emit: step.emit,
|
|
416
401
|
_serverTools,
|
|
402
|
+
ctx,
|
|
403
|
+
layers,
|
|
417
404
|
nodeId: step.id,
|
|
418
405
|
parentSpan: ctx.span,
|
|
419
406
|
signal,
|
|
420
407
|
};
|
|
408
|
+
if (params.resolvedTools) {
|
|
409
|
+
return {
|
|
410
|
+
...base,
|
|
411
|
+
tools: params.resolvedTools,
|
|
412
|
+
allowedToolNames: params.allowedToolNames,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
return base;
|
|
421
416
|
}
|
|
422
417
|
/**
|
|
423
418
|
* Records a finished model response on the context: appends response items to
|
|
@@ -881,9 +876,37 @@ export async function executeInvokeTool(step, input, ctx, harness, layers) {
|
|
|
881
876
|
});
|
|
882
877
|
}
|
|
883
878
|
const parsedArgs = parseResult.value;
|
|
879
|
+
// The started event precedes the gate so a parked call (e.g. one awaiting an
|
|
880
|
+
// external permission decision) is visible as pending rather than invisible
|
|
881
|
+
// until approved — the same ordering the model-driven path guarantees.
|
|
882
|
+
const broadcaster = getBroadcaster(baseCtx);
|
|
883
|
+
const agentName = baseCtx.harness.config.name;
|
|
884
|
+
emitFrameworkEvent({
|
|
885
|
+
broadcaster,
|
|
886
|
+
agentName,
|
|
887
|
+
eventType: 'tool_call_started',
|
|
888
|
+
data: {
|
|
889
|
+
name: step.tool.name,
|
|
890
|
+
callId: step.id,
|
|
891
|
+
},
|
|
892
|
+
});
|
|
893
|
+
const emitCompleted = (error, output) => {
|
|
894
|
+
emitFrameworkEvent({
|
|
895
|
+
broadcaster,
|
|
896
|
+
agentName,
|
|
897
|
+
eventType: 'tool_call_completed',
|
|
898
|
+
data: {
|
|
899
|
+
name: step.tool.name,
|
|
900
|
+
callId: step.id,
|
|
901
|
+
error,
|
|
902
|
+
output,
|
|
903
|
+
},
|
|
904
|
+
});
|
|
905
|
+
};
|
|
884
906
|
if (layers && layers.length > 0) {
|
|
885
|
-
const decision = await harness.beforeToolCall(layers, step.tool.name, parsedArgs, baseCtx);
|
|
907
|
+
const decision = await harness.beforeToolCall(layers, step.tool.name, parsedArgs, baseCtx, step.id);
|
|
886
908
|
if (decision.action !== SteeringAction.Allow) {
|
|
909
|
+
emitCompleted(true, `Tool call denied: ${decision.guidance ?? 'steering rule violation'}`);
|
|
887
910
|
throw new NoeticErrorImpl({
|
|
888
911
|
kind: 'steering_denied',
|
|
889
912
|
guidance: decision.guidance,
|
|
@@ -917,6 +940,7 @@ export async function executeInvokeTool(step, input, ctx, harness, layers) {
|
|
|
917
940
|
args: parsedArgs,
|
|
918
941
|
output: result,
|
|
919
942
|
});
|
|
943
|
+
emitCompleted(false, typeof result === 'string' ? result : JSON.stringify(result));
|
|
920
944
|
return frameworkCast(result);
|
|
921
945
|
}
|
|
922
946
|
catch (e) {
|
|
@@ -928,6 +952,7 @@ export async function executeInvokeTool(step, input, ctx, harness, layers) {
|
|
|
928
952
|
args: parsedArgs,
|
|
929
953
|
error: e,
|
|
930
954
|
});
|
|
955
|
+
emitCompleted(true, e instanceof Error ? e.message : String(e));
|
|
931
956
|
if (e instanceof NoeticErrorImpl) {
|
|
932
957
|
throw e;
|
|
933
958
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isNoeticError } from '@noetic-tools/types';
|
|
1
2
|
import { emitFrameworkEvent } from './broadcaster-utils.js';
|
|
2
3
|
import { EventBroadcaster } from './event-broadcaster.js';
|
|
3
4
|
import { MessageQueue } from './message-queue.js';
|
|
@@ -240,6 +241,9 @@ export class SessionRunner {
|
|
|
240
241
|
data: {
|
|
241
242
|
turnId,
|
|
242
243
|
reason: error.message,
|
|
244
|
+
// The typed kind ('cancelled', 'model_refused', …) so consumers can
|
|
245
|
+
// classify the abort without parsing the human-readable message.
|
|
246
|
+
errorKind: isNoeticError(error) ? error.noeticError.kind : undefined,
|
|
243
247
|
},
|
|
244
248
|
});
|
|
245
249
|
this.rejectWaiters(error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noetic-tools/core",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Step primitives, interpreter, runtime, and context layer contract for Noetic agents.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -68,8 +68,8 @@
|
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@noetic-tools/context": "^1.
|
|
72
|
-
"@noetic-tools/types": "^3.
|
|
71
|
+
"@noetic-tools/context": "^1.3.0",
|
|
72
|
+
"@noetic-tools/types": "^3.3.0",
|
|
73
73
|
"@openrouter/agent": "^0.6.0",
|
|
74
74
|
"zod": "^4.0.0"
|
|
75
75
|
},
|