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
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 };
|
|
|