{"version":3,"file":"parse-partial-json.mjs","names":[],"sources":["../../../../../../../ai/src/object-stream/parse-partial-json.ts"],"sourcesContent":["/**\n * Best-effort parse of a possibly-truncated JSON string into the value it\n * is \"on its way to\" becoming. Used by {@link streamObject} to emit a\n * partial object snapshot from each streamed delta before the full reply\n * has arrived.\n *\n * Returns `undefined` when the prefix can't yet be coerced into a value\n * (so the caller simply waits for more text). The FINAL parse in\n * `streamObject` is always strict `JSON.parse` — this tolerant parser only\n * powers the in-flight snapshots, so a too-clever completion never affects\n * the authoritative result.\n *\n * @example\n * parsePartialJson('{\"name\":\"Al');      // → { name: \"Al\" }\n * parsePartialJson('{\"items\":[1,2,');   // → { items: [1, 2] }\n * parsePartialJson('{\"a\":1,\"b\"');       // → { a: 1 } (drops the dangling key)\n */\nexport function parsePartialJson(text: string): unknown | undefined {\n  const trimmed = text.trim();\n  if (!trimmed) return undefined;\n\n  // Fast path: already-valid JSON.\n  const direct = tryParse(trimmed);\n  if (direct.ok) return direct.value;\n\n  const completed = completePartialJson(trimmed);\n  if (completed === undefined) return undefined;\n\n  const parsed = tryParse(completed);\n  return parsed.ok ? parsed.value : undefined;\n}\n\nfunction tryParse(text: string): { ok: true; value: unknown } | { ok: false } {\n  try {\n    return { ok: true, value: JSON.parse(text) };\n  } catch {\n    return { ok: false };\n  }\n}\n\n/**\n * Reconstruct a parseable JSON string from a truncated prefix by closing\n * open strings/containers and trimming dangling separators, keys, and\n * partial literals. Tries the most faithful completion first, then falls\n * back to dropping the unfinished tail.\n */\nfunction completePartialJson(text: string): string | undefined {\n  const stack: Array<\"{\" | \"[\"> = [];\n  let inString = false;\n  let escaped = false;\n\n  for (const ch of text) {\n    if (inString) {\n      if (escaped) escaped = false;\n      else if (ch === \"\\\\\") escaped = true;\n      else if (ch === '\"') inString = false;\n      continue;\n    }\n    if (ch === '\"') inString = true;\n    else if (ch === \"{\" || ch === \"[\") stack.push(ch);\n    else if (ch === \"}\" || ch === \"]\") stack.pop();\n  }\n\n  const closers = () =>\n    stack\n      .map(c => (c === \"{\" ? \"}\" : \"]\"))\n      .reverse()\n      .join(\"\");\n\n  // Faithful completion: close an open string, drop a trailing comma,\n  // fill a dangling `key:` with null, then close containers.\n  let core = text;\n  if (inString) core += '\"';\n  core = core.replace(/\\s+$/, \"\");\n  if (core.endsWith(\",\")) core = core.slice(0, -1);\n  if (core.endsWith(\":\")) core += \"null\";\n\n  const attempts: string[] = [core + closers()];\n\n  // Fallback 1: drop a dangling object key (a `\"...\"` with no value yet).\n  if (stack[stack.length - 1] === \"{\") {\n    const droppedKey = core.replace(/,?\\s*\"(?:[^\"\\\\]|\\\\.)*\"\\s*$/, \"\");\n    attempts.push(droppedKey.replace(/,\\s*$/, \"\") + closers());\n  }\n\n  // Fallback 2: drop a partial trailing literal / number (e.g. `tr`, `12.`).\n  const droppedLiteral = core.replace(/[:,]?\\s*[A-Za-z0-9.+\\-eE]+$/, match =>\n    match.trimStart().startsWith(\":\") ? \":null\" : \"\",\n  );\n  attempts.push(droppedLiteral.replace(/,\\s*$/, \"\") + closers());\n\n  for (const candidate of attempts) {\n    if (tryParse(candidate).ok) return candidate;\n  }\n\n  return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAiBA,SAAgB,iBAAiB,MAAmC;CAClE,MAAM,UAAU,KAAK,KAAK;CAC1B,IAAI,CAAC,SAAS,OAAO;CAGrB,MAAM,SAAS,SAAS,OAAO;CAC/B,IAAI,OAAO,IAAI,OAAO,OAAO;CAE7B,MAAM,YAAY,oBAAoB,OAAO;CAC7C,IAAI,cAAc,QAAW,OAAO;CAEpC,MAAM,SAAS,SAAS,SAAS;CACjC,OAAO,OAAO,KAAK,OAAO,QAAQ;AACpC;AAEA,SAAS,SAAS,MAA4D;CAC5E,IAAI;EACF,OAAO;GAAE,IAAI;GAAM,OAAO,KAAK,MAAM,IAAI;EAAE;CAC7C,QAAQ;EACN,OAAO,EAAE,IAAI,MAAM;CACrB;AACF;;;;;;;AAQA,SAAS,oBAAoB,MAAkC;CAC7D,MAAM,QAA0B,CAAC;CACjC,IAAI,WAAW;CACf,IAAI,UAAU;CAEd,KAAK,MAAM,MAAM,MAAM;EACrB,IAAI,UAAU;GACZ,IAAI,SAAS,UAAU;QAClB,IAAI,OAAO,MAAM,UAAU;QAC3B,IAAI,OAAO,MAAK,WAAW;GAChC;EACF;EACA,IAAI,OAAO,MAAK,WAAW;OACtB,IAAI,OAAO,OAAO,OAAO,KAAK,MAAM,KAAK,EAAE;OAC3C,IAAI,OAAO,OAAO,OAAO,KAAK,MAAM,IAAI;CAC/C;CAEA,MAAM,gBACJ,MACG,KAAI,MAAM,MAAM,MAAM,MAAM,GAAI,CAAC,CACjC,QAAQ,CAAC,CACT,KAAK,EAAE;CAIZ,IAAI,OAAO;CACX,IAAI,UAAU,QAAQ;CACtB,OAAO,KAAK,QAAQ,QAAQ,EAAE;CAC9B,IAAI,KAAK,SAAS,GAAG,GAAG,OAAO,KAAK,MAAM,GAAG,EAAE;CAC/C,IAAI,KAAK,SAAS,GAAG,GAAG,QAAQ;CAEhC,MAAM,WAAqB,CAAC,OAAO,QAAQ,CAAC;CAG5C,IAAI,MAAM,MAAM,SAAS,OAAO,KAAK;EACnC,MAAM,aAAa,KAAK,QAAQ,8BAA8B,EAAE;EAChE,SAAS,KAAK,WAAW,QAAQ,SAAS,EAAE,IAAI,QAAQ,CAAC;CAC3D;CAGA,MAAM,iBAAiB,KAAK,QAAQ,gCAA+B,UACjE,MAAM,UAAU,CAAC,CAAC,WAAW,GAAG,IAAI,UAAU,EAChD;CACA,SAAS,KAAK,eAAe,QAAQ,SAAS,EAAE,IAAI,QAAQ,CAAC;CAE7D,KAAK,MAAM,aAAa,UACtB,IAAI,SAAS,SAAS,CAAC,CAAC,IAAI,OAAO;AAIvC"}