UNPKG

@adonis-agora/agent

Version:

Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.

231 lines • 7.35 kB
class Cut extends Error { } /** * Parse a JSON prefix (see {@link PartialJson}). `undefined` when the text is not the beginning of a * JSON document at all — a syntax error before the cut. */ export function parsePartialJson(text) { const open = new WeakSet(); const pending = new WeakMap(); let at = 0; const skipSpace = () => { while (at < text.length) { const char = text.charCodeAt(at); // space, \t, \n, \r if (char === 32 || char === 9 || char === 10 || char === 13) at += 1; else break; } }; const fail = () => { throw new SyntaxError(`unexpected character at ${at}`); }; /** A string from the opening quote at `at`. `cut`: the text ended inside it. */ const readString = () => { at += 1; let out = ''; while (at < text.length) { const char = text[at]; if (char === '"') { at += 1; return { value: out, cut: false }; } if (char === '\\') { const next = text[at + 1]; if (next === undefined) { at = text.length; return { value: out, cut: true }; } if (next === 'u') { const hex = text.slice(at + 2, at + 6); if (hex.length < 4) { at = text.length; return { value: out, cut: true }; } if (!/^[0-9a-fA-F]{4}$/.test(hex)) fail(); out += String.fromCharCode(Number.parseInt(hex, 16)); at += 6; continue; } const escapes = { '"': '"', '\\': '\\', '/': '/', b: '\b', f: '\f', n: '\n', r: '\r', t: '\t', }; const escaped = escapes[next]; if (escaped === undefined) fail(); out += escaped; at += 2; continue; } out += char; at += 1; } return { value: out, cut: true }; }; /** * One value at `at`. Throws {@link Cut} when the text ends before anything of it can be kept (a * number, a literal); a string or container cut mid-way is returned with `cut: true`. */ const readValue = () => { skipSpace(); if (at >= text.length) throw new Cut(); const char = text[at]; if (char === '{') return readObject(); if (char === '[') return readArray(); if (char === '"') return readString(); const rest = text.slice(at); const number = /^-?\d*(?:\.\d*)?(?:[eE][+-]?\d*)?/.exec(rest)?.[0] ?? ''; if (number.length > 0) { // A number that runs to the end of the text may not be finished (`12` may yet be `123`). if (at + number.length >= text.length) throw new Cut(); if (!/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(number)) fail(); at += number.length; return { value: Number(number), cut: false }; } for (const [word, value] of [ ['true', true], ['false', false], ['null', null], ]) { if (rest.startsWith(word)) { at += word.length; return { value, cut: false }; } // The beginning of the word, and nothing after it yet. if (word.startsWith(rest)) throw new Cut(); } return fail(); }; const readObject = () => { const object = {}; open.add(object); at += 1; skipSpace(); if (text[at] === '}') { at += 1; open.delete(object); return { value: object, cut: false }; } for (;;) { skipSpace(); if (at >= text.length) return { value: object, cut: true }; if (text[at] !== '"') fail(); const key = readString(); if (key.cut) return { value: object, cut: true }; skipSpace(); if (at >= text.length) { pending.set(object, key.value); return { value: object, cut: true }; } if (text[at] !== ':') fail(); at += 1; let member; try { member = readValue(); } catch (error) { if (!(error instanceof Cut)) throw error; pending.set(object, key.value); return { value: object, cut: true }; } object[key.value] = member.value; if (member.cut) { pending.set(object, key.value); return { value: object, cut: true }; } skipSpace(); if (at >= text.length) return { value: object, cut: true }; if (text[at] === ',') { at += 1; continue; } if (text[at] === '}') { at += 1; open.delete(object); return { value: object, cut: false }; } fail(); } }; const readArray = () => { const array = []; open.add(array); at += 1; skipSpace(); if (text[at] === ']') { at += 1; open.delete(array); return { value: array, cut: false }; } for (;;) { let item; try { item = readValue(); } catch (error) { if (!(error instanceof Cut)) throw error; pending.set(array, array.length); return { value: array, cut: true }; } array.push(item.value); if (item.cut) { pending.set(array, array.length - 1); return { value: array, cut: true }; } skipSpace(); if (at >= text.length) return { value: array, cut: true }; if (text[at] === ',') { at += 1; continue; } if (text[at] === ']') { at += 1; open.delete(array); return { value: array, cut: false }; } fail(); } }; let root; try { root = readValue(); } catch (error) { if (error instanceof Cut) root = { value: undefined, cut: true }; else return undefined; } skipSpace(); return { value: root.value, complete: !root.cut && at >= text.length, isOpen: (container) => open.has(container), pendingMember: (container) => pending.get(container), }; } //# sourceMappingURL=partial-json.js.map