{"version":3,"file":"adapter.mjs","names":["#baseline","#engine","#resolveEngine","#enginePromise"],"sources":["../../../../src/batteries/llm/webllm_chat_completions/adapter.ts"],"sourcesContent":["/**\n * Cross-environment executor adapter for WebLLM Chat Completions compatible endpoints.\n *\n * @module @nhtio/adk/batteries/llm/webllm_chat_completions/adapter\n *\n * @remarks\n * Cross-environment LLM adapter for the WebLLM Chat Completions wire shape. Chat Completions was\n * chosen as the ADK's reference adapter because it is the de-facto interchange format for the\n * majority of OpenAI-compatible gateways (vLLM, Together, Groq, Fireworks, Ollama, Azure OpenAI,\n * OpenRouter, DeepSeek, Mistral La Plateforme, and most self-hosted deployments). Its tool-call\n * synthetic-history shape (`role: 'assistant', tool_calls: [...]` followed by `role: 'tool'` with\n * `tool_call_id`) is the lowest-common-denominator that every conformant gateway accepts.\n *\n * The adapter is built around three pluggable layers:\n *\n * 1. **Translation helpers** — the thirteen swappable functions exported from `./helpers` turn\n *    ADK primitives ({@link @nhtio/adk!Tokenizable}, {@link @nhtio/adk!Memory}, {@link @nhtio/adk!Message}, {@link @nhtio/adk!Thought},\n *    {@link @nhtio/adk!ToolCall}, {@link @nhtio/adk!Tool}, {@link @nhtio/adk!ArtifactTool}, {@link @nhtio/adk!SpooledArtifact}) into Chat\n *    Completions wire shapes. Consumers override individual helpers via `options.helpers.*` to\n *    customise envelope formats, bucket ordering, thought surfacing, or JSON Schema generation\n *    without forking the adapter.\n * 2. **Three-layer options merging** — constructor baseline, per-`executor()` overrides, and\n *    per-iteration `ctx.stash.webLLMChatCompletions` overrides combine with key-by-key\n *    precedence for `helpers` and wholesale replacement for everything else.\n *    The merged shape is re-validated on every iteration so a malformed stash override\n *    fails loud, not silently.\n * 3. **WebLLM engine invocation** — accepts a preloaded `engine` or lazy `createEngine` factory.\n *    The resolved request body is passed directly to WebLLM's OpenAI-compatible chat API.\n *\n * Per-iteration flow (steps 1–9 of the plan):\n * 1. Merge constructor / executor / stash options and re-validate.\n * 2. Resolve helpers, falling back to bundled `default*` for each unset field.\n * 3. Forge artifact-query tools by walking `ctx.turnToolCalls`, collecting unique\n *    `SpooledArtifact` constructors, calling `<Ctor>.forgeTools(ctx)` on each, and merging the\n *    results with `ctx.tools`.\n * 4. Pre-render every persisted tool-call result into the prompt-ready string the timeline will\n *    use, cached by `tc.id`.\n * 5. When `tokenEncoding !== null`, sum the token weight of every persisted bucket and throw\n *    {@link @nhtio/adk/batteries!E_WEBLLM_CHAT_COMPLETIONS_CONTEXT_OVERFLOW} when the total exceeds `contextWindow`.\n * 6. Build the request body via `buildChatCompletionsHistory`; carry vendor-opaque reasoning\n *    blocks through the `_adk_reasoning_payloads` side-channel.\n * 7. Resolve or lazily create a WebLLM engine and call `engine.chat.completions.create(body)`.\n * 8. Streaming path: consume WebLLM's async chunk iterable; surface deltas through\n *    `helpers.reportMessage` / `reportThought` / `reportToolCall`; assemble tool-call deltas via\n *    the accumulator; persist `Message` / `Thought` / `ToolCall` records on stream end.\n * 9. Non-streaming path: consume the returned Chat Completion object; same persistence +\n *    tool-execution loop.\n */\n\nimport { DateTime } from 'luxon'\nimport { sha256 } from 'js-sha256'\nimport { v6 as uuidv6 } from 'uuid'\nimport { validateOptions } from './validation'\nimport { emitLifecycle } from '../chat_common/lifecycle'\nimport { isError, isInstanceOf, isObject } from '@nhtio/adk/guards'\nimport { resolveToolCallParser } from '../chat_common/tool_parsers'\nimport { canonicalStringify } from '../../../lib/utils/canonical_json'\nimport { InMemorySpoolStore } from '@nhtio/adk/batteries/storage/in_memory'\nimport { looksLikeSpooledArtifact, normalizeToolName } from '../chat_common/helpers'\nimport {\n  Tokenizable,\n  ToolCall,\n  Message,\n  Thought,\n  SpooledArtifact,\n  Media,\n  ArtifactTool,\n} from '@nhtio/adk/common'\nimport {\n  E_INVALID_WEBLLM_CHAT_COMPLETIONS_OPTIONS,\n  E_WEBLLM_CHAT_COMPLETIONS_CONTEXT_OVERFLOW,\n  E_WEBLLM_CHAT_COMPLETIONS_STREAM_ERROR,\n  E_WEBLLM_CHAT_COMPLETIONS_INVALID_TOOL_CALL_ARGS,\n} from './exceptions'\nimport {\n  defaultDescriptionToChatCompletionsJsonSchema,\n  defaultRenderUntrustedContent,\n  defaultRenderTrustedContent,\n  defaultRenderStandingInstructions,\n  defaultRenderMemories,\n  defaultRenderRetrievables,\n  defaultRenderRetrievableHandleBody,\n  defaultRenderRetrievableSafetyDirective,\n  defaultRenderFirstPartyRetrievables,\n  defaultRenderThirdPartyPublicRetrievables,\n  defaultRenderThirdPartyPrivateRetrievables,\n  defaultRenderTimelineMessage,\n  defaultRenderThought,\n  defaultFilterThoughts,\n  defaultToolsToChatCompletionsTools,\n  defaultRenderChatCompletionsSystemPrompt,\n  defaultRenderChatCompletionsToolCallResult,\n  defaultBuildChatCompletionsHistory,\n  defaultCreateChatCompletionsToolCallDeltaAccumulator,\n  extractReasoningFields,\n} from './helpers'\nimport type { Tool, Memory } from '@nhtio/adk/common'\nimport type { DispatchContext } from '@nhtio/adk/types'\nimport type { BatteryLifecyclePhase } from '../chat_common/lifecycle'\nimport type { TokenEncoding, TokenEncodingId } from '@nhtio/adk/types'\nimport type { DispatchExecutorFn, DispatchExecutorHelpers } from '@nhtio/adk/dispatch_runner'\nimport type {\n  WebLLMChatCompletionsAdapterOptions,\n  ChatCompletionsHelpers,\n  WebLLMChatCompletionsRequestBody,\n  ChatCompletionsChunk,\n  ChatCompletionsResponse,\n  AssembledToolCall,\n  ChatCompletionsContentBlock,\n  WebLLMEngine,\n  ReasoningField,\n} from './types'\n\n// MLC init-progress `text` markers that indicate the COMPILE sub-phase (WebGPU pipeline / shader build\n// / loading-from-cache), as opposed to network fetch. Best-effort: a miss just routes to `loading`, and\n// the coarse `compiling` marker before engine-create fires regardless.\nconst COMPILE_PROGRESS_RE =\n  /shader|compil|from cache|gpu pipeline|finish loading|loading model from/i\n\n// ─── ADK-control keys (stripped before sending the request body) ──────────\n\nconst ADK_CONTROL_KEYS: ReadonlySet<string> = new Set([\n  'engine',\n  'createEngine',\n  'engineConfig',\n  'chatOptions',\n  'stream',\n  'bucketOrder',\n  'contextWindow',\n  'selfIdentity',\n  'thoughtSurfacing',\n  'tokenEncoding',\n  'replayCompatibility',\n  'reasoningFieldPrecedence',\n  'helpers',\n  'strictToolChoice',\n  'unsupportedMediaPolicy',\n  'onInitProgress',\n  'isWebGPUAvailable',\n  'autoAck',\n  'enableThinking',\n  // Observability hooks — never sent to the engine.\n  'onRawGeneration',\n  'onPromptAssembled',\n  'toolCallIdFilter',\n])\n\n// ─── Option merging ───────────────────────────────────────────────────────────\n\nconst mergeHelpers = (\n  layers: ReadonlyArray<Partial<ChatCompletionsHelpers> | undefined>\n): Partial<ChatCompletionsHelpers> | undefined => {\n  let merged: Partial<ChatCompletionsHelpers> | undefined\n  for (const layer of layers) {\n    if (!layer) continue\n    merged = { ...(merged ?? {}), ...layer }\n  }\n  return merged\n}\n\nconst mergeOptions = (\n  baseline: WebLLMChatCompletionsAdapterOptions,\n  exec: Partial<WebLLMChatCompletionsAdapterOptions> | undefined,\n  stash: Partial<WebLLMChatCompletionsAdapterOptions> | undefined\n): Partial<WebLLMChatCompletionsAdapterOptions> => {\n  const layers = [baseline as Partial<WebLLMChatCompletionsAdapterOptions>, exec ?? {}, stash ?? {}]\n  const out: Record<string, unknown> = {}\n  for (const layer of layers) {\n    for (const [k, v] of Object.entries(layer)) {\n      if (v === undefined) continue\n      if (k === 'helpers') continue\n      out[k] = v\n    }\n  }\n  const helpers = mergeHelpers(layers.map((l) => l.helpers))\n  if (helpers !== undefined) out.helpers = helpers\n  return out as Partial<WebLLMChatCompletionsAdapterOptions>\n}\n\n// ─── Helper resolution ────────────────────────────────────────────────────────\n\nconst resolveHelpers = (\n  overrides: Partial<ChatCompletionsHelpers> | undefined\n): ChatCompletionsHelpers => {\n  const src = overrides ?? {}\n  return {\n    descriptionToChatCompletionsJsonSchema:\n      src.descriptionToChatCompletionsJsonSchema ?? defaultDescriptionToChatCompletionsJsonSchema,\n    renderUntrustedContent: src.renderUntrustedContent ?? defaultRenderUntrustedContent,\n    renderTrustedContent: src.renderTrustedContent ?? defaultRenderTrustedContent,\n    renderStandingInstructions: src.renderStandingInstructions ?? defaultRenderStandingInstructions,\n    renderMemories: src.renderMemories ?? defaultRenderMemories,\n    renderRetrievables: src.renderRetrievables ?? defaultRenderRetrievables,\n    renderRetrievableHandleBody:\n      src.renderRetrievableHandleBody ?? defaultRenderRetrievableHandleBody,\n    renderRetrievableSafetyDirective:\n      src.renderRetrievableSafetyDirective ?? defaultRenderRetrievableSafetyDirective,\n    renderFirstPartyRetrievables:\n      src.renderFirstPartyRetrievables ?? defaultRenderFirstPartyRetrievables,\n    renderThirdPartyPublicRetrievables:\n      src.renderThirdPartyPublicRetrievables ?? defaultRenderThirdPartyPublicRetrievables,\n    renderThirdPartyPrivateRetrievables:\n      src.renderThirdPartyPrivateRetrievables ?? defaultRenderThirdPartyPrivateRetrievables,\n    renderTimelineMessage: src.renderTimelineMessage ?? defaultRenderTimelineMessage,\n    renderThought: src.renderThought ?? defaultRenderThought,\n    filterThoughts: src.filterThoughts ?? defaultFilterThoughts,\n    toolsToChatCompletionsTools:\n      src.toolsToChatCompletionsTools ?? defaultToolsToChatCompletionsTools,\n    renderChatCompletionsSystemPrompt:\n      src.renderChatCompletionsSystemPrompt ?? defaultRenderChatCompletionsSystemPrompt,\n    renderChatCompletionsToolCallResult:\n      src.renderChatCompletionsToolCallResult ?? defaultRenderChatCompletionsToolCallResult,\n    buildChatCompletionsHistory:\n      src.buildChatCompletionsHistory ?? defaultBuildChatCompletionsHistory,\n    createChatCompletionsToolCallDeltaAccumulator:\n      src.createChatCompletionsToolCallDeltaAccumulator ??\n      defaultCreateChatCompletionsToolCallDeltaAccumulator,\n  }\n}\n\n// ─── ID helpers ───────────────────────────────────────────────────────────────\n\n// Canonical (key-order-insensitive) checksum — MUST match the contract documented\n// on canonicalStringify and used by Tool.executor + dispatch_runner's streaming\n// helper, so ctx.toolCallCount(checksum) detects semantically-identical repeat\n// calls regardless of argument key order.\nconst computeChecksum = (tool: string, args: unknown): string =>\n  sha256(canonicalStringify({ tool, args }))\n\nconst nowIso = (): string => DateTime.now().toISO() ?? new Date().toISOString()\n\n// ─── Token measurement ────────────────────────────────────────────────────────\n\nconst estimateTokensOf = async (\n  value: { estimateTokens: (encoding: TokenEncoding) => number | Promise<number> },\n  encoding: TokenEncodingId\n): Promise<number> => {\n  return Promise.resolve(value.estimateTokens(encoding as TokenEncoding))\n}\n\n// ─── Adapter class ────────────────────────────────────────────────────────────\n\n/**\n * Opinionated cross-environment LLM adapter for the WebLLM Chat Completions wire shape.\n *\n * @remarks\n * Construction validates options eagerly via {@link @nhtio/adk/batteries!validateOptions} and throws\n * {@link @nhtio/adk/batteries!E_INVALID_WEBLLM_CHAT_COMPLETIONS_OPTIONS} on failure — config bugs fail loud, not at\n * dispatch time. The returned instance is reusable: call {@link WebLLMChatCompletionsAdapter.executor}\n * once per `DispatchRunner` configuration to obtain an {@link @nhtio/adk!DispatchExecutorFn} bound to the\n * baseline plus optional executor-scope overrides.\n *\n * Per-iteration overrides live on the active {@link @nhtio/adk!DispatchContext}'s\n * `stash.webLLMChatCompletions` slot and take highest precedence — they merge into the\n * executor-scope shape on every iteration. `helpers` merge key-by-key across all three layers;\n * every other field is replaced wholesale at the highest layer that\n * sets it.\n */\nexport class WebLLMChatCompletionsAdapter {\n  /**\n   * Customary key for per-iteration overrides on `ctx.stash`. The adapter reads\n   * `ctx.stash.get(WebLLMChatCompletionsAdapter.STASH_KEY, {})` at the start of every\n   * iteration and merges the value into the resolved options shape.\n   */\n  public static readonly STASH_KEY = 'webLLMChatCompletions' as const\n\n  readonly #baseline: WebLLMChatCompletionsAdapterOptions\n  #engine: WebLLMEngine | undefined\n  #enginePromise: Promise<WebLLMEngine> | undefined\n\n  /**\n   * Whether the runtime can host a WebLLM engine — i.e. WebGPU (`navigator.gpu`) is present.\n   *\n   * @returns `true` when WebGPU is available in the current environment.\n   */\n  public static isAvailable(): boolean {\n    return (\n      typeof globalThis.navigator !== 'undefined' &&\n      'gpu' in globalThis.navigator &&\n      typeof (globalThis.navigator as { gpu?: unknown }).gpu !== 'undefined'\n    )\n  }\n\n  /**\n   * @param options - Constructor-baseline options. Re-validated on every iteration after\n   *   per-dispatch and per-iteration overrides are layered in.\n   * @throws {@link @nhtio/adk/batteries!E_INVALID_WEBLLM_CHAT_COMPLETIONS_OPTIONS} when `options` does not satisfy\n   *   {@link @nhtio/adk/batteries!webLLMChatCompletionsOptionsSchema}.\n   */\n  constructor(options: unknown) {\n    this.#baseline = validateOptions(options)\n    this.#engine = this.#baseline.engine\n  }\n\n  /**\n   * Eagerly loads (and caches) the engine so the first dispatch does not pay the model-load cost.\n   *\n   * @param overrides - Optional option overrides layered over the constructor baseline.\n   * @returns The resolved {@link WebLLMEngine}.\n   */\n  async preload(overrides?: Partial<WebLLMChatCompletionsAdapterOptions>): Promise<WebLLMEngine> {\n    const merged = validateOptions(mergeOptions(this.#baseline, overrides, undefined))\n    return this.#resolveEngine(merged)\n  }\n\n  /** Drops the cached engine and any in-flight load so the next dispatch re-resolves it. */\n  reset(): void {\n    this.#engine = undefined\n    this.#enginePromise = undefined\n  }\n\n  /**\n   * Instance-level availability check, honouring an injected\n   * {@link WebLLMChatCompletionsAdapterOptions.isWebGPUAvailable} override.\n   *\n   * @returns `true` when a WebLLM engine can run in the current environment.\n   */\n  isAvailable(): boolean {\n    return (this.#baseline.isWebGPUAvailable ?? WebLLMChatCompletionsAdapter.isAvailable)()\n  }\n\n  async #resolveEngine(merged: WebLLMChatCompletionsAdapterOptions): Promise<WebLLMEngine> {\n    if (merged.engine) {\n      this.#engine = merged.engine\n      return merged.engine\n    }\n    if (this.#engine) return this.#engine\n    const available = (merged.isWebGPUAvailable ?? WebLLMChatCompletionsAdapter.isAvailable)()\n    if (!available) {\n      throw new E_INVALID_WEBLLM_CHAT_COMPLETIONS_OPTIONS([\n        'WebLLM requires a browser/runtime with WebGPU support',\n      ])\n    }\n    this.#enginePromise ??= (async () => {\n      emitLifecycle(merged, 'webllm', merged.model, 'loading', {\n        detail: 'loading model + booting WebGPU runtime',\n      })\n      // Forward each MLC init-progress report ({progress 0..1, text, timeElapsed}) into a normalized\n      // `loading` lifecycle report, while still calling the consumer's onInitProgress verbatim.\n      const hasLifecycle =\n        merged.onLifecycle ??\n        merged.onLoading ??\n        merged.onReady ??\n        merged.onGenerating ??\n        merged.onError\n      const forwardedInitProgress = hasLifecycle\n        ? (report: { progress?: number; text?: string }) => {\n            // MLC's init reports cover BOTH fetch and shader/graph compilation; the text distinguishes\n            // them (\"Loading model from cache\" / \"shader\" / \"GPU\" → the compile sub-phase). Route a\n            // compile-flavored report to `compiling`, everything else to `loading`. Any miss falls back\n            // to `loading` (no regression — the coarse `compiling` emit below still fires before create).\n            const phase: BatteryLifecyclePhase = COMPILE_PROGRESS_RE.test(report?.text ?? '')\n              ? 'compiling'\n              : 'loading'\n            emitLifecycle(merged, 'webllm', merged.model, phase, {\n              ...(typeof report?.progress === 'number' ? { progress: report.progress } : {}),\n              ...(report?.text ? { detail: report.text } : {}),\n              raw: report,\n            })\n            merged.onInitProgress?.(report as never)\n          }\n        : merged.onInitProgress\n      try {\n        const createEngine =\n          merged.createEngine ??\n          (async ({ model, engineConfig, chatOptions, onInitProgress }) => {\n            const { CreateMLCEngine } = await import('@mlc-ai/web-llm')\n            return (await CreateMLCEngine(\n              model,\n              { ...(engineConfig ?? {}), initProgressCallback: onInitProgress },\n              chatOptions\n            )) as WebLLMEngine\n          })\n        // Coarse `compiling` marker: by the time MLC has the weights and is building the WebGPU pipeline,\n        // this fires even if no init-report text matched COMPILE_PROGRESS_RE (the boundary the LiteRT chat\n        // demo marks). Per-report `compiling` above refines it when MLC's text is granular enough.\n        emitLifecycle(merged, 'webllm', merged.model, 'compiling', {\n          detail: 'compiling model + WebGPU shaders',\n        })\n        const engine = await createEngine({\n          model: merged.model,\n          engineConfig: merged.engineConfig,\n          chatOptions: merged.chatOptions,\n          onInitProgress: forwardedInitProgress,\n        })\n        this.#engine = engine\n        emitLifecycle(merged, 'webllm', merged.model, 'ready', { detail: 'engine ready' })\n        return engine\n      } catch (err) {\n        this.#enginePromise = undefined\n        emitLifecycle(merged, 'webllm', merged.model, 'error', { error: err })\n        throw err\n      }\n    })()\n    return this.#enginePromise\n  }\n\n  /**\n   * Returns an {@link @nhtio/adk!DispatchExecutorFn} bound to this adapter's baseline plus optional\n   * executor-scope overrides. The returned function is reusable across iterations — every\n   * iteration re-merges with `ctx.stash[STASH_KEY]` and re-validates the result.\n   *\n   * @param overrides - Optional executor-scope overrides. Higher precedence than the baseline,\n   *   lower precedence than `ctx.stash[STASH_KEY]`.\n   * @returns An {@link @nhtio/adk!DispatchExecutorFn} suitable for `DispatchRunner`.\n   */\n  executor(overrides?: Partial<WebLLMChatCompletionsAdapterOptions>): DispatchExecutorFn {\n    const baseline = this.#baseline\n    const adapterClass = WebLLMChatCompletionsAdapter\n    return async (ctx: DispatchContext, helpers: DispatchExecutorHelpers): Promise<void> => {\n      // Bridge helpers.log → the legacy `warn: (msg) => void` slot exposed by the per-call\n      // helper signatures. Helpers downstream don't need to know the structured shape — they\n      // emit a single string, which we route to `helpers.log.warn` with a stable `kind` so\n      // observability middleware can still filter and aggregate.\n      const localWarn = (msg: string): void => {\n        helpers.log.warn({ kind: 'helper-warning', message: msg })\n      }\n\n      // ── Step 1: merge & validate ──────────────────────────────────────────\n      const stashRaw = ctx.stash.get(adapterClass.STASH_KEY, {}) as unknown\n      const stashOverrides =\n        stashRaw && typeof stashRaw === 'object'\n          ? (stashRaw as Partial<WebLLMChatCompletionsAdapterOptions>)\n          : {}\n      const mergedRaw = mergeOptions(baseline, overrides, stashOverrides)\n      const merged = validateOptions(mergedRaw)\n\n      // Cross-field invariant: tokenEncoding non-null requires contextWindow.\n      if (merged.tokenEncoding !== null && merged.contextWindow === undefined) {\n        throw new E_INVALID_WEBLLM_CHAT_COMPLETIONS_OPTIONS([\n          'tokenEncoding is non-null but contextWindow is undefined',\n        ])\n      }\n\n      // ── Step 2: resolve helpers ───────────────────────────────────────────\n      const resolvedHelpers = resolveHelpers(merged.helpers)\n\n      // ── Step 3: artifact-reader tools ─────────────────────────────────────\n      // Forged by the DispatchRunner CORE into `ctx.tools` before the input pipeline runs (generation is a\n      // generic core concern; this battery owns only representation). Read the pre-forged `ctx.tools`\n      // directly — no local merge, no bindContext here.\n\n      // ── Step 4: pre-render tool-call results ──────────────────────────────\n      const renderedToolCallResults = new Map<ToolCall, string | ChatCompletionsContentBlock[]>()\n      for (const tc of ctx.turnToolCalls) {\n        const rendered = await resolvedHelpers.renderChatCompletionsToolCallResult({\n          toolCall: tc,\n          results: tc.results as\n            | Tokenizable\n            | SpooledArtifact\n            | SpooledArtifact[]\n            | Media\n            | Media[],\n          tool: ctx.tools.get(tc.tool) as Tool | ArtifactTool | undefined,\n          renderUntrustedContent: resolvedHelpers.renderUntrustedContent,\n          renderTrustedContent: resolvedHelpers.renderTrustedContent,\n          unsupportedMediaPolicy: merged.unsupportedMediaPolicy ?? 'throw',\n          warn: localWarn,\n        })\n        renderedToolCallResults.set(tc, rendered)\n      }\n\n      // ── Step 5: context window enforcement ────────────────────────────────\n      if (merged.tokenEncoding !== null && merged.contextWindow !== undefined) {\n        const encoding = merged.tokenEncoding as TokenEncodingId\n        let spTokens = await estimateTokensOf(ctx.systemPrompt, encoding)\n        let siTokens = 0\n        for (const si of ctx.standingInstructions) {\n          siTokens += await estimateTokensOf(si, encoding)\n        }\n        let memTokens = 0\n        for (const mem of ctx.turnMemories as Set<Memory>) {\n          memTokens += await estimateTokensOf(mem.content, encoding)\n        }\n        let retTokens = 0\n        for (const r of ctx.turnRetrievables) {\n          retTokens +=\n            !r.inline && SpooledArtifact.isSpooledArtifact(r.content) && r.content.hasSizeHints()\n              ? r.content.estimateHandleTokens(\n                  r.id,\n                  encoding as TokenEncoding,\n                  resolvedHelpers.renderRetrievableHandleBody\n                )\n              : await estimateTokensOf(r.content, encoding)\n        }\n        let tlTokens = 0\n        for (const msg of ctx.turnMessages) {\n          if (msg.content !== undefined) {\n            tlTokens += await estimateTokensOf(msg.content, encoding)\n          }\n        }\n        for (const th of ctx.turnThoughts) {\n          tlTokens += await estimateTokensOf(th.content, encoding)\n        }\n        for (const rendered of renderedToolCallResults.values()) {\n          const textPart =\n            typeof rendered === 'string'\n              ? rendered\n              : rendered\n                  .filter((b): b is { type: 'text'; text: string } => b.type === 'text')\n                  .map((b) => b.text)\n                  .join('\\n')\n          const tk = new Tokenizable(textPart)\n          tlTokens += await estimateTokensOf(tk, encoding)\n        }\n        // Tool DECLARATIONS: WebLLM sends the visible tools as `body.tools` and MLC applies the model's\n        // conversation template IN-PROCESS (browser), wrapping them per-model — so the exact rendered\n        // string isn't reproducible here without the template. Tally the serialized tool JSON (the\n        // reproducible, dominant component the adapter actually passes) as an honest FLOOR. Without this\n        // the guard undercounts a tool-heavy prompt by the entire declaration block.\n        let toolTokens = 0\n        const visibleTools = ctx.tools.visible()\n        if (visibleTools.length > 0) {\n          const toolsJson = JSON.stringify(\n            resolvedHelpers.toolsToChatCompletionsTools(visibleTools, {\n              descriptionToChatCompletionsJsonSchema:\n                resolvedHelpers.descriptionToChatCompletionsJsonSchema,\n            })\n          )\n          toolTokens = await estimateTokensOf(new Tokenizable(toolsJson), encoding)\n        }\n        const total = spTokens + siTokens + memTokens + retTokens + tlTokens + toolTokens\n        const perBucketObj = {\n          systemPrompt: spTokens,\n          standingInstructions: siTokens,\n          memories: memTokens,\n          retrievables: retTokens,\n          timeline: tlTokens,\n          tools: toolTokens,\n        }\n        helpers.log.debug({\n          kind: 'context-window-usage',\n          message: `Context window usage: ${total}/${merged.contextWindow} tokens`,\n          payload: {\n            total,\n            limit: merged.contextWindow,\n            encoding,\n            perBucket: perBucketObj,\n          },\n        })\n        if (total > merged.contextWindow) {\n          const perBucket = JSON.stringify(perBucketObj)\n          throw new E_WEBLLM_CHAT_COMPLETIONS_CONTEXT_OVERFLOW([\n            total,\n            merged.contextWindow,\n            encoding,\n            perBucket,\n          ])\n        }\n      }\n\n      // ── Step 5b: tool_choice + forged artifact-tools guard ────────────────\n      // When `tool_choice` (or the `allowed_tools` variant) forces the model onto a specific\n      // tool name and that name resolves to an ephemeral, forged artifact-query tool, surface\n      // it as a structured warning (or throw under `strictToolChoice: true`). Forging an\n      // artifact-query tool by name is almost always a misconfiguration — the tool may not\n      // exist in the next iteration once the artifact ages out of `ctx.turnToolCalls`.\n      const forcedToolNames: string[] = []\n      const toolChoice = merged.tool_choice\n      let toolChoiceVariant: 'function' | 'allowed_tools' = 'function'\n      if (toolChoice && typeof toolChoice === 'object') {\n        if ('function' in toolChoice && toolChoice.type === 'function') {\n          forcedToolNames.push(toolChoice.function.name)\n        } else if ('custom' in toolChoice && toolChoice.type === 'custom') {\n          forcedToolNames.push(toolChoice.custom.name)\n        } else if (toolChoice.type === 'allowed_tools') {\n          toolChoiceVariant = 'allowed_tools'\n          for (const entry of toolChoice.allowed_tools.tools) {\n            if ('function' in entry) forcedToolNames.push(entry.function.name)\n            else if ('custom' in entry) forcedToolNames.push(entry.custom.name)\n          }\n        }\n      }\n      const forcedForgedHits: Array<{ toolName: string }> = []\n      for (const name of forcedToolNames) {\n        const t = ctx.tools.get(name) as { ephemeral?: boolean } | undefined\n        if (t?.ephemeral === true) {\n          forcedForgedHits.push({ toolName: name })\n        }\n      }\n      if (forcedForgedHits.length > 0) {\n        if (merged.strictToolChoice === true) {\n          throw new E_INVALID_WEBLLM_CHAT_COMPLETIONS_OPTIONS([\n            `tool_choice forces forged ephemeral artifact-query tool(s): ${forcedForgedHits\n              .map((h) => h.toolName)\n              .join(\n                ', '\n              )} — these may not exist on the next iteration. Remove the override or unset strictToolChoice.`,\n          ])\n        }\n        helpers.log.warn({\n          kind: 'tool-choice-forged-artifact',\n          message: `tool_choice forces ${forcedForgedHits.length} forged ephemeral artifact-query tool(s); this is almost always a misconfiguration`,\n          payload: {\n            toolNames: forcedForgedHits.map((h) => h.toolName),\n            variant: toolChoiceVariant,\n          },\n        })\n      }\n\n      // ── Step 6: build request body ────────────────────────────────────────\n      const { messages: wireMessages, reasoningPayloads } =\n        await resolvedHelpers.buildChatCompletionsHistory({\n          systemPrompt: ctx.systemPrompt,\n          standingInstructions: ctx.standingInstructions,\n          memories: ctx.turnMemories,\n          retrievables: ctx.turnRetrievables,\n          messages: ctx.turnMessages,\n          thoughts: ctx.turnThoughts,\n          toolCalls: ctx.turnToolCalls,\n          tools: ctx.tools,\n          renderedToolCallResults,\n          bucketOrder: merged.bucketOrder ?? [\n            'standingInstructions',\n            'memories',\n            'retrievables',\n            'timeline',\n          ],\n          selfIdentity: merged.selfIdentity ?? 'assistant',\n          thoughtSurfacing: merged.thoughtSurfacing ?? 'all-self',\n          replayCompatibility: merged.replayCompatibility ?? [],\n          renderChatCompletionsToolCallResult: resolvedHelpers.renderChatCompletionsToolCallResult,\n          renderChatCompletionsSystemPrompt: resolvedHelpers.renderChatCompletionsSystemPrompt,\n          renderStandingInstructions: resolvedHelpers.renderStandingInstructions,\n          renderMemories: resolvedHelpers.renderMemories,\n          renderRetrievables: resolvedHelpers.renderRetrievables,\n          renderRetrievableHandleBody: resolvedHelpers.renderRetrievableHandleBody,\n          renderRetrievableSafetyDirective: resolvedHelpers.renderRetrievableSafetyDirective,\n          renderFirstPartyRetrievables: resolvedHelpers.renderFirstPartyRetrievables,\n          renderThirdPartyPublicRetrievables: resolvedHelpers.renderThirdPartyPublicRetrievables,\n          renderThirdPartyPrivateRetrievables: resolvedHelpers.renderThirdPartyPrivateRetrievables,\n          renderTimelineMessage: resolvedHelpers.renderTimelineMessage,\n          renderThought: resolvedHelpers.renderThought,\n          filterThoughts: resolvedHelpers.filterThoughts,\n          renderUntrustedContent: resolvedHelpers.renderUntrustedContent,\n          renderTrustedContent: resolvedHelpers.renderTrustedContent,\n          unsupportedMediaPolicy: merged.unsupportedMediaPolicy ?? 'throw',\n          warn: localWarn,\n        })\n\n      const stream = merged.stream ?? true\n      const body: WebLLMChatCompletionsRequestBody = {\n        model: merged.model,\n        messages: wireMessages,\n        stream,\n      }\n      for (const [k, v] of Object.entries(merged)) {\n        if (ADK_CONTROL_KEYS.has(k)) continue\n        if (k === 'model' || k === 'messages' || k === 'stream') continue\n        if (v === undefined) continue\n        ;(body as Record<string, unknown>)[k] = v\n      }\n      // Thread the explicit thinking flag into extra_body.enable_thinking so the underlying chat\n      // template never decides for itself (Qwen3/DeepSeek default thinking ON). Default OFF; merged with\n      // any caller-supplied extra_body without clobbering it.\n      body.extra_body = {\n        ...(body.extra_body ?? {}),\n        enable_thinking: merged.enableThinking ?? false,\n      }\n      const toolsArr = ctx.tools.visible()\n      if (toolsArr.length > 0) {\n        body.tools = resolvedHelpers.toolsToChatCompletionsTools(toolsArr, {\n          descriptionToChatCompletionsJsonSchema:\n            resolvedHelpers.descriptionToChatCompletionsJsonSchema,\n        })\n      }\n      if (reasoningPayloads.length > 0) {\n        body._adk_reasoning_payloads = reasoningPayloads\n      }\n\n      // One id for this whole generation — correlates the TO tap (onPromptAssembled) with the FROM tap\n      // (onRawGeneration) below.\n      const dispatchStreamId = uuidv6()\n\n      // Prompt-assembled observability tap: the EXACT request body going TO the engine, the instant it is\n      // built and BEFORE create(). Mirror of onRawGeneration. Handed back AS-IS — no redaction — and\n      // swallow observer errors so it can never corrupt the generation path.\n      if (merged.onPromptAssembled) {\n        try {\n          merged.onPromptAssembled({\n            battery: 'webllm_chat_completions',\n            kind: 'request-body',\n            messages: body.messages,\n            tools: body.tools,\n            requestBody: body,\n            streamed: stream,\n            streamId: dispatchStreamId,\n          })\n        } catch {\n          /* observer errors are non-fatal */\n        }\n      }\n\n      // ── Step 7: invoke WebLLM engine ─────────────────────────────────────\n      let completion: ChatCompletionsResponse | AsyncIterable<ChatCompletionsChunk>\n      try {\n        const engine = await this.#resolveEngine(merged)\n        emitLifecycle(merged, 'webllm', merged.model, 'generating')\n        completion = (await engine.chat.completions.create(body as never)) as\n          | ChatCompletionsResponse\n          | AsyncIterable<ChatCompletionsChunk>\n      } catch (err) {\n        helpers.log.error({\n          kind: 'webllm-engine-error',\n          message: `WebLLM engine failure: ${isError(err) ? err.message : String(err)}`,\n          payload: { detail: isError(err) ? err.message : String(err) },\n        })\n        emitLifecycle(merged, 'webllm', merged.model, 'error', { error: err })\n        ctx.nack(\n          new E_WEBLLM_CHAT_COMPLETIONS_STREAM_ERROR([isError(err) ? err.message : String(err)])\n        )\n        return\n      }\n\n      // Spool store used to back string / Uint8Array tool returns. Bytes are written under the\n      // call id; the resulting SpooledArtifact (or tool-configured subclass) is the model-visible\n      // handle for the rest of the turn. Injectable via the `spoolStore` option so consumers can\n      // back artifacts with durable storage (OPFS, Flydrive); defaults to an ephemeral per-dispatch\n      // in-memory store. NOTE: an injected durable store persists across turns, so call ids must be\n      // globally unique for that store (see the `spoolStore` option docs).\n      const spoolStore = merged.spoolStore ?? new InMemorySpoolStore()\n\n      // ── Inner helper: persist + execute one assembled tool call ───────────\n      const executeAndPersistToolCall = async (call: AssembledToolCall): Promise<void> => {\n        // Resolve the vendor id once at ingress so reporting, persistence, and spooling agree.\n        const callId = merged.toolCallIdFilter?.(call.id, ctx) ?? call.id\n        const tool = ctx.tools.get(call.name)\n        // Parse args defensively. The model may emit non-JSON or a non-object\n        // JSON value (string, number, array, null); both are recoverable error\n        // conditions, NOT dispatch-killers. A parse failure short-circuits to\n        // a persisted error ToolCall — formatted by `E_WEBLLM_CHAT_COMPLETIONS_INVALID_TOOL_CALL_ARGS`\n        // so consumers can match on the stable error code — letting the model\n        // self-correct on the next iteration.\n        let args: Record<string, unknown> = {}\n        let parseError:\n          | InstanceType<typeof E_WEBLLM_CHAT_COMPLETIONS_INVALID_TOOL_CALL_ARGS>\n          | undefined\n        if (call.args && call.args.length > 0) {\n          try {\n            const parsed: unknown = JSON.parse(call.args)\n            if (isObject(parsed)) {\n              args = parsed\n            } else {\n              const receivedKind = Array.isArray(parsed)\n                ? 'array'\n                : parsed === null\n                  ? 'null'\n                  : typeof parsed\n              parseError = new E_WEBLLM_CHAT_COMPLETIONS_INVALID_TOOL_CALL_ARGS([\n                `must be a JSON object; received ${receivedKind}`,\n                call.args,\n              ])\n            }\n          } catch {\n            parseError = new E_WEBLLM_CHAT_COMPLETIONS_INVALID_TOOL_CALL_ARGS([\n              'are not valid JSON',\n              call.args,\n            ])\n          }\n        }\n        const completedAt = nowIso()\n        if (parseError !== undefined) {\n          const toolName = normalizeToolName(call.name)\n          const results = new Tokenizable(parseError.message)\n          helpers.reportToolCall(callId, { tool: toolName, args })\n          helpers.reportToolCall(callId, {\n            results,\n            isError: true,\n            isComplete: true,\n          })\n          const checksum = computeChecksum(toolName, args)\n          await ctx.storeToolCall(\n            new ToolCall({\n              id: callId,\n              tool: toolName,\n              args,\n              checksum,\n              isComplete: true,\n              isError: true,\n              results,\n              createdAt: completedAt,\n              updatedAt: completedAt,\n              completedAt,\n            })\n          )\n          return\n        }\n        if (!tool) {\n          const toolName = normalizeToolName(call.name)\n          // List the tools that DO exist so the model can self-correct on the next iteration\n          // instead of guessing. Without this, a single typo'd / hallucinated tool name yields\n          // a dead-end \"not found\" with no path forward.\n          const available = ctx.tools\n            .all()\n            .map((t) => t.name)\n            .sort()\n          const errText =\n            available.length > 0\n              ? `Tool not found: ${toolName}. Available tools: ${available.join(', ')}.`\n              : `Tool not found: ${toolName}. No tools are available this turn.`\n          const results = new Tokenizable(errText)\n          helpers.reportToolCall(callId, { tool: toolName, args })\n          helpers.reportToolCall(callId, {\n            results,\n            isError: true,\n            isComplete: true,\n          })\n          const checksum = computeChecksum(toolName, args)\n          await ctx.storeToolCall(\n            new ToolCall({\n              id: callId,\n              tool: toolName,\n              args,\n              checksum,\n              isComplete: true,\n              isError: true,\n              results,\n              createdAt: completedAt,\n              updatedAt: completedAt,\n              completedAt,\n            })\n          )\n          return\n        }\n        helpers.reportToolCall(callId, { tool: tool.name, args })\n        const isArtifactTool = ArtifactTool.isArtifactTool(tool)\n        let results: Tokenizable | SpooledArtifact | SpooledArtifact[] | Media | Media[] =\n          new Tokenizable('')\n        let toolHadError = false\n        try {\n          const raw = await tool.executor(ctx)(args)\n          if (isArtifactTool) {\n            // ArtifactTool: handler returns a string | Tokenizable that *is* the model-visible\n            // answer to a query against a prior artifact. No spool write, no SpooledArtifact\n            // construction — pass through (or wrap a bare string in Tokenizable).\n            if (Tokenizable.isTokenizable(raw)) {\n              results = raw\n            } else if (typeof raw === 'string') {\n              results = new Tokenizable(raw)\n            } else {\n              throw new Error(\n                `ArtifactTool \"${tool.name}\" returned a non-string/non-Tokenizable value`\n              )\n            }\n          } else if (Media.isMedia(raw)) {\n            results = raw\n          } else if (Array.isArray(raw) && raw.length > 0 && raw.every((m) => Media.isMedia(m))) {\n            results = raw as Media[]\n          } else if (looksLikeSpooledArtifact(raw)) {\n            results = raw as SpooledArtifact\n          } else if (typeof raw === 'string' || isInstanceOf(raw, 'Uint8Array', Uint8Array)) {\n            const reader = await spoolStore.write(callId, raw as string | Uint8Array)\n            const ArtifactCtor = (tool as Tool).artifactConstructor?.() ?? SpooledArtifact\n            results = new ArtifactCtor(reader)\n          } else {\n            // Defensive fallback — wrap stringified value so the model gets *something*.\n            const reader = await spoolStore.write(callId, String(raw))\n            const ArtifactCtor = (tool as Tool).artifactConstructor?.() ?? SpooledArtifact\n            results = new ArtifactCtor(reader)\n          }\n        } catch (err) {\n          toolHadError = true\n          // Surface field-level validation detail from the cause chain so the\n          // model can self-correct on the specific offending field. `E_INVALID_TOOL_ARGS`\n          // carries the joi `ValidationException` on `cause`, whose message is the\n          // joined field-level error text (e.g. `\"text\" is required`).\n          let detailMsg = isError(err) ? err.message : String(err)\n          if (\n            isError(err) &&\n            isError(err.cause) &&\n            err.cause.message &&\n            err.cause.message !== err.message\n          ) {\n            detailMsg = `${detailMsg} ${err.cause.message}`\n          }\n          results = new Tokenizable(detailMsg)\n        }\n        helpers.reportToolCall(callId, {\n          results,\n          isError: toolHadError,\n          isComplete: true,\n        })\n        const checksum = computeChecksum(tool.name, args)\n        const completedAt2 = nowIso()\n        await ctx.storeToolCall(\n          new ToolCall({\n            id: callId,\n            tool: tool.name,\n            args,\n            checksum,\n            isComplete: true,\n            isError: toolHadError,\n            results,\n            fromArtifactTool: isArtifactTool,\n            // ArtifactTool results are the documented exception: they inline the slice the model queried\n            // from a prior artifact (handing back a handle to a query result would be recursion). Every\n            // other result keeps the secure default (inline:false → handle).\n            inline: isArtifactTool,\n            createdAt: completedAt2,\n            updatedAt: completedAt2,\n            completedAt: completedAt2,\n          })\n        )\n      }\n\n      // FALLBACK tool-call recovery. WebLLM's OpenAI-shaped `message.tool_calls` is authoritative; this is\n      // consulted ONLY when the engine returned zero structured calls AND the caller opted in via\n      // `localToolCallParser`. On-device small models routinely emit a call in a surface form the chat\n      // template does not lift into `tool_calls` (`<call:name{…}`, a ```json block, bare `name\\nkey:\n      // value`), landing it in `content`. Pure parse — returns assembled calls with `args` as the JSON\n      // STRING `executeAndPersistToolCall` expects (empty when disabled or no match), so each call site can\n      // reflect them in the onRawGeneration tap and then execute. Fully backward-compatible: absent option\n      // → `[]` → today's native-only behaviour.\n      const parseFallbackToolCalls = (content: string): AssembledToolCall[] => {\n        if (merged.localToolCallParser === undefined) return []\n        if (typeof content !== 'string' || content.length === 0) return []\n        const parser = resolveToolCallParser(merged.localToolCallParser)\n        const toolNames = ctx.tools.visible().map((t) => t.name)\n        return parser(content, { toolNames }).calls.map((c) => ({\n          id: uuidv6(),\n          type: 'function' as const,\n          name: c.name,\n          args: JSON.stringify(c.arguments),\n        }))\n      }\n\n      const selfIdentity = merged.selfIdentity ?? 'assistant'\n      const reasoningPrecedence = merged.reasoningFieldPrecedence ?? [\n        'reasoning',\n        'reasoning_content',\n      ]\n\n      // ── Step 8: streaming path ────────────────────────────────────────────\n      if (stream) {\n        const chunks = completion as AsyncIterable<ChatCompletionsChunk>\n        if (!chunks || typeof chunks[Symbol.asyncIterator] !== 'function') {\n          ctx.nack(new E_WEBLLM_CHAT_COMPLETIONS_STREAM_ERROR(['engine did not return a stream']))\n          return\n        }\n        const accumulator = resolvedHelpers.createChatCompletionsToolCallDeltaAccumulator()\n        const streamId = dispatchStreamId\n\n        let partialMessageContent = ''\n        let sawMessageDelta = false\n\n        // Reasoning may stream under more than one provider-specific field at once. Accumulate each\n        // field's text under its own live stream id; at completion we dedup by content to decide\n        // whether to persist one Thought or one per divergent field. The first field to appear keeps\n        // the bare `streamId` (the common single-field case); any others get a `:field` suffix.\n        const thoughtAccum = new Map<ReasoningField, string>()\n        let primaryThoughtField: ReasoningField | undefined\n        const thoughtStreamId = (field: ReasoningField): string =>\n          field === primaryThoughtField ? streamId : `${streamId}:${field}`\n\n        const drainAndPersist = async (): Promise<void> => {\n          if (sawMessageDelta) {\n            helpers.reportMessage(streamId, '', { isComplete: true })\n            await ctx.storeMessage(\n              new Message({\n                id: streamId,\n                role: 'assistant',\n                content: partialMessageContent,\n                identity: selfIdentity,\n                createdAt: nowIso(),\n                updatedAt: nowIso(),\n              })\n            )\n          }\n          // Finalize every reasoning stream we opened live, then dedup by content to decide how many\n          // Thoughts to persist: one (fields agreed, or only one present) vs one per divergent field.\n          for (const field of thoughtAccum.keys()) {\n            helpers.reportThought(thoughtStreamId(field), '', { isComplete: true })\n          }\n          const thoughtExtracts = extractReasoningFields(\n            Object.fromEntries(thoughtAccum) as Partial<Record<ReasoningField, string>>,\n            reasoningPrecedence\n          )\n          if (thoughtExtracts.length === 1) {\n            await ctx.storeThought(\n              new Thought({\n                id: streamId,\n                content: thoughtExtracts[0].content,\n                identity: selfIdentity,\n                createdAt: nowIso(),\n                updatedAt: nowIso(),\n              })\n            )\n          } else {\n            for (const { field, content } of thoughtExtracts) {\n              await ctx.storeThought(\n                new Thought({\n                  id: thoughtStreamId(field),\n                  content,\n                  identity: selfIdentity,\n                  createdAt: nowIso(),\n                  updatedAt: nowIso(),\n                })\n              )\n            }\n          }\n          const nativeCalls = accumulator.drain()\n          // Fallback recovery (opt-in): if the engine streamed no structured calls, try to parse one out of\n          // the accumulated `content`. Consulted only when native calls are absent, so the engine wins.\n          const calls =\n            nativeCalls.length > 0 ? nativeCalls : parseFallbackToolCalls(partialMessageContent)\n\n          // Raw-generation observability tap (FROM the engine) — streaming path. `rawText`/`cleanedText`\n          // are the accumulated assistant content; `toolCalls` are the calls this dispatch will act on\n          // (native, or fallback-recovered — args JSON-parsed best-effort). Fired once at stream drain;\n          // observer errors swallowed.\n          if (merged.onRawGeneration) {\n            try {\n              merged.onRawGeneration({\n                rawText: partialMessageContent,\n                cleanedText: partialMessageContent,\n                reasoning: thoughtExtracts.map((r) => r.content),\n                toolCalls: calls.map((c) => {\n                  let parsedArgs: Record<string, unknown> = {}\n                  try {\n                    const p: unknown = JSON.parse(c.args || '{}')\n                    if (isObject(p)) parsedArgs = p as Record<string, unknown>\n                  } catch {\n                    /* leave args empty on unparseable JSON */\n                  }\n                  return { name: c.name, arguments: parsedArgs as never }\n                }),\n                streamed: true,\n                streamId: dispatchStreamId,\n              })\n            } catch {\n              /* observer errors are non-fatal */\n            }\n          }\n\n          helpers.log.debug({\n            kind: 'accumulator-finalised',\n            message: `Stream finalised: ${calls.length} tool call(s), message=${sawMessageDelta}, thoughtFields=${thoughtExtracts.length}`,\n            payload: {\n              toolCallCount: calls.length,\n              sawMessageDelta,\n              thoughtFieldCount: thoughtExtracts.length,\n            },\n          })\n          if (calls.length === 0) {\n            // No tool calls — terminal text answer. Self-ack only when opted in;\n            // otherwise leave unsignalled so the implementor's output pipeline\n            // owns turn completion (autoAck defaults to false).\n            if (merged.autoAck) ctx.ack()\n            return\n          }\n          for (const call of calls) {\n            if (ctx.abortSignal.aborted) return\n            await executeAndPersistToolCall(call)\n          }\n          // Tool calls produced — do NOT ack; the runner will iterate again.\n        }\n\n        try {\n          for await (const chunk of chunks) {\n            if (ctx.abortSignal.aborted) return\n            const delta = chunk.choices?.[0]?.delta\n            if (!delta) continue\n            if (typeof delta.content === 'string' && delta.content.length > 0) {\n              sawMessageDelta = true\n              partialMessageContent += delta.content\n              helpers.reportMessage(streamId, delta.content)\n            }\n            // Stream every reasoning field present on this delta, in precedence order. No\n            // mid-stream dedup — deltas are fragments; we compare full contents at completion.\n            for (const field of reasoningPrecedence) {\n              const reasoning = delta[field]\n              if (typeof reasoning !== 'string' || reasoning.length === 0) continue\n              if (!thoughtAccum.has(field)) {\n                primaryThoughtField ??= field\n                thoughtAccum.set(field, '')\n              }\n              thoughtAccum.set(field, thoughtAccum.get(field)! + reasoning)\n              helpers.reportThought(thoughtStreamId(field), reasoning)\n            }\n            if (Array.isArray(delta.tool_calls)) {\n              for (const d of delta.tool_calls) {\n                accumulator.feed(d)\n              }\n            }\n          }\n          await drainAndPersist()\n        } catch (err) {\n          if (ctx.abortSignal.aborted) return\n          helpers.log.error({\n            kind: 'stream-error',\n            message: `WebLLM stream failed: ${isError(err) ? err.message : String(err)}`,\n            payload: { detail: isError(err) ? err.message : String(err) },\n          })\n          emitLifecycle(merged, 'webllm', merged.model, 'error', { error: err })\n          ctx.nack(\n            new E_WEBLLM_CHAT_COMPLETIONS_STREAM_ERROR([isError(err) ? err.message : String(err)])\n          )\n          return\n        }\n        emitLifecycle(merged, 'webllm', merged.model, 'complete')\n        return\n      }\n\n      // ── Step 9: non-streaming path ────────────────────────────────────────\n      const parsed = completion as ChatCompletionsResponse\n      const choice = parsed.choices?.[0]\n      if (!choice) {\n        // Empty response, no tool calls — terminal. Self-ack only when opted in.\n        emitLifecycle(merged, 'webllm', merged.model, 'complete')\n        if (merged.autoAck) ctx.ack()\n        return\n      }\n      const msg = choice.message\n      const responseId = parsed.id ?? uuidv6()\n\n      if (msg && typeof msg.content === 'string' && msg.content.length > 0) {\n        const messageId = `${responseId}:message`\n        helpers.reportMessage(messageId, msg.content, { isComplete: true })\n        await ctx.storeMessage(\n          new Message({\n            id: messageId,\n            role: 'assistant',\n            content: msg.content,\n            identity: selfIdentity,\n            createdAt: nowIso(),\n            updatedAt: nowIso(),\n          })\n        )\n      }\n      // Reasoning may arrive under more than one provider-specific field. A single extracted\n      // entry (only one field present, or several but identical) keeps the historical\n      // `${responseId}:thought` id; divergent fields each surface as their own thought.\n      const reasoningExtracts = extractReasoningFields(msg, reasoningPrecedence)\n      for (const { field, content } of reasoningExtracts) {\n        const thoughtId =\n          reasoningExtracts.length > 1 ? `${responseId}:thought:${field}` : `${responseId}:thought`\n        helpers.reportThought(thoughtId, content, { isComplete: true })\n        await ctx.storeThought(\n          new Thought({\n            id: thoughtId,\n            content,\n            identity: selfIdentity,\n            createdAt: nowIso(),\n            updatedAt: nowIso(),\n          })\n        )\n      }\n\n      const rawCalls = msg?.tool_calls ?? []\n      const content = typeof msg?.content === 'string' ? msg.content : ''\n\n      // Fallback recovery (opt-in): if the engine returned no structured calls, try to parse one out of the\n      // assistant `content`. Consulted only when native calls are absent, so the engine always wins.\n      const nativeCalls: AssembledToolCall[] = rawCalls.map((tc) => ({\n        id: tc.id,\n        type: tc.type ?? 'function',\n        name: tc.function?.name ?? '',\n        args: tc.function?.arguments ?? '',\n      }))\n      const calls = nativeCalls.length > 0 ? nativeCalls : parseFallbackToolCalls(content)\n\n      // Raw-generation observability tap (FROM the engine) — non-streaming path. `rawText`/`cleanedText`\n      // are the returned assistant content; `toolCalls` are the calls this dispatch will act on (native, or\n      // fallback-recovered — args JSON-parsed best-effort). Fired once per terminal generation; observer\n      // errors swallowed.\n      if (merged.onRawGeneration) {\n        try {\n          merged.onRawGeneration({\n            rawText: content,\n            cleanedText: content,\n            reasoning: reasoningExtracts.map((r) => r.content),\n            toolCalls: calls.map((c) => {\n              let parsedArgs: Record<string, unknown> = {}\n              try {\n                const p: unknown = JSON.parse(c.args || '{}')\n                if (isObject(p)) parsedArgs = p as Record<string, unknown>\n              } catch {\n                /* leave args empty on unparseable JSON */\n              }\n              return { name: c.name, arguments: parsedArgs as never }\n            }),\n            streamed: false,\n            streamId: dispatchStreamId,\n          })\n        } catch {\n          /* observer errors are non-fatal */\n        }\n      }\n\n      if (calls.length === 0) {\n        // No tool calls — terminal text answer. Self-ack only when opted in;\n        // otherwise the implementor's output pipeline owns completion.\n        emitLifecycle(merged, 'webllm', merged.model, 'complete')\n        if (merged.autoAck) ctx.ack()\n        return\n      }\n      for (const call of calls) {\n        if (ctx.abortSignal.aborted) return\n        await executeAndPersistToolCall(call)\n      }\n      // Tool calls produced — do NOT ack; the runner will iterate again.\n      emitLifecycle(merged, 'webllm', merged.model, 'complete')\n    }\n  }\n\n  /**\n   * Returns `true` when `value` is an {@link WebLLMChatCompletionsAdapter} instance.\n   *\n   * @param value - The value to test.\n   * @returns `true` when `value` is an `WebLLMChatCompletionsAdapter` instance.\n   */\n  public static isWebLLMChatCompletionsAdapter(\n    value: unknown\n  ): value is WebLLMChatCompletionsAdapter {\n    return isInstanceOf(value, 'WebLLMChatCompletionsAdapter', WebLLMChatCompletionsAdapter)\n  }\n}\n"],"mappings":"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"}