import { Registry } from "./registry"; import { ENCODE_METHOD, DECODE_METHOD } from "../utils/encoder_symbols"; import type { Media } from "./media"; import type { AdkEncodableSnapshot } from "./encodable"; import type { Schema, Description } from '@nhtio/validation'; import type { DispatchContext } from "../contracts/dispatch_context"; import type { SpooledArtifact, SpooledArtifactConstructor } from "./spooled_artifact"; /** * A zero-arg function that returns the {@link @nhtio/adk!SpooledArtifactConstructor} the consumer should * use when wrapping this tool's serialised output into a `ToolCall.results` field. * * @remarks * Why a resolver (and not the constructor itself)? `tool.ts` participates in a module-load * cycle with `spooled_artifact.ts` and `artifact_tool.ts` (`ArtifactTool extends Tool` closes * the loop). Any eager value-level reference to `SpooledArtifact` in `tool.ts` would crash the * cycle with a TDZ error. A resolver lets `tool.ts` validate "is a function" at module-load * time and defer the actual constructor check to validate-time (which always runs after every * module body has executed). Wrap-sites invoke `tool.artifactConstructor?.() ?? SpooledArtifact` * to obtain the final constructor. */ export type ArtifactConstructorResolver = () => SpooledArtifactConstructor; /** * The execution function for a {@link Tool}. * * @remarks * Receives the raw arguments passed to the executor, the active {@link @nhtio/adk!DispatchContext}, and the * tool's metadata registry. * * Return shapes: * - `string` / `Uint8Array` — opaque serialised output. The ADK does not persist the bytes * itself; the consumer's executor middleware is responsible for storing them and wrapping * them via `tool.artifactConstructor?.() ?? SpooledArtifact` when assembling the `ToolCall` * record. * - {@link @nhtio/adk!SpooledArtifact} — a pre-built, reader-backed result. Return this when * the handler has streamed bytes into storage and wrapped the reader; the consumer passes it * through without materialising or re-spooling it. Its concrete class determines the forged * `artifact_*` query tools. * - {@link @nhtio/adk!Media} / `Media[]` — explicit-modality silo. Bypasses * {@link Tool.artifactConstructor} — the handler returns the final result shape directly. * The LLM battery renders each `Media` as a provider-specific content block. */ export type ToolHandler = (args: unknown, ctx: DispatchContext, meta: Registry) => string | Uint8Array | SpooledArtifact | Media | Media[] | Promise; /** * Plain input object supplied to {@link Tool} at construction time. * * @typeParam A - The {@link @nhtio/adk!SpooledArtifact} subtype used to wrap this tool's results when * the consumer assembles a `ToolCall.results` field. Defaults to {@link @nhtio/adk!SpooledArtifact} * (plain text). Tools producing JSON output should set this to `SpooledJsonArtifact`; tools * producing markdown should set it to `SpooledMarkdownArtifact`; consumers can also pass a * custom subclass. */ export interface RawTool { /** Unique identifier used in LLM tool definitions. Recommend lowercase snake_case. */ name: string; /** Human-readable description passed to the model to explain what the tool does. */ description: string; /** @nhtio/validation schema for the tool's input arguments. Annotate with `.description()`, `.note()`, `.example()` etc. to produce rich LLM tool definitions via `.describe()`. */ inputSchema: Schema; /** Execution function. Not exposed as a public property — invoke via `executor()`. */ handler: ToolHandler; /** * Zero-arg resolver returning the {@link @nhtio/adk!SpooledArtifactConstructor} the consumer should use * when wrapping this tool's serialised output into a `ToolCall.results` field. Optional — * when omitted, wrap-sites fall back to {@link @nhtio/adk!SpooledArtifact} (plain text). * * @remarks * Recommended call shape: `artifactConstructor: () => SpooledJsonArtifact`. The closure is * the indirection that lets `tool.ts` validate this field without eagerly importing * `SpooledArtifact` (which would crash the `tool.ts ↔ spooled_artifact.ts ↔ artifact_tool.ts` * module-load cycle). At validate time the schema invokes the resolver and verifies its * return value is a `SpooledArtifact`-derived constructor — wrong-shape resolvers throw * {@link @nhtio/adk!E_INVALID_INITIAL_TOOL_VALUE}. * * Wrap-sites (storage batteries, scripted executors) read the constructor via * `tool.artifactConstructor?.() ?? SpooledArtifact`. */ artifactConstructor?: ArtifactConstructorResolver; /** Optional arbitrary metadata for this tool (e.g. RBAC scopes, feature flags). Defaults to `{}`. Stored in a {@link @nhtio/adk!Registry} for dot-path access. */ meta?: Record; /** * When `true`, marks this tool as owned by a specific {@link @nhtio/adk!DispatchContext} so that * `ToolRegistry.pruneEphemeral()` will drop it at ctx-completion. * * @remarks * The flag is advisory at the `Tool` level — registries decide what to do with it. The canonical * producer of ephemeral tools is `SpooledArtifact.forgeTools(ctx)`, which sets this to `true` * on every artifact-query tool it emits. * * @defaultValue `false` */ ephemeral?: boolean; /** * When `true`, declares that this tool's output should be treated as **trusted developer/user * intent** rather than as untrusted third-party text when surfaced to the model. * * @remarks * LLM batteries read this flag per call when rendering tool-call results. The default * untrusted envelope (e.g. `` in the OpenAI Chat Completions battery) is the * secure-by-default treatment for arbitrary tool output. A tool whose output is authored by the * user or operator (Q&A tools surfacing user-authored answers, human-in-the-loop approval * gates, feedback-collection tools, configuration tools returning developer-authored * constants) sets this to `true` so the LLM battery routes the result through its trusted * envelope (`` in the OpenAI Chat Completions battery). * * Trust is a property of the tool's output, not a property of how a particular battery is * wired — putting the flag here means the trust signal travels with the tool wherever it is * registered, no per-battery string lists, no name-matching to fail-open on typos. * * @defaultValue `false` */ trusted?: boolean; /** * Self-declared merge collision policy. Honoured by `ToolRegistry.merge` (NOT by * `ToolRegistry.register`) when this tool collides with another of the same name. * * @remarks * - `'throw'` (default): defer to the merge-level `options.onCollision`. If that is also * `'throw'`, the merge raises `E_TOOL_ALREADY_REGISTERED`. This matches the default behaviour * of `ToolRegistry.register`. * - `'replace'`: this tool always wins the collision, regardless of the merge-level option. * - `'keep'`: this tool always loses to any previously-registered tool of the same name. * * Forged artifact-query tools set this to `'replace'` so that merging multiple * `Subclass.forgeTools(ctx)` outputs (whose base-method tools overlap by name) resolves * silently — the descriptors, snapshot, and handler behaviour are interchangeable across * subclasses, so replacement is a behavioural no-op. * * @defaultValue `'throw'` */ onCollision?: 'throw' | 'replace' | 'keep'; } /** * A tool definition that serves as the single source of truth for a callable tool: its name, * description, input schema, execution handler, and the {@link @nhtio/adk!SpooledArtifact} subclass that * wraps its serialised output. * * @typeParam A - The {@link @nhtio/adk!SpooledArtifact} subtype this tool's results should be wrapped in. * Defaults to {@link @nhtio/adk!SpooledArtifact}. * * @remarks * The `inputSchema` is a `@nhtio/validation` schema. It is used at runtime to validate incoming * arguments before the handler is called, and its `.describe()` output provides all the metadata * needed to build a provider-specific LLM tool definition — annotate the schema with * `.description()`, `.note()`, `.example()` etc. once, and that information is available in both * contexts without duplication. * * The handler is private — invoke it only through `executor(ctx)` which validates args, fires * observability events (with a stable `callId` derived from the tool name and arguments), and * wraps handler errors in {@link @nhtio/adk!E_TOOL_DOWNSTREAM_ERROR}. The handler returns serialised bytes * (`string | Uint8Array`); persistence is the consumer's responsibility. * * `artifactConstructor` is the {@link @nhtio/adk!SpooledArtifact} subclass the consumer should use when * wrapping the handler's output into a `ToolCall.results` field. The author declares it once * on the tool instance; the consumer reads it when assembling persisted records. */ export declare class Tool { #private; /** * Validator schema that accepts a {@link RawTool} object. * * @remarks * Reusable fragment for any schema that needs to validate or nest a tool entry * (e.g. `TurnRunnerConfig.tools`). */ static schema: import("@nhtio/validation").ObjectSchema>; /** * Returns `true` if `value` is a {@link Tool} instance. * * @param value - The value to test. * @returns `true` when `value` is a {@link Tool} instance. */ static isTool(value: unknown): value is Tool; /** The tool's unique name, as exposed to the model in the tool definition. */ readonly name: string; /** Human/model-facing description of what the tool does. */ readonly description: string; /** Validation schema for the tool's arguments; also drives the generated parameter definition. */ readonly inputSchema: Schema; /** Resolver for the artifact constructor used to wrap the handler's output, if any. */ readonly artifactConstructor: ArtifactConstructorResolver | undefined; /** Arbitrary per-tool metadata registry, passed through to the handler. */ readonly meta: Registry; /** When `true`, the tool's results are not persisted to history (transient/one-shot). */ readonly ephemeral: boolean; /** When `true`, the tool's output is treated as trusted content by the LLM battery's envelopes. */ readonly trusted: boolean; /** How registration resolves a name clash in a {@link ToolRegistry}: throw, replace, or keep the existing. */ readonly onCollision: 'throw' | 'replace' | 'keep'; /** * @param raw - The raw tool input validated against `rawToolSchema`. * @throws {@link @nhtio/adk!E_INVALID_INITIAL_TOOL_VALUE} when `raw` does not satisfy the schema. */ constructor(raw: RawTool); /** * Validates `args` against the tool's input schema asynchronously. * * @remarks * Async to support schemas with external validators (e.g. database lookups, API calls). * A validation failure throws {@link @nhtio/adk!E_INVALID_TOOL_ARGS} — this indicates a programming error * in the tool call loop, not a downstream failure. * * @param args - The arguments to validate. * @returns The validated (and coerced) arguments. * @throws {@link @nhtio/adk!E_INVALID_TOOL_ARGS} when `args` does not satisfy the input schema. */ validate(args: unknown): Promise; /** * Returns a bound executor function for this tool against the given turn context. * * @remarks * The executor: (1) computes a stable `callId` as `sha256(canonicalStringify({tool, args}))` * over the **raw, pre-validation args**, (2) validates `args` via {@link Tool.validate}, * (3) emits `toolExecutionStart` on the context (with the computed `callId`), (4) calls the * handler, (5) emits `toolExecutionEnd` (with the same `callId`), (6) wraps any handler error * in {@link @nhtio/adk!E_TOOL_DOWNSTREAM_ERROR} before re-throwing. * * The handler usually returns serialised bytes (`string | Uint8Array`) — persistence is then the * consumer's concern, and {@link Tool.artifactConstructor} is the class a wrap-site uses when * wrapping those bytes into a `ToolCall.results` field. **A handler may instead return a * {@link @nhtio/adk!SpooledArtifact} it built itself** (streaming into storage and wrapping the * reader), in which case wrap-sites pass it through untouched and `artifactConstructor` does not * apply — the returned instance's own class is what artifact-tool forging reads. * * Pattern mirrors `Middleware.runner()` — call once per turn, reuse the returned function. * * @param ctx - The active turn context. Provides emit functions and turn ID. * @returns An async function `(args) => Promise`. * A `SpooledArtifact` is a result the HANDLER built (streamed to storage and wrapped itself); a * wrap-site passes it through unwrapped rather than re-spooling it. `string`/`Uint8Array` are the * opposite case — bytes the consumer must still store and wrap. */ executor(ctx: DispatchContext): (args: unknown) => Promise; /** * Returns a fully serialisable snapshot of this tool's definition. * * @remarks * The `inputSchema` property is the result of calling `.describe()` on the raw schema — a plain * object carrying all the annotation metadata (descriptions, notes, examples, types) without any * validator functions. Use this to build provider-specific LLM tool definitions. * * @returns `{ name, description, inputSchema }` where `inputSchema` is the schema description. */ describe(): { name: string; description: string; inputSchema: Description; }; /** * Serialise this Tool into an `@nhtio/encoder` snapshot. * * @remarks * Three halves with different serialisation strategies: * * - **Plain config** (`name`, `description`, `meta`, flags) — emitted verbatim. * - **`inputSchema`** — delegated to `@nhtio/validation`'s `encode()`, which captures the full schema * description plus its library version into a compact string; decode rebuilds the live `Schema`. * - **`handler` / `artifactConstructor`** — emitted as **functions**. The encoder's `FunctionSerializer` * serialises them by **source text** (`fn.toString()`). This is the sharp edge: a handler that * closes over `ctx`, service clients, or config loses those bindings on decode — only the source * survives, and the captured variables read back as `undefined`. A `.bind()`-ed or native handler * cannot be serialised at all and the encoder throws. Tools are rarely serialised in practice; when * they are, write handlers that reconstruct their dependencies inside the body rather than closing * over them. * * @returns A {@link RawTool}-shaped snapshot (with `inputSchema` as an encoded string). */ [ENCODE_METHOD](): AdkEncodableSnapshot; /** * Reconstruct a {@link Tool} from a {@link Tool.[ENCODE_METHOD]} snapshot. * * @remarks * Rebuilds the live `Schema` via `@nhtio/validation`'s `decode()`, then re-validates through the * constructor. The rehydrated `handler` carries only its source text — see * {@link Tool.[ENCODE_METHOD]} for the closure caveat. * * @param data - The snapshot produced by {@link Tool.[ENCODE_METHOD]}. * @returns A fully-validated {@link Tool}. */ static [DECODE_METHOD](data: AdkEncodableSnapshot): Tool; }