'use strict'; // DSH unified parser. Three markers: // DSH - classic JSON // DSH_B64 - base64 UTF-8 JSON // DSH_FILE - raw file content const L = String.fromCharCode(10214); const R = String.fromCharCode(10215); const BS = String.fromCharCode(92); const Q = String.fromCharCode(34); const OPEN_DSH = L + 'DSH' + R; const CLOSE_DSH = L + '/DSH' + R; const OPEN_B64 = L + 'DSH_B64' + R; const CLOSE_B64 = L + '/DSH_B64' + R; const OPEN_FILE = L + 'DSH_FILE'; const CLOSE_FILE = L + '/DSH_FILE' + R; const B64_CLEAN = new RegExp('[^A-Za-z0-9+/=]', 'g'); function b64DecodeUtf8(b64) { const clean = String(b64 || '').replace(B64_CLEAN, ''); const bin = atob(clean); const bytes = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); return new TextDecoder('utf-8').decode(bytes); } function tryParseJson(s) { try { return JSON.parse(s); } catch (e) { return null; } } function parseFileHeader(header) { const pIdx = header.indexOf('path'); if (pIdx < 0) return null; const eq = header.indexOf('=', pIdx); if (eq < 0) return null; const q1 = header.indexOf(Q, eq); if (q1 < 0) return null; const q2 = header.indexOf(Q, q1 + 1); if (q2 < 0) return null; return header.slice(q1 + 1, q2); } function makeInvalid(reason, rawSnippet, closeIdx, closeLen) { return { call: { tool: 'invalid', args: { _reason: reason, _raw: String(rawSnippet || '').slice(0, 200) } }, nextIdx: (typeof closeIdx === 'number' && closeIdx >= 0) ? closeIdx + closeLen : -1 }; } function parseOne(text, startIdx) { if (text.startsWith(OPEN_FILE, startIdx)) { const openEnd = text.indexOf(R, startIdx + OPEN_FILE.length); if (openEnd < 0) return null; const header = text.slice(startIdx + OPEN_FILE.length, openEnd); const path = parseFileHeader(header); if (!path) return null; const closeIdx = text.indexOf(CLOSE_FILE, openEnd + 1); if (closeIdx < 0) return null; let content = text.slice(openEnd + 1, closeIdx); if (content.charCodeAt(0) === 10) content = content.slice(1); return { call: { tool: 'dsh_write', args: { path: path, content: content } }, nextIdx: closeIdx + CLOSE_FILE.length }; } if (text.startsWith(OPEN_B64, startIdx)) { const closeIdx = text.indexOf(CLOSE_B64, startIdx + OPEN_B64.length); if (closeIdx < 0) return null; const raw = text.slice(startIdx + OPEN_B64.length, closeIdx); let json = null; try { json = b64DecodeUtf8(raw); } catch (e) { return makeInvalid('b64_decode_failed', raw, closeIdx, CLOSE_B64.length); } const obj = tryParseJson(json); if (!obj || typeof obj !== 'object') return makeInvalid('b64_json_invalid', json, closeIdx, CLOSE_B64.length); if (!obj.tool) return makeInvalid('b64_no_tool_field', json, closeIdx, CLOSE_B64.length); return { call: obj, nextIdx: closeIdx + CLOSE_B64.length }; } if (text.startsWith(OPEN_DSH, startIdx)) { const closeIdx = text.indexOf(CLOSE_DSH, startIdx + OPEN_DSH.length); if (closeIdx < 0) return null; const raw = text.slice(startIdx + OPEN_DSH.length, closeIdx); const obj = tryParseJson(raw); if (!obj || typeof obj !== 'object') return makeInvalid('json_parse_failed', raw, closeIdx, CLOSE_DSH.length); if (!obj.tool) return makeInvalid('no_tool_field', raw, closeIdx, CLOSE_DSH.length); return { call: obj, nextIdx: closeIdx + CLOSE_DSH.length }; } return null; } function extractCalls(text, maxCalls) { const limit = Math.max(1, maxCalls | 0) || 3; const calls = []; let pos = 0; while (calls.length < limit) { const i = text.indexOf(L, pos); if (i < 0) break; const parsed = parseOne(text, i); if (!parsed) { pos = i + L.length; continue; } calls.push(parsed.call); pos = parsed.nextIdx; } return { calls: calls, consumed: pos }; } function parseChatText(root) { const SEL = (typeof globalThis !== 'undefined' && globalThis.DSH_SELECTORS) ? globalThis.DSH_SELECTORS : null; if (!SEL || !root || typeof root.querySelectorAll !== 'function') { return { calls: [], consumed: 0 }; } const texts = []; for (let k = 0; k < SEL.MESSAGE_SELECTORS.length; k++) { const sel = SEL.MESSAGE_SELECTORS[k]; const nodes = root.querySelectorAll(sel); if (!nodes || nodes.length === 0) continue; for (let j = 0; j < nodes.length; j++) { texts.push(SEL.collectText(nodes[j])); } if (texts.length > 0) break; } const joined = texts.join('\n'); return extractCalls(joined, 10); } if (typeof module !== 'undefined' && module.exports) { module.exports = { extractCalls: extractCalls, b64DecodeUtf8: b64DecodeUtf8, parseChatText: parseChatText }; }