Class: ToolRegistry
A mutable, turn-scoped collection of @nhtio/adk!Tool instances.
Remarks
Each TurnRunner.run() call constructs a fresh ToolRegistry from the runner's configured baseline tools, so middleware edits are isolated to the current turn and cannot bleed across concurrent or subsequent turns.
Tool instances are immutable, so all() returns a fresh array without deep-cloning.
register() throws @nhtio/adk!E_TOOL_ALREADY_REGISTERED if a tool with the same name is already present — pass overwrite: true to replace it explicitly.
Constructors
Constructor
new ToolRegistry(tools?: Tool<SpooledArtifact>[]): ToolRegistry;Parameters
| Parameter | Type | Description |
|---|---|---|
tools? | Tool<SpooledArtifact>[] | Optional initial tools. Insertion order is preserved. Duplicate names throw @nhtio/adk!E_TOOL_ALREADY_REGISTERED — ensure each tool has a unique name. |
Returns
ToolRegistry
Throws
@nhtio/adk!E_TOOL_ALREADY_REGISTERED when two tools in tools share a name.
Methods
all()
all(): Tool<SpooledArtifact>[];Returns a fresh array of all registered tools in insertion order.
Returns
Remarks
Since @nhtio/adk!Tool instances are immutable, no deep-cloning is needed.
bindContext()
bindContext(ctx: DispatchContext): () => void;Binds this registry to a @nhtio/adk!DispatchContext so that pruneEphemeral runs automatically when the context is acked.
Parameters
| Parameter | Type | Description |
|---|---|---|
ctx | DispatchContext | The execution context whose ack event should trigger pruning. |
Returns
An unsubscribe function — calling it before ctx.ack() prevents pruning. Rarely useful outside of tests.
() => void
Remarks
The handler does NOT fire on @nhtio/adk!DispatchContext.nack — failed executor runs leave any forged tools in place so the consumer can inspect what was registered when debugging the failure. Subscriptions are short-lived and die with the context regardless.
Forgetting this call after merging in Subclass.forgeTools(ctx) output means ephemeral tools accumulate across executor invocations, and subsequent forgeTools(ctx) calls in later iterations will see a stale callId enum that excludes new tool calls. The plan-documented pattern is:
const executor: DispatchExecutorFn = async (ctx) => {
const forged = SpooledArtifact.forgeTools(ctx)
const merged = ToolRegistry.merge([main, forged])
main.bindContext(ctx)
const result = await llm.invoke({ tools: merged.all(), ... })
ctx.ack()
}See
get()
get(name: string):
| Tool<SpooledArtifact>
| undefined;Returns the tool registered under name, or undefined if not present.
Parameters
| Parameter | Type | Description |
|---|---|---|
name | string | The tool name to look up. |
Returns
| Tool<SpooledArtifact> | undefined
has()
has(name: string): boolean;Returns true if a tool with the given name is registered.
Parameters
| Parameter | Type | Description |
|---|---|---|
name | string | The tool name to test. |
Returns
boolean
pruneEphemeral()
pruneEphemeral(): void;Removes every tool whose @nhtio/adk!Tool.ephemeral flag is true.
Returns
void
Remarks
Synchronous and idempotent — calling it twice in a row is a no-op the second time. The canonical caller is ToolRegistry.bindContext, which schedules this method to run at @nhtio/adk!DispatchContext.ack. Non-ephemeral tools are left untouched.
register()
register(tool: Tool, overwrite?: boolean): void;Adds a tool to the registry.
Parameters
| Parameter | Type | Description |
|---|---|---|
tool | Tool | The tool to register. |
overwrite? | boolean | When true, silently replaces an existing tool with the same name. Defaults to false. |
Returns
void
Throws
@nhtio/adk!E_TOOL_ALREADY_REGISTERED when a tool with the same name is already registered and overwrite is not true.
unregister()
unregister(name: string): void;Removes the tool with the given name from the registry.
Parameters
| Parameter | Type | Description |
|---|---|---|
name | string | The name of the tool to remove. |
Returns
void
Remarks
No-ops if no tool with that name is registered.
isToolRegistry()
static isToolRegistry(value: unknown): value is ToolRegistry;Returns true if value is a ToolRegistry instance.
Parameters
| Parameter | Type | Description |
|---|---|---|
value | unknown | The value to test. |
Returns
value is ToolRegistry
true when value is a ToolRegistry instance.
merge()
static merge(registries: ToolRegistry[], options?: MergeOptions): ToolRegistry;Combines multiple ToolRegistry instances into a fresh registry without mutating any input.
Parameters
| Parameter | Type | Description |
|---|---|---|
registries | ToolRegistry[] | Registries to merge, in priority order (left-to-right insertion). |
options? | MergeOptions | Merge-level collision policy. Defaults to { onCollision: 'throw' }. |
Returns
ToolRegistry
A fresh ToolRegistry containing the resolved union of all inputs.
Remarks
Iteration is left-to-right across registries and then in each registry's insertion order. Collisions are resolved by consulting the incoming tool's @nhtio/adk!Tool.onCollision first:
'replace'(per-tool): the incoming tool wins, replacing the existing entry.'keep'(per-tool): the existing entry wins; the incoming tool is dropped.'throw'(per-tool, the default): fall back to the merge-leveloptions.onCollision.
The merge-level options.onCollision defaults to 'throw', which mirrors register.
The result is a brand-new registry; no input is mutated and no event subscription is propagated. Each Tool's ephemeral flag carries through unchanged — the flag lives on the tool, not the registry, so bindContext(ctx) on the merged registry will prune the forged tools as expected.
Throws
@nhtio/adk!E_TOOL_ALREADY_REGISTERED when the resolved collision policy is 'throw' and a collision occurs.