You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
118 lines
3.8 KiB
118 lines
3.8 KiB
'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 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 null; } |
|
const obj = tryParseJson(json); |
|
if (!obj || typeof obj !== 'object' || !obj.tool) return null; |
|
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' || !obj.tool) return null; |
|
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, 3); |
|
} |
|
if (typeof module !== 'undefined' && module.exports) { |
|
module.exports = { |
|
extractCalls: extractCalls, |
|
b64DecodeUtf8: b64DecodeUtf8, |
|
parseChatText: parseChatText |
|
}; |
|
} |
|
|
|
|