{"version":3,"file":"tool_parsers-DgZhgxV8.mjs","names":[],"sources":["../src/batteries/llm/chat_common/tool_parsers.ts"],"sourcesContent":["/**\n * Runtime-agnostic tool-call text parsers for text-only LLM batteries.\n *\n * @remarks\n * **Why this exists.** On-device runtimes like transformers.js and LiteRT-LM (v0.13.1) are\n * text-in / text-out: they inject tool definitions into the chat template, but the model emits its\n * tool calls as **raw text in the assistant message**, in a format specific to the model family it\n * was fine-tuned on. Unlike the OpenAI/Ollama wire batteries — where the provider returns a\n * structured `tool_calls` array — these batteries must parse the call out of the text themselves.\n *\n * This mirrors how vLLM / SGLang / Ollama do it: one **post-hoc** parser per model family, selected\n * by a flag, run *after* generation (sub-millisecond, fails gracefully, never constrains decoding).\n * Each family parser is anchored on a literal marker (or, for the weak-signal JSON/pythonic forms, on\n * the callee name matching a real tool) so cross-family false positives are structurally impossible.\n *\n * **Formats are model-specific and drift across versions** (e.g. Gemma has three incompatible tool\n * formats across its generations; gpt-oss's Harmony channel ordering is an active upstream bug). The\n * bundled defaults target the small ONNX models that actually run in transformers.js; the `'auto'`\n * driver fails gracefully and a custom {@link ToolCallParserFn} is the escape hatch for anything else.\n *\n * NOT `@module`-tagged: private to the bundled LLM batteries, re-exported through their public\n * surfaces (`transformers_js`, `litert_lm`). Consumers import from those battery subpaths.\n */\n\nimport { isObject } from '@nhtio/adk/guards'\n\n// ─── Contract ─────────────────────────────────────────────────────────────────────────────────────\n\n/** A JSON-serialisable value — the shape of parsed tool-call arguments. */\nexport type JsonValue =\n  | string\n  | number\n  | boolean\n  | null\n  | JsonValue[]\n  | { [key: string]: JsonValue }\n\n/** A single tool call extracted from model output. `arguments` is a parsed object, never a string. */\nexport interface ParsedToolCall {\n  /** The tool name the model called. */\n  name: string\n  /** The parsed argument object (never a JSON string). */\n  arguments: Record<string, JsonValue>\n}\n\n/**\n * The result of running a {@link ToolCallParserFn} over assistant text.\n *\n * @remarks\n * `cleanedText` is the prose with every consumed tool-call span removed and trimmed, so the visible\n * assistant message never carries raw markup. On no-match a parser MUST return\n * `{ calls: [], cleanedText: rawText }` verbatim — that is the signal the `'auto'` driver uses to\n * detect \"this parser made no claim\" and move to the next one.\n */\nexport interface ToolCallParseResult {\n  /** The tool calls extracted, in document order. Empty when the parser made no claim. */\n  calls: ParsedToolCall[]\n  /** The assistant prose with every consumed tool-call span removed; equals the input on no-match. */\n  cleanedText: string\n}\n\n/** Context passed to a parser: the names of the tools actually offered this turn. */\nexport interface ToolCallParserContext {\n  /** The visible tool names this turn — lets a parser reject calls to tools that don't exist. */\n  toolNames: ReadonlyArray<string>\n}\n\n/** A synchronous tool-call text parser. */\nexport type ToolCallParserFn = (rawText: string, ctx: ToolCallParserContext) => ToolCallParseResult\n\n/** The bundled family parser names, plus `'auto'` (try-all) and `'none'` (disable). */\nexport type ToolCallParserName =\n  | 'auto'\n  | 'hermes'\n  | 'gemma'\n  | 'gpt_oss'\n  | 'pythonic'\n  | 'bare_pythonic'\n  | 'loose_keyed'\n  | 'llama3_json'\n  | 'mistral'\n  | 'qwen3_coder'\n  | 'phi'\n  | 'none'\n\n// ─── Shared helpers ─────────────────────────────────────────────────────────────────────────────────\n\nconst NO_MATCH = (rawText: string): ToolCallParseResult => ({ calls: [], cleanedText: rawText })\n\nconst asArgsObject = (value: unknown): Record<string, JsonValue> =>\n  isObject(value) ? (value as Record<string, JsonValue>) : {}\n\n/**\n * Remove a set of `[start, end)` spans from `text` (descending by start so offsets stay valid),\n * collapse the resulting whitespace seams, and trim.\n */\nconst removeSpans = (text: string, spans: Array<[number, number]>): string => {\n  let out = text\n  for (const [start, end] of [...spans].sort((a, b) => b[0] - a[0])) {\n    out = out.slice(0, start) + out.slice(end)\n  }\n  return out.replace(/\\n{3,}/g, '\\n\\n').trim()\n}\n\n/** Try to JSON.parse; return undefined on failure (parsers decline rather than throw). */\nconst tryJsonParse = (s: string): unknown => {\n  try {\n    return JSON.parse(s)\n  } catch {\n    return undefined\n  }\n}\n\n/**\n * From `text`, with `from` pointing AT an opening `open` char, return the index of its matching\n * `close` char — string-aware, so a delimiter inside a JSON string literal (e.g. a `}` or `]` or a\n * `</tool_call>`-shaped substring inside `{\"text\":\"…\"}`) is NOT miscounted. Returns -1 if the run is\n * unbalanced (e.g. a truncated stream). This is the brace/bracket scan that makes the marker-anchored\n * parsers robust to embedded markup in argument values — the dominant failure class the red-team panel\n * found in the old lazy/greedy regexes.\n */\nconst scanBalanced = (text: string, from: number, open: string, close: string): number => {\n  let depth = 0\n  let inStr = false\n  let escaped = false\n  for (let i = from; i < text.length; i++) {\n    const ch = text[i]\n    if (inStr) {\n      if (escaped) escaped = false\n      else if (ch === '\\\\') escaped = true\n      else if (ch === '\"') inStr = false\n      continue\n    }\n    if (ch === '\"') inStr = true\n    else if (ch === open) depth++\n    else if (ch === close) {\n      depth--\n      if (depth === 0) return i\n    }\n  }\n  return -1\n}\n\n// ─── Hermes: <tool_call>{json}</tool_call> (Hermes 2/3, Qwen2.5, Qwen3-Instruct) ──────────────────────\n\nconst HERMES_OPEN = '<tool_call>'\n\n/**\n * Parse Hermes-style `<tool_call>{\"name\":…,\"arguments\":{…}}</tool_call>` tags. The de-facto standard,\n * reused by Qwen2.5/Qwen3-Instruct. Anchored on the literal tags — zero collision with bare JSON.\n *\n * @remarks\n * The JSON object after each `<tool_call>` is located by a string-aware balanced-brace scan (not a lazy\n * `</tool_call>` regex), so an embedded `</tool_call>` or `{`/`}` inside a string argument value (e.g.\n * `{\"text\":\"</tool_call>\"}`) survives instead of truncating the call. The literal `</tool_call>` close\n * is still consumed for span removal when present immediately after the object.\n */\nexport const hermesToolCallParser: ToolCallParserFn = (rawText) => {\n  const calls: ParsedToolCall[] = []\n  const spans: Array<[number, number]> = []\n  let searchFrom = 0\n  for (;;) {\n    const openIdx = rawText.indexOf(HERMES_OPEN, searchFrom)\n    if (openIdx === -1) break\n    const braceStart = rawText.indexOf('{', openIdx + HERMES_OPEN.length)\n    if (braceStart === -1) break\n    const braceEnd = scanBalanced(rawText, braceStart, '{', '}')\n    if (braceEnd === -1) {\n      // Unbalanced (truncated stream) — stop; nothing more is parseable.\n      break\n    }\n    const obj = tryJsonParse(rawText.slice(braceStart, braceEnd + 1))\n    // The close tag is REQUIRED and must sit immediately after the object (allowing whitespace). The\n    // brace-scan already found the true object end, so an embedded `</tool_call>` inside a string value\n    // is BEFORE braceEnd and never consulted — but a genuinely truncated call (no real close) declines.\n    const afterObj = rawText.slice(braceEnd + 1)\n    const closeRel = /^\\s*<\\/tool_call>/.exec(afterObj)\n    if (closeRel && isObject(obj) && typeof obj.name === 'string') {\n      const o = obj as { name: string; arguments?: unknown }\n      calls.push({ name: o.name, arguments: asArgsObject(o.arguments) })\n      spans.push([openIdx, braceEnd + 1 + closeRel[0].length])\n      searchFrom = braceEnd + 1 + closeRel[0].length\n    } else {\n      // Malformed/truncated candidate: skip it but keep scanning for a later valid call.\n      searchFrom = braceEnd + 1\n    }\n  }\n  return calls.length > 0 ? { calls, cleanedText: removeSpans(rawText, spans) } : NO_MATCH(rawText)\n}\n\n/** Default {@link hermesToolCallParser}. */\nexport const defaultHermesToolCallParser = hermesToolCallParser\n\n// ─── Gemma E2B/E4B: call:NAME{key:value} (wrapper + <|\"|> tokens are decoder-stripped at runtime) ─────\n// The tokenizer_config TEMPLATE wraps calls as `<|tool_call>call:NAME{k:<|\"|>v<|\"|>}<tool_call|>`, but a\n// REAL onnx-community/gemma-4-E2B-it-ONNX run emits the bare inner form `call:get_weather{city:Paris}` —\n// the `<|tool_call>`/`<tool_call|>`/`<|\"|>` markers are SPECIAL TOKENS the decoder strips. Verified\n// empirically via the real-model matrix (do NOT trust the template's literal tokens for the runtime\n// form). So we accept BOTH: the wrapped template form AND the stripped bare form, with unquoted scalars.\n\n// Anchors locate the call HEAD (`[<|tool_call>] call:NAME`) up to — but not including — the opening\n// `{`; the argument block is then BRACE-SCANNED (string-aware, see scanGemmaArgs) rather than matched\n// by a flat `\\{[^{}]*\\}` regex, so a NESTED block survives. A real E4B `provide_answer` emits\n// `{answer:<|“|>…<|”|>,sources:[{path:<|“|>…<|”|>,title:…}]}` — nested arrays/objects AND curly smart\n// quotes — which the old single-level regex silently dropped (the whole call then leaked as prose).\nconst GEMMA_WRAP_HEAD = /<\\|tool_call>\\s*call:\\s*([A-Za-z_]\\w*)\\s*(?=\\{)/g\nconst GEMMA_BARE_HEAD = /call:\\s*([A-Za-z_]\\w*)\\s*(?=\\{)/g\n// Prefix-LESS bare head: `NAME{` with NO `call:` — a real runtime shape (Gemma E2B/E4B drops the\n// `call:` lead entirely, e.g. `say_i_dont_know{reason: \"…\"}`). Collision-prone (any `word{` in prose\n// matches), so this pass is GATED on ctx.toolNames — only an actual offered tool name is accepted.\nconst GEMMA_NOPREFIX_HEAD = /([A-Za-z_]\\w*)\\s*(?=\\{)/g\nconst GEMMA_WRAP_TAIL = '<tool_call|>'\n\n// Any double-quote glyph that delimits a Gemma string value: ASCII `\"` or the curly pair `“ ”`. The\n// runtime emits curly quotes (both as the `<|“|>` delimiter token and bare inside values); a straight\n// apostrophe `’` is NOT here — it is a literal inside a value, not a delimiter.\nconst GEMMA_DQUOTE = /[\"“”]/\n\n/**\n * Scan a Gemma argument block `{ … }` for the index of its matching `}`, treating string regions as\n * opaque so structural `{ } [ ]` inside a value never unbalance the scan. String state toggles on any\n * {@link GEMMA_DQUOTE} glyph and on a `<|\"|>`/`<|“|>`/`<|”|>` delimiter TOKEN (consumed whole). `from`\n * indexes the opening `{`. Returns the matching close index, or -1 if unterminated.\n */\nconst scanGemmaArgs = (text: string, from: number): number => {\n  let depth = 0\n  let inStr = false\n  for (let i = from; i < text.length; i++) {\n    // A quote delimiter — `<|\"|>` (5 chars), curly `“`/`”`, or ASCII `\"`. Collapse a RUN of consecutive\n    // delimiters (e.g. the triple-quote `\"\"\"` opener, or a doubled `<|\"|><|\"|>`) into a SINGLE toggle, so\n    // string state stays balanced and the matching `}` is found. Without the run-collapse, `\"\"\"` toggles\n    // three times → stays \"in string\" → the scan never balances and the whole call is dropped.\n    const delimW = gemmaQuoteDelimiterWidth(text, i)\n    if (delimW > 0) {\n      let j = i + delimW\n      for (\n        let w = gemmaQuoteDelimiterWidth(text, j);\n        w > 0;\n        w = gemmaQuoteDelimiterWidth(text, j)\n      ) {\n        j += w\n      }\n      inStr = !inStr\n      i = j - 1\n      continue\n    }\n    const ch = text[i]\n    if (inStr) continue\n    if (ch === '{' || ch === '[') depth++\n    else if (ch === '}' || ch === ']') {\n      depth--\n      if (depth === 0) return i\n    }\n  }\n  return -1\n}\n\n/**\n * Whether the delimiter token at `block[i]` is a Gemma string-quote delimiter, and how wide it is. A\n * delimiter is either the 5-char `<|\"|>` / `<|“|>` / `<|”|>` special-token placeholder the decoder leaks,\n * a bare curly smart quote `“`/`”`, or a plain ASCII `\"`. Returns the delimiter width (5, or 1) or 0 if\n * `block[i]` is not a quote delimiter.\n */\nconst gemmaQuoteDelimiterWidth = (block: string, i: number): number => {\n  if (\n    block[i] === '<' &&\n    block[i + 1] === '|' &&\n    GEMMA_DQUOTE.test(block[i + 2] ?? '') &&\n    block[i + 3] === '|' &&\n    block[i + 4] === '>'\n  ) {\n    return 5\n  }\n  const ch = block[i]\n  if (ch === '\"' || ch === '“' || ch === '”') return 1\n  return 0\n}\n\n/**\n * Canonicalise the string delimiters in a Gemma arg block to ASCII `\"`, tracking string state so a\n * `:` `,` `{` `}` `[` `]` (or a smart quote) appearing INSIDE a value is never treated as structure.\n * Handles the `<|\"|>`/`<|“|>`/`<|”|>` delimiter TOKENS and bare curly `“ ”` glyphs (both open and\n * close a string). The straight apostrophe `’` is a literal, not a delimiter. Inside a string, a raw\n * ASCII `\"` is escaped to `\\\"` so the resulting JSON stays valid.\n *\n * A RUN of consecutive quote delimiters (any mix of `\"`, curly, `<|\"|>`) collapses to a SINGLE toggle —\n * so Gemma's Python-style triple-quote opener `answer:\"\"\"…` (and the doubled `<|\"|><|\"|>` the decoder\n * sometimes emits) becomes one `\"` instead of `\"\"\"` (which JSON.parse reads as an empty string `\"\"`\n * followed by garbage). Verified against a real E2B `provide_answer{answer:\"\"\"# …}` capture that failed\n * to parse and surfaced as `E_LLM_EXECUTION_EXECUTOR_ERROR`.\n */\nconst canonicaliseGemmaStrings = (block: string): string => {\n  let out = ''\n  let inStr = false\n  for (let i = 0; i < block.length; i++) {\n    const delimW = gemmaQuoteDelimiterWidth(block, i)\n    if (delimW > 0) {\n      // Collapse a run of consecutive quote delimiters (e.g. `\"\"\"`, `<|\"|><|\"|>`) into a single toggle.\n      let j = i + delimW\n      while (true) {\n        const w = gemmaQuoteDelimiterWidth(block, j)\n        if (w === 0) break\n        j += w\n      }\n      out += '\"'\n      inStr = !inStr\n      i = j - 1\n      continue\n    }\n    const ch = block[i]\n    // Inside a string, escape raw JSON control characters. A real multi-paragraph `answer` value carries\n    // literal newlines/tabs, which JSON.parse rejects (\"Bad control character in string literal\") unless\n    // escaped. A lone backslash is also escaped so it cannot accidentally escape the closing quote.\n    if (inStr) {\n      if (ch === '\\n') {\n        out += '\\\\n'\n        continue\n      }\n      if (ch === '\\r') {\n        out += '\\\\r'\n        continue\n      }\n      if (ch === '\\t') {\n        out += '\\\\t'\n        continue\n      }\n      if (ch === '\\\\') {\n        out += '\\\\\\\\'\n        continue\n      }\n    }\n    out += ch\n  }\n  return out\n}\n\n/** A bare scalar token is \"already JSON\" — a quoted string, a nested `[`/`{` opener, a COMPLETE JSON\n * number, or a literal — and must NOT be re-quoted. Matching a FULL number (not merely a leading digit)\n * is load-bearing: a digit-led non-number like a UUID `1f173d33-…`, a version, or a hash must still be\n * quoted, else the call yields invalid JSON and leaks as prose. */\nconst isJsonReadyScalar = (v: string): boolean =>\n  /^[\"[{]/.test(v) ||\n  /^-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$/.test(v) ||\n  /^(?:true|false|null)$/.test(v)\n\n/**\n * Quote bare keys (`key:` → `\"key\":`), bare scalar values (`:Paris` → `:\"Paris\"`), and bare array\n * ELEMENTS (`[search_docs_semantic]` → `[\"search_docs_semantic\"]`) that the decoder-stripped form leaves\n * unquoted — but ONLY in structural position (outside quoted strings). Operates on a block whose string\n * delimiters are already canonical ASCII `\"` (see {@link canonicaliseGemmaStrings}), walking it\n * quote-aware so a `:` `,` or word inside a value is left untouched. Tokens already JSON (quoted,\n * number/sign, true/false/null, or a nested `[`/`{`) pass through.\n */\nconst quoteBareGemmaTokens = (block: string): string => {\n  let out = ''\n  let inStr = false\n  for (let i = 0; i < block.length; i++) {\n    const ch = block[i]\n    if (ch === '\"') {\n      inStr = !inStr\n      out += ch\n      continue\n    }\n    if (inStr) {\n      out += ch\n      continue\n    }\n    // Structural region. Quote a bare key (identifier directly after a `{`/`,`/`[` opener, ws allowed),\n    // emitting only the quoted name + ws — the following `:` is left for the next iteration so the value\n    // branch below can run on it. `(?=\\s*:)` confirms it is a key, not a bare-word value.\n    const keyM = /^([A-Za-z_]\\w*)(\\s*)(?=:)/.exec(block.slice(i))\n    if (keyM && /[{,[]\\s*$/.test(out)) {\n      out += `\"${keyM[1]}\"${keyM[2]}`\n      i += keyM[0].length - 1\n      continue\n    }\n    // Quote a bare scalar value right after a structural ':' — up to the next ',' '}' ']'.\n    if (ch === ':') {\n      const rest = block.slice(i + 1)\n      const valM = /^\\s*([^,}\\]]+?)\\s*(?=[,}\\]])/.exec(rest)\n      if (valM && !isJsonReadyScalar(valM[1])) {\n        out += `:\"${valM[1]}\"`\n        i += 1 + valM[0].length - 1\n        continue\n      }\n    }\n    // Quote a bare ARRAY ELEMENT / list member sitting right after a structural `[` or `,` opener\n    // (e.g. `tools_to_use:[search_docs_semantic]` or `[a,b]`). The key branch needs a following `:` and\n    // the value branch needs a leading `:`, so both miss enum-array elements — the decoder-stripped\n    // `make_plan` `tools_to_use:[search_docs_semantic]` stayed unquoted → invalid JSON → the whole\n    // planner call failed to parse and leaked as prose. Only fires when `out` ends with a `[`/`,` opener\n    // and the element is not already JSON (a nested `{`/`[`, quoted string, number, or literal).\n    if (/[[,]\\s*$/.test(out)) {\n      const elM = /^([^,{}[\\]\"\\s][^,}\\]]*?)\\s*(?=[,}\\]])/.exec(block.slice(i))\n      if (elM && !isJsonReadyScalar(elM[1])) {\n        out += `\"${elM[1]}\"`\n        i += elM[0].length - 1\n        continue\n      }\n    }\n    out += ch\n  }\n  return out\n}\n\n/**\n * Append any `}`/`]` closers the model dropped, so an under-closed JSON object still parses. Walks the\n * already-canonical (ASCII-quoted) text quote-aware, tracks the structural `{`/`[` stack, and emits the\n * matching closers in reverse order. Real Gemma E4B `provide_answer` output omits the OUTER `}` (the\n * `<tool_call|>` wrapper terminator stands in for it: `…}]<tool_call|>` with two `}` opened but one\n * closed) — without this, the unbalanced object is dropped and the whole cited answer leaks as prose.\n *\n * Also closes an UNTERMINATED string: a long `provide_answer` answer that the model truncates mid-value\n * (the output-token cap cuts it, or the stream ends: `{answer:\"\"\"# Overview…<cut>`) leaves the string\n * open. Verified live (Gemma-4 E2B): a broad \"give me an overview\" request whose answer overran the cap\n * produced exactly this shape, which failed JSON.parse → the call was dropped → the turn errored. When the\n * walk ends INSIDE a string, close it (`\"`) BEFORE emitting the structural closers, so the (truncated)\n * answer still commits instead of taking the whole turn down.\n */\nconst closeUnbalancedJson = (jsonish: string): string => {\n  const stack: string[] = []\n  let inStr = false\n  for (let i = 0; i < jsonish.length; i++) {\n    const ch = jsonish[i]\n    if (ch === '\"' && jsonish[i - 1] !== '\\\\') {\n      inStr = !inStr\n      continue\n    }\n    if (inStr) continue\n    if (ch === '{') stack.push('}')\n    else if (ch === '[') stack.push(']')\n    else if (ch === '}' || ch === ']') stack.pop()\n  }\n  let out = jsonish\n  // A dangling open string (truncated mid-value) must be closed first, else the appended `}`/`]` land\n  // inside the string and JSON.parse still fails.\n  if (inStr) out += '\"'\n  for (let i = stack.length - 1; i >= 0; i--) out += stack[i]\n  return out\n}\n\n/**\n * Normalise a Gemma KEY-VALUE SEPARATOR `=` to `:` in structural position. Real Gemma E2B/E4B output\n * sometimes assigns a top-level arg with `=` instead of `:` — observed live: `provide_answer{answer=\"…\",\n * sources:[…]}` (the `answer` key used `=`, while `sources`/`path` used `:`). The rest of the block is\n * valid JSON-ish, but the `=` means the key branch in {@link quoteBareGemmaTokens} (which keys on a\n * following `:`) never quotes that key, so the whole object fails to parse and the cited answer leaks as\n * prose. Rewrite only an `identifier=` sitting in KEY position — directly after a `{`/`,`/`[` opener (ws\n * allowed) — to `identifier:`, walking the block quote-aware so an `=` inside a string value (or a\n * Python-ish `k=v` already handled elsewhere) is left untouched. Runs AFTER string canonicalisation (so\n * `inStr` tracking is reliable) and BEFORE bare-token quoting (so the now-`:` key gets quoted normally).\n */\nconst normaliseGemmaKeySeparators = (block: string): string => {\n  let out = ''\n  let inStr = false\n  for (let i = 0; i < block.length; i++) {\n    const ch = block[i]\n    if (ch === '\"') {\n      inStr = !inStr\n      out += ch\n      continue\n    }\n    if (inStr) {\n      out += ch\n      continue\n    }\n    // A bare `identifier` directly after a structural opener, followed by `=`, is a key assigned with the\n    // wrong separator. Emit the name + ws + `:` and skip the `=`. `(?=\\s*=)`-style intent via an explicit\n    // `=` capture; the `out` tail must end with a `{`/`,`/`[` opener (ws allowed) to be a key position.\n    const keyEqM = /^([A-Za-z_]\\w*)(\\s*)=/.exec(block.slice(i))\n    if (keyEqM && /[{,[]\\s*$/.test(out)) {\n      out += `${keyEqM[1]}${keyEqM[2]}:`\n      i += keyEqM[0].length - 1\n      continue\n    }\n    out += ch\n  }\n  return out\n}\n\n/**\n * Normalise a Gemma arg block (`{k:<|\"|>v<|\"|>}`, stripped `{k:v}`, or a nested\n * `{a:<|“|>…<|”|>,b:[{…}]}`) into JSON. Done in quote-aware passes — canonicalise the string delimiters\n * to ASCII `\"`, normalise a wrong `=` key separator to `:`, quote the bare keys/scalars that remain,\n * then close any dropped `}`/`]` — so a `:` `,` or word inside a value (e.g. a `“Class: TurnRunner”`\n * title) is never mistaken for structure and a model that omits the outer closer still parses.\n */\nconst gemmaArgsToJson = (argsBlock: string): unknown => {\n  const canonical = canonicaliseGemmaStrings(argsBlock)\n  const colonised = normaliseGemmaKeySeparators(canonical)\n  const quoted = quoteBareGemmaTokens(colonised)\n  const balanced = closeUnbalancedJson(quoted)\n  return tryJsonParse(balanced)\n}\n\n/** Collect Gemma calls whose heads match `headRe`; brace-scan each arg block. */\nconst collectGemmaCalls = (\n  rawText: string,\n  headRe: RegExp,\n  wrapped: boolean,\n  allowed?: Set<string>\n): { calls: ParsedToolCall[]; spans: Array<[number, number]> } => {\n  const calls: ParsedToolCall[] = []\n  const spans: Array<[number, number]> = []\n  for (const m of rawText.matchAll(headRe)) {\n    // When gated (the prefix-less pass), only an actual offered tool name may open a call — otherwise\n    // any `word{` in prose would false-match.\n    if (allowed && !allowed.has(m[1])) continue\n    const headStart = m.index ?? 0\n    const braceStart = headStart + m[0].length // lookahead leaves m[0] ending right before `{`\n    // Find the arg-block boundary. Normally the matching `}`. But the wrapped form's REAL output drops\n    // the outer `}` and lets `<tool_call|>` terminate the call (`…}]<tool_call|>`), so the brace scan\n    // never balances — when wrapped, fall back to the wrapper tail as the boundary and let\n    // gemmaArgsToJson's `closeUnbalancedJson` repair the missing closer.\n    let braceEnd = scanGemmaArgs(rawText, braceStart)\n    let spanEnd: number\n    let blockEnd: number\n    if (braceEnd !== -1) {\n      blockEnd = braceEnd + 1\n      spanEnd = braceEnd + 1\n      // Consume a `<tool_call|>` terminator sitting immediately after the close brace — for the WRAPPED\n      // form (`<|tool_call>call:…}<tool_call|>`) AND the bare form, since the decoder strips the leading\n      // `<|tool_call>` head but the runtime still emits the trailing `<tool_call|>` (`call:NAME{…}<tool_call|>`).\n      // Anchored `^\\s*` right after the brace, so it can only eat a real adjacent terminator, never prose.\n      const tail = new RegExp(\n        `^\\\\s*${GEMMA_WRAP_TAIL.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}`\n      ).exec(rawText.slice(braceEnd + 1))\n      if (tail) spanEnd = braceEnd + 1 + tail[0].length\n    } else if (wrapped) {\n      const tailIdx = rawText.indexOf(GEMMA_WRAP_TAIL, braceStart)\n      if (tailIdx !== -1) {\n        blockEnd = tailIdx // up to (not including) the `<tool_call|>` terminator\n        spanEnd = tailIdx + GEMMA_WRAP_TAIL.length\n      } else {\n        // TRUNCATION fallback: wrapped head but no matching `}` AND no `<tool_call|>` tail — the call was\n        // cut off mid-value (e.g. a long `provide_answer` answer that overran the output-token cap). Take\n        // the rest of the string as the block and let gemmaArgsToJson (closeUnbalancedJson) repair the\n        // dangling string + missing closers, so the truncated answer still commits instead of the whole\n        // call being dropped (which surfaced as a turn-killing error).\n        blockEnd = rawText.length\n        spanEnd = rawText.length\n      }\n    } else {\n      // Bare/prefix-less head with no matching `}` — same truncation fallback: consume to end and repair.\n      blockEnd = rawText.length\n      spanEnd = rawText.length\n    }\n    const parsed = gemmaArgsToJson(rawText.slice(braceStart, blockEnd))\n    if (!isObject(parsed)) continue\n    calls.push({ name: m[1], arguments: asArgsObject(parsed) })\n    spans.push([headStart, spanEnd])\n  }\n  return { calls, spans }\n}\n\n/**\n * Parse Gemma E2B/E4B tool calls. Accepts the wrapped template form\n * (`<|tool_call>call:NAME{k:<|\"|>v<|\"|>}<tool_call|>`) AND the decoder-stripped runtime form\n * (`call:NAME{k:v}`, special tokens removed, scalars unquoted — the shape a real ONNX run emits),\n * including NESTED argument blocks with curly smart quotes (the form a real E4B `provide_answer`\n * emits: `{answer:<|“|>…<|”|>,sources:[{path:…}]}`), AND the PREFIX-LESS bare form `NAME{…}` with no\n * `call:` lead (e.g. `say_i_dont_know{reason: \"…\"}` — a real E2B/E4B runtime shape), gated on\n * `ctx.toolNames`. Targets the E2B/E4B form only — Gemma 3 (`tool_code` fences) and FunctionGemma\n * (`<start_function_call>`) are out of scope (use a custom {@link ToolCallParserFn}).\n */\nexport const gemmaToolCallParser: ToolCallParserFn = (rawText, ctx) => {\n  // Prefer the wrapped form (anchored, collision-free); fall back to the bare `call:NAME{` form, then to\n  // the prefix-less `NAME{` form — each only if the previous matched nothing (the earlier anchors also\n  // appear inside the later shapes, so never run more than one). The prefix-less pass is GATED on\n  // ctx.toolNames since `word{` alone is too weak a signal to claim without an allowlist.\n  let { calls, spans } = collectGemmaCalls(rawText, GEMMA_WRAP_HEAD, true)\n  if (calls.length === 0) {\n    ;({ calls, spans } = collectGemmaCalls(rawText, GEMMA_BARE_HEAD, false))\n  }\n  if (calls.length === 0 && ctx && ctx.toolNames.length > 0) {\n    ;({ calls, spans } = collectGemmaCalls(\n      rawText,\n      GEMMA_NOPREFIX_HEAD,\n      false,\n      new Set(ctx.toolNames)\n    ))\n  }\n  return calls.length > 0 ? { calls, cleanedText: removeSpans(rawText, spans) } : NO_MATCH(rawText)\n}\n\n/** Default {@link gemmaToolCallParser}. */\nexport const defaultGemmaToolCallParser = gemmaToolCallParser\n\n// ─── gpt-oss Harmony: <|channel|>commentary to=functions.NAME ...<|message|>{json}<|call|> ─────────────\n\n// Anchor: locates the channel header + callee name + the `<|message|>` marker. The JSON payload that\n// follows is then brace-scanned (string-aware) rather than lazily matched up to `<|call|>` — a `<|call|>`\n// substring inside a JSON string value would otherwise truncate the call.\nconst GPT_OSS_HEAD_RE =\n  /<\\|channel\\|>commentary\\s+to=functions\\.([A-Za-z0-9_]+)[\\s\\S]*?<\\|message\\|>/g\nconst GPT_OSS_CALL = '<|call|>'\n\n/**\n * Parse gpt-oss Harmony tool calls on the `commentary` channel:\n * `<|channel|>commentary to=functions.NAME <|constrain|>json<|message|>{…}<|call|>`. The arguments\n * payload after `<|message|>` is JSON, located by a string-aware balanced-brace scan so an embedded\n * `<|call|>` / brace inside a string value survives. Anchored on the literal Harmony markers.\n *\n * @remarks The Harmony channel/constrain ordering has a documented upstream template-vs-spec drift;\n * this matches the common ordering. Verify against a real gpt-oss ONNX run when one is available.\n */\nexport const gptOssToolCallParser: ToolCallParserFn = (rawText) => {\n  const calls: ParsedToolCall[] = []\n  const spans: Array<[number, number]> = []\n  for (const m of rawText.matchAll(GPT_OSS_HEAD_RE)) {\n    const headStart = m.index ?? 0\n    const afterMsg = headStart + m[0].length\n    const braceStart = rawText.indexOf('{', afterMsg)\n    if (braceStart === -1) continue\n    const braceEnd = scanBalanced(rawText, braceStart, '{', '}')\n    if (braceEnd === -1) continue\n    const obj = tryJsonParse(rawText.slice(braceStart, braceEnd + 1))\n    if (!isObject(obj)) continue\n    // The trailing `<|call|>` marker is REQUIRED and must sit right after the object (allowing\n    // whitespace). The brace-scan found the true object end, so an embedded `<|call|>` inside a string\n    // value is never consulted; a genuinely missing terminator (truncated stream) declines.\n    const afterObj = rawText.slice(braceEnd + 1)\n    const callRel = new RegExp(`^\\\\s*${GPT_OSS_CALL.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}`).exec(\n      afterObj\n    )\n    if (!callRel) continue\n    calls.push({ name: m[1], arguments: asArgsObject(obj) })\n    spans.push([headStart, braceEnd + 1 + callRel[0].length])\n  }\n  return calls.length > 0 ? { calls, cleanedText: removeSpans(rawText, spans) } : NO_MATCH(rawText)\n}\n\n/** Default {@link gptOssToolCallParser}. */\nexport const defaultGptOssToolCallParser = gptOssToolCallParser\n\n// ─── Pythonic: [get_weather(city='SF'), ...] (Llama 3.2/4, Olmo3) ─────────────────────────────────────\n\nconst PYTHONIC_SHAPE_RE = /^\\[\\s*[A-Za-z_]\\w*\\s*\\(.*\\)\\s*\\]$/s\nconst PYTHONIC_CALL_RE = /([A-Za-z_]\\w*)\\s*\\(([^)]*)\\)/g\n\n// Curly/smart quote pairs small models emit instead of ASCII quotes (e.g. Gemma: “…”, ‘…’).\n// Matching them lets us read string literals the model clearly intended as strings.\nconst SMART_QUOTE_PAIRS: ReadonlyArray<[string, string]> = [\n  ['“', '”'],\n  ['‘', '’'],\n  ['「', '」'],\n]\n\n/**\n * Read a single pythonic literal: quoted string (ASCII or smart quotes), number, True/False/None,\n * or a bracketed list of literals (`[a, b]`). Lists recurse so `sources=[“/a”, “/b”]` parses to a\n * real array. Unrecognised tokens fall through to the raw trimmed string.\n */\nconst readPythonLiteral = (raw: string): JsonValue => {\n  const t = raw.trim()\n  // ASCII-quoted string.\n  if (/^(['\"]).*\\1$/s.test(t)) return t.slice(1, -1)\n  // Smart/curly-quoted string (small-model output) — strip the matching pair.\n  for (const [open, close] of SMART_QUOTE_PAIRS) {\n    if (t.length >= 2 && t.startsWith(open) && t.endsWith(close)) return t.slice(1, -1)\n  }\n  // List literal: `[x, y, …]` → array of recursively-read literals.\n  if (t.startsWith('[') && t.endsWith(']')) {\n    const inner = t.slice(1, -1).trim()\n    if (inner.length === 0) return []\n    return splitPythonArgs(inner).map((el) => readPythonLiteral(el))\n  }\n  if (t === 'True' || t === 'true') return true\n  if (t === 'False' || t === 'false') return false\n  if (t === 'None' || t === 'null') return null\n  const n = Number(t)\n  return Number.isFinite(n) ? n : t\n}\n\n/** Split `k=v, k2=v2` on top-level commas (ignores commas inside quotes/brackets). */\nconst splitPythonArgs = (s: string): string[] => {\n  const parts: string[] = []\n  let depth = 0\n  let quote: string | undefined\n  let cur = ''\n  for (const ch of s) {\n    if (quote) {\n      cur += ch\n      if (ch === quote) quote = undefined\n      continue\n    }\n    if (ch === '\"' || ch === \"'\") {\n      quote = ch\n      cur += ch\n      continue\n    }\n    if (ch === '(' || ch === '[' || ch === '{') depth++\n    if (ch === ')' || ch === ']' || ch === '}') depth--\n    if (ch === ',' && depth === 0) {\n      parts.push(cur)\n      cur = ''\n      continue\n    }\n    cur += ch\n  }\n  if (cur.trim().length > 0) parts.push(cur)\n  return parts\n}\n\n/**\n * Parse pythonic tool calls — `[get_weather(city='SF'), get_time()]`. Requires the **whole trimmed\n * output** to be the bracketed call list, so it cannot false-positive on incidental prose. Parallel\n * calls are inherent to the format.\n *\n * **Disambiguation, NOT authorization.** The `[fn(args), …]` shape (whole-output) is the structural\n * signal that this is a call list. It deliberately does NOT check callees against `ctx.toolNames` —\n * whether a tool is *allowed* is the consumer's call, and the dispatch layer already replies \"Tool not\n * found: … Available tools: …\" so the model can self-correct. Dropping an unknown-tool call here would\n * hide the request and that feedback loop.\n */\nexport const pythonicToolCallParser: ToolCallParserFn = (rawText) => {\n  const t = rawText.trim()\n  if (!PYTHONIC_SHAPE_RE.test(t)) return NO_MATCH(rawText)\n  const inner = t.slice(1, -1)\n  const calls: ParsedToolCall[] = []\n  for (const m of inner.matchAll(PYTHONIC_CALL_RE)) {\n    const name = m[1]\n    const args: Record<string, JsonValue> = {}\n    for (const pair of splitPythonArgs(m[2])) {\n      const eq = pair.indexOf('=')\n      if (eq < 0) continue\n      args[pair.slice(0, eq).trim()] = readPythonLiteral(pair.slice(eq + 1))\n    }\n    calls.push({ name, arguments: args })\n  }\n  return calls.length > 0 ? { calls, cleanedText: '' } : NO_MATCH(rawText)\n}\n\n/** Default {@link pythonicToolCallParser}. */\nexport const defaultPythonicToolCallParser = pythonicToolCallParser\n\n// ─── Bare pythonic: NAME(k=v, …) without the [ ] list wrapper (weak signal — gated on toolNames) ──────\n\n// A bare call: optional leading \"/\" or \"call:\" noise, then NAME( … ). Captures name + the paren body.\n// Non-global; we scan with a stateful regex below so we can require the callee ∈ toolNames.\nconst BARE_PYTHONIC_CALL_RE = /(?:^|[\\s>/])(?:call:)?\\s*([A-Za-z_]\\w*)\\s*\\(([^)]*)\\)/g\n\n/**\n * Parse a BARE pythonic call — `provide_answer(answer=“…”, sources=[“/x”])` — i.e. the pythonic\n * `NAME(kwargs)` form WITHOUT the surrounding `[ … ]` list wrapper that {@link pythonicToolCallParser}\n * requires. Small models (observed: Gemma-4 E2B via transformers.js) emit this for a single call,\n * sometimes with a leading `/`, a `call:` prefix, or smart quotes in the args.\n *\n * Because the bare shape is a WEAK signal (it can resemble incidental prose like \"see foo(bar)\"),\n * this parser is gated HARD: it only claims a call whose callee is a real offered tool\n * (`ctx.toolNames`). That gate is what makes dropping the `[ ]` requirement safe. Runs after the\n * strict bracketed pythonic parser in the `'auto'` order.\n */\nexport const barePythonicToolCallParser: ToolCallParserFn = (rawText, ctx) => {\n  const allowed = new Set(ctx.toolNames)\n  if (allowed.size === 0) return NO_MATCH(rawText)\n  const calls: ParsedToolCall[] = []\n  const spans: Array<[number, number]> = []\n  for (const m of rawText.matchAll(BARE_PYTHONIC_CALL_RE)) {\n    const name = m[1]\n    if (!allowed.has(name)) continue // gate: only real tools — keeps prose from false-positiving\n    const args: Record<string, JsonValue> = {}\n    for (const pair of splitPythonArgs(m[2])) {\n      const eq = pair.indexOf('=')\n      if (eq < 0) continue\n      args[pair.slice(0, eq).trim()] = readPythonLiteral(pair.slice(eq + 1))\n    }\n    calls.push({ name, arguments: args })\n    // Span covers just the NAME(...) substring (m[0] may include a leading delimiter char).\n    const start = (m.index ?? 0) + m[0].indexOf(name)\n    spans.push([start, (m.index ?? 0) + m[0].length])\n  }\n  if (calls.length === 0) return NO_MATCH(rawText)\n  return { calls, cleanedText: removeSpans(rawText, spans) }\n}\n\n/** Default {@link barePythonicToolCallParser}. */\nexport const defaultBarePythonicToolCallParser = barePythonicToolCallParser\n\n// ─── Loose keyed: bare `tool_name` line + `key: value` lines (degenerate small-model form) ────────────\n\n/** Coerce a loose scalar value string into a JSON value (number/bool/null, else the trimmed string). */\nconst readLooseScalar = (raw: string): JsonValue => {\n  const s = raw.trim().replace(/,\\s*$/, '')\n  if (s === 'true') return true\n  if (s === 'false') return false\n  if (s === 'null') return null\n  if (/^-?\\d+(\\.\\d+)?$/.test(s)) return Number(s)\n  // Strip one layer of surrounding quotes if present.\n  const m = /^\"([\\s\\S]*)\"$|^'([\\s\\S]*)'$/.exec(s)\n  return m ? (m[1] ?? m[2] ?? '') : s\n}\n\n/**\n * Parse the DEGENERATE keyed form a small instruct model emits when it ignores every structured\n * tool-call grammar: the bare tool NAME on its own line, then one or more `argname: value` lines.\n *\n * @remarks\n * Observed verbatim from **Gemma-4 E2B on LiteRT-web** (raw-captured): asked to call an answer tool it\n * emits, with no `call:`/braces/brackets/JSON at all —\n * ```\n * say_i_dont_know\n * reason: The documentation does not contain a definition for that.\n * ```\n * No marker-anchored or pythonic/JSON parser claims this, so the \"call\" leaks into the visible answer as\n * prose AND the turn looks like a refusal (the tool the model meant to invoke never fires). A 2B can't\n * be reliably *instructed* into a format (changing the prompt's documented format did not change the\n * output), so the robust path is to parse the shape it actually produces.\n *\n * WEAK SIGNAL, gated HARD (like {@link barePythonicToolCallParser}): it only claims when the FIRST\n * non-empty line is EXACTLY a real offered tool name (`ctx.toolNames`) and is followed by at least one\n * `key: value` line. That gate is what keeps it from misreading ordinary prose (\"Note: …\", a heading\n * with a colon). Single call only (the degenerate form has no list syntax); runs LAST in `'auto'`.\n */\nexport const looseKeyedToolCallParser: ToolCallParserFn = (rawText, ctx) => {\n  const allowed = new Set(ctx.toolNames)\n  if (allowed.size === 0) return NO_MATCH(rawText)\n  const lines = rawText.split('\\n')\n  // Find the first non-empty line; it must be EXACTLY a known tool name (after trimming).\n  let i = 0\n  while (i < lines.length && lines[i].trim() === '') i++\n  if (i >= lines.length) return NO_MATCH(rawText)\n  const name = lines[i].trim()\n  if (!allowed.has(name)) return NO_MATCH(rawText)\n  // Collect the trailing `key: value` lines.\n  const args: Record<string, JsonValue> = {}\n  let j = i + 1\n  let lastConsumed = i\n  for (; j < lines.length; j++) {\n    const line = lines[j]\n    if (line.trim() === '') {\n      lastConsumed = j\n      continue\n    }\n    const kv = /^\\s*([A-Za-z_]\\w*)\\s*:\\s*([\\s\\S]*)$/.exec(line)\n    if (!kv) break // a non-keyed line ends the call body\n    args[kv[1]] = readLooseScalar(kv[2])\n    lastConsumed = j\n  }\n  if (Object.keys(args).length === 0) return NO_MATCH(rawText) // bare name alone is too weak to claim\n  // Consume from the name line through the last keyed line.\n  let start = 0\n  for (let k = 0; k < i; k++) start += lines[k].length + 1\n  let end = start\n  for (let k = i; k <= lastConsumed; k++) end += lines[k].length + (k < lines.length - 1 ? 1 : 0)\n  return { calls: [{ name, arguments: args }], cleanedText: removeSpans(rawText, [[start, end]]) }\n}\n\n/** Default {@link looseKeyedToolCallParser}. */\nexport const defaultLooseKeyedToolCallParser = looseKeyedToolCallParser\n\n// ─── Llama3-JSON: bare {\"name\":…, \"parameters\":…} (weakest signal — runs late) ────────────────────────\n\n/** Strip a single surrounding ```` ```json … ``` ```` (or bare ```` ``` ```` ) fence, if present. */\nconst stripCodeFence = (text: string): string => {\n  const m = /^```(?:json)?\\s*\\n?([\\s\\S]*?)\\n?```$/.exec(text.trim())\n  return m ? m[1].trim() : text.trim()\n}\n\n/**\n * Parse bare top-level JSON tool call(s) — `{\"name\":\"x\",\"parameters\":{…}}` (or `\"arguments\"`),\n * optionally wrapped in a ```` ```json … ``` ```` fence (a common small-model emission — verified via\n * the real-model matrix on Qwen2.5-Coder-0.5B). The weakest signal, so it is gated hard: after\n * un-fencing, the output must be ONLY top-level JSON object(s) AND every callee must be a real tool\n * (`ctx.toolNames`). Runs after every marker-anchored family in `'auto'`.\n *\n * @remarks\n * **Parallel calls.** Small Llama-family / Qwen-Coder models emit MULTIPLE calls as several top-level\n * objects separated by `,` / `;` / whitespace (verified on the real-model matrix:\n * Llama-3.2-1B → `{…}; {…}`, Qwen2.5-Coder-0.5B → a fenced `{…},\\n{…}`). So we string-aware brace-scan\n * the (un-fenced) text into successive balanced `{…}` objects, accepting only the separators above\n * between them — if any NON-separator prose sits between or around the objects, the whole thing declines\n * (the hard whole-output gate, so this never false-positives on JSON embedded in a sentence).\n *\n * **Disambiguation, NOT authorization.** This parser is marker-free, so it must distinguish a tool call\n * from arbitrary JSON content. It does that STRUCTURALLY: whole-output-is-object(s) + each object has a\n * string `name`. It deliberately does NOT check the callee against `ctx.toolNames` — whether a requested\n * tool is *allowed* is the consumer's decision, not the parser's. An unknown-tool call is surfaced like\n * any other; the dispatch layer already replies \"Tool not found: … Available tools: …\" so the model can\n * self-correct. Silently dropping the call here would hide both the request and that feedback loop.\n */\nexport const llama3JsonToolCallParser: ToolCallParserFn = (rawText) => {\n  const t = stripCodeFence(rawText)\n  if (!t.startsWith('{') || !t.endsWith('}')) return NO_MATCH(rawText)\n\n  const calls: ParsedToolCall[] = []\n  let i = 0\n  while (i < t.length) {\n    // Only object separators (`,`/`;`) and whitespace are allowed between top-level objects.\n    if (/[\\s,;]/.test(t[i]!)) {\n      i++\n      continue\n    }\n    if (t[i] !== '{') return NO_MATCH(rawText) // non-separator prose → not a pure call list; decline\n    const end = scanBalanced(t, i, '{', '}')\n    if (end === -1) return NO_MATCH(rawText)\n    const obj = tryJsonParse(t.slice(i, end + 1))\n    if (!isObject(obj)) return NO_MATCH(rawText)\n    const o = obj as { name?: unknown; parameters?: unknown; arguments?: unknown }\n    // Structural disambiguation only: a string `name` makes it call-shaped. Authorization is downstream.\n    if (typeof o.name !== 'string') return NO_MATCH(rawText)\n    calls.push({ name: o.name, arguments: asArgsObject(o.parameters ?? o.arguments) })\n    i = end + 1\n  }\n  return calls.length > 0 ? { calls, cleanedText: '' } : NO_MATCH(rawText)\n}\n\n/** Default {@link llama3JsonToolCallParser}. */\nexport const defaultLlama3JsonToolCallParser = llama3JsonToolCallParser\n\n// ─── Mistral: [TOOL_CALLS] [ {json}, ... ] ────────────────────────────────────────────────────────────\n\nconst MISTRAL_BOT_TOKEN = '[TOOL_CALLS]'\n\n/**\n * Parse Mistral tool calls — the `[TOOL_CALLS]` token followed by a JSON array of\n * `{ name, arguments }`. Anchored on the literal `[TOOL_CALLS]` token.\n *\n * @remarks\n * The array is located by a string-aware balanced-bracket scan from the first `[` after the token (same\n * approach as {@link phiToolCallParser}), so a `]` inside a string argument value (e.g.\n * `{\"text\":\"a]b\"}`) does not truncate the array the way the old greedy `\\[[\\s\\S]*\\]` regex did.\n */\nexport const mistralToolCallParser: ToolCallParserFn = (rawText) => {\n  const tokenIdx = rawText.indexOf(MISTRAL_BOT_TOKEN)\n  if (tokenIdx === -1) return NO_MATCH(rawText)\n  const arrStart = rawText.indexOf('[', tokenIdx + MISTRAL_BOT_TOKEN.length)\n  if (arrStart === -1) return NO_MATCH(rawText)\n  const arrEnd = scanBalanced(rawText, arrStart, '[', ']')\n  if (arrEnd === -1) return NO_MATCH(rawText)\n  const arr = tryJsonParse(rawText.slice(arrStart, arrEnd + 1))\n  if (!Array.isArray(arr)) return NO_MATCH(rawText)\n  const calls: ParsedToolCall[] = []\n  for (const entry of arr) {\n    if (isObject(entry) && typeof entry.name === 'string') {\n      const e = entry as { name: string; arguments?: unknown }\n      calls.push({ name: e.name, arguments: asArgsObject(e.arguments) })\n    }\n  }\n  return calls.length > 0\n    ? { calls, cleanedText: removeSpans(rawText, [[tokenIdx, arrEnd + 1]]) }\n    : NO_MATCH(rawText)\n}\n\n/** Default {@link mistralToolCallParser}. */\nexport const defaultMistralToolCallParser = mistralToolCallParser\n\n// ─── Qwen3-Coder: <tool_call><function=NAME><parameter=k>v</parameter></function></tool_call> ─────────\n\nconst QWEN3_CODER_RE =\n  /<tool_call>\\s*<function=([A-Za-z0-9_]+)>([\\s\\S]*?)<\\/function>\\s*<\\/tool_call>/g\nconst QWEN3_PARAM_RE = /<parameter=([A-Za-z0-9_]+)>([\\s\\S]*?)<\\/parameter>/g\n\n/**\n * Parse Qwen3-Coder's custom per-parameter XML — `<tool_call><function=NAME><parameter=k>v</parameter>\n * …</function></tool_call>`. Values are taken as trimmed strings (the format is untyped). Anchored on\n * the literal `<function=` tag, distinct from Hermes's JSON-in-`<tool_call>`.\n */\nexport const qwen3CoderToolCallParser: ToolCallParserFn = (rawText) => {\n  const calls: ParsedToolCall[] = []\n  const spans: Array<[number, number]> = []\n  for (const m of rawText.matchAll(QWEN3_CODER_RE)) {\n    const args: Record<string, JsonValue> = {}\n    for (const p of m[2].matchAll(QWEN3_PARAM_RE)) {\n      args[p[1]] = p[2].trim()\n    }\n    calls.push({ name: m[1], arguments: args })\n    spans.push([m.index ?? 0, (m.index ?? 0) + m[0].length])\n  }\n  return calls.length > 0 ? { calls, cleanedText: removeSpans(rawText, spans) } : NO_MATCH(rawText)\n}\n\n/** Default {@link qwen3CoderToolCallParser}. */\nexport const defaultQwen3CoderToolCallParser = qwen3CoderToolCallParser\n\n// ─── Phi-4-mini: functools[{\"name\":…,\"arguments\":{…}}, …] ─────────────────────────────────────────────\n// Verified against vLLM's `phi4_mini_json` parser: the begin-of-tool token is the literal `functools`,\n// followed by a JSON ARRAY of {name, arguments} objects (Microsoft's documented Phi-4 emission shape).\n// NOT the `<|tool_calls|>` markers some third-party docs claim — grounded against the real parser.\n\nconst PHI_BOT_TOKEN = 'functools'\n\n/**\n * Parse Phi-4-mini tool calls: the literal `functools` token followed by a JSON array of\n * `{ name, arguments }` objects (e.g. `functools[{\"name\":\"get_weather\",\"arguments\":{\"city\":\"SF\"}}]`).\n *\n * @remarks\n * Anchored on the `functools` begin-of-tool token (vLLM's `phi4_mini_json`). The array is located by\n * scanning for the first `[` after the token and matching to its balanced closing `]`, so trailing\n * prose after the call does not break parsing. Declines (no-match) if the payload is not a JSON array\n * of name-bearing objects.\n */\nexport const phiToolCallParser: ToolCallParserFn = (rawText) => {\n  const tokenIdx = rawText.indexOf(PHI_BOT_TOKEN)\n  if (tokenIdx === -1) return NO_MATCH(rawText)\n  const arrStart = rawText.indexOf('[', tokenIdx + PHI_BOT_TOKEN.length)\n  if (arrStart === -1) return NO_MATCH(rawText)\n  // Find the balanced closing bracket (string-aware so brackets inside JSON strings don't miscount).\n  let depth = 0\n  let inStr = false\n  let escaped = false\n  let arrEnd = -1\n  for (let i = arrStart; i < rawText.length; i++) {\n    const ch = rawText[i]\n    if (inStr) {\n      if (escaped) escaped = false\n      else if (ch === '\\\\') escaped = true\n      else if (ch === '\"') inStr = false\n      continue\n    }\n    if (ch === '\"') inStr = true\n    else if (ch === '[') depth++\n    else if (ch === ']') {\n      depth--\n      if (depth === 0) {\n        arrEnd = i\n        break\n      }\n    }\n  }\n  if (arrEnd === -1) return NO_MATCH(rawText)\n  const arr = tryJsonParse(rawText.slice(arrStart, arrEnd + 1))\n  if (!Array.isArray(arr)) return NO_MATCH(rawText)\n  const calls: ParsedToolCall[] = []\n  for (const entry of arr) {\n    if (isObject(entry) && typeof entry.name === 'string') {\n      const e = entry as { name: string; arguments?: unknown }\n      calls.push({ name: e.name, arguments: asArgsObject(e.arguments) })\n    }\n  }\n  return calls.length > 0\n    ? { calls, cleanedText: removeSpans(rawText, [[tokenIdx, arrEnd + 1]]) }\n    : NO_MATCH(rawText)\n}\n\n/** Default {@link phiToolCallParser}. */\nexport const defaultPhiToolCallParser = phiToolCallParser\n\n// ─── none: never matches ──────────────────────────────────────────────────────────────────────────\n\n/** A parser that never extracts anything — disables tool-call parsing entirely. */\nexport const noneToolCallParser: ToolCallParserFn = (rawText) => NO_MATCH(rawText)\n\n/** Default {@link noneToolCallParser}. */\nexport const defaultNoneToolCallParser = noneToolCallParser\n\n// ─── auto: try-all in priority order, first non-empty wins ────────────────────────────────────────\n\n/** The bundled family parsers keyed by name (excluding `'auto'`/`'none'`). */\nexport const BUNDLED_TOOL_CALL_PARSERS: Readonly<\n  Record<Exclude<ToolCallParserName, 'auto' | 'none'>, ToolCallParserFn>\n> = {\n  hermes: hermesToolCallParser,\n  gemma: gemmaToolCallParser,\n  gpt_oss: gptOssToolCallParser,\n  pythonic: pythonicToolCallParser,\n  bare_pythonic: barePythonicToolCallParser,\n  loose_keyed: looseKeyedToolCallParser,\n  llama3_json: llama3JsonToolCallParser,\n  mistral: mistralToolCallParser,\n  qwen3_coder: qwen3CoderToolCallParser,\n  phi: phiToolCallParser,\n}\n\n/**\n * The default `'auto'` precedence. Marker-anchored families first (collision-free); the weak-signal\n * pythonic/llama3_json forms run last and are gated on callee∈toolNames.\n */\nexport const DEFAULT_TOOL_CALL_PARSER_ORDER: ReadonlyArray<\n  Exclude<ToolCallParserName, 'auto' | 'none'>\n> = [\n  'hermes',\n  'gemma',\n  'gpt_oss',\n  'phi',\n  'pythonic',\n  'bare_pythonic',\n  'llama3_json',\n  'mistral',\n  'qwen3_coder',\n  'loose_keyed',\n]\n// (phi is marker-anchored on the `functools` token → placed with the other marker families, ahead of\n// the weak-signal pythonic/llama3_json forms; the rest keep their original precedence. `loose_keyed`\n// is the WEAKEST signal — a bare `name`+`key: value` form — so it runs DEAD LAST, only claiming text\n// whose first line is exactly an offered tool name.)\n\n/**\n * Compose an `'auto'` parser: run each family parser in `order` until one returns a non-empty\n * `calls` array; that result wins. Returns no-match if none claim the text.\n */\nexport const createAutoToolCallParser = (\n  parsers: Partial<\n    Record<Exclude<ToolCallParserName, 'auto' | 'none'>, ToolCallParserFn>\n  > = BUNDLED_TOOL_CALL_PARSERS,\n  order: ReadonlyArray<\n    Exclude<ToolCallParserName, 'auto' | 'none'>\n  > = DEFAULT_TOOL_CALL_PARSER_ORDER\n): ToolCallParserFn => {\n  return (rawText, ctx) => {\n    for (const name of order) {\n      const parser = parsers[name]\n      if (!parser) continue\n      const result = parser(rawText, ctx)\n      if (result.calls.length > 0) return result\n    }\n    return NO_MATCH(rawText)\n  }\n}\n\n/** Default {@link createAutoToolCallParser}. */\nexport const defaultCreateAutoToolCallParser = createAutoToolCallParser\n\n/**\n * Resolve a `toolCallParser` option (a name, `'auto'`, `'none'`, or a custom fn) to a concrete\n * {@link ToolCallParserFn}.\n *\n * @param option - The option value. Defaults to `'auto'` when undefined.\n * @param parsers - Override the bundled family parsers (e.g. swap the Gemma parser).\n */\nexport const resolveToolCallParser = (\n  option: ToolCallParserName | ToolCallParserFn | undefined,\n  parsers: Partial<\n    Record<Exclude<ToolCallParserName, 'auto' | 'none'>, ToolCallParserFn>\n  > = BUNDLED_TOOL_CALL_PARSERS\n): ToolCallParserFn => {\n  if (typeof option === 'function') return option\n  if (option === undefined || option === 'auto') return createAutoToolCallParser(parsers)\n  if (option === 'none') return noneToolCallParser\n  const parser = parsers[option] ?? BUNDLED_TOOL_CALL_PARSERS[option]\n  return parser ?? noneToolCallParser\n}\n\n/** Default {@link resolveToolCallParser}. */\nexport const defaultResolveToolCallParser = resolveToolCallParser\n"],"mappings":"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"}