{"version":3,"file":"ai-model/prompt/util.mjs","sources":["../../../../src/ai-model/prompt/util.ts"],"sourcesContent":["import type { SubGoal, SubGoalStatus } from '@/types';\n\n/**\n * Extract content from an XML tag in a string, searching from the end.\n * This approach handles cases where models prepend thinking content (like <think>...</think>)\n * before the actual response tags, or when there are incomplete/nested tags.\n *\n * Strategy: Find the LAST closing tag, then search backwards for the nearest opening tag.\n * This ensures we get the last complete tag pair, even if there are incomplete tags before it.\n *\n * @param xmlString - The XML string to parse\n * @param tagName - The name of the tag to extract (case-insensitive)\n * @returns The trimmed content of the tag, or undefined if not found\n */\nexport function extractXMLTag(\n  xmlString: string,\n  tagName: string,\n): string | undefined {\n  const lowerXmlString = xmlString.toLowerCase();\n  const lowerTagName = tagName.toLowerCase();\n  const closeTag = `</${lowerTagName}>`;\n  const openTag = `<${lowerTagName}>`;\n\n  // Find the last closing tag\n  const lastCloseIndex = lowerXmlString.lastIndexOf(closeTag);\n  if (lastCloseIndex === -1) {\n    // Fallback: handle half-open tags like `<action-type>Input` without\n    // matching close tag. Extract until the next XML tag boundary.\n    const lastOpenIndex = lowerXmlString.lastIndexOf(openTag);\n    if (lastOpenIndex === -1) {\n      return undefined;\n    }\n\n    const contentStart = lastOpenIndex + openTag.length;\n    const remaining = xmlString.substring(contentStart);\n    const nextTagIndex = remaining.indexOf('<');\n    const content =\n      nextTagIndex === -1 ? remaining : remaining.substring(0, nextTagIndex);\n\n    return content.trim();\n  }\n\n  // Search backwards from the closing tag to find the nearest opening tag\n  const searchArea = lowerXmlString.substring(0, lastCloseIndex);\n  const lastOpenIndex = searchArea.lastIndexOf(openTag);\n  if (lastOpenIndex === -1) {\n    return undefined;\n  }\n\n  // Extract content between the tags (use original string to preserve case)\n  const contentStart = lastOpenIndex + openTag.length;\n  const contentEnd = lastCloseIndex;\n  const content = xmlString.substring(contentStart, contentEnd);\n\n  return content.trim();\n}\n\n/**\n * Parse sub-goals from XML content\n * Handles both formats:\n * - <sub-goal index=\"1\" status=\"pending\">description</sub-goal>\n * - <sub-goal index=\"1\" status=\"finished\" />\n */\nexport function parseSubGoalsFromXML(xmlContent: string): SubGoal[] {\n  const subGoals: SubGoal[] = [];\n\n  // Match both self-closing and regular sub-goal tags\n  const regex =\n    /<sub-goal\\s+index=\"(\\d+)\"\\s+status=\"(pending|finished)\"(?:\\s*\\/>|>([\\s\\S]*?)<\\/sub-goal>)/gi;\n\n  let match: RegExpExecArray | null;\n  match = regex.exec(xmlContent);\n  while (match !== null) {\n    const index = Number.parseInt(match[1], 10);\n    const status = match[2] as SubGoalStatus;\n    const description = match[3]?.trim() || '';\n\n    subGoals.push({ index, status, description });\n    match = regex.exec(xmlContent);\n  }\n\n  return subGoals;\n}\n\n/**\n * Extract indexes of sub-goals marked as finished from <mark-sub-goal-done> content\n */\nexport function parseMarkFinishedIndexes(xmlContent: string): number[] {\n  const indexes: number[] = [];\n\n  // Match self-closing sub-goal tags with status=\"finished\"\n  const regex = /<sub-goal\\s+index=\"(\\d+)\"\\s+status=\"finished\"\\s*\\/>/gi;\n\n  let match: RegExpExecArray | null;\n  match = regex.exec(xmlContent);\n  while (match !== null) {\n    indexes.push(Number.parseInt(match[1], 10));\n    match = regex.exec(xmlContent);\n  }\n\n  return indexes;\n}\n\nexport const distanceThreshold = 16;\n\nexport function distance(\n  point1: { x: number; y: number },\n  point2: { x: number; y: number },\n) {\n  return Math.sqrt((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2);\n}\n"],"names":["extractXMLTag","xmlString","tagName","lowerXmlString","lowerTagName","closeTag","openTag","lastCloseIndex","lastOpenIndex","contentStart","remaining","nextTagIndex","content","searchArea","contentEnd","parseSubGoalsFromXML","xmlContent","subGoals","regex","match","index","Number","status","description","parseMarkFinishedIndexes","indexes","distanceThreshold","distance","point1","point2","Math"],"mappings":"AAcO,SAASA,cACdC,SAAiB,EACjBC,OAAe;IAEf,MAAMC,iBAAiBF,UAAU,WAAW;IAC5C,MAAMG,eAAeF,QAAQ,WAAW;IACxC,MAAMG,WAAW,CAAC,EAAE,EAAED,aAAa,CAAC,CAAC;IACrC,MAAME,UAAU,CAAC,CAAC,EAAEF,aAAa,CAAC,CAAC;IAGnC,MAAMG,iBAAiBJ,eAAe,WAAW,CAACE;IAClD,IAAIE,AAAmB,OAAnBA,gBAAuB;QAGzB,MAAMC,gBAAgBL,eAAe,WAAW,CAACG;QACjD,IAAIE,AAAkB,OAAlBA,eACF;QAGF,MAAMC,eAAeD,gBAAgBF,QAAQ,MAAM;QACnD,MAAMI,YAAYT,UAAU,SAAS,CAACQ;QACtC,MAAME,eAAeD,UAAU,OAAO,CAAC;QACvC,MAAME,UACJD,AAAiB,OAAjBA,eAAsBD,YAAYA,UAAU,SAAS,CAAC,GAAGC;QAE3D,OAAOC,QAAQ,IAAI;IACrB;IAGA,MAAMC,aAAaV,eAAe,SAAS,CAAC,GAAGI;IAC/C,MAAMC,gBAAgBK,WAAW,WAAW,CAACP;IAC7C,IAAIE,AAAkB,OAAlBA,eACF;IAIF,MAAMC,eAAeD,gBAAgBF,QAAQ,MAAM;IACnD,MAAMQ,aAAaP;IACnB,MAAMK,UAAUX,UAAU,SAAS,CAACQ,cAAcK;IAElD,OAAOF,QAAQ,IAAI;AACrB;AAQO,SAASG,qBAAqBC,UAAkB;IACrD,MAAMC,WAAsB,EAAE;IAG9B,MAAMC,QACJ;IAEF,IAAIC;IACJA,QAAQD,MAAM,IAAI,CAACF;IACnB,MAAOG,AAAU,SAAVA,MAAgB;QACrB,MAAMC,QAAQC,OAAO,QAAQ,CAACF,KAAK,CAAC,EAAE,EAAE;QACxC,MAAMG,SAASH,KAAK,CAAC,EAAE;QACvB,MAAMI,cAAcJ,KAAK,CAAC,EAAE,EAAE,UAAU;QAExCF,SAAS,IAAI,CAAC;YAAEG;YAAOE;YAAQC;QAAY;QAC3CJ,QAAQD,MAAM,IAAI,CAACF;IACrB;IAEA,OAAOC;AACT;AAKO,SAASO,yBAAyBR,UAAkB;IACzD,MAAMS,UAAoB,EAAE;IAG5B,MAAMP,QAAQ;IAEd,IAAIC;IACJA,QAAQD,MAAM,IAAI,CAACF;IACnB,MAAOG,AAAU,SAAVA,MAAgB;QACrBM,QAAQ,IAAI,CAACJ,OAAO,QAAQ,CAACF,KAAK,CAAC,EAAE,EAAE;QACvCA,QAAQD,MAAM,IAAI,CAACF;IACrB;IAEA,OAAOS;AACT;AAEO,MAAMC,oBAAoB;AAE1B,SAASC,SACdC,MAAgC,EAChCC,MAAgC;IAEhC,OAAOC,KAAK,IAAI,CAAEF,AAAAA,CAAAA,OAAO,CAAC,GAAGC,OAAO,CAAC,AAAD,KAAM,IAAKD,AAAAA,CAAAA,OAAO,CAAC,GAAGC,OAAO,CAAC,AAAD,KAAM;AACzE"}