{"version":3,"file":"helpers.cjs","names":[],"sources":["../../../../src/batteries/llm/litert_lm/helpers.ts"],"sourcesContent":["/**\n * Translation helpers for the LiteRT-LM adapter.\n *\n * @module @nhtio/adk/batteries/llm/litert_lm/helpers\n *\n * @remarks\n * Two layers:\n *\n * 1. **Re-exported format-agnostic helpers** from `chat_common` — they operate on ADK primitives and\n *    produce plain strings / trust envelopes / a JSON-Schema from a joi description, with no wire-format\n *    coupling. LiteRT reuses them verbatim (the same way the WebLLM battery does).\n * 2. **LiteRT-native mappers** defined here — the wire-shape functions the chat-completions batteries\n *    implement against the OpenAI wire format, rewritten against LiteRT's `Message` / `Tool` /\n *    `tool_response` / `Preface` shapes.\n */\n\nimport { Media } from '@nhtio/adk'\nimport { SpooledArtifact } from '@nhtio/adk'\nimport { E_UNSUPPORTED_MEDIA_MODALITY } from './exceptions'\nimport {\n  neutraliseDeveloperRulesTag,\n  sanitizeMimeType,\n  sanitizeFilenameForDescription,\n  defaultRenderArtifactHandleBody,\n} from '../chat_common/helpers'\nimport {\n  descriptionToChatCompletionsJsonSchema,\n  renderUntrustedContent as commonRenderUntrustedContent,\n  renderTrustedContent as commonRenderTrustedContent,\n  renderChatCompletionsSystemPrompt,\n  renderStandingInstructions,\n  renderMemories,\n  renderRetrievables,\n  renderRetrievableSafetyDirective,\n  renderFirstPartyRetrievables,\n  renderThirdPartyPublicRetrievables,\n  renderThirdPartyPrivateRetrievables,\n  renderRetrievableHandleBody,\n  renderThought,\n  filterThoughts,\n} from '../openai_chat_completions/helpers'\nimport type { Tokenizable } from '@nhtio/adk'\nimport type { ArtifactTool, Tool } from '@nhtio/adk'\nimport type { Message, Memory, Retrievable, Thought, ToolCall, ToolRegistry } from '@nhtio/adk'\nimport type {\n  LiteRtMessage,\n  LiteRtMessageContentItem,\n  LiteRtTool,\n  LiteRtPreface,\n  LiteRtLmBucketOrder,\n  UnsupportedMediaPolicy,\n  DescriptionLike,\n  JsonSchema,\n} from './types'\n\n// ── Re-export the entire format-agnostic layer (reused verbatim, like WebLLM) ─────────────────────\n\nexport {\n  descriptionToChatCompletionsJsonSchema,\n  defaultDescriptionToChatCompletionsJsonSchema,\n  renderUntrustedContent,\n  defaultRenderUntrustedContent,\n  renderTrustedContent,\n  defaultRenderTrustedContent,\n  renderStandingInstructions,\n  defaultRenderStandingInstructions,\n  renderMemories,\n  defaultRenderMemories,\n  renderRetrievables,\n  defaultRenderRetrievables,\n  renderRetrievableSafetyDirective,\n  defaultRenderRetrievableSafetyDirective,\n  renderFirstPartyRetrievables,\n  defaultRenderFirstPartyRetrievables,\n  renderThirdPartyPublicRetrievables,\n  defaultRenderThirdPartyPublicRetrievables,\n  renderThirdPartyPrivateRetrievables,\n  defaultRenderThirdPartyPrivateRetrievables,\n  renderThought,\n  defaultRenderThought,\n  filterThoughts,\n  defaultFilterThoughts,\n  renderChatCompletionsSystemPrompt,\n  defaultRenderChatCompletionsSystemPrompt,\n  extractReasoningFields,\n} from '../openai_chat_completions/helpers'\n\n// The shared SpooledArtifact handle-pattern machinery (renders the \"this is a spooled artifact, call\n// these tools to read it\" envelope) + its structural guard. Surfaced on the battery barrel so consumers\n// can override/compose it like any other render helper.\nexport {\n  renderArtifactHandleBody,\n  defaultRenderArtifactHandleBody,\n  renderRetrievableHandleBody,\n  defaultRenderRetrievableHandleBody,\n  looksLikeSpooledArtifact,\n} from '../chat_common/helpers'\n\n// Re-export the shared text-parser layer so consumers import everything from this battery's barrel.\n// LiteRT-LM (v0.13.1) is text-only: tool calls + reasoning arrive as text in `content`, parsed here.\nexport * from '../chat_common/tool_parsers'\nexport * from '../chat_common/reasoning_parsers'\nexport * from '../chat_common/lifecycle'\nexport * from '../chat_common/generation'\n// Shared WebGPU memory observability (budget probe, OOM detector, live instrument).\nexport * from '../chat_common/gpu_budget'\n\n// ── LiteRT-native mappers ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Convert ADK {@link @nhtio/adk!Tool} / {@link @nhtio/adk!ArtifactTool} instances into LiteRT\n * {@link LiteRtTool} definitions.\n *\n * @remarks\n * Reuses {@link descriptionToChatCompletionsJsonSchema} (format-agnostic: joi `describe()` → JSON\n * Schema) for the `parameters` field — LiteRT's `Tool.parameters` follows JSON Schema, same as the\n * chat-completions `function.parameters`.\n */\nexport const toolsToLiteRtTools = (\n  tools: ReadonlyArray<Tool | ArtifactTool>,\n  deps: {\n    descriptionToChatCompletionsJsonSchema: (d: DescriptionLike) => JsonSchema\n  } = { descriptionToChatCompletionsJsonSchema }\n): LiteRtTool[] => {\n  const out: LiteRtTool[] = []\n  for (const tool of tools) {\n    const described = tool.describe()\n    const parameters = deps.descriptionToChatCompletionsJsonSchema(\n      described.inputSchema as unknown as DescriptionLike\n    )\n    out.push({\n      name: described.name,\n      description: described.description,\n      parameters:\n        parameters && Object.keys(parameters).length > 0\n          ? (parameters as LiteRtTool['parameters'])\n          : { type: 'object', properties: {} },\n    })\n  }\n  return out\n}\n\n/** Default {@link toolsToLiteRtTools}. */\nexport const defaultToolsToLiteRtTools = toolsToLiteRtTools\n\n/**\n * Render tool definitions as a SYSTEM-PROMPT text block (the prompt-injection tool-delivery path),\n * rather than the native `preface.tools` field.\n *\n * @remarks\n * **Why this exists.** LiteRT-LM applies the model's OWN bundled chat template; for the Gemma-4\n * `.litertlm` preview builds, the template's tools branch is broken — passing `preface.tools` throws\n * `Failed to apply template: undefined value` inside the wasm runtime (a known Gemma-4 chat-template\n * bug, also seen in llama.cpp / mlx-lm / LM Studio when `tools[]` hits the native template). The\n * portable fix every other browser runtime uses (WebLLM/MLC, Open WebUI's \"default\" mode) is to\n * describe the tools as TEXT in the system prompt and parse the model's emitted call out of the output\n * — which the shared {@link createAutoToolCallParser} already does (its `gemma` family handles the\n * decoder-stripped `call:NAME{…}` runtime form, plus hermes/pythonic as fallbacks).\n *\n * The block lists each tool's name, description, and JSON-Schema parameters, then instructs Gemma's\n * OWN trained call format `call:NAME{key:value, …}` — NOT the pythonic `[func(arg=value)]` form. This\n * matters: the LiteRT-web runtime is Gemma-only, and a real Gemma E2B/E4B run emits the\n * decoder-stripped `call:NAME{…}` shape natively (verified via the real-model matrix; the `gemma`\n * family in {@link createAutoToolCallParser} is built for exactly this). Instructing the pythonic form\n * instead FIGHTS the model's training — a small instruct model, caught between its trained format and a\n * conflicting instruction, degenerates to an unparseable hybrid (e.g. `say_i_dont_know\\nreason: …`)\n * that no parser catches, so the \"call\" leaks into the answer as prose. Teaching the model the format\n * it already knows makes its natural output parse on the first try. A concrete example is included\n * because a 2B follows a shown example far more reliably than an abstract grammar.\n */\nexport const renderToolsAsPromptText = (\n  tools: ReadonlyArray<Tool | ArtifactTool>,\n  deps: {\n    descriptionToChatCompletionsJsonSchema: (d: DescriptionLike) => JsonSchema\n  } = { descriptionToChatCompletionsJsonSchema }\n): string => {\n  const defs = toolsToLiteRtTools(tools, deps)\n  if (defs.length === 0) return ''\n  const lines = defs.map(\n    (d) =>\n      `- ${d.name}: ${d.description ?? ''}\\n  parameters (JSON Schema): ${JSON.stringify(d.parameters)}`\n  )\n  return [\n    '<tool_definitions>',\n    'You have access to the following tools. To call one, emit EXACTLY this format and nothing else',\n    'on that line:',\n    '  call:tool_name{arg1:value1, arg2:value2}',\n    'For example, to call a tool named search with a query: call:search{query:turn runner}',\n    'Use only the tools listed below. Put string values as-is (no surrounding quotes needed).',\n    '',\n    ...lines,\n    '</tool_definitions>',\n  ].join('\\n')\n}\n\n/** Default {@link renderToolsAsPromptText}. */\nexport const defaultRenderToolsAsPromptText = renderToolsAsPromptText\n\n/**\n * Render a media kind/mime/filename into a LiteRT content item, or fall back per\n * `unsupportedMediaPolicy`.\n *\n * @remarks\n * The preview `.litertlm` models are text-in/text-out, and the exact multimodal content-item wire\n * shape is not yet stable in the published types. This maps image/audio/document/video to a\n * best-effort `{ type, path }`-style item when the matching modality flag is enabled, and otherwise\n * degrades through the shared `unsupportedMediaPolicy` (stash text / synthetic description / throw).\n * Verify the content-item shape against the installed `.d.ts` + a real multimodal model before\n * relying on the native path.\n */\nexport const renderMediaToLiteRtContent = async (input: {\n  media: Media\n  nonce: string\n  unsupportedMediaPolicy: UnsupportedMediaPolicy\n  modalityEnabled: boolean\n  renderUntrustedContent: typeof commonRenderUntrustedContent\n  renderTrustedContent: typeof commonRenderTrustedContent\n  warn?: (msg: string) => void\n}): Promise<LiteRtMessageContentItem[]> => {\n  const { media, modalityEnabled } = input\n  const kind = media.kind\n\n  const supportedNatively = modalityEnabled && (kind === 'image' || kind === 'audio') ? true : false\n\n  if (supportedNatively) {\n    const b64 = await media.asBase64()\n    // LiteRT content items carry inline data via a data: URI in the `path` slot for the preview\n    // builds; refine against the real multimodal model when one ships.\n    const safeMime = sanitizeMimeType(media.mimeType, kind)\n    return [\n      {\n        type: kind,\n        path: `data:${safeMime};base64,${b64}`,\n      } as LiteRtMessageContentItem,\n    ]\n  }\n\n  // Fall back to a text representation through the shared policy ('throw' raises here).\n  const fallbackText = await resolveMediaFallbackText(\n    media,\n    input.unsupportedMediaPolicy,\n    input.warn\n  )\n  const envelope =\n    media.trustTier === 'first-party'\n      ? input.renderTrustedContent(fallbackText, {\n          nonce: input.nonce,\n          kind: 'media-fallback',\n        } as never)\n      : input.renderUntrustedContent(fallbackText, {\n          nonce: input.nonce,\n          kind: 'media-fallback',\n        } as never)\n  return [{ type: 'text', text: envelope }]\n}\n\n/** Default stash keys probed for fallback text when a policy yields no explicit list. */\nconst DEFAULT_STASH_FALLBACK_KEYS = ['text:transcript', 'text:caption', 'text:description']\n\n/** Synthetic one-line description used when no stash text is available and the policy permits it. */\nconst syntheticMediaDescription = (media: Media): string =>\n  `[media: ${sanitizeFilenameForDescription(media.filename)}, kind=${media.kind}, mime=${sanitizeMimeType(media.mimeType, media.kind === 'image' || media.kind === 'audio' ? media.kind : undefined)}]`\n\n/**\n * Resolve a media instance to fallback text per the policy.\n *\n * @remarks\n * Mirrors the OpenAI battery's contract exactly:\n * - `'throw'` → raises {@link E_UNSUPPORTED_MEDIA_MODALITY} (the documented failure path).\n * - `'fallback-stash'` (string or object form) → returns the first non-empty stash entry, or\n *   degrades to a synthetic description on a miss (warning), never silently vanishing.\n * - `'synthetic-description'` → the synthetic one-liner.\n *\n * Always returns a usable string except when the policy is `'throw'` (which never returns).\n *\n * @throws {@link E_UNSUPPORTED_MEDIA_MODALITY} when `policy === 'throw'`.\n */\nconst resolveMediaFallbackText = async (\n  media: Media,\n  policy: UnsupportedMediaPolicy,\n  warn?: (msg: string) => void\n): Promise<string> => {\n  if (policy === 'throw') {\n    throw new E_UNSUPPORTED_MEDIA_MODALITY([media.kind, media.mimeType, media.filename])\n  }\n\n  if (\n    policy === 'fallback-stash' ||\n    (typeof policy === 'object' && policy.mode === 'fallback-stash')\n  ) {\n    const stashKeys = typeof policy === 'object' ? policy.stashKeys : DEFAULT_STASH_FALLBACK_KEYS\n    for (const key of stashKeys) {\n      const entry = media.stash.get<{ value?: unknown } | undefined>(key)\n      const value = entry && typeof entry === 'object' ? entry.value : undefined\n      if (typeof value === 'string' && value.length > 0) return value\n    }\n    warn?.(\n      `unsupportedMediaPolicy='fallback-stash' for ${media.filename} (${media.kind}): no matching stash entry — falling through to synthetic description.`\n    )\n    return syntheticMediaDescription(media)\n  }\n\n  // policy === 'synthetic-description'\n  return syntheticMediaDescription(media)\n}\n\n/**\n * Render a {@link @nhtio/adk!ToolCall}'s `results` into a LiteRT `tool_response` content item.\n *\n * @remarks\n * A {@link @nhtio/adk!SpooledArtifact} result renders as a HANDLE (metadata + the forged `artifact_*`\n * tools to read it) when its `ToolCall.inline === false` — the secure default — and inline via\n * `asString()` only when a producer opted into `inline: true`. Applies the trust envelope (reusing the\n * shared `renderTrustedContent`/`renderUntrustedContent`). Media results degrade to text via\n * {@link renderMediaToLiteRtContent}'s fallback path (LiteRT tool responses are text-shaped).\n */\nexport const renderLiteRtToolResult = async (input: {\n  toolCall: ToolCall\n  results: Tokenizable | SpooledArtifact | SpooledArtifact[] | Media | Media[]\n  tool: Tool | ArtifactTool | undefined\n  unsupportedMediaPolicy: UnsupportedMediaPolicy\n  renderUntrustedContent: typeof commonRenderUntrustedContent\n  renderTrustedContent: typeof commonRenderTrustedContent\n  /**\n   * Override for the artifact-handle body renderer (see {@link renderArtifactHandleBody}). Defaults to\n   * the shared {@link defaultRenderArtifactHandleBody}. The adapter threads the consumer's\n   * `helpers.renderArtifactHandleBody` here so an app can change which forged `artifact_*` reader the\n   * model is steered toward first.\n   */\n  renderArtifactHandleBody?: typeof defaultRenderArtifactHandleBody\n  warn?: (msg: string) => void\n}): Promise<LiteRtMessageContentItem> => {\n  const { results, toolCall, tool } = input\n  const renderHandle = input.renderArtifactHandleBody ?? defaultRenderArtifactHandleBody\n  const isTrusted = tool?.trusted === true\n\n  let body: string\n  // Whether `body` is a non-inlined artifact HANDLE (vs. inlined content). Only the `kind` label on\n  // the envelope changes — the trust TIER is still the producing tool's (trusted vs untrusted), since a\n  // handle to a third-party artifact carries the same injection hazard as its inlined content would.\n  let isHandle = false\n  if (\n    Media.isMedia(results) ||\n    (Array.isArray(results) && results.every((r) => Media.isMedia(r)))\n  ) {\n    const mediaList = Media.isMedia(results) ? [results] : (results as Media[])\n    const parts: string[] = []\n    for (const m of mediaList) {\n      const text = await resolveMediaFallbackText(m, input.unsupportedMediaPolicy, input.warn)\n      parts.push(text)\n    }\n    body = parts.join('\\n\\n')\n  } else if (\n    !Array.isArray(results) &&\n    SpooledArtifact.isSpooledArtifact(results) &&\n    toolCall.inline === false\n  ) {\n    // Handle pattern: the producer marked this result non-inline, so emit a directions-bearing handle\n    // (metadata + the forged artifact_* tools to read it) instead of dumping the body. This is what\n    // makes the spool/thrift pattern usable — the catalog/search result stays out of the prompt and\n    // the model pulls only the slices it needs. Parity with the OpenAI + Ollama batteries.\n    let byteLength = 0\n    let lineCount = 0\n    try {\n      byteLength = await results.byteLength()\n    } catch {\n      /* best-effort metadata */\n    }\n    try {\n      lineCount = await results.lineCount()\n    } catch {\n      /* best-effort metadata */\n    }\n    body = renderHandle({\n      callId: toolCall.id,\n      artifact: results,\n      byteLength,\n      lineCount,\n    })\n    isHandle = true\n  } else if (Array.isArray(results)) {\n    const parts: string[] = []\n    for (const a of results) parts.push(await (a as SpooledArtifact).asString())\n    body = parts.join('\\n\\n')\n  } else if (SpooledArtifact.isSpooledArtifact(results)) {\n    body = await results.asString()\n  } else {\n    body = (results as Tokenizable).toString()\n  }\n\n  const envelope = isTrusted\n    ? input.renderTrustedContent(body, {\n        nonce: toolCall.checksum,\n        kind: isHandle ? 'trusted-artifact-handle' : 'trusted-tool-result',\n        tool: toolCall.tool,\n      } as never)\n    : input.renderUntrustedContent(body, {\n        nonce: toolCall.checksum,\n        kind: isHandle ? 'artifact-handle' : 'tool-result',\n        tool: toolCall.tool,\n      } as never)\n\n  return {\n    type: 'tool_response',\n    tool_response: {\n      name: toolCall.tool,\n      response: { content: envelope },\n    },\n  } as LiteRtMessageContentItem\n}\n\n/** Default {@link renderLiteRtToolResult}. */\nexport const defaultRenderLiteRtToolResult = renderLiteRtToolResult\n\n/**\n * Build the LiteRT conversation input from the ADK dispatch context buckets.\n *\n * @remarks\n * Maps the ADK history model onto LiteRT's `createConversation({ preface })` + per-turn\n * `sendMessage(messages)` shape:\n *\n * - **Leading buckets** (system prompt + standingInstructions / memories / retrievables before\n *   `timeline` in `bucketOrder`) → a single `preface.messages` system message.\n * - **Tools** → `preface.tools`.\n * - **Timeline** (messages, surviving thoughts, tool calls — chronological) → the `messages` array\n *   sent for this turn, each a LiteRT `Message`. Thoughts render via the shared `renderThought` into\n *   assistant messages; tool calls render an assistant `tool_calls` message + a `tool` role message\n *   carrying the pre-rendered `tool_response` content item.\n *\n * Returns `{ preface, messages }` for `engine.createConversation({ preface })` then\n * `conversation.sendMessageStreaming(messages)`.\n */\nexport const buildLiteRtConversationInput = async (input: {\n  systemPrompt: Tokenizable\n  standingInstructions: Iterable<Tokenizable>\n  memories: Iterable<Memory>\n  retrievables: Iterable<Retrievable>\n  messages: Iterable<Message>\n  thoughts: Iterable<Thought>\n  toolCalls: Iterable<ToolCall>\n  tools: ToolRegistry\n  renderedToolCallResults: Map<ToolCall, LiteRtMessageContentItem>\n  /**\n   * The live dispatch context, threaded so DYNAMIC (evaluatable) {@link Tokenizable} content resolves\n   * against it at assembly via `.render(renderCtx)`. Optional — a static Tokenizable ignores it, and\n   * callers outside a dispatch may omit it (the evaluator's no-context fallback applies). Typed loosely\n   * (the primitive types the arg as `DispatchContext`; this battery does not import that contract).\n   */\n  renderCtx?: unknown\n  bucketOrder: LiteRtLmBucketOrder\n  selfIdentity: string\n  thoughtSurfacing: 'all-self' | 'latest-self' | 'all'\n  replayCompatibility: ReadonlyArray<string>\n  /**\n   * How tool definitions reach the model. `'prompt'` (default) renders them as system-prompt text (the\n   * portable path — the Gemma-4 `.litertlm` template throws on native `tools`); `'native'` uses\n   * `preface.tools` (the chat-template path — only for models whose template handles tools).\n   */\n  toolDelivery?: 'prompt' | 'native'\n  /**\n   * Whether the model's \"thinking\" mode is enabled. Passed EXPLICITLY into the chat template via\n   * `preface.extra_context.enable_thinking` so the template never decides for itself. Defaults to\n   * `false`.\n   */\n  enableThinking?: boolean\n  toolsToLiteRtTools: typeof toolsToLiteRtTools\n  renderToolsAsPromptText?: typeof renderToolsAsPromptText\n  renderThought: typeof renderThought\n  filterThoughts: typeof filterThoughts\n  renderUntrustedContent: typeof commonRenderUntrustedContent\n  renderTrustedContent: typeof commonRenderTrustedContent\n  renderChatCompletionsSystemPrompt: typeof renderChatCompletionsSystemPrompt\n  renderStandingInstructions: typeof renderStandingInstructions\n  renderMemories: typeof renderMemories\n  renderRetrievables: typeof renderRetrievables\n  renderRetrievableSafetyDirective: typeof renderRetrievableSafetyDirective\n  renderFirstPartyRetrievables: typeof renderFirstPartyRetrievables\n  renderThirdPartyPublicRetrievables: typeof renderThirdPartyPublicRetrievables\n  renderThirdPartyPrivateRetrievables: typeof renderThirdPartyPrivateRetrievables\n  renderRetrievableHandleBody?: typeof renderRetrievableHandleBody\n  // Multimodal: the two SessionConfig modality flags + the policy + the media renderer. When a user\n  // message carries `attachments` and the matching flag is on, the message's `content` becomes a\n  // MessageContentItem[] ([text, image|audio, …]); otherwise media degrades via the policy. Off by\n  // default → text-only behavior byte-for-byte unchanged.\n  visionModalityEnabled?: boolean\n  audioModalityEnabled?: boolean\n  unsupportedMediaPolicy?: UnsupportedMediaPolicy\n  renderMediaToLiteRtContent?: typeof renderMediaToLiteRtContent\n  warn?: (msg: string) => void\n}): Promise<{ preface: LiteRtPreface; messages: LiteRtMessage[] }> => {\n  // Leading system content (system prompt + before-timeline buckets), reusing the shared renderer.\n  const systemText = await input.renderChatCompletionsSystemPrompt({\n    systemPrompt: input.systemPrompt,\n    standingInstructions: input.standingInstructions,\n    memories: input.memories,\n    retrievables: input.retrievables,\n    renderCtx: input.renderCtx,\n    bucketOrder: input.bucketOrder,\n    renderStandingInstructions: input.renderStandingInstructions,\n    renderMemories: input.renderMemories,\n    renderRetrievables: input.renderRetrievables,\n    renderRetrievableSafetyDirective: input.renderRetrievableSafetyDirective,\n    renderFirstPartyRetrievables: input.renderFirstPartyRetrievables,\n    renderThirdPartyPublicRetrievables: input.renderThirdPartyPublicRetrievables,\n    renderThirdPartyPrivateRetrievables: input.renderThirdPartyPrivateRetrievables,\n    renderRetrievableHandleBody: input.renderRetrievableHandleBody,\n    renderUntrustedContent: input.renderUntrustedContent,\n  })\n\n  // Tool delivery: 'prompt' (default) renders tool defs as system text (the portable path — the\n  // Gemma-4 .litertlm template throws on native `tools`); 'native' uses preface.tools.\n  const toolDelivery = input.toolDelivery ?? 'prompt'\n  const visibleTools = input.tools.visible()\n  const renderToolsText = input.renderToolsAsPromptText ?? renderToolsAsPromptText\n  const toolsPromptText =\n    toolDelivery === 'prompt' && visibleTools.length > 0 ? renderToolsText(visibleTools) : ''\n\n  const prefaceMessages: LiteRtMessage[] = []\n  const systemParts: string[] = []\n  if (typeof systemText === 'string' && systemText.length > 0) systemParts.push(systemText)\n  if (toolsPromptText.length > 0) systemParts.push(toolsPromptText)\n  if (systemParts.length > 0) {\n    prefaceMessages.push({ role: 'system', content: systemParts.join('\\n\\n') })\n  }\n  const toolDefs = toolDelivery === 'native' ? input.toolsToLiteRtTools(visibleTools) : []\n  const preface: LiteRtPreface = {\n    messages: prefaceMessages,\n    ...(toolDefs.length > 0 ? { tools: toolDefs } : {}),\n    // Always pass the thinking flag EXPLICITLY into the template's variable scope — the gemma-4\n    // template gates `<|think|>` on `enable_thinking is defined and enable_thinking`, so leaving it\n    // unset lets the runtime decide. Default off.\n    extra_context: { enable_thinking: input.enableThinking ?? false },\n  }\n\n  // Timeline: messages + surviving thoughts + tool calls, chronological.\n  const survivingThoughts = input.filterThoughts(\n    input.thoughts,\n    input.thoughtSurfacing,\n    input.selfIdentity,\n    input.replayCompatibility\n  )\n  type Item =\n    | { kind: 'message'; at: number; value: Message }\n    | { kind: 'thought'; at: number; value: Thought }\n    | { kind: 'toolCall'; at: number; value: ToolCall }\n  const items: Item[] = []\n  for (const m of input.messages)\n    items.push({ kind: 'message', at: m.createdAt.toMillis(), value: m })\n  for (const t of survivingThoughts)\n    items.push({ kind: 'thought', at: t.createdAt.toMillis(), value: t })\n  for (const tc of input.toolCalls)\n    items.push({ kind: 'toolCall', at: tc.createdAt.toMillis(), value: tc })\n  items.sort((a, b) => a.at - b.at)\n\n  const messages: LiteRtMessage[] = []\n  for (const item of items) {\n    if (item.kind === 'message') {\n      const m = item.value\n      const role = m.role === 'user' ? 'user' : 'assistant'\n      // Neutralise a body-embedded no-nonce developer-rules tier (envelope-mimicry defense).\n      // `.render(renderCtx)` resolves a DYNAMIC Tokenizable against the live dispatch context (static → its\n      // string, as before); the overflow guard measured the same ctx, so counts and content agree.\n      const text = neutraliseDeveloperRulesTag(\n        m.content !== undefined ? m.content.render(input.renderCtx as never) : ''\n      )\n      // Multimodal: a message with attachments + the renderer wired → emit a content-item array\n      // (text first, then one item per attachment via the shared media renderer, which honors the\n      // modality flags + degrades disabled/unsupported kinds through `unsupportedMediaPolicy`).\n      const renderMedia = input.renderMediaToLiteRtContent\n      if (renderMedia && m.attachments.length > 0) {\n        const contentItems: LiteRtMessageContentItem[] =\n          text.length > 0 ? [{ type: 'text', text }] : []\n        for (const media of m.attachments) {\n          const modalityEnabled =\n            media.kind === 'image'\n              ? (input.visionModalityEnabled ?? false)\n              : media.kind === 'audio'\n                ? (input.audioModalityEnabled ?? false)\n                : false\n          const rendered = await renderMedia({\n            media,\n            nonce: m.id,\n            unsupportedMediaPolicy: input.unsupportedMediaPolicy ?? 'throw',\n            modalityEnabled,\n            renderUntrustedContent: input.renderUntrustedContent,\n            renderTrustedContent: input.renderTrustedContent,\n            warn: input.warn,\n          })\n          contentItems.push(...rendered)\n        }\n        messages.push({ role, content: contentItems })\n      } else {\n        messages.push({ role, content: text })\n      }\n    } else if (item.kind === 'thought') {\n      const t = item.value\n      const envelope = input.renderThought(t.content.render(input.renderCtx as never), {\n        nonce: t.id,\n        kind: 'self-reasoning',\n        from: t.identity?.identifier ?? input.selfIdentity,\n      } as never)\n      messages.push({ role: 'assistant', content: envelope })\n    } else {\n      const tc = item.value\n      // Assistant turn that issued the tool call.\n      messages.push({\n        role: 'assistant',\n        content: '',\n        tool_calls: [\n          {\n            type: 'function',\n            function: {\n              name: tc.tool,\n              arguments: (tc.args ?? {}) as Record<string, never>,\n            },\n          },\n        ],\n      })\n      // The tool result, as a tool-role message carrying the pre-rendered tool_response item.\n      const rendered = input.renderedToolCallResults.get(tc)\n      if (rendered !== undefined) {\n        messages.push({ role: 'tool', content: [rendered] })\n      }\n    }\n  }\n\n  return { preface, messages }\n}\n\n/** Default {@link buildLiteRtConversationInput}. */\nexport const defaultBuildLiteRtConversationInput = buildLiteRtConversationInput\n\n/**\n * A streaming accumulator over LiteRT `ReadableStream<Message>` chunks.\n *\n * @remarks\n * Each chunk is a partial {@link LiteRtMessage}. The `@litert-lm/core` v0.13.1 JS runtime is\n * **text-in / text-out**: it emits `content` only and does NOT populate `tool_calls` or `channels` on\n * output (those wire fields exist for feeding history back IN). Tool calls and reasoning come out as\n * **raw text inside `content`**, in the model family's own format — the adapter parses them out after\n * the stream drains via the shared tool-call / reasoning parser layer.\n *\n * So the accumulator collects assistant `content` text only, handling BOTH delivery conventions —\n * incremental deltas (append) and full-accumulated snapshots (replace) — by detecting whether the\n * incoming value extends the running buffer (`startsWith`) or is a fresh fragment. It also tolerates\n * `content` arriving as a `MessageContentItem[]` by concatenating the text items.\n */\nexport interface LiteRtStreamAccumulator {\n  /** Feed one chunk; returns the newly-appended content text (for incremental reporting). */\n  feed(chunk: LiteRtMessage): { contentDelta: string }\n  /** Final assembled content text. */\n  content(): string\n}\n\n/** Extract the text from a chunk's `content` (string or text-item array). */\nconst chunkContentText = (content: LiteRtMessage['content']): string => {\n  if (typeof content === 'string') return content\n  if (Array.isArray(content)) {\n    return content\n      .filter((i) => i.type === 'text' && typeof i.text === 'string')\n      .map((i) => i.text as string)\n      .join('')\n  }\n  return ''\n}\n\n/** Create a {@link LiteRtStreamAccumulator}. */\nexport const createLiteRtStreamAccumulator = (): LiteRtStreamAccumulator => {\n  let contentBuf = ''\n\n  // Append `incoming` onto `prev`, tolerating either delta or full-accumulated delivery.\n  const merge = (prev: string, incoming: string): { next: string; delta: string } => {\n    if (incoming.length === 0) return { next: prev, delta: '' }\n    if (prev.length === 0) return { next: incoming, delta: incoming }\n    if (incoming.startsWith(prev)) {\n      // Full-accumulated snapshot — the delta is the suffix beyond what we already have.\n      return { next: incoming, delta: incoming.slice(prev.length) }\n    }\n    // Incremental fragment — append.\n    return { next: prev + incoming, delta: incoming }\n  }\n\n  return {\n    feed(chunk) {\n      const text = chunkContentText(chunk.content)\n      if (text.length === 0) return { contentDelta: '' }\n      const { next, delta } = merge(contentBuf, text)\n      contentBuf = next\n      return { contentDelta: delta }\n    },\n    content: () => contentBuf,\n  }\n}\n\n/** Default {@link createLiteRtStreamAccumulator}. */\nexport const defaultCreateLiteRtStreamAccumulator = createLiteRtStreamAccumulator\n"],"mappings":"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"}