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

'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);
const json = b64DecodeUtf8(raw);
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
};
}