Skip to content
4 min read · 839 words

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

ts
new ToolRegistry(tools?: Tool<SpooledArtifact>[]): ToolRegistry;

Parameters

ParameterTypeDescription
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()

ts
all(): Tool<SpooledArtifact>[];

Returns a fresh array of all registered tools in insertion order.

Returns

Tool<SpooledArtifact>[]

Remarks

Since @nhtio/adk!Tool instances are immutable, no deep-cloning is needed.


bindContext()

ts
bindContext(ctx: DispatchContext): () => void;

Binds this registry to a @nhtio/adk!DispatchContext so that pruneEphemeral runs automatically when the context is acked.

Parameters

ParameterTypeDescription
ctxDispatchContextThe 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:

ts
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()

ts
get(name: string):
  | Tool<SpooledArtifact>
  | undefined;

Returns the tool registered under name, or undefined if not present.

Parameters

ParameterTypeDescription
namestringThe tool name to look up.

Returns

| Tool<SpooledArtifact> | undefined


has()

ts
has(name: string): boolean;

Returns true if a tool with the given name is registered.

Parameters

ParameterTypeDescription
namestringThe tool name to test.

Returns

boolean


pruneEphemeral()

ts
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()

ts
register(tool: Tool, overwrite?: boolean): void;

Adds a tool to the registry.

Parameters

ParameterTypeDescription
toolToolThe tool to register.
overwrite?booleanWhen 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()

ts
unregister(name: string): void;

Removes the tool with the given name from the registry.

Parameters

ParameterTypeDescription
namestringThe name of the tool to remove.

Returns

void

Remarks

No-ops if no tool with that name is registered.


isToolRegistry()

ts
static isToolRegistry(value: unknown): value is ToolRegistry;

Returns true if value is a ToolRegistry instance.

Parameters

ParameterTypeDescription
valueunknownThe value to test.

Returns

value is ToolRegistry

true when value is a ToolRegistry instance.


merge()

ts
static merge(registries: ToolRegistry[], options?: MergeOptions): ToolRegistry;

Combines multiple ToolRegistry instances into a fresh registry without mutating any input.

Parameters

ParameterTypeDescription
registriesToolRegistry[]Registries to merge, in priority order (left-to-right insertion).
options?MergeOptionsMerge-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-level options.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.