{"version":3,"file":"spooled_artifact-Bw_7swIp.mjs","names":["#store","#name","#description","#inputSchema","#handler","#artifactConstructor","#meta","#ephemeral","#trusted","#onCollision","#tools","#hidden","#reader","#sizeHints"],"sources":["../src/lib/classes/registry.ts","../src/lib/utils/canonical_json.ts","../src/lib/contracts/spooled_artifact_constructor.ts","../src/lib/classes/tool.ts","../src/lib/classes/artifact_tool.ts","../src/lib/classes/tool_registry.ts","../src/lib/contracts/spool_reader.ts","../src/lib/contracts/reader_resolvers.ts","../src/lib/classes/spooled_artifact.ts"],"sourcesContent":["import { dset } from 'dset'\nimport { klona } from 'klona'\nimport { default as delve } from 'dlv'\nimport { isInstanceOf, isObject } from '../utils/guards'\nimport { ENCODE_METHOD, DECODE_METHOD } from '../utils/encoder_symbols'\nimport { E_INVALID_INITIAL_REGISTRY_VALUE } from '../exceptions/runtime'\nimport type { AdkEncodableSnapshot } from './encodable'\n\n/**\n * A controlled-mutation key-value store with dot-path access and deep-clone isolation.\n *\n * @remarks\n * The registry enforces a safe read/write contract: callers never hold a live reference into\n * the internal store. Every value that enters (`set`) or leaves (`get`, `all`) is deep-cloned\n * via `klona`, so mutations to a retrieved value cannot affect stored state and vice versa.\n *\n * Keys are dot-delimited paths (e.g. `\"user.profile.name\"`), resolved via `dlv` for reads and\n * `dset` for writes; intermediate objects are created automatically on write.\n */\nexport class Registry {\n  #store: Record<string, unknown>\n\n  /**\n   * @param initial - Optional plain object to seed the registry. Deep-cloned on construction.\n   * @throws {@link @nhtio/adk!E_INVALID_INITIAL_REGISTRY_VALUE} when `initial` is defined but not a plain object.\n   */\n  constructor(initial?: Record<string, unknown>) {\n    if ('undefined' !== typeof initial && !isObject(initial)) {\n      throw new E_INVALID_INITIAL_REGISTRY_VALUE()\n    }\n    this.#store = initial ? klona(initial) : {}\n  }\n\n  /**\n   * Returns `true` if `value` is a {@link Registry} instance.\n   *\n   * @remarks\n   * Uses {@link @nhtio/adk!isInstanceOf} for cross-realm safety.\n   *\n   * @param value - The value to test.\n   * @returns `true` when `value` is a {@link Registry} instance.\n   */\n  public static isRegistry(value: unknown): value is Registry {\n    return isInstanceOf(value, 'Registry', Registry)\n  }\n\n  /**\n   * Retrieves the value at `key`, returning `defaultValue` if the path is absent.\n   *\n   * @remarks\n   * The returned value is a deep clone — mutating it will not affect the stored state.\n   *\n   * @typeParam T - Expected type of the value at `key`.\n   * @param key - Dot-delimited path into the store (e.g. `\"user.name\"`).\n   * @param defaultValue - Fallback returned when the path resolves to `undefined`.\n   * @returns A deep clone of the stored value cast to `T`, or `defaultValue` when the path is absent.\n   */\n  get<T = unknown>(key: string, defaultValue?: T): T {\n    const cloned = klona(this.#store)\n    const value = delve(cloned, key)\n    return 'undefined' === typeof value ? (defaultValue as T) : (value as T)\n  }\n\n  /**\n   * Sets the value at `key`, creating intermediate objects as needed.\n   *\n   * @remarks\n   * The stored value is isolated from the caller — mutating `value` after this call will not\n   * affect what is held in the registry.\n   *\n   * @param key - Dot-delimited path into the store (e.g. `\"user.name\"`).\n   * @param value - Value to store at the path.\n   */\n  set(key: string, value: unknown): void {\n    dset(this.#store, key, value)\n  }\n\n  /**\n   * Returns `true` if the registry has a value at `key`, `false` otherwise.\n   *\n   * @remarks\n   * A key resolving to `undefined` is treated as absent — same convention as {@link Registry.get}'s\n   * `defaultValue` fallback. No clone is performed; this is a pure existence check.\n   *\n   * @param key - Dot-delimited path into the store (e.g. `\"user.name\"`).\n   * @returns `true` when the path resolves to a value other than `undefined`.\n   */\n  has(key: string): boolean {\n    return 'undefined' !== typeof delve(this.#store, key)\n  }\n\n  /**\n   * Returns all leaf dot-paths present in the registry.\n   *\n   * @remarks\n   * The store is deep-cloned before traversal. Plain objects are walked recursively with path\n   * segments joined by dots; arrays, primitives, `null`, and class instances are treated as leaves.\n   *\n   * @returns A string array of dot-delimited paths to leaf values in the store.\n   */\n  keys(): string[] {\n    const store = klona(this.#store)\n    const keys: string[] = []\n\n    const isPlainRecord = (value: unknown): value is Record<string, unknown> => {\n      if (!isObject(value)) return false\n      const prototype = Object.getPrototypeOf(value)\n      return prototype === Object.prototype || prototype === null\n    }\n\n    const walk = (value: unknown, segments: string[]): void => {\n      if (!isPlainRecord(value)) {\n        if (segments.length > 0) keys.push(segments.join('.'))\n        return\n      }\n\n      for (const [segment, child] of Object.entries(value)) {\n        walk(child, [...segments, segment])\n      }\n    }\n\n    walk(store, [])\n    return keys\n  }\n\n  /**\n   * Returns a deep clone of the entire store contents.\n   *\n   * @returns A plain object snapshot of all stored key-value pairs.\n   */\n  all(): Record<string, unknown> {\n    return klona(this.#store)\n  }\n\n  /**\n   * Serialise this Registry into an `@nhtio/encoder` snapshot.\n   *\n   * @remarks\n   * The snapshot is a deep clone of the store ({@link Registry.all}). Leaf values that are themselves\n   * registered encodable instances round-trip; anything the encoder cannot serialise throws at encode\n   * time (standard encoder behaviour). Round-trips via {@link Registry.[DECODE_METHOD]}.\n   *\n   * @returns A deep-cloned plain-object snapshot of the store.\n   */\n  [ENCODE_METHOD](): AdkEncodableSnapshot {\n    return this.all()\n  }\n\n  /**\n   * Reconstruct a {@link Registry} from a {@link Registry.[ENCODE_METHOD]} snapshot.\n   *\n   * @param data - The store snapshot produced by {@link Registry.[ENCODE_METHOD]}.\n   * @returns A fresh {@link Registry} seeded with the snapshot.\n   */\n  static [DECODE_METHOD](data: AdkEncodableSnapshot): Registry {\n    return new Registry(data as Record<string, unknown>)\n  }\n}\n","/**\n * Canonical `JSON.stringify` that sorts object keys recursively so that semantically-equal\n * objects produce identical strings.\n *\n * @remarks\n * Used wherever the ADK derives a stable identity from a structured value — for example,\n * `Tool.executor` computing the `callId` for `ToolExecutionStart`/`End` events, and the\n * `reportToolCall` executor helper computing the `checksum` field on `TurnToolCallContent`.\n * Both code paths hash `canonicalStringify({ tool, args })` so that argument key order does not\n * affect the resulting identifier.\n *\n * Arrays are serialised in their declared order (order is meaningful for an array). Object keys\n * are sorted with `Array.prototype.sort()`'s default lexicographic comparator.\n *\n * @param value - The value to serialise.\n * @returns A canonical JSON string representation of `value`.\n */\nexport function canonicalStringify(value: unknown): string {\n  if (value === null || typeof value !== 'object') return JSON.stringify(value)\n  if (Array.isArray(value)) return '[' + value.map((v) => canonicalStringify(v)).join(',') + ']'\n  const obj = value as Record<string, unknown>\n  const keys = Object.keys(obj).sort()\n  return '{' + keys.map((k) => JSON.stringify(k) + ':' + canonicalStringify(obj[k])).join(',') + '}'\n}\n","import { validator } from '@nhtio/validation'\nimport { passesSchema } from '../utils/validation'\nimport type { SpooledArtifact } from '../classes/spooled_artifact'\n\n/**\n * Constructor signature for any {@link @nhtio/adk!SpooledArtifact} (the class itself or a subclass).\n *\n * @remarks\n * Re-declared here at the contract level so consumers — and the `Tool.artifactConstructor`\n * resolver validator in particular — can talk about the constructor shape without value-importing\n * the {@link @nhtio/adk!SpooledArtifact} class (which would close the `tool.ts` ↔ `spooled_artifact.ts` ↔\n * `artifact_tool.ts` module cycle at load time and TDZ-crash `ArtifactTool extends Tool`).\n *\n * @typeParam A - The {@link @nhtio/adk!SpooledArtifact} subtype the constructor produces.\n */\nexport type SpooledArtifactConstructorLike<A extends SpooledArtifact = SpooledArtifact> = new (\n  ...args: any[]\n) => A\n\nconst ARTIFACT_METHODS = [\n  'head',\n  'tail',\n  'grep',\n  'cat',\n  'byteLength',\n  'lineCount',\n  'estimateTokens',\n] as const\n\n/**\n * Validator schema used to validate a {@link SpooledArtifactConstructorLike} value.\n *\n * @remarks\n * Because the validator is invoked at validate-time (not at module-load), it is safe to inspect\n * the constructor's prototype here. The check is duck-typed: the value must be a function whose\n * `prototype` carries every canonical artifact instance method (`head`, `tail`, `grep`, `cat`,\n * `byteLength`, `lineCount`, `estimateTokens`). This mirrors {@link spoolReaderSchema}'s\n * cross-realm-safe duck-type pattern — `instanceof SpooledArtifact` would be tighter but would\n * force a value-import of the class and reopen the module cycle.\n */\nexport const spooledArtifactConstructorSchema = validator\n  .any()\n  .required()\n  .custom((value, helpers) => {\n    if (typeof value !== 'function') return helpers.error('any.invalid')\n    const proto = (value as { prototype?: unknown }).prototype\n    if (proto === undefined || proto === null) return helpers.error('any.invalid')\n    if (\n      ARTIFACT_METHODS.every((m) => typeof (proto as Record<string, unknown>)[m] === 'function')\n    ) {\n      return value\n    }\n    return helpers.error('any.invalid')\n  })\n\n/**\n * Returns `true` if `value` is a constructor whose prototype carries every canonical\n * {@link @nhtio/adk!SpooledArtifact} instance method.\n *\n * @remarks\n * Duck-typed; does not use `instanceof SpooledArtifact`. Used by the `Tool.artifactConstructor`\n * resolver validator and any other site that needs to recognise a `SpooledArtifact`-like\n * constructor without pulling the class into its module-load graph.\n *\n * @param value - The value to test.\n * @returns `true` when `value` is a `SpooledArtifact`-shaped constructor.\n */\nexport const implementsSpooledArtifactConstructor = (\n  value: unknown\n): value is SpooledArtifactConstructorLike => {\n  return passesSchema(spooledArtifactConstructorSchema, value)\n}\n\n/**\n * Creates the validator fragment for a resolver returning a spooled-artifact constructor.\n *\n * The resolver is invoked during validation, then its result is checked with the canonical\n * cross-realm-safe constructor guard. Keeping this fragment here makes `Tool` and `Retrievable`\n * share exactly the same validation behaviour.\n */\nexport const artifactConstructorResolverSchema = () =>\n  // eslint-disable-next-line adk/require-validator-any-required -- disposition is set by the caller's .optional()/.required() appended to this returned schema\n  validator.any().custom((value, helpers) => {\n    if (typeof value !== 'function') return helpers.error('any.invalid')\n    let resolved: unknown\n    try {\n      resolved = (value as () => unknown)()\n    } catch {\n      return helpers.error('any.invalid')\n    }\n    return implementsSpooledArtifactConstructor(resolved) ? value : helpers.error('any.invalid')\n  })\n","import { DateTime } from 'luxon'\nimport { sha256 } from 'js-sha256'\nimport { Registry } from './registry'\nimport { isInstanceOf, isError } from '../utils/guards'\nimport { canonicalStringify } from '../utils/canonical_json'\nimport { ENCODE_METHOD, DECODE_METHOD } from '../utils/encoder_symbols'\nimport { validator, encode as encodeSchema, decode as decodeSchema } from '@nhtio/validation'\nimport { artifactConstructorResolverSchema } from '../contracts/spooled_artifact_constructor'\nimport { validateOrThrow, asyncValidateOrThrow, ValidationException } from '../utils/validation'\nimport {\n  E_INVALID_INITIAL_TOOL_VALUE,\n  E_INVALID_TOOL_ARGS,\n  E_TOOL_DOWNSTREAM_ERROR,\n} from '../exceptions/runtime'\nimport type { Media } from './media'\nimport type { AdkEncodableSnapshot } from './encodable'\nimport type { Schema, Description } from '@nhtio/validation'\nimport type { DispatchContext } from '../contracts/dispatch_context'\nimport type { SpooledArtifact, SpooledArtifactConstructor } from './spooled_artifact'\n\n/**\n * A zero-arg function that returns the {@link @nhtio/adk!SpooledArtifactConstructor} the consumer should\n * use when wrapping this tool's serialised output into a `ToolCall.results` field.\n *\n * @remarks\n * Why a resolver (and not the constructor itself)? `tool.ts` participates in a module-load\n * cycle with `spooled_artifact.ts` and `artifact_tool.ts` (`ArtifactTool extends Tool` closes\n * the loop). Any eager value-level reference to `SpooledArtifact` in `tool.ts` would crash the\n * cycle with a TDZ error. A resolver lets `tool.ts` validate \"is a function\" at module-load\n * time and defer the actual constructor check to validate-time (which always runs after every\n * module body has executed). Wrap-sites invoke `tool.artifactConstructor?.() ?? SpooledArtifact`\n * to obtain the final constructor.\n */\nexport type ArtifactConstructorResolver<A extends SpooledArtifact = SpooledArtifact> =\n  () => SpooledArtifactConstructor<A>\n\n/**\n * The execution function for a {@link Tool}.\n *\n * @remarks\n * Receives the raw arguments passed to the executor, the active {@link @nhtio/adk!DispatchContext}, and the\n * tool's metadata registry.\n *\n * Return shapes:\n * - `string` / `Uint8Array` — opaque serialised output. The ADK does not persist the bytes\n *   itself; the consumer's executor middleware is responsible for storing them and wrapping\n *   them via `tool.artifactConstructor?.() ?? SpooledArtifact` when assembling the `ToolCall`\n *   record.\n * - {@link @nhtio/adk!SpooledArtifact} — a pre-built, reader-backed result. Return this when\n *   the handler has streamed bytes into storage and wrapped the reader; the consumer passes it\n *   through without materialising or re-spooling it. Its concrete class determines the forged\n *   `artifact_*` query tools.\n * - {@link @nhtio/adk!Media} / `Media[]` — explicit-modality silo. Bypasses\n *   {@link Tool.artifactConstructor} — the handler returns the final result shape directly.\n *   The LLM battery renders each `Media` as a provider-specific content block.\n */\nexport type ToolHandler = (\n  args: unknown,\n  ctx: DispatchContext,\n  meta: Registry\n) =>\n  | string\n  | Uint8Array\n  | SpooledArtifact\n  | Media\n  | Media[]\n  | Promise<string | Uint8Array | SpooledArtifact | Media | Media[]>\n\n/**\n * Plain input object supplied to {@link Tool} at construction time.\n *\n * @typeParam A - The {@link @nhtio/adk!SpooledArtifact} subtype used to wrap this tool's results when\n *   the consumer assembles a `ToolCall.results` field. Defaults to {@link @nhtio/adk!SpooledArtifact}\n *   (plain text). Tools producing JSON output should set this to `SpooledJsonArtifact`; tools\n *   producing markdown should set it to `SpooledMarkdownArtifact`; consumers can also pass a\n *   custom subclass.\n */\nexport interface RawTool<A extends SpooledArtifact = SpooledArtifact> {\n  /** Unique identifier used in LLM tool definitions. Recommend lowercase snake_case. */\n  name: string\n  /** Human-readable description passed to the model to explain what the tool does. */\n  description: string\n  /** @nhtio/validation schema for the tool's input arguments. Annotate with `.description()`, `.note()`, `.example()` etc. to produce rich LLM tool definitions via `.describe()`. */\n  inputSchema: Schema\n  /** Execution function. Not exposed as a public property — invoke via `executor()`. */\n  handler: ToolHandler\n  /**\n   * Zero-arg resolver returning the {@link @nhtio/adk!SpooledArtifactConstructor} the consumer should use\n   * when wrapping this tool's serialised output into a `ToolCall.results` field. Optional —\n   * when omitted, wrap-sites fall back to {@link @nhtio/adk!SpooledArtifact} (plain text).\n   *\n   * @remarks\n   * Recommended call shape: `artifactConstructor: () => SpooledJsonArtifact`. The closure is\n   * the indirection that lets `tool.ts` validate this field without eagerly importing\n   * `SpooledArtifact` (which would crash the `tool.ts ↔ spooled_artifact.ts ↔ artifact_tool.ts`\n   * module-load cycle). At validate time the schema invokes the resolver and verifies its\n   * return value is a `SpooledArtifact`-derived constructor — wrong-shape resolvers throw\n   * {@link @nhtio/adk!E_INVALID_INITIAL_TOOL_VALUE}.\n   *\n   * Wrap-sites (storage batteries, scripted executors) read the constructor via\n   * `tool.artifactConstructor?.() ?? SpooledArtifact`.\n   */\n  artifactConstructor?: ArtifactConstructorResolver<A>\n  /** 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. */\n  meta?: Record<string, unknown>\n  /**\n   * When `true`, marks this tool as owned by a specific {@link @nhtio/adk!DispatchContext} so that\n   * `ToolRegistry.pruneEphemeral()` will drop it at ctx-completion.\n   *\n   * @remarks\n   * The flag is advisory at the `Tool` level — registries decide what to do with it. The canonical\n   * producer of ephemeral tools is `SpooledArtifact.forgeTools(ctx)`, which sets this to `true`\n   * on every artifact-query tool it emits.\n   *\n   * @defaultValue `false`\n   */\n  ephemeral?: boolean\n  /**\n   * When `true`, declares that this tool's output should be treated as **trusted developer/user\n   * intent** rather than as untrusted third-party text when surfaced to the model.\n   *\n   * @remarks\n   * LLM batteries read this flag per call when rendering tool-call results. The default\n   * untrusted envelope (e.g. `<untrusted_content>` in the OpenAI Chat Completions battery) is the\n   * secure-by-default treatment for arbitrary tool output. A tool whose output is authored by the\n   * user or operator (Q&A tools surfacing user-authored answers, human-in-the-loop approval\n   * gates, feedback-collection tools, configuration tools returning developer-authored\n   * constants) sets this to `true` so the LLM battery routes the result through its trusted\n   * envelope (`<trusted_content>` in the OpenAI Chat Completions battery).\n   *\n   * Trust is a property of the tool's output, not a property of how a particular battery is\n   * wired — putting the flag here means the trust signal travels with the tool wherever it is\n   * registered, no per-battery string lists, no name-matching to fail-open on typos.\n   *\n   * @defaultValue `false`\n   */\n  trusted?: boolean\n  /**\n   * Self-declared merge collision policy. Honoured by `ToolRegistry.merge` (NOT by\n   * `ToolRegistry.register`) when this tool collides with another of the same name.\n   *\n   * @remarks\n   * - `'throw'` (default): defer to the merge-level `options.onCollision`. If that is also\n   *   `'throw'`, the merge raises `E_TOOL_ALREADY_REGISTERED`. This matches the default behaviour\n   *   of `ToolRegistry.register`.\n   * - `'replace'`: this tool always wins the collision, regardless of the merge-level option.\n   * - `'keep'`: this tool always loses to any previously-registered tool of the same name.\n   *\n   * Forged artifact-query tools set this to `'replace'` so that merging multiple\n   * `Subclass.forgeTools(ctx)` outputs (whose base-method tools overlap by name) resolves\n   * silently — the descriptors, snapshot, and handler behaviour are interchangeable across\n   * subclasses, so replacement is a behavioural no-op.\n   *\n   * @defaultValue `'throw'`\n   */\n  onCollision?: 'throw' | 'replace' | 'keep'\n}\n\n/**\n * Validator schema for a {@link RawTool}.\n */\nconst rawToolSchema = validator.object<RawTool>({\n  name: validator.string().required(),\n  description: validator.string().required(),\n  inputSchema: validator\n    .any()\n    .custom((value, helpers) => {\n      if (validator.isSchema(value) && (value as any).type === 'object') return value\n      return helpers.error('any.invalid')\n    })\n    .required(),\n  handler: validator.function().required(),\n  artifactConstructor: artifactConstructorResolverSchema().optional(),\n  // eslint-disable-next-line adk/require-validator-any-required -- map value type-arg: meta holds arbitrary values; disposition is set by .default({}) on the object\n  meta: validator.object().pattern(validator.string(), validator.any()).default({}),\n  ephemeral: validator.boolean().default(false),\n  trusted: validator.boolean().default(false),\n  onCollision: validator.string().valid('throw', 'replace', 'keep').default('throw'),\n})\n\n/**\n * A tool definition that serves as the single source of truth for a callable tool: its name,\n * description, input schema, execution handler, and the {@link @nhtio/adk!SpooledArtifact} subclass that\n * wraps its serialised output.\n *\n * @typeParam A - The {@link @nhtio/adk!SpooledArtifact} subtype this tool's results should be wrapped in.\n *   Defaults to {@link @nhtio/adk!SpooledArtifact}.\n *\n * @remarks\n * The `inputSchema` is a `@nhtio/validation` schema. It is used at runtime to validate incoming\n * arguments before the handler is called, and its `.describe()` output provides all the metadata\n * needed to build a provider-specific LLM tool definition — annotate the schema with\n * `.description()`, `.note()`, `.example()` etc. once, and that information is available in both\n * contexts without duplication.\n *\n * The handler is private — invoke it only through `executor(ctx)` which validates args, fires\n * observability events (with a stable `callId` derived from the tool name and arguments), and\n * wraps handler errors in {@link @nhtio/adk!E_TOOL_DOWNSTREAM_ERROR}. The handler returns serialised bytes\n * (`string | Uint8Array`); persistence is the consumer's responsibility.\n *\n * `artifactConstructor` is the {@link @nhtio/adk!SpooledArtifact} subclass the consumer should use when\n * wrapping the handler's output into a `ToolCall.results` field. The author declares it once\n * on the tool instance; the consumer reads it when assembling persisted records.\n */\nexport class Tool<A extends SpooledArtifact = SpooledArtifact> {\n  /**\n   * Validator schema that accepts a {@link RawTool} object.\n   *\n   * @remarks\n   * Reusable fragment for any schema that needs to validate or nest a tool entry\n   * (e.g. `TurnRunnerConfig.tools`).\n   */\n  public static schema = rawToolSchema\n\n  /**\n   * Returns `true` if `value` is a {@link Tool} instance.\n   *\n   * @param value - The value to test.\n   * @returns `true` when `value` is a {@link Tool} instance.\n   */\n  public static isTool(value: unknown): value is Tool {\n    return isInstanceOf(value, 'Tool', Tool)\n  }\n\n  /** The tool's unique name, as exposed to the model in the tool definition. */\n  declare readonly name: string\n  /** Human/model-facing description of what the tool does. */\n  declare readonly description: string\n  /** Validation schema for the tool's arguments; also drives the generated parameter definition. */\n  declare readonly inputSchema: Schema\n  /** Resolver for the artifact constructor used to wrap the handler's output, if any. */\n  declare readonly artifactConstructor: ArtifactConstructorResolver<A> | undefined\n  /** Arbitrary per-tool metadata registry, passed through to the handler. */\n  declare readonly meta: Registry\n  /** When `true`, the tool's results are not persisted to history (transient/one-shot). */\n  declare readonly ephemeral: boolean\n  /** When `true`, the tool's output is treated as trusted content by the LLM battery's envelopes. */\n  declare readonly trusted: boolean\n  /** How registration resolves a name clash in a {@link ToolRegistry}: throw, replace, or keep the existing. */\n  declare readonly onCollision: 'throw' | 'replace' | 'keep'\n\n  #name: string\n  #description: string\n  #inputSchema: Schema\n  #handler: ToolHandler\n  #artifactConstructor: ArtifactConstructorResolver<A> | undefined\n  #meta: Registry\n  #ephemeral: boolean\n  #trusted: boolean\n  #onCollision: 'throw' | 'replace' | 'keep'\n\n  /**\n   * @param raw - The raw tool input validated against `rawToolSchema`.\n   * @throws {@link @nhtio/adk!E_INVALID_INITIAL_TOOL_VALUE} when `raw` does not satisfy the schema.\n   */\n  constructor(raw: RawTool<A>) {\n    let resolved: RawTool<A> & {\n      meta: Record<string, unknown>\n      ephemeral: boolean\n      trusted: boolean\n      onCollision: 'throw' | 'replace' | 'keep'\n    }\n    try {\n      resolved = validateOrThrow<typeof resolved>(\n        rawToolSchema,\n        raw as RawTool,\n        true\n      ) as typeof resolved\n    } catch (err) {\n      throw new E_INVALID_INITIAL_TOOL_VALUE({ cause: isError(err) ? err : undefined })\n    }\n\n    this.#name = resolved.name\n    this.#description = resolved.description\n    this.#inputSchema = resolved.inputSchema\n    this.#handler = resolved.handler\n    this.#artifactConstructor = resolved.artifactConstructor as\n      | ArtifactConstructorResolver<A>\n      | undefined\n    this.#meta = new Registry(resolved.meta)\n    this.#ephemeral = resolved.ephemeral\n    this.#trusted = resolved.trusted\n    this.#onCollision = resolved.onCollision\n\n    Object.defineProperties(this, {\n      name: {\n        get: () => this.#name,\n        enumerable: true,\n        configurable: false,\n      },\n      description: {\n        get: () => this.#description,\n        enumerable: true,\n        configurable: false,\n      },\n      inputSchema: {\n        get: () => this.#inputSchema,\n        enumerable: true,\n        configurable: false,\n      },\n      artifactConstructor: {\n        get: () => this.#artifactConstructor,\n        enumerable: true,\n        configurable: false,\n      },\n      meta: {\n        get: () => this.#meta,\n        enumerable: true,\n        configurable: false,\n      },\n      ephemeral: {\n        get: () => this.#ephemeral,\n        enumerable: true,\n        configurable: false,\n      },\n      trusted: {\n        get: () => this.#trusted,\n        enumerable: true,\n        configurable: false,\n      },\n      onCollision: {\n        get: () => this.#onCollision,\n        enumerable: true,\n        configurable: false,\n      },\n    })\n  }\n\n  /**\n   * Validates `args` against the tool's input schema asynchronously.\n   *\n   * @remarks\n   * Async to support schemas with external validators (e.g. database lookups, API calls).\n   * A validation failure throws {@link @nhtio/adk!E_INVALID_TOOL_ARGS} — this indicates a programming error\n   * in the tool call loop, not a downstream failure.\n   *\n   * @param args - The arguments to validate.\n   * @returns The validated (and coerced) arguments.\n   * @throws {@link @nhtio/adk!E_INVALID_TOOL_ARGS} when `args` does not satisfy the input schema.\n   */\n  async validate(args: unknown): Promise<unknown> {\n    try {\n      return await asyncValidateOrThrow(this.#inputSchema, args)\n    } catch (err) {\n      if (isInstanceOf(err, 'ValidationException', ValidationException)) {\n        throw new E_INVALID_TOOL_ARGS({ cause: err })\n      }\n      throw err\n    }\n  }\n\n  /**\n   * Returns a bound executor function for this tool against the given turn context.\n   *\n   * @remarks\n   * The executor: (1) computes a stable `callId` as `sha256(canonicalStringify({tool, args}))`\n   * over the **raw, pre-validation args**, (2) validates `args` via {@link Tool.validate},\n   * (3) emits `toolExecutionStart` on the context (with the computed `callId`), (4) calls the\n   * handler, (5) emits `toolExecutionEnd` (with the same `callId`), (6) wraps any handler error\n   * in {@link @nhtio/adk!E_TOOL_DOWNSTREAM_ERROR} before re-throwing.\n   *\n   * The handler usually returns serialised bytes (`string | Uint8Array`) — persistence is then the\n   * consumer's concern, and {@link Tool.artifactConstructor} is the class a wrap-site uses when\n   * wrapping those bytes into a `ToolCall.results` field. **A handler may instead return a\n   * {@link @nhtio/adk!SpooledArtifact} it built itself** (streaming into storage and wrapping the\n   * reader), in which case wrap-sites pass it through untouched and `artifactConstructor` does not\n   * apply — the returned instance's own class is what artifact-tool forging reads.\n   *\n   * Pattern mirrors `Middleware.runner()` — call once per turn, reuse the returned function.\n   *\n   * @param ctx - The active turn context. Provides emit functions and turn ID.\n   * @returns An async function `(args) => Promise<string | Uint8Array | SpooledArtifact | Media | Media[]>`.\n   *   A `SpooledArtifact` is a result the HANDLER built (streamed to storage and wrapped itself); a\n   *   wrap-site passes it through unwrapped rather than re-spooling it. `string`/`Uint8Array` are the\n   *   opposite case — bytes the consumer must still store and wrap.\n   */\n  executor(\n    ctx: DispatchContext\n  ): (args: unknown) => Promise<string | Uint8Array | SpooledArtifact | Media | Media[]> {\n    return async (\n      args: unknown\n    ): Promise<string | Uint8Array | SpooledArtifact | Media | Media[]> => {\n      // Compute callId over raw args (pre-validation) so two invocations with the same\n      // (tool, raw args) produce the same identifier even if validation coerces values.\n      const callId = sha256(canonicalStringify({ tool: this.#name, args }))\n      const validatedArgs = await this.validate(args)\n      const startedAt = DateTime.now()\n      ctx.emitToolExecutionStart({\n        toolName: this.#name,\n        turnId: ctx.id,\n        callId,\n        args: validatedArgs,\n        startedAt,\n      })\n      try {\n        const result = await this.#handler(validatedArgs, ctx, this.#meta)\n        const endedAt = DateTime.now()\n        ctx.emitToolExecutionEnd({\n          toolName: this.#name,\n          turnId: ctx.id,\n          callId,\n          startedAt,\n          endedAt,\n          durationMs: endedAt.diff(startedAt).milliseconds,\n          isError: false,\n        })\n        return result\n      } catch (err) {\n        const endedAt = DateTime.now()\n        ctx.emitToolExecutionEnd({\n          toolName: this.#name,\n          turnId: ctx.id,\n          callId,\n          startedAt,\n          endedAt,\n          durationMs: endedAt.diff(startedAt).milliseconds,\n          isError: true,\n        })\n        throw new E_TOOL_DOWNSTREAM_ERROR({ cause: isError(err) ? err : undefined })\n      }\n    }\n  }\n\n  /**\n   * Returns a fully serialisable snapshot of this tool's definition.\n   *\n   * @remarks\n   * The `inputSchema` property is the result of calling `.describe()` on the raw schema — a plain\n   * object carrying all the annotation metadata (descriptions, notes, examples, types) without any\n   * validator functions. Use this to build provider-specific LLM tool definitions.\n   *\n   * @returns `{ name, description, inputSchema }` where `inputSchema` is the schema description.\n   */\n  describe(): { name: string; description: string; inputSchema: Description } {\n    return {\n      name: this.#name,\n      description: this.#description,\n      inputSchema: this.#inputSchema.describe(),\n    }\n  }\n\n  /**\n   * Serialise this Tool into an `@nhtio/encoder` snapshot.\n   *\n   * @remarks\n   * Three halves with different serialisation strategies:\n   *\n   * - **Plain config** (`name`, `description`, `meta`, flags) — emitted verbatim.\n   * - **`inputSchema`** — delegated to `@nhtio/validation`'s `encode()`, which captures the full schema\n   *   description plus its library version into a compact string; decode rebuilds the live `Schema`.\n   * - **`handler` / `artifactConstructor`** — emitted as **functions**. The encoder's `FunctionSerializer`\n   *   serialises them by **source text** (`fn.toString()`). This is the sharp edge: a handler that\n   *   closes over `ctx`, service clients, or config loses those bindings on decode — only the source\n   *   survives, and the captured variables read back as `undefined`. A `.bind()`-ed or native handler\n   *   cannot be serialised at all and the encoder throws. Tools are rarely serialised in practice; when\n   *   they are, write handlers that reconstruct their dependencies inside the body rather than closing\n   *   over them.\n   *\n   * @returns A {@link RawTool}-shaped snapshot (with `inputSchema` as an encoded string).\n   */\n  [ENCODE_METHOD](): AdkEncodableSnapshot {\n    return {\n      name: this.#name,\n      description: this.#description,\n      inputSchema: encodeSchema(this.#inputSchema),\n      handler: this.#handler,\n      artifactConstructor: this.#artifactConstructor,\n      meta: this.#meta.all(),\n      ephemeral: this.#ephemeral,\n      trusted: this.#trusted,\n      onCollision: this.#onCollision,\n    }\n  }\n\n  /**\n   * Reconstruct a {@link Tool} from a {@link Tool.[ENCODE_METHOD]} snapshot.\n   *\n   * @remarks\n   * Rebuilds the live `Schema` via `@nhtio/validation`'s `decode()`, then re-validates through the\n   * constructor. The rehydrated `handler` carries only its source text — see\n   * {@link Tool.[ENCODE_METHOD]} for the closure caveat.\n   *\n   * @param data - The snapshot produced by {@link Tool.[ENCODE_METHOD]}.\n   * @returns A fully-validated {@link Tool}.\n   */\n  static [DECODE_METHOD](data: AdkEncodableSnapshot): Tool {\n    const snapshot = data as Omit<RawTool, 'inputSchema'> & { inputSchema: string }\n    return new Tool({\n      ...snapshot,\n      inputSchema: decodeSchema(snapshot.inputSchema),\n    })\n  }\n}\n","import { Tool } from './tool'\nimport { isInstanceOf } from '../utils/guards'\nimport { E_INVALID_INITIAL_TOOL_VALUE } from '../exceptions/runtime'\nimport { validator, decode as decodeSchema } from '@nhtio/validation'\nimport { ENCODE_METHOD, DECODE_METHOD } from '../utils/encoder_symbols'\nimport { validateOrThrow, ValidationException } from '../utils/validation'\nimport type { Registry } from './registry'\nimport type { Schema } from '@nhtio/validation'\nimport type { Tokenizable } from './tokenizable'\nimport type { AdkEncodableSnapshot } from './encodable'\nimport type { DispatchContext } from '../contracts/dispatch_context'\n\n/**\n * The execution function for an {@link ArtifactTool}.\n *\n * @remarks\n * Identical to the base tool handler except the return type is narrowed to\n * `string | Tokenizable | Promise<string | Tokenizable>`. Forged artifact-query tools emit\n * model-visible strings — the ADK wraps a bare-string return into a {@link @nhtio/adk!Tokenizable}\n * at the result-wrapping site so downstream code can rely on\n * `ToolCall.results instanceof Tokenizable` for every `ArtifactTool` invocation.\n */\nexport type ArtifactToolHandler = (\n  args: unknown,\n  ctx: DispatchContext,\n  meta: Registry\n) => string | Tokenizable | Promise<string | Tokenizable>\n\n/**\n * Plain input object supplied to {@link ArtifactTool} at construction time.\n *\n * @remarks\n * Mirrors the base `RawTool` except `artifactConstructor` is forbidden — an `ArtifactTool`\n * emits a {@link @nhtio/adk!Tokenizable} directly into `ToolCall.results` and explicitly opts out of\n * `SpooledArtifact` wrapping. The forbidden field is enforced by {@link ArtifactTool.schema}\n * at construction time.\n */\nexport interface RawArtifactTool {\n  /** Unique identifier used in LLM tool definitions. Recommend lowercase snake_case. */\n  name: string\n  /** Human-readable description passed to the model to explain what the tool does. */\n  description: string\n  /** @nhtio/validation schema for the tool's input arguments. */\n  inputSchema: Schema\n  /** Execution function. Returns a string or {@link @nhtio/adk!Tokenizable}; the ADK wraps a bare string into a `Tokenizable` at the result-wrapping site. */\n  handler: ArtifactToolHandler\n  /** Optional arbitrary metadata for this tool. Defaults to `{}`. */\n  meta?: Record<string, unknown>\n  /**\n   * When `true`, marks this tool as owned by a specific {@link @nhtio/adk!DispatchContext}.\n   *\n   * @remarks\n   * `ArtifactTool` instances produced by `SpooledArtifact.forgeTools(ctx)` set this to `true`\n   * so that `ToolRegistry.pruneEphemeral()` drops them at ctx-completion.\n   *\n   * @defaultValue `false`\n   */\n  ephemeral?: boolean\n  /**\n   * When `true`, declares that this tool's output should be treated as **trusted developer/user\n   * intent** rather than as untrusted third-party text when surfaced to the model.\n   *\n   * @remarks\n   * Forged artifact-query tools default to `false` because their results are derived from\n   * spooled artifact bodies — which may themselves be untrusted upstream tool output. The\n   * trust signal does not promote handle-query results above the trust tier of the underlying\n   * artifact.\n   *\n   * @defaultValue `false`\n   */\n  trusted?: boolean\n  /**\n   * Self-declared merge collision policy honoured by `ToolRegistry.merge`.\n   *\n   * @remarks\n   * Forged artifact-query tools set this to `'replace'` so that merging multiple\n   * `Subclass.forgeTools(ctx)` outputs (whose base-method tools overlap by name) resolves\n   * silently — the descriptors, snapshot, and handler behaviour are interchangeable across\n   * subclasses, so replacement is a behavioural no-op.\n   *\n   * @defaultValue `'throw'`\n   */\n  onCollision?: 'throw' | 'replace' | 'keep'\n}\n\n/**\n * Validator schema for a {@link RawArtifactTool}.\n *\n * @remarks\n * Mirrors the base tool schema but explicitly forbids `artifactConstructor` — the entire\n * point of `ArtifactTool` is to opt out of `SpooledArtifact` wrapping.\n */\nconst rawArtifactToolSchema = validator.object<RawArtifactTool & { artifactConstructor?: never }>({\n  name: validator.string().required(),\n  description: validator.string().required(),\n  inputSchema: validator\n    .any()\n    .custom((value, helpers) => {\n      if (validator.isSchema(value) && (value as any).type === 'object') return value\n      return helpers.error('any.invalid')\n    })\n    .required(),\n  handler: validator.function().required(),\n  // eslint-disable-next-line adk/require-validator-any-required -- map value type-arg: meta holds arbitrary values; disposition is set by .default({}) on the object\n  meta: validator.object().pattern(validator.string(), validator.any()).default({}),\n  ephemeral: validator.boolean().default(false),\n  trusted: validator.boolean().default(false),\n  onCollision: validator.string().valid('throw', 'replace', 'keep').default('throw'),\n  artifactConstructor: validator.any().forbidden(),\n})\n\n/**\n * A {@link @nhtio/adk!Tool} subclass whose handler return value is wrapped directly in a\n * {@link @nhtio/adk!Tokenizable} (not a {@link @nhtio/adk!SpooledArtifact}) when it\n * lands on `ToolCall.results`.\n *\n * @remarks\n * `ArtifactTool` is the canonical producer for **forged artifact-query tools** — the tools\n * `SpooledArtifact.forgeTools(ctx)` emits so the model can `head`, `tail`, `grep`, `json_get`,\n * `md_headings` (etc.) an artifact that is already in `ctx.turnToolCalls`.\n *\n * The difference from {@link @nhtio/adk!Tool} is structural, not stylistic:\n *\n * - A normal `Tool`'s handler returns bytes the ADK wraps in a fresh `SpooledArtifact`.\n *   The artifact lands in `ToolCall.results`, joins `ctx.turnToolCalls`, and is itself a\n *   first-class queryable artifact in the turn.\n * - An `ArtifactTool`'s handler returns a string that is **already the model-visible answer**\n *   to a query against an existing artifact. The ADK wraps it in a `Tokenizable` rather\n *   than a `SpooledArtifact`; nothing new is queryable on its own. Subsequent\n *   `forgeTools(ctx)` calls exclude `ToolCall`s produced by an `ArtifactTool` from the\n *   `callId` enum (via the `ToolCall.fromArtifactTool` marker) — this is the structural fix\n *   that breaks the otherwise-recursive grep-on-the-grep-result loop.\n *\n * Consumers who want to build their own artifact-query tools (e.g. for a domain-specific\n * spooled subclass not shipped by the ADK) should extend or instantiate this class.\n */\nexport class ArtifactTool extends Tool {\n  /**\n   * Validator schema that accepts a {@link RawArtifactTool} object.\n   *\n   * @remarks\n   * Differs from {@link @nhtio/adk!Tool.schema} by forbidding `artifactConstructor` — wrapping is\n   * exactly the thing this class opts out of. Typed identically to {@link @nhtio/adk!Tool.schema} so the\n   * subclass relationship `class ArtifactTool extends Tool` remains structurally sound; the\n   * runtime validation rules still differ as declared by `rawArtifactToolSchema`.\n   */\n  public static schema = rawArtifactToolSchema as unknown as typeof Tool.schema\n\n  /**\n   * Returns `true` if `value` is an {@link ArtifactTool} instance.\n   *\n   * @remarks\n   * Uses {@link @nhtio/adk!isInstanceOf} for cross-realm safety — `instanceof` would fail for instances\n   * created in a different module copy or VM context.\n   *\n   * @param value - The value to test.\n   * @returns `true` when `value` is an {@link ArtifactTool} instance.\n   */\n  public static isArtifactTool(value: unknown): value is ArtifactTool {\n    return isInstanceOf(value, 'ArtifactTool', ArtifactTool)\n  }\n\n  /**\n   * @param raw - Raw tool input validated against {@link ArtifactTool.schema}.\n   *\n   * @throws {@link @nhtio/adk!E_INVALID_INITIAL_TOOL_VALUE} when `raw` does not satisfy\n   *   {@link ArtifactTool.schema} (most commonly, when `artifactConstructor` is supplied — it is\n   *   explicitly forbidden on this class) or when the base {@link @nhtio/adk!Tool} constructor rejects the\n   *   input for any reason.\n   */\n  constructor(raw: RawArtifactTool) {\n    // Enforce the forbidden `artifactConstructor` field up-front so the error reports against\n    // ArtifactTool's contract, not the base Tool's. The base Tool constructor re-validates\n    // against its own schema and stores the resolved fields.\n    try {\n      validateOrThrow(rawArtifactToolSchema, raw, true)\n    } catch (err) {\n      if (isInstanceOf(err, 'ValidationException', ValidationException)) {\n        throw new E_INVALID_INITIAL_TOOL_VALUE({ cause: err })\n      }\n      throw err\n    }\n    super(raw as never)\n  }\n\n  /**\n   * Serialise this ArtifactTool into an `@nhtio/encoder` snapshot.\n   *\n   * @remarks\n   * Reuses the base {@link Tool.[ENCODE_METHOD]} (function-serialised `handler`, validation-encoded\n   * `inputSchema`, plain config) and strips `artifactConstructor` — which {@link ArtifactTool.schema}\n   * forbids. The handler's closure caveat from {@link Tool.[ENCODE_METHOD]} applies here too.\n   *\n   * @returns A {@link RawArtifactTool}-shaped snapshot (with `inputSchema` as an encoded string).\n   */\n  [ENCODE_METHOD](): AdkEncodableSnapshot {\n    const base = super[ENCODE_METHOD]() as Record<string, unknown>\n    delete base.artifactConstructor\n    return base\n  }\n\n  /**\n   * Reconstruct an {@link ArtifactTool} from an {@link ArtifactTool.[ENCODE_METHOD]} snapshot.\n   *\n   * @param data - The snapshot produced by {@link ArtifactTool.[ENCODE_METHOD]}.\n   * @returns A fully-validated {@link ArtifactTool}.\n   */\n  static [DECODE_METHOD](data: AdkEncodableSnapshot): ArtifactTool {\n    const snapshot = data as Omit<RawArtifactTool, 'inputSchema'> & { inputSchema: string }\n    return new ArtifactTool({\n      ...snapshot,\n      inputSchema: decodeSchema(snapshot.inputSchema),\n    })\n  }\n}\n","import { isInstanceOf } from '../utils/guards'\nimport { E_TOOL_ALREADY_REGISTERED } from '../exceptions/runtime'\nimport { ENCODE_METHOD, DECODE_METHOD } from '../utils/encoder_symbols'\nimport type { Tool } from './tool'\nimport type { AdkEncodableSnapshot } from './encodable'\nimport type { DispatchContext } from '../contracts/dispatch_context'\n\n/**\n * Options accepted by {@link ToolRegistry.merge}.\n */\nexport interface MergeOptions {\n  /**\n   * What to do when two registries contain a tool with the same name AND neither tool's own\n   * `onCollision` resolves the collision.\n   *\n   * @remarks\n   * - `'throw'` (default): raise {@link @nhtio/adk!E_TOOL_ALREADY_REGISTERED} on the first unresolved\n   *   collision. Mirrors the default behaviour of {@link ToolRegistry.register} and surfaces\n   *   accidental name shadowing immediately.\n   * - `'replace'`: the later registry's tool wins.\n   * - `'keep'`: the earlier registry's tool wins; later occurrences are dropped.\n   *\n   * Per-tool {@link @nhtio/adk!Tool.onCollision} takes precedence: if the incoming tool declares\n   * `'replace'` or `'keep'`, that policy wins regardless of this option. Only when the incoming\n   * tool's policy is `'throw'` (the default) does this fallback apply.\n   *\n   * @defaultValue `'throw'`\n   */\n  onCollision?: 'throw' | 'replace' | 'keep'\n}\n\n/**\n * A mutable, turn-scoped collection of {@link @nhtio/adk!Tool} instances.\n *\n * @remarks\n * Each `TurnRunner.run()` call constructs a fresh `ToolRegistry` from the runner's configured\n * baseline tools, so middleware edits are isolated to the current turn and cannot bleed across\n * concurrent or subsequent turns.\n *\n * `Tool` instances are immutable, so `all()` returns a fresh array without deep-cloning.\n *\n * `register()` throws {@link @nhtio/adk!E_TOOL_ALREADY_REGISTERED} if a tool with the same name is already\n * present — pass `overwrite: true` to replace it explicitly.\n *\n * Tools can be **hidden** — registered and callable, but excluded from the default tool list\n * rendered to the model. This is useful for discovery patterns where an agent has a tool that\n * enumerates available tools, and the model picks one to call by name in a subsequent iteration.\n * Hidden state is a property of the registry, not the tool: the same tool can be visible in one\n * registry and hidden in another. See {@link hide}, {@link visible}, {@link hidden}.\n */\nexport class ToolRegistry {\n  #tools: Map<string, Tool>\n  #hidden: Set<string>\n\n  /**\n   * Returns `true` if `value` is a {@link ToolRegistry} instance.\n   *\n   * @param value - The value to test.\n   * @returns `true` when `value` is a {@link ToolRegistry} instance.\n   */\n  public static isToolRegistry(value: unknown): value is ToolRegistry {\n    return isInstanceOf(value, 'ToolRegistry', ToolRegistry)\n  }\n\n  /**\n   * @param tools - Optional initial tools. Insertion order is preserved. Duplicate names throw\n   *   {@link @nhtio/adk!E_TOOL_ALREADY_REGISTERED} — ensure each tool has a unique name.\n   * @throws {@link @nhtio/adk!E_TOOL_ALREADY_REGISTERED} when two tools in `tools` share a name.\n   */\n  constructor(tools?: Tool[]) {\n    this.#tools = new Map()\n    this.#hidden = new Set()\n    for (const tool of tools ?? []) {\n      this.register(tool)\n    }\n  }\n\n  /**\n   * Adds a tool to the registry.\n   *\n   * @param tool - The tool to register.\n   * @param overwrite - When `true`, silently replaces an existing tool with the same name.\n   *   Defaults to `false`.\n   * @throws {@link @nhtio/adk!E_TOOL_ALREADY_REGISTERED} when a tool with the same name is already registered\n   *   and `overwrite` is not `true`.\n   */\n  register(tool: Tool, overwrite?: boolean): void {\n    if (this.#tools.has(tool.name) && !overwrite) {\n      throw new E_TOOL_ALREADY_REGISTERED()\n    }\n    this.#tools.set(tool.name, tool)\n  }\n\n  /**\n   * Removes the tool with the given name from the registry.\n   *\n   * @remarks\n   * Also removes the name from the hidden set if present. No-ops if no tool with that name is\n   * registered.\n   *\n   * @param name - The name of the tool to remove.\n   */\n  unregister(name: string): void {\n    this.#tools.delete(name)\n    this.#hidden.delete(name)\n  }\n\n  /**\n   * Returns the tool registered under `name`, or `undefined` if not present.\n   *\n   * @param name - The tool name to look up.\n   */\n  get(name: string): Tool | undefined {\n    return this.#tools.get(name)\n  }\n\n  /**\n   * Returns `true` if a tool with the given name is registered.\n   *\n   * @param name - The tool name to test.\n   */\n  has(name: string): boolean {\n    return this.#tools.has(name)\n  }\n\n  /**\n   * Returns a fresh array of all registered tools in insertion order.\n   *\n   * @remarks\n   * Includes both visible and hidden tools. Use {@link visible} to get only non-hidden tools, or\n   * {@link hidden} to get only hidden tools.\n   *\n   * Since {@link @nhtio/adk!Tool} instances are immutable, no deep-cloning is needed.\n   */\n  all(): Tool[] {\n    return Array.from(this.#tools.values())\n  }\n\n  /**\n   * Returns a fresh array of registered tools that are **not** hidden, in insertion order.\n   *\n   * @remarks\n   * This is the accessor LLM batteries should use when building the tool list for the model.\n   * Hidden tools are still callable (they resolve via {@link get}) but are excluded from the\n   * rendered tool definitions.\n   */\n  visible(): Tool[] {\n    return this.all().filter((t) => !this.#hidden.has(t.name))\n  }\n\n  /**\n   * Returns a fresh array of registered tools that **are** hidden, in insertion order.\n   *\n   * @remarks\n   * The converse of {@link visible}. Useful for discovery tools that enumerate all available\n   * tools, and for propagating hidden state across {@link merge}.\n   */\n  hidden(): Tool[] {\n    return this.all().filter((t) => this.#hidden.has(t.name))\n  }\n\n  /**\n   * Marks one or more registered tools as hidden.\n   *\n   * @remarks\n   * Hidden tools remain registered and callable via {@link get}, but are excluded from\n   * {@link visible} (and therefore from the LLM tool list). No-ops for any name that is not\n   * currently registered — the end result (the tool is not visible) matches the intent.\n   *\n   * @param names - One or more tool names to hide.\n   */\n  hide(...names: string[]): void {\n    for (const name of names) {\n      this.#hidden.add(name)\n    }\n  }\n\n  /**\n   * Unmarks one or more tools as hidden, making them visible again.\n   *\n   * @remarks\n   * No-ops for any name that is not currently hidden.\n   *\n   * @param names - One or more tool names to unhide.\n   */\n  unhide(...names: string[]): void {\n    for (const name of names) {\n      this.#hidden.delete(name)\n    }\n  }\n\n  /**\n   * Replaces the entire hidden set with the given tool names.\n   *\n   * @remarks\n   * Any previously hidden tool not in `names` becomes visible. Names that are not registered are\n   * silently ignored — they are added to the set but have no effect until a tool with that name\n   * is registered.\n   *\n   * @param names - The complete set of tool names to hide.\n   */\n  setHidden(...names: string[]): void {\n    this.#hidden = new Set(names)\n  }\n\n  /**\n   * Unhides every tool in the registry.\n   */\n  clearHidden(): void {\n    this.#hidden.clear()\n  }\n\n  /**\n   * Removes every tool whose {@link @nhtio/adk!Tool.ephemeral} flag is `true`.\n   *\n   * @remarks\n   * Also removes pruned tool names from the hidden set. Synchronous and idempotent — calling it\n   * twice in a row is a no-op the second time. The canonical caller is\n   * {@link ToolRegistry.bindContext}, which schedules this method to run at\n   * {@link @nhtio/adk!DispatchContext.ack}. Non-ephemeral tools are left untouched.\n   */\n  pruneEphemeral(): void {\n    for (const [name, tool] of this.#tools) {\n      if (tool.ephemeral) {\n        this.#tools.delete(name)\n        this.#hidden.delete(name)\n      }\n    }\n  }\n\n  /**\n   * Binds this registry to a {@link @nhtio/adk!DispatchContext} so that {@link pruneEphemeral} runs\n   * automatically when the context is acked.\n   *\n   * @remarks\n   * The handler does NOT fire on {@link @nhtio/adk!DispatchContext.nack} — failed executor runs leave\n   * any forged tools in place so the consumer can inspect what was registered when debugging the\n   * failure. Subscriptions are short-lived and die with the context regardless.\n   *\n   * ARTIFACT READERS ARE FORGED BY THE CORE. As of the core-forge change, the `DispatchRunner` forges\n   * artifact-reader tools from prior-turn `SpooledArtifact` results into `ctx.tools` (and calls\n   * `ctx.tools.bindContext(ctx)`) once per iteration, BEFORE the input pipeline — so a battery executor no\n   * longer forges or binds; it reads the already-forged `ctx.tools` for both representation (rendering the\n   * tool declarations) and resolution (looking up an incoming call by name). The lifecycle is unchanged:\n   * ephemeral readers are still pruned on `ack`, and the core re-forges each iteration (prune-then-forge) so\n   * the `callId` enum never goes stale.\n   *\n   * `bindContext` remains the public seam for a CUSTOM consumer that forges its own ephemeral tools outside\n   * the core path. If you forge into a long-lived registry yourself, bind it (or prune manually) or the\n   * ephemeral tools accumulate and later `forgeTools(ctx)` calls see a stale `callId` enum. The pattern for a\n   * hand-rolled forge is:\n   *\n   * ```ts\n   * // Custom forge (the core already does this for artifact readers on ctx.tools):\n   * const forged = SpooledArtifact.forgeTools(ctx)\n   * for (const tool of forged.all()) ctx.tools.register(tool, true)\n   * ctx.tools.bindContext(ctx) // prune the ephemeral readers when the dispatch acks\n   * ```\n   *\n   * @param ctx - The execution context whose `ack` event should trigger pruning.\n   * @returns An unsubscribe function — calling it before `ctx.ack()` prevents pruning. Rarely\n   *   useful outside of tests.\n   *\n   * @see {@link @nhtio/adk!SpooledArtifact.forgeTools}\n   * @see {@link @nhtio/adk!DispatchContext.onAck}\n   */\n  bindContext(ctx: DispatchContext): () => void {\n    return ctx.onAck(() => this.pruneEphemeral())\n  }\n\n  /**\n   * Combines multiple {@link ToolRegistry} instances into a fresh registry without mutating any\n   * input.\n   *\n   * @remarks\n   * Iteration is left-to-right across `registries` and then in each registry's insertion order.\n   * Collisions are resolved by consulting the **incoming** tool's {@link @nhtio/adk!Tool.onCollision} first:\n   *\n   * - `'replace'` (per-tool): the incoming tool wins, replacing the existing entry.\n   * - `'keep'` (per-tool): the existing entry wins; the incoming tool is dropped.\n   * - `'throw'` (per-tool, the default): fall back to the merge-level `options.onCollision`.\n   *\n   * The merge-level `options.onCollision` defaults to `'throw'`, which mirrors {@link register}.\n   *\n   * The result is a brand-new registry; no input is mutated and no event subscription is\n   * propagated. Each `Tool`'s `ephemeral` flag carries through unchanged — the flag lives on the\n   * tool, not the registry, so `bindContext(ctx)` on the merged registry will prune the forged\n   * tools as expected.\n   *\n   * Hidden state is also propagated: a tool that is hidden in any source registry remains hidden\n   * in the merged result, provided it survives collision resolution.\n   *\n   * @param registries - Registries to merge, in priority order (left-to-right insertion).\n   * @param options - Merge-level collision policy. Defaults to `{ onCollision: 'throw' }`.\n   * @returns A fresh {@link ToolRegistry} containing the resolved union of all inputs.\n   * @throws {@link @nhtio/adk!E_TOOL_ALREADY_REGISTERED} when the resolved collision policy is `'throw'`\n   *   and a collision occurs.\n   */\n  static merge(registries: ToolRegistry[], options?: MergeOptions): ToolRegistry {\n    const policy = options?.onCollision ?? 'throw'\n    const merged = new ToolRegistry()\n    for (const registry of registries) {\n      for (const tool of registry.all()) {\n        const existing = merged.get(tool.name)\n        if (!existing) {\n          merged.register(tool)\n          continue\n        }\n        const incomingPolicy = tool.onCollision\n        if (incomingPolicy === 'replace') {\n          merged.register(tool, true)\n          continue\n        }\n        if (incomingPolicy === 'keep') {\n          continue\n        }\n        // Incoming policy is 'throw' — fall back to the merge-level option.\n        if (policy === 'replace') {\n          merged.register(tool, true)\n          continue\n        }\n        if (policy === 'keep') {\n          continue\n        }\n        throw new E_TOOL_ALREADY_REGISTERED()\n      }\n      // Propagate hidden state: any tool that was hidden in a source registry stays hidden\n      // in the merged result, as long as it survived collision resolution.\n      for (const tool of registry.hidden()) {\n        if (merged.has(tool.name)) {\n          merged.hide(tool.name)\n        }\n      }\n    }\n    return merged\n  }\n\n  /**\n   * Serialise this ToolRegistry into an `@nhtio/encoder` snapshot.\n   *\n   * @remarks\n   * Emits the live {@link @nhtio/adk!Tool} instances (the encoder recurses into each — so every tool's\n   * handler-closure caveat from {@link Tool.[ENCODE_METHOD]} applies) plus the hidden-set names. Both\n   * {@link @nhtio/adk!Tool} and {@link @nhtio/adk!ArtifactTool} entries round-trip to their correct\n   * subtype. Round-trips via {@link ToolRegistry.[DECODE_METHOD]}.\n   *\n   * @returns A `{ tools, hidden }` snapshot.\n   */\n  [ENCODE_METHOD](): AdkEncodableSnapshot {\n    return {\n      tools: this.all(),\n      hidden: this.hidden().map((tool) => tool.name),\n    }\n  }\n\n  /**\n   * Reconstruct a {@link ToolRegistry} from a {@link ToolRegistry.[ENCODE_METHOD]} snapshot.\n   *\n   * @param data - The snapshot produced by {@link ToolRegistry.[ENCODE_METHOD]}.\n   * @returns A fresh {@link ToolRegistry} with the same tools and hidden set.\n   */\n  static [DECODE_METHOD](data: AdkEncodableSnapshot): ToolRegistry {\n    const snapshot = data as { tools: Tool[]; hidden: string[] }\n    const registry = new ToolRegistry(snapshot.tools)\n    registry.setHidden(...snapshot.hidden)\n    return registry\n  }\n}\n","import { validator } from '@nhtio/validation'\nimport { passesSchema } from '../utils/validation'\nimport type { ReaderDescriptor } from './reader_descriptor'\n\n/**\n * Backing store contract for a {@link @nhtio/adk!SpooledArtifact}.\n *\n * @remarks\n * Implementations may read from memory, a file handle, a network stream, or any other byte\n * source. The interface is intentionally minimal — the artifact layer handles all higher-level\n * operations (`head`, `tail`, `grep`, etc.) by composing calls to these three primitives.\n *\n * Line indexing is 0-based. Implementations must return `undefined` from {@link SpoolReader.line}\n * when the index is out of range rather than throwing.\n *\n * All three methods may be synchronous or asynchronous to accommodate both in-memory and I/O-\n * backed implementations without forcing unnecessary promise overhead on simple cases.\n */\nexport interface SpoolReader {\n  /**\n   * Returns the line at the given 0-based index, or `undefined` when out of range.\n   *\n   * @param index - 0-based line index.\n   * @returns The raw line string (without trailing newline), or `undefined`.\n   */\n  line(index: number): string | undefined | Promise<string | undefined>\n\n  /**\n   * Returns the total number of bytes in the underlying data.\n   *\n   * @remarks\n   * Used for reporting and token-estimation purposes. Byte length is distinct from character\n   * length for multi-byte encodings.\n   *\n   * @returns The byte length of the underlying data.\n   */\n  byteLength(): number | Promise<number>\n\n  /**\n   * Returns the total number of lines in the underlying data.\n   *\n   * @remarks\n   * Required so consumers know when to stop iterating; the line count must remain stable for the\n   * lifetime of the reader.\n   *\n   * @returns The total line count.\n   */\n  lineCount(): number | Promise<number>\n\n  /**\n   * Returns the full underlying content as a single decoded string, byte-faithful to the source.\n   *\n   * @remarks\n   * Unlike {@link SpoolReader.line}, this method preserves trailing newlines and any non-`\\n`\n   * line terminators (e.g. `\\r\\n`) present in the original bytes. It is the primitive that\n   * powers `SpooledArtifact.asString()` — the round-trip-faithful alternative to assembling\n   * the artifact body from per-line reads.\n   *\n   * Implementations should make this O(n) in the size of the underlying data and may cache the\n   * result if the read source is durable. Streaming implementations may choose not to cache.\n   *\n   * @returns The full underlying content as a single string.\n   */\n  readAll(): string | Promise<string>\n\n  /**\n   * Optionally emit a serialisable {@link ReaderDescriptor} so a {@link @nhtio/adk!SpooledArtifact}\n   * backed by this reader can round-trip through `encode()`/`decode()` as a **handle**.\n   *\n   * @remarks\n   * Synchronous by contract — the encoder's `[ENCODE_METHOD]()` is synchronous and cannot await. The\n   * descriptor describes *where the bytes live* (a spool key, or an inlined string for in-memory\n   * readers) — never the live binding (`Disk`, OPFS root), which the matching resolver re-injects on\n   * decode. A reader that omits this method is treated as non-describable: line/text reads still work at\n   * runtime, the `SpooledArtifact` simply cannot be serialised, and encoding it throws\n   * {@link @nhtio/adk!E_READER_NOT_DESCRIBABLE}.\n   *\n   * @returns A tagged, serialisable handle, or `undefined`/absent when the reader cannot describe itself.\n   */\n  describe?(): ReaderDescriptor | undefined\n}\n\n/**\n * Validator schema used to validate a {@link SpoolReader} value.\n *\n * @remarks\n * Because `SpoolReader` is a structural interface with no associated constructor, validation is\n * duck-typed: the value must be an object, class instance, or function with `line`, `byteLength`,\n * and `lineCount` present as callable properties. Arity is not enforced — implementations may add\n * optional parameters beyond the contract.\n */\nexport const spoolReaderSchema = validator\n  .any()\n  .required()\n  .custom((value, helpers) => {\n    if (\n      value !== null &&\n      value !== undefined &&\n      typeof (value as any).line === 'function' &&\n      typeof (value as any).byteLength === 'function' &&\n      typeof (value as any).lineCount === 'function' &&\n      typeof (value as any).readAll === 'function'\n    ) {\n      return value as SpoolReader\n    }\n    return helpers.error('any.invalid')\n  })\n\n/**\n * Returns `true` if `value` implements the {@link SpoolReader} interface.\n *\n * @remarks\n * Duck-typed: checks that `value` is non-null with `line`, `byteLength`, `lineCount`, and\n * `readAll` as callable functions. Does not use `instanceof` — there is no `SpoolReader`\n * constructor.\n *\n * @param value - The value to test.\n * @returns `true` when `value` conforms to the {@link SpoolReader} interface.\n */\nexport const implementsSpoolReader = (value: unknown): value is SpoolReader => {\n  return passesSchema(spoolReaderSchema, value)\n}\n","/**\n * Decode-time registries that re-bind a serialised {@link ReaderDescriptor} to a live reader.\n *\n * @module\n *\n * @remarks\n * Reader-backed primitives serialise as **handles**: `encode()` writes a `{ tag, locator }` descriptor;\n * `decode()` looks up the resolver registered for `tag` and calls it with the `locator` to produce a\n * working {@link @nhtio/adk!MediaReader} / {@link @nhtio/adk!SpoolReader}. The resolver closure is where\n * the *live binding* the locator cannot carry — a flydrive `Disk`, an OPFS root, `fetch` — is\n * re-injected.\n *\n * Two separate registries (media vs spool) so a `media:` tag can never resolve to a spool reader or vice\n * versa. The `register*` functions are re-exported from the `@nhtio/adk/batteries/encoding` battery for\n * consumers; the `resolve*` functions are called by the primitives' `[DECODE_METHOD]`.\n */\n\nimport { E_NO_READER_RESOLVER } from '../exceptions/runtime'\nimport type { MediaReader } from './media_reader'\nimport type { SpoolReader } from './spool_reader'\nimport type { LocatorValue, ReaderDescriptor } from './reader_descriptor'\n\n/**\n * A factory that re-binds a descriptor's `locator` to a live {@link MediaReader}.\n *\n * @param locator - The JSON pointer captured at encode time.\n * @returns A working media reader over the same bytes.\n */\nexport type MediaReaderResolver = (locator: LocatorValue) => MediaReader\n\n/**\n * A factory that re-binds a descriptor's `locator` to a live {@link SpoolReader}.\n *\n * @param locator - The JSON pointer captured at encode time.\n * @returns A working spool reader over the same bytes.\n */\nexport type SpoolReaderResolver = (locator: LocatorValue) => SpoolReader\n\nconst mediaResolvers = new Map<string, MediaReaderResolver>()\nconst spoolResolvers = new Map<string, SpoolReaderResolver>()\n\n/**\n * Register (or replace) the resolver that re-binds a `media:` reader handle on decode.\n *\n * @remarks\n * Call once at application startup, before `decode()`. For durable stores, the resolver closure must\n * capture the same live binding the bytes were written with (e.g. the `fetch`-equivalent, an HTTP\n * client). The in-memory and fetch resolvers auto-register when the encoding battery loads; you only\n * register custom or durable ones yourself. Idempotent: re-registering the same `tag` overwrites.\n *\n * @param tag - The descriptor tag this resolver handles (e.g. `\"media:in-memory\"`).\n * @param resolver - Factory turning a captured locator back into a live {@link MediaReader}.\n */\nexport const registerMediaReaderResolver = (tag: string, resolver: MediaReaderResolver): void => {\n  mediaResolvers.set(tag, resolver)\n}\n\n/**\n * Register (or replace) the resolver that re-binds a `spool:` reader handle on decode.\n *\n * @remarks\n * Call once at application startup, before `decode()`. Durable-store resolvers (flydrive, OPFS) must\n * capture the live `Disk`/OPFS root — the locator carries only the key. The in-memory resolver\n * auto-registers when the encoding battery loads. Idempotent: re-registering the same `tag` overwrites.\n *\n * @param tag - The descriptor tag this resolver handles (e.g. `\"spool:flydrive\"`).\n * @param resolver - Factory turning a captured locator back into a live {@link SpoolReader}.\n */\nexport const registerSpoolReaderResolver = (tag: string, resolver: SpoolReaderResolver): void => {\n  spoolResolvers.set(tag, resolver)\n}\n\n/**\n * Re-bind a media reader descriptor to a live {@link MediaReader}.\n *\n * @remarks\n * Called by {@link @nhtio/adk!Media}'s `[DECODE_METHOD]`. Throws if no resolver is registered for the\n * descriptor's `tag` — the fix is to register one (with its live binding) before decoding.\n *\n * @param descriptor - The handle captured at encode time.\n * @returns A working media reader.\n * @throws {@link @nhtio/adk!E_NO_READER_RESOLVER} when no resolver is registered for `descriptor.tag`.\n */\nexport const resolveMediaReader = (descriptor: ReaderDescriptor): MediaReader => {\n  const resolver = mediaResolvers.get(descriptor.tag)\n  if (!resolver) {\n    throw new E_NO_READER_RESOLVER([descriptor.tag])\n  }\n  return resolver(descriptor.locator)\n}\n\n/**\n * Re-bind a spool reader descriptor to a live {@link SpoolReader}.\n *\n * @remarks\n * Called by {@link @nhtio/adk!SpooledArtifact}'s `[DECODE_METHOD]` (and its subclasses'). Throws if no\n * resolver is registered for the descriptor's `tag`.\n *\n * @param descriptor - The handle captured at encode time.\n * @returns A working spool reader.\n * @throws {@link @nhtio/adk!E_NO_READER_RESOLVER} when no resolver is registered for `descriptor.tag`.\n */\nexport const resolveSpoolReader = (descriptor: ReaderDescriptor): SpoolReader => {\n  const resolver = spoolResolvers.get(descriptor.tag)\n  if (!resolver) {\n    throw new E_NO_READER_RESOLVER([descriptor.tag])\n  }\n  return resolver(descriptor.locator)\n}\n","import { validator } from '@nhtio/validation'\nimport { isInstanceOf } from '../utils/guards'\nimport { ArtifactTool } from './artifact_tool'\nimport { ToolRegistry } from './tool_registry'\nimport { Tokenizable, TokenEncoding } from './tokenizable'\nimport { implementsSpoolReader } from '../contracts/spool_reader'\nimport { resolveSpoolReader } from '../contracts/reader_resolvers'\nimport { ENCODE_METHOD, DECODE_METHOD } from '../utils/encoder_symbols'\nimport {\n  E_NOT_A_SPOOL_READER,\n  E_READER_NOT_DESCRIBABLE,\n  E_ARTIFACT_ID_COLLISION,\n} from '../exceptions/runtime'\nimport type { ObjectSchema } from '@nhtio/validation'\nimport type { AdkEncodableSnapshot } from './encodable'\nimport type { SpoolReader } from '../contracts/spool_reader'\nimport type { DispatchContext } from '../contracts/dispatch_context'\nimport type { ReaderDescriptor } from '../contracts/reader_descriptor'\n\n/**\n * Constructor signature for {@link SpooledArtifact} and any subclass.\n *\n * @remarks\n * Used by {@link @nhtio/adk!Tool} to declare the artifact subclass the consumer should use when wrapping\n * the handler's serialised output. The variadic rest parameter accommodates subclass-specific\n * constructor arguments (e.g. `SpooledJsonArtifact(reader, format?)`).\n *\n * @typeParam A - The {@link SpooledArtifact} subtype the constructor produces.\n */\nexport type SpooledArtifactConstructor<A extends SpooledArtifact = SpooledArtifact> = new (\n  reader: SpoolReader,\n  ...rest: any[]\n) => A\n\n/**\n * Metadata table entry for one of the artifact's existing query methods, used by\n * {@link SpooledArtifact.forgeTools} to surface that method as an {@link @nhtio/adk!ArtifactTool}.\n *\n * @remarks\n * This is a metadata shape, not a general method → tool pipeline. `forgeTools` knows how to\n * marshal arguments for a fixed, closed set of method names (the base seven on\n * {@link SpooledArtifact} and the JSON/Markdown methods on the bundled subclasses); a\n * descriptor is the place to attach a tool name, description, args schema, and optional\n * serializer to one of those methods. Adding a descriptor for a method whose name is not\n * in that closed set will produce a tool whose handler invokes the method with no arguments.\n *\n * For new methods that require custom argument marshalling, branching, multi-step logic,\n * cross-artifact joins, or any other behaviour beyond \"call this existing method,\" override\n * {@link SpooledArtifact.forgeTools} and mint the {@link @nhtio/adk!ArtifactTool} directly — do not try\n * to express it through a descriptor.\n *\n * Zero-arg methods are the exception: a descriptor with no `argsSchema` (or one that adds\n * only `callId`-adjacent fields you don't consume) works for any method that takes no\n * arguments, regardless of name.\n */\nexport interface ToolMethodDescriptor {\n  /** Absolute tool name as exposed to the LLM (e.g. `'artifact_head'`). */\n  name: string\n  /** Method to invoke on the resolved artifact instance (e.g. `'head'`, `'json_get'`). */\n  method: string\n  /** Human-readable description passed to the model. Should mention \"in this turn\" so the model understands the artifact's lifecycle scope. */\n  description: string\n  /** Schema for the method's own args, NOT including `callId`. `forgeTools()` injects `callId`. */\n  argsSchema?: ObjectSchema\n  /** Optional formatter for non-string return values. Defaults: string → as-is; string[] → newline-join; number → `String(n)`; otherwise `JSON.stringify(value, null, 2)`. */\n  serialise?: (result: unknown) => string\n}\n\n/**\n * Returns the effective artifact tool descriptors from a constructor's static prototype chain.\n *\n * Core subclasses declare only their own `toolMethods`; a static property shadows its ancestor\n * rather than concatenating it. This walks leaf-first and deduplicates by `name`, the absolute\n * model-facing tool identifier, rather than `method`, which is the instance dispatch name and may\n * legitimately differ (and is not the vocabulary exposed to callers). The nearest declaration\n * wins, matching tool collision replacement semantics.\n *\n * @param ctor The artifact constructor whose effective descriptors are wanted.\n * @returns A frozen, leaf-first array of descriptors, or an empty array when none are declared.\n */\nexport const effectiveToolMethods = (ctor: unknown): readonly ToolMethodDescriptor[] => {\n  const seen = new Set<string>()\n  const out: ToolMethodDescriptor[] = []\n  // Only objects and functions have a prototype chain; a null/undefined/primitive ctor has no\n  // declarations and `Object.getPrototypeOf` would throw on it.\n  let current: unknown = typeof ctor === 'object' || typeof ctor === 'function' ? ctor : null\n  while (current !== null && current !== Function.prototype) {\n    const own = Object.getOwnPropertyDescriptor(current, 'toolMethods')\n    // Read via the descriptor's getter when it is accessor-backed, so a `static get toolMethods()`\n    // declaration is honoured rather than silently skipped (a data descriptor has `value`).\n    const value = own\n      ? 'get' in own && typeof own.get === 'function'\n        ? own.get.call(current)\n        : own.value\n      : undefined\n    if (Array.isArray(value)) {\n      for (const descriptor of value as unknown[]) {\n        if (\n          descriptor &&\n          typeof descriptor === 'object' &&\n          typeof (descriptor as ToolMethodDescriptor).name === 'string' &&\n          !seen.has((descriptor as ToolMethodDescriptor).name)\n        ) {\n          const method = descriptor as ToolMethodDescriptor\n          seen.add(method.name)\n          out.push(method)\n        }\n      }\n    }\n    current = Object.getPrototypeOf(current)\n  }\n  return Object.freeze(out)\n}\n\nconst noArgsSchema = validator.object<Record<string, never>>({})\n\n/**\n * Collect artifact IDs from the current turn's tool calls and retrievables that are instances of a specific artifact class.\n *\n * @param ctx - The dispatch context containing tool calls and retrievables.\n * @param requires - The artifact class constructor to filter by.\n * @returns An array of unique artifact IDs matching the specified class, with collision detection.\n * @throws {@link @nhtio/adk!E_ARTIFACT_ID_COLLISION} when the same ID appears in both tool calls and retrievables.\n */\nexport function collectArtifactCompatibleIds(\n  ctx: {\n    turnToolCalls: Iterable<{\n      id: string\n      fromArtifactTool?: boolean\n      results: unknown\n    }>\n    turnRetrievables: Iterable<{\n      id: string\n      inline: boolean\n      content: unknown\n    }>\n  },\n  requires: SpooledArtifactConstructor\n): string[] {\n  const toolIds = [...ctx.turnToolCalls]\n    .filter((tc) => !tc.fromArtifactTool && isInstanceOf(tc.results, requires.name, requires))\n    .map((tc) => tc.id)\n  const retrievableIds = [...(ctx.turnRetrievables ?? [])]\n    .filter((r) => !r.inline && isInstanceOf(r.content, requires.name, requires))\n    .map((r) => r.id)\n  const collisions = retrievableIds.filter((id) => toolIds.includes(id))\n  if (collisions.length > 0) throw new E_ARTIFACT_ID_COLLISION([collisions[0]])\n  return [...new Set([...toolIds, ...retrievableIds])]\n}\n\n/**\n * Resolve an artifact by ID from the current turn's tool calls or retrievables, with type narrowing.\n *\n * @param ctx - The dispatch context containing tool calls and retrievables.\n * @param id - The artifact ID to resolve.\n * @param requires - The artifact class constructor to narrow by; only artifacts of this type are returned.\n * @returns The resolved artifact and its source (tool call or retrievable), or undefined if not found.\n */\nexport function resolveArtifactById(\n  ctx: {\n    turnToolCalls: Iterable<{\n      id: string\n      fromArtifactTool?: boolean\n      results: unknown\n    }>\n    turnRetrievables: Iterable<{\n      id: string\n      inline: boolean\n      content: unknown\n    }>\n  },\n  id: string,\n  requires: SpooledArtifactConstructor\n): { artifact: SpooledArtifact; source: 'toolCall' | 'retrievable' } | undefined {\n  const tc = [...ctx.turnToolCalls].find((t) => t.id === id && !t.fromArtifactTool)\n  if (tc && isInstanceOf(tc.results, requires.name, requires))\n    return { artifact: tc.results, source: 'toolCall' }\n  const r = [...(ctx.turnRetrievables ?? [])].find((v) => v.id === id && !v.inline)\n  if (r && isInstanceOf(r.content, requires.name, requires))\n    return { artifact: r.content, source: 'retrievable' }\n  return undefined\n}\n\n/**\n * Default serialiser for {@link @nhtio/adk!ArtifactTool} handler return values when a descriptor does not\n * provide its own. Exported for reuse by subclass `forgeTools` overrides.\n *\n * @param result - The artifact-method return value.\n * @returns A string suitable for inclusion in an LLM tool-call response.\n */\nexport const defaultSerialise = (result: unknown): string => {\n  if (result === undefined) return '(undefined)'\n  if (result === null) return 'null'\n  if (typeof result === 'string') return result\n  if (Array.isArray(result) && result.every((r) => typeof r === 'string')) {\n    if (result.length === 0) return '(empty list)'\n    return (result as string[]).join('\\n')\n  }\n  if (typeof result === 'number') return String(result)\n  return JSON.stringify(result, null, 2)\n}\n\nconst baseToolMethods: ReadonlyArray<ToolMethodDescriptor> = Object.freeze([\n  {\n    name: 'artifact_head',\n    method: 'head',\n    description:\n      'Return the first n lines of a spooled artifact produced earlier in this turn. Takes a callId selecting which artifact to inspect.',\n    argsSchema: validator.object({\n      n: validator.number().integer().min(1).default(10).description('Number of lines to return.'),\n    }),\n  },\n  {\n    name: 'artifact_tail',\n    method: 'tail',\n    description:\n      'Return the last n lines of a spooled artifact produced earlier in this turn. Takes a callId selecting which artifact to inspect.',\n    argsSchema: validator.object({\n      n: validator.number().integer().min(1).default(10).description('Number of lines to return.'),\n    }),\n  },\n  {\n    name: 'artifact_grep',\n    method: 'grep',\n    description:\n      'Return all lines matching a regular expression pattern from a spooled artifact produced earlier in this turn.',\n    argsSchema: validator.object({\n      pattern: validator\n        .string()\n        .required()\n        .description('Regular expression pattern, applied via JavaScript RegExp.'),\n      flags: validator\n        .string()\n        .allow('')\n        .pattern(/^[imsu]*$/)\n        .optional()\n        .description(\n          \"Optional RegExp flags. Allowed: 'i' (case-insensitive), 'm' (multiline), 's' (dotAll), 'u' (unicode). 'g' and 'y' are disallowed because per-line matching is stateless.\"\n        ),\n    }),\n  },\n  {\n    name: 'artifact_cat',\n    method: 'cat',\n    description:\n      'Return lines from a spooled artifact produced earlier in this turn, optionally bounded to a range.',\n    argsSchema: validator.object({\n      start: validator.number().integer().min(0).optional().description('Start line (inclusive).'),\n      end: validator.number().integer().min(0).optional().description('End line (exclusive).'),\n    }),\n  },\n  {\n    name: 'artifact_byte_length',\n    method: 'byteLength',\n    description:\n      'Return the total byte length of a spooled artifact produced earlier in this turn.',\n    argsSchema: noArgsSchema,\n  },\n  {\n    name: 'artifact_line_count',\n    method: 'lineCount',\n    description: 'Return the total line count of a spooled artifact produced earlier in this turn.',\n    argsSchema: noArgsSchema,\n  },\n  {\n    name: 'artifact_estimate_tokens',\n    method: 'estimateTokens',\n    description:\n      'Estimate the total token count of a spooled artifact produced earlier in this turn under a named encoding.',\n    argsSchema: validator.object({\n      encoding: validator\n        .string()\n        .valid(...TokenEncoding)\n        .required()\n        .description('Token encoding identifier.'),\n    }),\n  },\n])\n\n/**\n * A lazy, line-oriented view over an arbitrary backing store.\n *\n * @remarks\n * All I/O methods are async to remain compatible with both in-memory and streaming\n * {@link @nhtio/adk!SpoolReader} implementations. Token estimation delegates to\n * {@link @nhtio/adk!Tokenizable.estimateTokens} — the same backends used elsewhere in the ADK.\n *\n * The class is read-only by design: mutation of the underlying data is the responsibility of the\n * producer that created the {@link @nhtio/adk!SpoolReader}, not the consumer reading from this artifact.\n */\nexport class SpooledArtifact {\n  /**\n   * The set of artifact-query methods this class surfaces via {@link SpooledArtifact.forgeTools}.\n   *\n   * @remarks\n   * The base set covers the generic line-oriented operations every artifact supports:\n   * `artifact_head`, `artifact_tail`, `artifact_grep`, `artifact_cat`, `artifact_byte_length`,\n   * `artifact_line_count`, `artifact_estimate_tokens`. Each `toolMethods` array lists **only**\n   * its own class's descriptors — subclasses do not concatenate inherited descriptors. The\n   * subclass instead overrides {@link SpooledArtifact.forgeTools} to merge the base registry\n   * (produced by `SpooledArtifact.forgeTools(ctx)`) with its own — see\n   * {@link @nhtio/adk!SpooledJsonArtifact.forgeTools} and {@link @nhtio/adk!SpooledMarkdownArtifact.forgeTools} for\n   * the canonical shape and the pattern downstream consumers should follow when building\n   * their own `SpooledArtifact` subclasses.\n   *\n   * Tool names are absolute (not subclass-prefixed). Forged tools carry\n   * `Tool.onCollision = 'replace'` so merging multiple subclasses' `forgeTools()` outputs is\n   * silent — every same-named tool dispatches the same method on whatever artifact the\n   * `callId` resolves to, so the overlap is behaviourally interchangeable.\n   *\n   * Frozen at module load.\n   */\n  public static toolMethods: ReadonlyArray<ToolMethodDescriptor> = baseToolMethods\n\n  #reader: SpoolReader\n  #sizeHints: { byteLength: number; lineCount: number } | undefined\n\n  /**\n   * @param reader - The backing store to read from.\n   * @throws {@link @nhtio/adk!E_NOT_A_SPOOL_READER} when `reader` does not implement {@link @nhtio/adk!SpoolReader}.\n   */\n  constructor(reader: SpoolReader) {\n    if (!implementsSpoolReader(reader)) {\n      throw new E_NOT_A_SPOOL_READER()\n    }\n    this.#reader = reader\n  }\n\n  /**\n   * Emit the backing reader's serialisable {@link ReaderDescriptor}, or throw if it cannot describe\n   * itself.\n   *\n   * @remarks\n   * `protected` so subclasses can build their own encode snapshots over the base reader (which is\n   * otherwise private). Throws {@link @nhtio/adk!E_READER_NOT_DESCRIBABLE} when the reader has no\n   * `describe()` (or returns `undefined`) — there is no serialisable handle to write.\n   *\n   * @returns The reader's tagged handle descriptor.\n   * @throws {@link @nhtio/adk!E_READER_NOT_DESCRIBABLE} when the reader is not describable.\n   */\n  protected readerDescriptor(): ReaderDescriptor {\n    const descriptor = this.#reader.describe?.()\n    if (!descriptor) {\n      throw new E_READER_NOT_DESCRIBABLE(['reader'])\n    }\n    return descriptor\n  }\n\n  /**\n   * Serialise this SpooledArtifact into an `@nhtio/encoder` snapshot — the reader **handle**, not the\n   * bytes.\n   *\n   * @remarks\n   * Emits the backing reader's {@link ReaderDescriptor}; decode re-binds the reader through the\n   * registered resolver. Throws {@link @nhtio/adk!E_READER_NOT_DESCRIBABLE} when the reader cannot\n   * describe itself. Subclasses override this to include their own discriminators (e.g.\n   * {@link @nhtio/adk!SpooledJsonArtifact} adds `format`).\n   *\n   * @returns A snapshot consumed by {@link SpooledArtifact.[DECODE_METHOD]}.\n   */\n  [ENCODE_METHOD](): AdkEncodableSnapshot {\n    return { reader: this.readerDescriptor() }\n  }\n\n  /**\n   * Reconstruct a {@link SpooledArtifact} from an {@link SpooledArtifact.[ENCODE_METHOD]} snapshot.\n   *\n   * @remarks\n   * Re-binds the reader via {@link @nhtio/adk!resolveSpoolReader}; throws\n   * {@link @nhtio/adk!E_NO_READER_RESOLVER} when no resolver is registered for the descriptor's tag.\n   *\n   * @param data - The snapshot produced by {@link SpooledArtifact.[ENCODE_METHOD]}.\n   * @returns A fresh {@link SpooledArtifact} backed by a freshly-resolved reader.\n   */\n  static [DECODE_METHOD](data: AdkEncodableSnapshot): SpooledArtifact {\n    const snapshot = data as { reader: ReaderDescriptor }\n    return new SpooledArtifact(resolveSpoolReader(snapshot.reader))\n  }\n\n  /**\n   * Returns the line at the given 0-based index, or `undefined` when out of range.\n   *\n   * @remarks\n   * Protected so subclasses can scan the backing store line-by-line without allocating\n   * intermediate arrays. Delegates directly to the {@link @nhtio/adk!SpoolReader}.\n   *\n   * @param index - 0-based line index.\n   * @returns The raw line string, or `undefined` when out of range.\n   */\n  protected async line(index: number): Promise<string | undefined> {\n    return this.#reader.line(index)\n  }\n\n  /**\n   * Returns `true` if `value` is a {@link SpooledArtifact} instance (including any subclass).\n   *\n   * @remarks\n   * Uses the cross-realm-safe {@link @nhtio/adk!isInstanceOf} guard: `instanceof` first, then\n   * `Symbol.hasInstance`, then a `constructor.name` fallback. Subclass instances (e.g.\n   * {@link @nhtio/adk!SpooledJsonArtifact}) satisfy this guard because `instanceof` walks the prototype\n   * chain. The fallbacks handle the dual-module-copy case where two distinct `SpooledArtifact`\n   * classes coexist in the same realm (e.g. one bundled into a downstream library, one in the\n   * consumer's `node_modules`).\n   *\n   * @param value - The value to test.\n   * @returns `true` when `value` is a {@link SpooledArtifact} instance.\n   */\n  public static isSpooledArtifact(value: unknown): value is SpooledArtifact {\n    return isInstanceOf(value, 'SpooledArtifact', SpooledArtifact)\n  }\n\n  /**\n   * Returns `true` if `value` is a constructor function whose prototype chain includes\n   * {@link SpooledArtifact} (including `SpooledArtifact` itself).\n   *\n   * @remarks\n   * Used by {@link @nhtio/adk!Tool} to validate the optional `artifactConstructor` field. Performs an\n   * `instanceof`-based check on the prototype chain; falls back to a duck-type test that looks\n   * for the canonical SpooledArtifact instance methods on `value.prototype` for cross-realm\n   * safety (constructors passed from a different module copy or VM context).\n   *\n   * @param value - The value to test.\n   * @returns `true` when `value` is a constructor for `SpooledArtifact` or a subclass.\n   */\n  public static isSpooledArtifactConstructor(\n    value: unknown\n  ): value is SpooledArtifactConstructor<SpooledArtifact> {\n    if (typeof value !== 'function') return false\n    if (value === SpooledArtifact) return true\n    const proto = (value as { prototype?: unknown }).prototype\n    if (proto === undefined || proto === null) return false\n    if (isInstanceOf(proto, 'SpooledArtifact', SpooledArtifact)) return true\n    // Cross-realm duck-type fallback: prototype carries the canonical SpooledArtifact methods\n    const methods = ['head', 'tail', 'grep', 'cat', 'byteLength', 'lineCount', 'estimateTokens']\n    return methods.every((m) => typeof (proto as Record<string, unknown>)[m] === 'function')\n  }\n\n  /**\n   * Returns the first `n` lines of the artifact.\n   *\n   * @remarks\n   * If the artifact contains fewer than `n` lines, all available lines are returned. Matches the\n   * behaviour of POSIX `head -n`.\n   *\n   * @param n - Number of lines to return. Defaults to 10.\n   * @returns Array of line strings, without trailing newlines.\n   */\n  async head(n: number = 10): Promise<string[]> {\n    const count = await this.#reader.lineCount()\n    const limit = Math.min(n, count)\n    const lines: string[] = []\n    for (let i = 0; i < limit; i++) {\n      const line = await this.#reader.line(i)\n      if (line !== undefined) {\n        lines.push(line)\n      }\n    }\n    return lines\n  }\n\n  /**\n   * Returns the last `n` lines of the artifact.\n   *\n   * @remarks\n   * If the artifact contains fewer than `n` lines, all available lines are returned. Matches the\n   * behaviour of POSIX `tail -n`.\n   *\n   * @param n - Number of lines to return. Defaults to 10.\n   * @returns Array of line strings, without trailing newlines.\n   */\n  async tail(n: number = 10): Promise<string[]> {\n    const count = await this.#reader.lineCount()\n    const start = Math.max(0, count - n)\n    const lines: string[] = []\n    for (let i = start; i < count; i++) {\n      const line = await this.#reader.line(i)\n      if (line !== undefined) {\n        lines.push(line)\n      }\n    }\n    return lines\n  }\n\n  /**\n   * Returns all lines that match `pattern`.\n   *\n   * @remarks\n   * Behaves like POSIX `grep`: each line is tested against the pattern and included in the result\n   * when it matches. The pattern is applied as a JavaScript `RegExp`; flags (e.g. case-\n   * insensitivity) should be encoded in the expression itself.\n   *\n   * Stateful flags (`g`, `y`) on the supplied `RegExp` would normally cause `pattern.test()` to\n   * advance `lastIndex` across calls, producing skipped matches and order-dependent results. To\n   * keep the per-line semantics stateless, `grep` resets `pattern.lastIndex` to `0` before each\n   * line test. The forged `artifact_grep` tool also rejects `g` and `y` flags up-front at schema\n   * validation time.\n   *\n   * @param pattern - The regular expression to test each line against.\n   * @returns Array of matching line strings, in order.\n   */\n  async grep(pattern: RegExp): Promise<string[]> {\n    const count = await this.#reader.lineCount()\n    const matches: string[] = []\n    for (let i = 0; i < count; i++) {\n      const line = await this.#reader.line(i)\n      if (line !== undefined) {\n        pattern.lastIndex = 0\n        if (pattern.test(line)) {\n          matches.push(line)\n        }\n      }\n    }\n    return matches\n  }\n\n  /**\n   * Returns lines from the artifact, optionally bounded to a range.\n   *\n   * @remarks\n   * Without arguments, returns all lines — equivalent to POSIX `cat`. With `start` and/or `end`,\n   * behaves like `Array.prototype.slice`: `start` defaults to `0`, `end` defaults to the total\n   * line count, and only lines in `[start, end)` are fetched from the backing store. For large\n   * artifacts, prefer a bounded range or {@link SpooledArtifact.head} / {@link SpooledArtifact.tail}.\n   *\n   * @param start - 0-based start line index (inclusive). Defaults to `0`.\n   * @param end - 0-based end line index (exclusive). Defaults to `lineCount()`.\n   * @returns Array of line strings in the requested range.\n   */\n  async cat(start?: number, end?: number): Promise<string[]> {\n    const count = await this.#reader.lineCount()\n    const from = Math.max(0, start ?? 0)\n    const to = Math.min(count, end ?? count)\n    const lines: string[] = []\n    for (let i = from; i < to; i++) {\n      const line = await this.#reader.line(i)\n      if (line !== undefined) {\n        lines.push(line)\n      }\n    }\n    return lines\n  }\n\n  /**\n   * Returns the total byte length of the underlying data.\n   *\n   * @returns The byte length as reported by the {@link @nhtio/adk!SpoolReader}.\n   */\n  async byteLength(): Promise<number> {\n    return this.#sizeHints?.byteLength ?? this.#reader.byteLength()\n  }\n\n  /**\n   * Cache producer-computed size metadata for synchronous handle estimation.\n   *\n   * @param hints - The byte length and line count of the backing content.\n   * @internal\n   */\n  _setSizeHints(hints: { byteLength: number; lineCount: number }): void {\n    this.#sizeHints = hints\n  }\n\n  /**\n   * Returns whether producer-computed size metadata is available.\n   *\n   * @returns `true` when {@link SpooledArtifact._setSizeHints} has populated the cache.\n   */\n  hasSizeHints(): boolean {\n    return this.#sizeHints !== undefined\n  }\n\n  /**\n   * Returns the total number of lines in the artifact.\n   *\n   * @returns The line count as reported by the {@link @nhtio/adk!SpoolReader}.\n   */\n  async lineCount(): Promise<number> {\n    return this.#sizeHints?.lineCount ?? this.#reader.lineCount()\n  }\n\n  /**\n   * Estimates tokens for the exact handle-body metadata rendered for this artifact.\n   *\n   * The fallback renderer is an interim core-safe implementation. Its output is deliberately\n   * specified here for fan-in parity: the lines are the fixed prose and metadata strings below,\n   * with one `\\n` separator, and method entries formatted as `- name — description` (or `- name`).\n   * The canonical renderer in `chat_common` should eventually delegate to this builder rather than\n   * maintain a second copy.\n   *\n   * @param callId - The turn-local artifact identifier.\n   * @param encoding - The token encoding used for estimation.\n   * @param renderer - Optional renderer overriding the interim default.\n   * @returns A synchronous token estimate.\n   */\n  estimateHandleTokens(\n    callId: string,\n    encoding: TokenEncoding,\n    renderer?: (input: {\n      callId: string\n      artifact: unknown\n      byteLength: number\n      lineCount: number\n      estimatedTokens?: number\n      encoding?: string\n    }) => string\n  ): number {\n    if (!this.#sizeHints)\n      throw new Error('Cannot estimate artifact handle tokens without size hints')\n    const render =\n      renderer ??\n      ((input) => {\n        const ctor = (\n          input.artifact as {\n            constructor?: {\n              name?: string\n              toolMethods?: ReadonlyArray<{\n                name: string\n                description?: string\n              }>\n            }\n          }\n        ).constructor\n        const methods = effectiveToolMethods(ctor ?? {})\n        return [\n          'This tool returned a large artifact that was not inlined to preserve context budget.',\n          '',\n          'Artifact metadata:',\n          `- callId: ${input.callId}`,\n          `- kind: ${ctor?.name ?? 'SpooledArtifact'}`,\n          `- byteLength: ${input.byteLength}`,\n          `- lineCount: ${input.lineCount}`,\n          '',\n          `To read this artifact in this turn, call one of the following tools with`,\n          `callId=${input.callId}:`,\n          ...methods.map((m) => (m.description ? `- ${m.name} — ${m.description}` : `- ${m.name}`)),\n          '',\n          `The artifact persists in this turn's context — multiple queries against the same callId are allowed and efficient. Do not assume the body has been inlined anywhere else.`,\n        ].join('\\n')\n      })\n    const text = render({ callId, artifact: this, ...this.#sizeHints })\n    return Tokenizable.estimateTokens(text, encoding)\n  }\n\n  /**\n   * Estimates the total token count of the artifact under `encoding`.\n   *\n   * @remarks\n   * Reads the full byte-faithful content via {@link SpooledArtifact.asString} (which delegates to\n   * {@link @nhtio/adk!SpoolReader.readAll}) and delegates to {@link @nhtio/adk!Tokenizable.estimateTokens}. The estimate\n   * therefore reflects the actual source bytes — including trailing newlines and non-`\\n` line\n   * terminators that the line-based {@link SpooledArtifact.cat} view would otherwise discard or\n   * misrepresent.\n   *\n   * @param encoding - The encoding identifier to use for counting.\n   * @returns The estimated number of tokens.\n   */\n  async estimateTokens(encoding: TokenEncoding): Promise<number> {\n    const content = await this.#reader.readAll()\n    return Tokenizable.estimateTokens(content, encoding)\n  }\n\n  /**\n   * Returns the full artifact body as a single byte-faithful string.\n   *\n   * @remarks\n   * Round-trip faithful to whatever bytes the {@link @nhtio/adk!SpoolReader} was constructed over —\n   * preserves trailing newlines and non-`\\n` line terminators that {@link SpooledArtifact.cat}\n   * discards via its line-based view. This is the canonical primitive for \"inline the artifact\n   * content directly into a message\" use cases.\n   *\n   * `asString()` and the static `forgeTools(ctx)` factory on each subclass are independent\n   * alternatives — a consumer chooses per turn whether to inline the body in a message\n   * (`await tc.results.asString()`) or hand the model query tools\n   * (`SpooledArtifact.forgeTools(ctx)`). Neither calls the other; either works with neither.\n   *\n   * @returns The full content as a single string.\n   */\n  async asString(): Promise<string> {\n    return this.#reader.readAll()\n  }\n\n  /**\n   * Forges a fresh {@link @nhtio/adk!ToolRegistry} of ephemeral {@link @nhtio/adk!ArtifactTool} instances that let the\n   * LLM query artifacts already present in `ctx.turnToolCalls`.\n   *\n   * @remarks\n   * Standard subclass extension pattern — each class owns only its own `toolMethods` and its\n   * own `forgeTools`. The base `SpooledArtifact.forgeTools(ctx)` narrows the `callId` enum to\n   * any `tc.results instanceof SpooledArtifact` (so subclass instances are included — that's\n   * the whole point of inheritance) and dispatches the seven base methods (`head`, `tail`,\n   * `grep`, `cat`, `byteLength`, `lineCount`, `estimateTokens`) on the resolved artifact.\n   * Subclasses override `forgeTools` to call this static first and then register their own\n   * tools on the returned registry — see {@link @nhtio/adk!SpooledJsonArtifact.forgeTools} and\n   * {@link @nhtio/adk!SpooledMarkdownArtifact.forgeTools} for the canonical shape. There is no\n   * `requiresSubclass` field, no helper indirection, and no `this`-based class narrowing —\n   * just plain `instanceof ThisClass` at each subclass's own filter site.\n   *\n   * For each descriptor in this class's `toolMethods`, the factory:\n   *\n   * 1. Walks `ctx.turnToolCalls` to find `ToolCall`s whose `results instanceof SpooledArtifact`.\n   *    `ToolCall`s flagged `fromArtifactTool === true` are excluded — they carry a\n   *    {@link @nhtio/adk!Tokenizable}, not a `SpooledArtifact`, and including them would let the model\n   *    `artifact_grep` on a previous `artifact_grep` result (an infinite-recursion hazard with\n   *    no semantic value).\n   * 2. Returns an empty registry if no compatible callIds are found — no point shipping tools\n   *    whose `callId` enum is empty.\n   * 3. Otherwise mints an {@link @nhtio/adk!ArtifactTool} with `ephemeral: true` and `onCollision: 'replace'`\n   *    so multiple `Subclass.forgeTools(ctx)` outputs merge silently. The tool's `inputSchema`\n   *    includes a required `callId` field with `.valid(...compatibleIds)`, plus the descriptor's\n   *    own `argsSchema` fields.\n   *\n   * The handler resolves the artifact via `[...ctx.turnToolCalls].find(t => t.id === callId)`,\n   * dispatches the descriptor's method, and serialises the return value (string → as-is;\n   * string[] → newline-join; number → `String(n)`; otherwise `JSON.stringify(value, null, 2)`;\n   * `descriptor.serialise` overrides the defaults). `grep` is special-cased: the handler\n   * constructs `new RegExp(pattern, flags ?? '')` before invoking the artifact's `grep` method.\n   *\n   * The returned registry must be merged into the consumer's main registry and the main\n   * registry must be bound to `ctx` via {@link @nhtio/adk!ToolRegistry.bindContext}:\n   *\n   * ```ts\n   * const executor: DispatchExecutorFn = async (ctx) => {\n   *   const forged = SpooledArtifact.forgeTools(ctx)\n   *   const merged = ToolRegistry.merge([main, forged])\n   *   main.bindContext(ctx)\n   *   const result = await llm.invoke({ tools: merged.all(), ... })\n   *   ctx.ack() // ← ephemeral cleanup fires here\n   * }\n   * ```\n   *\n   * @warning You **must** call `registry.bindContext(ctx)` on the registry hosting these tools,\n   * or ephemeral cleanup will not run and the `callId` enum in subsequent executor calls will\n   * be stale (excluding new tool calls produced in the meantime).\n   *\n   * @param ctx - The execution context whose `turnToolCalls` snapshot defines the `callId` enum.\n   * @returns A fresh `ToolRegistry`. Empty when `turnToolCalls` contains no compatible artifacts.\n   *\n   * @see {@link @nhtio/adk!ToolRegistry.bindContext}\n   * @see {@link @nhtio/adk!ToolRegistry.merge}\n   * @see {@link @nhtio/adk!DispatchContext.onAck}\n   */\n  public static forgeTools(ctx: DispatchContext): ToolRegistry {\n    const requires: SpooledArtifactConstructor = SpooledArtifact\n    const compatibleIds = collectArtifactCompatibleIds(ctx, requires)\n    if (compatibleIds.length === 0) return new ToolRegistry([])\n\n    const tools: ArtifactTool[] = []\n    for (const descriptor of this.toolMethods) {\n      const callIdSchema = validator\n        .string()\n        .valid(...compatibleIds)\n        .required()\n        .description('ToolCall id of the artifact to query.')\n\n      const argsSchema = (descriptor.argsSchema ?? noArgsSchema).append({\n        callId: callIdSchema,\n      })\n\n      const serialise = descriptor.serialise ?? defaultSerialise\n\n      const tool = new ArtifactTool({\n        name: descriptor.name,\n        description: descriptor.description,\n        inputSchema: argsSchema,\n        ephemeral: true,\n        onCollision: 'replace',\n        handler: async (rawArgs, ctxInner) => {\n          const args = rawArgs as Record<string, unknown> & { callId: string }\n          const resolved = resolveArtifactById(ctxInner, args.callId, requires)\n          if (!resolved) return `Error: no artifact with id ${args.callId} in this turn`\n          const artifact = resolved.artifact\n          const methodArgs: unknown[] = []\n          if (descriptor.method === 'grep') {\n            const pattern = args.pattern as string\n            const flags = (args.flags as string | undefined) ?? ''\n            methodArgs.push(new RegExp(pattern, flags))\n          } else if (descriptor.method === 'head' || descriptor.method === 'tail') {\n            methodArgs.push((args.n as number | undefined) ?? 10)\n          } else if (descriptor.method === 'cat') {\n            methodArgs.push(args.start as number | undefined, args.end as number | undefined)\n          } else if (descriptor.method === 'estimateTokens') {\n            methodArgs.push(args.encoding as TokenEncoding)\n          }\n          const fn = (artifact as unknown as Record<string, (...a: unknown[]) => unknown>)[\n            descriptor.method\n          ]\n          if (typeof fn !== 'function') {\n            return `Error: artifact has no method ${descriptor.method}`\n          }\n          const result = await Promise.resolve(fn.apply(artifact, methodArgs))\n          return serialise(result)\n        },\n      })\n      tools.push(tool)\n    }\n    return new ToolRegistry(tools)\n  }\n}\n"],"mappings":"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"}