// @ts-nocheck function parseToolCallJson(jsonStr, calls, nameHint = '') { const raw = String(jsonStr).trim(); const candidates = [raw, raw.replace(/\{\{/g, '{').replace(/\}\}/g, '}')]; for (const s of candidates) { try { const obj = JSON.parse(s); if (!obj || typeof obj !== 'object') continue; const name = obj.name || nameHint; if (!name) continue; const args = obj.arguments != null ? obj.arguments : (obj.parameters != null ? obj.parameters : {}); calls.push({ id: `toolu_local_${Date.now()}_${calls.length}`, name: String(name), arguments: typeof args === 'string' ? safeJson(args) : (args || {}), }); return true; } catch (_) { } } console.warn('[tool-call-parse] JSON parse failed:', raw.slice(0, 100)); return false; } function safeJson(s) { try { return JSON.parse(s); } catch (_) { return { _raw: s }; } } function findBalancedJsonFrom(text, pos = 0) { const open = text.indexOf('{', pos); if (open === -1) return null; let depth = 0; let inStr = false; let esc = false; for (let i = open; i < text.length; i++) { const c = text[i]; if (esc) { esc = false; continue; } if (inStr) { if (c === '\\') esc = true; else if (c === '"') inStr = false; continue; } if (c === '"') { inStr = true; continue; } if (c === '{') depth++; else if (c === '}') { depth--; if (depth === 0) { return { json: text.slice(open, i + 1), start: open, end: i + 1 }; } } } return { incomplete: true, start: open }; } function findLastBalancedJson(text) { let best = null; let pos = 0; while (pos < text.length) { const hit = findBalancedJsonFrom(text, pos); if (!hit) break; if (hit.incomplete) break; best = hit; pos = hit.end; } return best; } function skipWs(text, pos) { while (pos < text.length && /\s/.test(text[pos])) pos++; return pos; } function hasClosingMarker(text, pos, marker) { const rest = text.slice(pos).trimStart(); return rest.toUpperCase().startsWith(marker); } const OPEN_TAG_RE = /(?:<\s*\|?\s*tool_calls?\s*\|?\s*>|<\s*tool_calls?\b[^>]*>|<\s*tool_call\b(?=[\s{]))/gi; const CLOSE_TAG_RE = /<\s*\/\s*\|?\s*tool_calls?\s*\|?\s*>/gi; function findCloseTag(s, from) { CLOSE_TAG_RE.lastIndex = from; const m = CLOSE_TAG_RE.exec(s); return m ? { index: m.index, end: m.index + m[0].length, text: m[0] } : null; } function extractNameHint(inner, jsonStart) { const before = inner.slice(0, jsonStart).trim(); if (!before) return ''; const line = before.split(/\n/).map(x => x.trim()).filter(Boolean).pop() || ''; if (/^[a-z][a-z0-9_]*$/i.test(line)) return line; return ''; } function collectToolCallSpans(buf, { allowOpenMarkers = false } = {}) { const spans = []; const s = String(buf); OPEN_TAG_RE.lastIndex = 0; let m; while ((m = OPEN_TAG_RE.exec(s)) !== null) { const afterTag = m.index + m[0].length; const close = findCloseTag(s, afterTag); if (!close) { if (allowOpenMarkers) { const hit = findBalancedJsonFrom(s, afterTag); if (hit?.json) { const nameHint = extractNameHint(s.slice(afterTag, hit.start), hit.start - afterTag); spans.push({ index: m.index, end: hit.end, json: hit.json, nameHint, }); } } continue; } const inner = s.slice(afterTag, close.index); const hit = findBalancedJsonFrom(inner, 0); if (hit?.json) { spans.push({ index: m.index, end: close.end, json: hit.json, nameHint: extractNameHint(inner, hit.start), }); } else { spans.push({ index: m.index, end: close.end, json: null, nameHint: '' }); } } CLOSE_TAG_RE.lastIndex = 0; while ((m = CLOSE_TAG_RE.exec(s)) !== null) { const before = s.slice(0, m.index); if (spans.some(sp => m.index >= sp.index && m.index < sp.end)) continue; const hit = findLastBalancedJson(before); if (!hit?.json) continue; const between = before.slice(hit.end).trim(); if (between && !/^[a-z][a-z0-9_]*$/i.test(between)) continue; const nameHint = between || ''; let start = hit.start; const head = before.slice(0, hit.start); const junk = head.match(/(?:^|\n)[ \t]*([A-Za-z|]{3,24})[ \t]*\n?[ \t]*$/); if (junk && /tool|call|ronic|function|invoke|xml/i.test(junk[1])) { const idx = head.lastIndexOf(junk[1]); if (idx >= 0) start = idx; } spans.push({ index: start, end: m.index + m[0].length, json: hit.json, nameHint }); } let searchFrom = 0; while (searchFrom < s.length) { const hit = findBalancedJsonFrom(s, searchFrom); if (!hit?.json) break; if (hit.incomplete) break; if (!spans.some(sp => hit.start >= sp.index && hit.start < sp.end)) { try { const obj = JSON.parse(hit.json); const n = obj?.name; if (n && /^(profile_|metro_)/.test(n) && (obj.arguments != null || obj.parameters != null)) { spans.push({ index: hit.start, end: hit.end, json: hit.json, nameHint: '' }); } } catch (_) { } } searchFrom = hit.end; } for (const marker of ['ICF', 'ICO']) { const re = new RegExp(marker, 'gi'); re.lastIndex = 0; while ((m = re.exec(s)) !== null) { const afterMarker = skipWs(s, m.index + m[0].length); const hit = findBalancedJsonFrom(s, afterMarker); if (!hit?.json) continue; let end = hit.end; if (hasClosingMarker(s, hit.end, marker)) { end = skipWs(s, hit.end) + marker.length; } else if (!allowOpenMarkers && marker !== 'ICO') { continue; } else if (!allowOpenMarkers && marker === 'ICO') { end = hit.end; } spans.push({ index: m.index, end, json: hit.json, nameHint: '' }); } } spans.sort((a, b) => a.index - b.index); const merged = []; for (const span of spans) { if (merged.length && span.index < merged[merged.length - 1].end) continue; merged.push(span); } return merged; } function consumeSpans(buf, spans, calls) { let safeText = ''; let cursor = 0; for (const hit of spans) { if (hit.index < cursor) continue; safeText += buf.slice(cursor, hit.index); if (hit.json) parseToolCallJson(hit.json, calls, hit.nameHint || ''); cursor = hit.end; } return { safeText, cursor }; } export function scanToolCallBuffer(buf) { const calls = []; const spans = collectToolCallSpans(buf, { allowOpenMarkers: false }); const { safeText: head, cursor } = consumeSpans(buf, spans, calls); const tail = buf.slice(cursor); OPEN_TAG_RE.lastIndex = 0; let openM = null; let tm; while ((tm = OPEN_TAG_RE.exec(tail)) !== null) openM = tm; if (openM && !findCloseTag(tail, openM.index + openM[0].length)) { return { safeText: head + tail.slice(0, openM.index), remainder: tail.slice(openM.index), toolCalls: calls, }; } for (const marker of ['ICF', 'ICO']) { const re = new RegExp(`${marker}(?!\\w)`, 'i'); const idx = tail.search(re); if (idx === -1) continue; const afterMarker = skipWs(tail, idx + marker.length); const hit = findBalancedJsonFrom(tail, afterMarker); if (hit?.incomplete) { return { safeText: head + tail.slice(0, idx), remainder: tail.slice(idx), toolCalls: calls }; } } const bare = tail.match(/\{[^{}]*"name"\s*:\s*"(?:profile_|metro_)[^"]*"\s*,\s*"arguments"\s*:\s*\{[^}]*$/); if (bare) { return { safeText: head + tail.slice(0, bare.index), remainder: tail.slice(bare.index), toolCalls: calls, }; } const partial = tail.match(/(?:<\s*\|?\s*t(?:o(?:o(?:l(?:_(?:c(?:a(?:l(?:l(?:s)?)?)?)?)?)?)?)?)?|IC(?:O|F)?)?$/i); if (partial) { return { safeText: head + tail.slice(0, partial.index), remainder: tail.slice(partial.index), toolCalls: calls }; } return { safeText: head + tail, remainder: '', toolCalls: calls }; } export function finalizeToolCallBuffer(buf) { if (!buf) return { safeText: '', toolCalls: [] }; const calls = []; const stripped = String(buf); const spans = collectToolCallSpans(stripped, { allowOpenMarkers: true }); const { safeText, cursor } = consumeSpans(stripped, spans, calls); const looseText = cleanToolCallArtifacts(safeText + stripped.slice(cursor)); return { safeText: looseText, toolCalls: calls }; } export function cleanToolCallArtifacts(fullText) { let s = String(fullText || ''); const spans = collectToolCallSpans(s, { allowOpenMarkers: true }); if (spans.length) { let out = ''; let cursor = 0; for (const hit of spans) { if (hit.index < cursor) continue; out += s.slice(cursor, hit.index); cursor = hit.end; } out += s.slice(cursor); s = out; } return s .replace(/<\s*\|?\s*\/?\s*tool_calls?\s*\|?\s*>/gi, '') .replace(/<\/?tool_call>/gi, '') .replace(/(?:^|\n)\s*(?:ICO|ICF)\s*(?=\n|\{|$)/gi, '\n') .replace(/(?:^|\n)\s*[A-Za-z|]{0,24}\s*\n?\s*\{[^{}]*"name"\s*:\s*"(?:profile_|metro_)[^"]*"[\s\S]*?\}\s*(?:\n\s*<\s*\/\s*\|?\s*tool_calls?\s*\|?\s*>)?/gi, '\n') .replace(/\n{3,}/g, '\n\n') .trim(); }