// v1.10.5: XML-tag tool-call fallback. Some models emit // value // in plain content instead of using the OpenAI tool_calls JSON channel. // The streaming loop in inference.ts extracts these blocks via these helpers. export const XML_TOOL_OPEN = ''; export const XML_TOOL_CLOSE = ''; export function parseXmlToolCall( block: string, ): { name: string; args: Record } | null { const nameMatch = block.match(/]+)>/); if (!nameMatch || !nameMatch[1]) return null; const name = nameMatch[1].trim(); if (!name) return null; const args: Record = {}; // Non-greedy body so each … pair is matched // independently even when multiple appear in the same block. const paramRe = /]+)>([\s\S]*?)<\/parameter>/g; for (const m of block.matchAll(paramRe)) { const key = (m[1] ?? '').trim(); if (!key) continue; const raw = (m[2] ?? '').trim(); try { args[key] = JSON.parse(raw); } catch { args[key] = raw; } } return { name, args }; } // Locate the first character that begins (or completely contains) an // unfinished opener in `s`. Returns -1 when `s` can be flushed // to the client in full without risking a partial tag leak. // Case 1: a full `` opener with no matching closer — caller // must keep everything from that index forward until the next // chunk arrives with the closer. // Case 2: `s` ends with a strict prefix of `` (e.g. ` pair before reaching this check. export function partialXmlOpenerStart(s: string): number { const fullOpener = s.indexOf(XML_TOOL_OPEN); if (fullOpener !== -1) return fullOpener; const lastLt = s.lastIndexOf('<'); if (lastLt === -1) return -1; const suffix = s.slice(lastLt); if (XML_TOOL_OPEN.startsWith(suffix) && suffix.length < XML_TOOL_OPEN.length) { return lastLt; } return -1; }