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.
 
 
 
 
 

93 lines
3.1 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 };
}
module.exports = { extractCalls: extractCalls, b64DecodeUtf8: b64DecodeUtf8 };