{"version":3,"file":"ai-model/service-caller/json.mjs","sources":["../../../../src/ai-model/service-caller/json.ts"],"sourcesContent":["import { jsonrepair } from 'jsonrepair';\n\n/**\n * Extract the JSON portion from a model response.\n *\n * This mainly handles fenced JSON like ```json { ... } ``` by removing the\n * fence. If that does not produce a JSON object, it falls back to a greedy regex\n * that extracts from the first \"{\" to the last \"}\". If that still fails, the\n * original response is returned.\n *\n * Expected model responses are JSON objects, possibly wrapped in markdown\n * fences. Natural language mixed with JSON, or arrays like\n * [{\"type\":\"Tap\"}, {\"type\":\"Hover\"}], are outside the supported contract and\n * are not reliably recoverable.\n *\n * This legacy extractor is also used by extraction responses that can be any\n * JSON value. In those cases, arrays/strings/numbers have no object braces and\n * pass through unchanged.\n */\nexport function extractJSONFromCodeBlock(response: string) {\n  try {\n    // First, try to match a JSON object directly in the response\n    const jsonMatch = response.match(/^\\s*(\\{[\\s\\S]*\\})\\s*$/);\n    if (jsonMatch) {\n      return jsonMatch[1];\n    }\n\n    // If no direct JSON object is found, try to extract JSON from a code block\n    const codeBlockMatch = response.match(\n      /```(?:json)?\\s*(\\{[\\s\\S]*?\\})\\s*```/,\n    );\n    if (codeBlockMatch) {\n      return codeBlockMatch[1];\n    }\n\n    // If no code block is found, try to find a JSON-like structure in the text\n    const jsonLikeMatch = response.match(/\\{[\\s\\S]*\\}/);\n    if (jsonLikeMatch) {\n      return jsonLikeMatch[0];\n    }\n  } catch {}\n  // If no JSON-like structure is found, return the original response\n  return response;\n}\n\nexport type JsonParserSource =\n  | 'generic-object'\n  | 'planning-action-param'\n  | 'locate'\n  | 'section-locator';\n\nexport interface JsonParserContext {\n  source: JsonParserSource;\n  preserveStringValueKeys?: string[];\n  requireObject?: boolean;\n}\n\nexport type JsonParser = (raw: string, context?: JsonParserContext) => unknown;\n\n/**\n * Trim whitespace in parsed JSON by normalizing:\n * 1. All object keys (e.g., \" prompt \" -> \"prompt\")\n * 2. All string values (e.g., \" Tap \" -> \"Tap\")\n * This handles LLM output that may include leading/trailing spaces.\n */\nfunction trimParsedJsonStrings(\n  obj: any,\n  context: Pick<JsonParserContext, 'preserveStringValueKeys'> = {},\n): any {\n  // Handle null and undefined\n  if (obj === null || obj === undefined) {\n    return obj;\n  }\n\n  // Handle arrays - recursively normalize each element\n  if (Array.isArray(obj)) {\n    return obj.map((item) => trimParsedJsonStrings(item, context));\n  }\n\n  // Handle objects\n  if (typeof obj === 'object') {\n    const normalized: any = {};\n\n    for (const [key, value] of Object.entries(obj)) {\n      // Trim the key to remove leading/trailing spaces\n      const trimmedKey = key.trim();\n      const preserveStringValue =\n        context.preserveStringValueKeys?.includes(trimmedKey) ?? false;\n\n      const normalizedValue =\n        typeof value === 'string'\n          ? preserveStringValue\n            ? value\n            : value.trim()\n          : trimParsedJsonStrings(value, context);\n\n      normalized[trimmedKey] = normalizedValue;\n    }\n\n    return normalized;\n  }\n\n  // Handle primitive strings\n  if (typeof obj === 'string') {\n    return obj.trim();\n  }\n\n  // Return other primitives as-is\n  return obj;\n}\n\nfunction repairKnownJsonIssues(\n  jsonBlock: string,\n  _rawResponse: string,\n): string {\n  // TODO: Add project-specific repairs that jsonrepair cannot handle.\n  return jsonBlock;\n}\n\nfunction assertJsonObject(\n  parsed: unknown,\n): asserts parsed is Record<string, unknown> {\n  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n    throw new Error(\n      `expected parsed LLM response to be a JSON object, got ${JSON.stringify(\n        parsed,\n      )}`,\n    );\n  }\n}\n\nfunction parseJsonWithRepair(jsonStr: string) {\n  try {\n    return JSON.parse(jsonStr);\n  } catch {\n    return JSON.parse(jsonrepair(jsonStr));\n  }\n}\n\nexport function parseModelResponseJson(\n  raw: string,\n  context?: JsonParserContext,\n) {\n  const cleanJsonString = extractJSONFromCodeBlock(raw);\n  const requireObject = context?.requireObject ?? true;\n\n  let parsedObj: unknown;\n\n  try {\n    parsedObj = parseJsonWithRepair(cleanJsonString);\n    if (requireObject) {\n      assertJsonObject(parsedObj);\n    }\n  } catch (e1) {\n    const code = repairKnownJsonIssues(cleanJsonString, raw);\n    if (code === cleanJsonString) {\n      throw new Error(\n        `failed to parse LLM response into JSON. Error - ${String(\n          e1,\n        )}. Response - \\n ${raw}`,\n      );\n    }\n\n    try {\n      parsedObj = parseJsonWithRepair(code);\n      if (requireObject) {\n        assertJsonObject(parsedObj);\n      }\n    } catch (e2) {\n      throw new Error(\n        `failed to parse LLM response into JSON. First error - ${String(\n          e1,\n        )}. Second error - ${String(e2)}. Response - \\n ${raw}`,\n      );\n    }\n  }\n\n  return trimParsedJsonStrings(parsedObj, context);\n}\n"],"names":["extractJSONFromCodeBlock","response","jsonMatch","codeBlockMatch","jsonLikeMatch","trimParsedJsonStrings","obj","context","Array","item","normalized","key","value","Object","trimmedKey","preserveStringValue","normalizedValue","repairKnownJsonIssues","jsonBlock","_rawResponse","assertJsonObject","parsed","Error","JSON","parseJsonWithRepair","jsonStr","jsonrepair","parseModelResponseJson","raw","cleanJsonString","requireObject","parsedObj","e1","code","String","e2"],"mappings":";AAmBO,SAASA,yBAAyBC,QAAgB;IACvD,IAAI;QAEF,MAAMC,YAAYD,SAAS,KAAK,CAAC;QACjC,IAAIC,WACF,OAAOA,SAAS,CAAC,EAAE;QAIrB,MAAMC,iBAAiBF,SAAS,KAAK,CACnC;QAEF,IAAIE,gBACF,OAAOA,cAAc,CAAC,EAAE;QAI1B,MAAMC,gBAAgBH,SAAS,KAAK,CAAC;QACrC,IAAIG,eACF,OAAOA,aAAa,CAAC,EAAE;IAE3B,EAAE,OAAM,CAAC;IAET,OAAOH;AACT;AAsBA,SAASI,sBACPC,GAAQ,EACRC,UAA8D,CAAC,CAAC;IAGhE,IAAID,QAAAA,KACF,OAAOA;IAIT,IAAIE,MAAM,OAAO,CAACF,MAChB,OAAOA,IAAI,GAAG,CAAC,CAACG,OAASJ,sBAAsBI,MAAMF;IAIvD,IAAI,AAAe,YAAf,OAAOD,KAAkB;QAC3B,MAAMI,aAAkB,CAAC;QAEzB,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIC,OAAO,OAAO,CAACP,KAAM;YAE9C,MAAMQ,aAAaH,IAAI,IAAI;YAC3B,MAAMI,sBACJR,QAAQ,uBAAuB,EAAE,SAASO,eAAe;YAE3D,MAAME,kBACJ,AAAiB,YAAjB,OAAOJ,QACHG,sBACEH,QACAA,MAAM,IAAI,KACZP,sBAAsBO,OAAOL;YAEnCG,UAAU,CAACI,WAAW,GAAGE;QAC3B;QAEA,OAAON;IACT;IAGA,IAAI,AAAe,YAAf,OAAOJ,KACT,OAAOA,IAAI,IAAI;IAIjB,OAAOA;AACT;AAEA,SAASW,sBACPC,SAAiB,EACjBC,YAAoB;IAGpB,OAAOD;AACT;AAEA,SAASE,iBACPC,MAAe;IAEf,IAAI,CAACA,UAAU,AAAkB,YAAlB,OAAOA,UAAuBb,MAAM,OAAO,CAACa,SACzD,MAAM,IAAIC,MACR,CAAC,sDAAsD,EAAEC,KAAK,SAAS,CACrEF,SACC;AAGT;AAEA,SAASG,oBAAoBC,OAAe;IAC1C,IAAI;QACF,OAAOF,KAAK,KAAK,CAACE;IACpB,EAAE,OAAM;QACN,OAAOF,KAAK,KAAK,CAACG,WAAWD;IAC/B;AACF;AAEO,SAASE,uBACdC,GAAW,EACXrB,OAA2B;IAE3B,MAAMsB,kBAAkB7B,yBAAyB4B;IACjD,MAAME,gBAAgBvB,SAAS,iBAAiB;IAEhD,IAAIwB;IAEJ,IAAI;QACFA,YAAYP,oBAAoBK;QAChC,IAAIC,eACFV,iBAAiBW;IAErB,EAAE,OAAOC,IAAI;QACX,MAAMC,OAAOhB,sBAAsBY,iBAAiBD;QACpD,IAAIK,SAASJ,iBACX,MAAM,IAAIP,MACR,CAAC,gDAAgD,EAAEY,OACjDF,IACA,gBAAgB,EAAEJ,KAAK;QAI7B,IAAI;YACFG,YAAYP,oBAAoBS;YAChC,IAAIH,eACFV,iBAAiBW;QAErB,EAAE,OAAOI,IAAI;YACX,MAAM,IAAIb,MACR,CAAC,sDAAsD,EAAEY,OACvDF,IACA,iBAAiB,EAAEE,OAAOC,IAAI,gBAAgB,EAAEP,KAAK;QAE3D;IACF;IAEA,OAAOvB,sBAAsB0B,WAAWxB;AAC1C"}