{"version":3,"file":"_shared.mjs","names":[],"sources":["../../../src/batteries/tools/_shared/index.ts"],"sourcesContent":["/**\n * Cross-battery helpers shared by the configured HTTP tool batteries (SearXNG, Scrapper, …).\n *\n * @module @nhtio/adk/batteries/tools/_shared\n *\n * @remarks\n * These are internal building blocks for the *factory-style* tool batteries — the ones that talk\n * to a configured HTTP instance behind custom auth and expose input/output middleware pipelines.\n * Rather than each battery carry its own copy, the common machinery lives here:\n *\n * - {@link resolveArtifact} / {@link resolveArtifactSync} — turn an {@link ArtifactResolver}\n *   (a constructor, a sync resolver, or an async / dynamic-import resolver) into the **sync**\n *   `() => SpooledArtifactConstructor` that `Tool.artifactConstructor` requires. Mirrors the vector\n *   battery's `resolveClientCtor`.\n * - {@link resolveHeaders} — collapse a static header object or a (sync/async) resolver into a\n *   plain header record for one request (refreshable-auth friendly).\n * - {@link runInputPipeline} / {@link runOutputPipeline} — the onion middleware runners (fresh\n *   runner per call, short-circuit + non-terminal detection), generic over the context type.\n *\n * This module imports harness primitives only through their specific subpath barrels\n * (`@nhtio/adk/spooled_artifact`, `@nhtio/adk/forge`, `@nhtio/adk/guards`) per the batteries\n * barrel-only rule.\n */\n\nimport { Middleware } from '@nhtio/middleware'\nimport { SpooledArtifact } from '@nhtio/adk/spooled_artifact'\nimport { isError, isObject, isInstanceOf } from '@nhtio/adk/guards'\nimport type { NextFn } from '@nhtio/middleware'\nimport type { SpooledArtifactConstructor } from '@nhtio/adk/forge'\n\n// ── Header resolution ────────────────────────────────────────────────────────\n\n/** A static set of request headers (used for custom instance authentication). */\nexport type ToolHeaders = Record<string, string>\n\n/**\n * A resolver returning request headers, sync or async. Use this form when the auth token is\n * refreshable — the resolver runs on every request, so a fresh token can be minted per call.\n */\nexport type ToolHeadersResolver = () => ToolHeaders | Promise<ToolHeaders>\n\n/**\n * Resolve the configured headers (a static object or a sync/async resolver) for a single request.\n *\n * @param headers - The static header record, the resolver, or `undefined`.\n * @returns A fresh, owned copy of the resolved headers (`{}` when none supplied).\n */\nexport const resolveHeaders = async (\n  headers: ToolHeaders | ToolHeadersResolver | undefined\n): Promise<ToolHeaders> => {\n  if (typeof headers === 'function') return { ...(await headers()) }\n  return { ...(headers ?? {}) }\n}\n\n// ── Artifact resolver ────────────────────────────────────────────────────────\n\n/** Convenience alias for the spooled-artifact constructor a tool wraps its output in. */\nexport type SpooledArtifactCtor = SpooledArtifactConstructor\n\n/**\n * The artifact configuration accepted by a factory: a constructor, a sync resolver, or an async /\n * dynamic-import resolver (which may yield a module namespace whose `default` is the constructor).\n *\n * @remarks\n * Mirrors the vector battery's `client` resolver and `Tool.artifactConstructor`'s indirection. The\n * async form lets a consumer `() => import('@nhtio/adk/spooled_artifact').then(m => m.SpooledMarkdownArtifact)`\n * so the artifact class never enters their static module graph.\n */\nexport type ArtifactResolver =\n  | SpooledArtifactCtor\n  | (() => SpooledArtifactCtor | { default: SpooledArtifactCtor })\n  | (() => Promise<SpooledArtifactCtor | { default: SpooledArtifactCtor }>)\n\n/** The sync subset of {@link ArtifactResolver} — a constructor or a sync resolver (no Promise). */\nexport type SyncArtifactResolver =\n  | SpooledArtifactCtor\n  | (() => SpooledArtifactCtor | { default: SpooledArtifactCtor })\n\n/** Unwrap a resolved value that may be a module namespace whose `default` is the constructor. */\nconst unwrapDefault = (value: unknown): unknown => {\n  if (isObject(value) && 'default' in value) {\n    const def = (value as { default?: unknown }).default\n    if (SpooledArtifact.isSpooledArtifactConstructor(def)) return def\n  }\n  return value\n}\n\n/**\n * Resolve an {@link ArtifactResolver} to the **sync** `() => SpooledArtifactCtor` that\n * `Tool.artifactConstructor` requires (the wrap-site and the construction-time validator both\n * invoke it synchronously, so an async resolver cannot be passed straight through).\n *\n * @remarks\n * A bare constructor is itself a function, so it is distinguished from a resolver via\n * `SpooledArtifact.isSpooledArtifactConstructor` (the same duck-typed guard the core validator\n * uses) rather than by arity. Async because a dynamic-import resolver must be awaited here.\n *\n * @param resolver - The artifact configuration. When `undefined`, callers should fall back to\n *   their own default (this function rejects `undefined` so the default lives with the caller).\n * @param onInvalid - Throws a battery-scoped error; receives a human-readable reason.\n * @returns A sync `() => SpooledArtifactCtor` suitable for `Tool.artifactConstructor`.\n */\nexport const resolveArtifact = async (\n  resolver: ArtifactResolver,\n  onInvalid: (reason: string) => never\n): Promise<() => SpooledArtifactCtor> => {\n  // A constructor: hand back a thunk that returns it.\n  if (SpooledArtifact.isSpooledArtifactConstructor(resolver)) {\n    const ctor = resolver\n    return () => ctor\n  }\n  // Otherwise it must be a resolver function.\n  if (typeof resolver !== 'function') {\n    onInvalid('artifact must be a SpooledArtifact constructor or a resolver returning one')\n  }\n  let resolved: unknown\n  try {\n    resolved = await (resolver as () => unknown)()\n  } catch (err) {\n    onInvalid(`artifact resolver threw: ${isError(err) ? err.message : String(err)}`)\n  }\n  resolved = unwrapDefault(resolved)\n  if (!SpooledArtifact.isSpooledArtifactConstructor(resolved)) {\n    onInvalid('artifact resolver did not resolve to a SpooledArtifact constructor')\n  }\n  const ctor = resolved as SpooledArtifactCtor\n  return () => ctor\n}\n\n/**\n * Synchronous {@link resolveArtifact}: accepts only the {@link SyncArtifactResolver} subset and\n * throws (via `onInvalid`) on an async resolver — a runtime guard for JS callers who bypass the\n * compile-time narrowing.\n *\n * @param resolver - A constructor or a sync resolver.\n * @param onInvalid - Throws a battery-scoped error; receives a human-readable reason.\n * @returns A sync `() => SpooledArtifactCtor` suitable for `Tool.artifactConstructor`.\n */\nexport const resolveArtifactSync = (\n  resolver: SyncArtifactResolver,\n  onInvalid: (reason: string) => never\n): (() => SpooledArtifactCtor) => {\n  if (SpooledArtifact.isSpooledArtifactConstructor(resolver)) {\n    const ctor = resolver\n    return () => ctor\n  }\n  if (typeof resolver !== 'function') {\n    onInvalid('artifact must be a SpooledArtifact constructor or a resolver returning one')\n  }\n  let resolved: unknown\n  try {\n    resolved = (resolver as () => unknown)()\n  } catch (err) {\n    onInvalid(`artifact resolver threw: ${isError(err) ? err.message : String(err)}`)\n  }\n  if (isInstanceOf(resolved, 'Promise', Promise)) {\n    onInvalid(\n      'artifact resolver is async; use the async factory variant for dynamic-import resolvers'\n    )\n  }\n  resolved = unwrapDefault(resolved)\n  if (!SpooledArtifact.isSpooledArtifactConstructor(resolved)) {\n    onInvalid('artifact resolver did not resolve to a SpooledArtifact constructor')\n  }\n  const ctor = resolved as SpooledArtifactCtor\n  return () => ctor\n}\n\n// ── Middleware pipeline runners ──────────────────────────────────────────────\n\n/** Internal sentinel a short-circuiting input stage throws to unwind the pipeline immediately. */\nconst SHORT_CIRCUIT = Symbol('adk.tools.shortCircuit')\n\ninterface ShortCircuitSignal {\n  [SHORT_CIRCUIT]: true\n  result: string\n}\n\n/** `true` when `value` is the short-circuit sentinel produced by {@link makeShortCircuit}. */\nexport const isShortCircuit = (value: unknown): value is { result: string } =>\n  isObject(value) && (value as Record<symbol, unknown>)[SHORT_CIRCUIT] === true\n\n/**\n * Build a `shortCircuit(result)` function for an input-pipeline context. Calling it throws the\n * internal sentinel, which {@link runInputPipeline} catches and converts into the verbatim result\n * (skipping the HTTP request entirely — e.g. a cache hit).\n *\n * @returns A function that, when called with a result string, throws the short-circuit sentinel.\n */\nexport const makeShortCircuit = (): ((result: string) => never) => {\n  return (result: string): never => {\n    const signal: ShortCircuitSignal = { [SHORT_CIRCUIT]: true, result }\n    throw signal\n  }\n}\n\n/** A generic onion middleware stage over a mutable context `C`. */\nexport type MiddlewareFn<C> = (ctx: C, next: NextFn) => void | Promise<void>\n\n/**\n * Run an input pipeline over `ctx`. Returns the short-circuit string when a stage short-circuited,\n * or `undefined` when the pipeline reached its terminal handler. A non-terminal pipeline (a stage\n * that neither called `next()` nor short-circuited) throws — the caller converts it to an\n * `Error:` string.\n *\n * @param mw - The `Middleware` instance holding the stages (a fresh `.runner()` is minted here).\n * @param ctx - The mutable input context handed to each stage.\n * @param label - Battery name, used in the non-terminal error message.\n * @returns The short-circuit result string, or `undefined` if the pipeline ran to completion.\n */\nexport const runInputPipeline = async <C>(\n  mw: Middleware<MiddlewareFn<C>>,\n  ctx: C,\n  label: string\n): Promise<string | undefined> => {\n  let reached = false\n  let caught: unknown\n  // Flag, not a value test: `throw undefined` is legal JS and `caught !== undefined`\n  // cannot tell it from \"no error\", so a stage rejecting with undefined is ignored.\n  let didCatch = false\n  await mw\n    .runner()\n    .errorHandler(async (error: unknown) => {\n      didCatch = true\n      caught = error\n    })\n    .finalHandler(async () => {\n      reached = true\n    })\n    .run((fn, next) => Promise.resolve(fn(ctx, next)))\n\n  if (didCatch) {\n    if (isShortCircuit(caught)) return caught.result\n    throw caught\n  }\n  if (!reached) {\n    throw new Error(`${label} input pipeline did not call next() and did not short-circuit.`)\n  }\n  return undefined\n}\n\n/**\n * Run an output pipeline over `ctx`; rethrow any stage error to the caller's try/catch. A\n * non-terminal pipeline (no `next()`) throws.\n *\n * @param mw - The `Middleware` instance holding the stages (a fresh `.runner()` is minted here).\n * @param ctx - The mutable output context handed to each stage.\n * @param label - Battery name, used in the non-terminal error message.\n */\nexport const runOutputPipeline = async <C>(\n  mw: Middleware<MiddlewareFn<C>>,\n  ctx: C,\n  label: string\n): Promise<void> => {\n  let reached = false\n  let caught: unknown\n  // Flag, not a value test: `throw undefined` is legal JS and `caught !== undefined`\n  // cannot tell it from \"no error\", so a stage rejecting with undefined is ignored.\n  let didCatch = false\n  await mw\n    .runner()\n    .errorHandler(async (error: unknown) => {\n      didCatch = true\n      caught = error\n    })\n    .finalHandler(async () => {\n      reached = true\n    })\n    .run((fn, next) => Promise.resolve(fn(ctx, next)))\n\n  if (didCatch) throw caught\n  if (!reached) throw new Error(`${label} output pipeline did not call next().`)\n}\n\n/**\n * Optional per-call gate run before a side-effecting tool executes. Throwing aborts the call\n * and surfaces through the standard tool-error path (`E_TOOL_DOWNSTREAM_ERROR` with the denial\n * as `cause`). The canonical implementation awaits `ctx.waitFor({ reason: 'tool_approval',\n * payload: call })` — the ADK gates primitive — and throws on denial; WHO approves and HOW is\n * the consumer's contract, this type is the seam.\n */\nexport type ToolGateFn = (\n  ctx: unknown,\n  call: { tool: string; args: unknown }\n) => void | Promise<void>\n\n/**\n * Await a configured {@link ToolGateFn} (no-op when absent). Factory batteries call this at\n * the top of their handlers so the gate runs before any side effect.\n *\n * @param gate - The configured gate, if any.\n * @param ctx - The dispatch context the handler received.\n * @param tool - The tool name (post-override).\n * @param args - The validated tool args.\n */\nexport const runToolGate = async (\n  gate: ToolGateFn | undefined,\n  ctx: unknown,\n  tool: string,\n  args: unknown\n): Promise<void> => {\n  if (gate) await gate(ctx, { tool, args })\n}\n"],"mappings":"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"}