@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
656 lines • 22.7 kB
JavaScript
/**
* The sandbox's JSX: a small JavaScript + JSX → `React.createElement` transpiler that runs INSIDE
* the sandbox frame (it is inlined into the frame's document, so the frame stays network-less and
* needs no `unsafe-eval`), and the partial mode that lets a view the model is still writing be drawn
* as it streams — only once what has arrived so far closes into something that parses.
*
* Every function here is self-contained (no imports, no module state): the frame runtime is built
* from their source text (`Function.prototype.toString`), so the same code is unit-tested in Node and
* run in the frame.
*/
/**
* JavaScript with JSX → plain JavaScript calling `React.createElement`. No TypeScript, no JSX
* namespaces. `class` and `for` attributes become `className` / `htmlFor`.
*
* `partial: true` reads a prefix of a program the model is still writing: an element, attribute,
* expression or bracket cut off at the end is closed (an attribute or token cut half-way is dropped,
* back to the last point the code was whole), so the result is a complete program drawing what has
* arrived. Without it, malformed input throws a `SyntaxError`.
*/
export function transpileJsx(source, options) {
const src = source;
const n = src.length;
const partial = options?.partial === true;
let i = 0;
/** The last significant token: '' (start), 'val', 'id:<word>' or a punctuation character. */
let last = '';
/** A partial read hit the end of the input: every open construct is being closed. */
let eof = false;
const EXPR_KEYWORDS = [
'return',
'case',
'typeof',
'in',
'of',
'new',
'delete',
'void',
'throw',
'yield',
'await',
'else',
'do',
'instanceof',
];
/** Keywords after which the code cannot end. */
const OPEN_KEYWORDS = [
'const',
'let',
'var',
'function',
'if',
'for',
'while',
'switch',
'new',
'typeof',
'in',
'of',
'instanceof',
'class',
'extends',
'case',
'do',
'else',
'try',
'catch',
'finally',
'throw',
'delete',
'void',
'await',
'yield',
'async',
'import',
'export',
];
const ENTITIES = {
amp: '&',
lt: '<',
gt: '>',
quot: '"',
apos: "'",
nbsp: ' ',
middot: '·',
times: '×',
divide: '÷',
ndash: '–',
mdash: '—',
hellip: '…',
copy: '©',
reg: '®',
deg: '°',
euro: '€',
larr: '←',
rarr: '→',
uarr: '↑',
darr: '↓',
check: '✓',
};
const fail = (message) => {
throw new SyntaxError(`${message} (at ${i})`);
};
const isIdStart = (c) => c !== undefined && /[A-Za-z_$]/.test(c);
const isId = (c) => c !== undefined && /[\w$]/.test(c);
const exprStart = () => {
if (last === '')
return true;
if (last === 'val')
return false;
if (last.startsWith('id:'))
return EXPR_KEYWORDS.indexOf(last.slice(3)) >= 0;
return last !== ')' && last !== ']' && last !== '}';
};
/** Can the code end right after the last token (closing what is open)? */
const complete = () => {
if (last === '' || last === 'val' || last === ')' || last === ']' || last === '}')
return true;
if (last === '{' || last === '[' || last === ',' || last === ';')
return true;
if (last.startsWith('id:'))
return OPEN_KEYWORDS.indexOf(last.slice(3)) < 0;
return false;
};
const decode = (text) => text.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[A-Za-z]+);/g, (whole, name) => {
if (name[0] === '#') {
const code = name[1] === 'x' ? parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10);
return Number.isFinite(code) ? String.fromCodePoint(code) : whole;
}
return ENTITIES[name] ?? whole;
});
/** JSX text as React reads it: lines trimmed where they meet, blank lines dropped. */
const jsxText = (raw) => {
const lines = raw.split(/\r\n|\n|\r/);
if (lines.length === 1)
return decode(raw);
const kept = [];
lines.forEach((line, index) => {
let value = line;
if (index > 0)
value = value.replace(/^\s+/, '');
if (index < lines.length - 1)
value = value.replace(/\s+$/, '');
if (value !== '')
kept.push(value);
});
return decode(kept.join(' '));
};
const propKey = (name) => {
const mapped = name === 'class' ? 'className' : name === 'for' ? 'htmlFor' : name;
return /^[A-Za-z_$][\w$]*$/.test(mapped) ? mapped : JSON.stringify(mapped);
};
const skipWs = () => {
while (i < n && /\s/.test(src[i]))
i++;
};
/** A quoted string. `null` when a partial read ends inside it. */
const str = (quote) => {
let j = i + 1;
while (j < n) {
const c = src[j];
if (c === '\\') {
j += 2;
continue;
}
if (c === quote) {
const text = src.slice(i, j + 1);
i = j + 1;
return text;
}
if (c === '\n')
fail('Unterminated string');
j++;
}
if (partial) {
eof = true;
i = n;
return null;
}
return fail('Unterminated string');
};
/** A template literal, its `${…}` read as code (JSX included). `null` when cut off. */
const template = () => {
let out = '`';
i++;
while (i < n) {
const c = src[i];
if (c === '\\') {
out += src.slice(i, i + 2);
i += 2;
continue;
}
if (c === '`') {
i++;
return `${out}\``;
}
if (c === '$' && src[i + 1] === '{') {
i += 2;
const saved = last;
last = '';
const inner = js(true);
last = saved;
if (eof)
return null;
out += `\${${inner}}`;
i++;
continue;
}
out += c;
i++;
}
if (partial) {
eof = true;
return null;
}
return fail('Unterminated template');
};
/** A regular expression literal. `null` when cut off. */
const regex = () => {
let j = i + 1;
let inClass = false;
while (j < n) {
const c = src[j];
if (c === '\\') {
j += 2;
continue;
}
if (c === '\n')
fail('Unterminated regular expression');
if (inClass) {
if (c === ']')
inClass = false;
}
else if (c === '[')
inClass = true;
else if (c === '/') {
j++;
while (j < n && /[a-z]/.test(src[j]))
j++;
const text = src.slice(i, j);
i = j;
return text;
}
j++;
}
if (partial) {
eof = true;
i = n;
return null;
}
return fail('Unterminated regular expression');
};
/** `{ … }` inside JSX: an expression, read as code. `null` when cut off with nothing whole in it. */
const braced = () => {
i++;
const saved = last;
last = '';
const inner = js(true);
last = saved;
if (eof)
return inner.trim() === '' ? null : inner;
i++;
return inner;
};
const create = (tag, props, children) => `React.createElement(${tag}, ${props}${children.length > 0 ? `, ${children.join(', ')}` : ''})`;
/** One JSX element (or fragment), at its `<`. `null` when a partial read cut its name. */
const element = () => {
i++;
skipWs();
let raw = '';
let tag = 'React.Fragment';
const segments = [];
let pairs = [];
const flush = () => {
if (pairs.length > 0)
segments.push(`{${pairs.join(', ')}}`);
pairs = [];
};
const props = () => {
flush();
if (segments.length === 0)
return 'null';
if (segments.length === 1 && segments[0].startsWith('{'))
return segments[0];
return `Object.assign({}, ${segments.join(', ')})`;
};
if (src[i] === '>') {
i++;
}
else {
let j = i;
while (j < n && /[\w$.:-]/.test(src[j]))
j++;
raw = src.slice(i, j);
if (j >= n && partial) {
eof = true;
i = n;
return null;
}
if (raw === '')
fail('Expected a tag name');
i = j;
tag = /^[a-z][\w-]*$/.test(raw) ? JSON.stringify(raw) : raw;
for (;;) {
skipWs();
if (i >= n) {
if (partial) {
eof = true;
return create(tag, props(), []);
}
fail('Unterminated JSX tag');
}
if (src[i] === '/' && src[i + 1] === '>') {
i += 2;
return create(tag, props(), []);
}
if (src[i] === '/' && i + 1 >= n && partial) {
eof = true;
i = n;
return create(tag, props(), []);
}
if (src[i] === '>') {
i++;
break;
}
if (src[i] === '{') {
const start = i;
i++;
skipWs();
if (src.slice(i, i + 3) !== '...') {
if (partial && i + 3 > n) {
eof = true;
i = n;
return create(tag, props(), []);
}
i = start;
fail('Expected a spread attribute');
}
i += 3;
const saved = last;
last = '';
const spread = js(true);
last = saved;
if (eof)
return create(tag, props(), []);
i++;
flush();
segments.push(spread);
continue;
}
let k = i;
while (k < n && /[\w$:-]/.test(src[k]))
k++;
const name = src.slice(i, k);
if (name === '')
fail('Expected an attribute');
i = k;
skipWs();
if (i >= n && partial) {
eof = true;
return create(tag, props(), []);
}
if (src[i] !== '=') {
pairs.push(`${propKey(name)}: true`);
continue;
}
i++;
skipWs();
const quote = src[i];
if (quote === '"' || quote === "'") {
const end = src.indexOf(quote, i + 1);
if (end < 0) {
if (partial) {
eof = true;
i = n;
return create(tag, props(), []);
}
fail('Unterminated attribute');
}
pairs.push(`${propKey(name)}: ${JSON.stringify(decode(src.slice(i + 1, end)))}`);
i = end + 1;
continue;
}
if (quote === '{') {
const value = braced();
// A value cut off is not the value: the attribute waits until it is whole.
if (eof)
return create(tag, props(), []);
pairs.push(`${propKey(name)}: ${(value ?? '').trim()}`);
continue;
}
if (quote === '<') {
const value = element();
if (value !== null)
pairs.push(`${propKey(name)}: ${value}`);
if (eof)
return create(tag, props(), []);
continue;
}
if (i >= n && partial) {
eof = true;
return create(tag, props(), []);
}
fail('Expected an attribute value');
}
}
const children = [];
for (;;) {
if (i >= n) {
if (partial) {
eof = true;
return create(tag, props(), children);
}
fail('Unterminated JSX element');
}
if (src[i] === '<' && (src[i + 1] === '/' || (i + 1 >= n && partial))) {
const close = src.indexOf('>', i);
if (close < 0) {
if (partial) {
eof = true;
i = n;
return create(tag, props(), children);
}
fail('Unterminated closing tag');
}
const name = src.slice(i + 2, close).trim();
if (name !== raw)
fail(`Expected </${raw}>, found </${name}>`);
i = close + 1;
return create(tag, props(), children);
}
if (src[i] === '<') {
const child = element();
if (child !== null)
children.push(child);
if (eof)
return create(tag, props(), children);
continue;
}
if (src[i] === '{') {
const value = braced();
if (value !== null && value.replace(/\/\*[\s\S]*?\*\//g, '').trim() !== '')
children.push(value);
if (eof)
return create(tag, props(), children);
continue;
}
let j = i;
while (j < n && src[j] !== '<' && src[j] !== '{')
j++;
// A partial read never shows half an entity.
let rawText = src.slice(i, j);
if (j >= n && partial)
rawText = rawText.replace(/&[#\w]*$/, '');
const text = jsxText(rawText);
if (text !== '')
children.push(JSON.stringify(text));
i = j;
}
};
/**
* Code, until the end (or, for an embedded expression, the `}` that closes it — not consumed).
* A partial read ending here closes the brackets still open, after going back to the last token
* the code could end on.
*/
function js(stopAtBrace) {
let out = '';
const stack = [];
let safeLength = 0;
let safeStack = [];
/** Conditional operators (`a ? b`) still waiting for their `:`, per bracket depth. */
const ternaries = [0];
/** The code may end right here (closing what is open). */
let whole = true;
const mark = (ok = true) => {
whole = ok && complete() && ternaries.every((count) => count === 0);
if (whole) {
safeLength = out.length;
safeStack = stack.slice();
}
};
const closing = (open) => (open === '{' ? '}' : open === '(' ? ')' : ']');
const finish = () => {
const body = whole ? out : out.slice(0, safeLength);
const open = whole ? stack : safeStack;
return body + open.slice().reverse().map(closing).join('');
};
while (i < n) {
const c = src[i];
if (c === '}' && stack.length === 0 && stopAtBrace)
return out;
if (c === ' ' || c === '\t' || c === '\n' || c === '\r') {
out += c;
i++;
continue;
}
if (c === '/' && src[i + 1] === '/') {
const end = src.indexOf('\n', i);
const stop = end < 0 ? n : end;
out += src.slice(i, stop);
i = stop;
continue;
}
if (c === '/' && src[i + 1] === '*') {
const end = src.indexOf('*/', i + 2);
if (end < 0) {
if (partial) {
eof = true;
i = n;
break;
}
fail('Unterminated comment');
}
out += src.slice(i, end + 2);
i = end + 2;
continue;
}
if (c === '"' || c === "'") {
const text = str(c);
if (text === null)
break;
out += text;
last = 'val';
mark();
continue;
}
if (c === '`') {
const text = template();
if (text === null)
break;
out += text;
last = 'val';
mark();
continue;
}
if (c === '/' && exprStart()) {
const text = regex();
if (text === null)
break;
out += text;
last = 'val';
mark();
continue;
}
if (c === '<' &&
exprStart() &&
(isIdStart(src[i + 1]) || src[i + 1] === '>' || (partial && i + 1 >= n))) {
if (i + 1 >= n) {
eof = true;
i = n;
break;
}
const jsx = element();
if (jsx !== null) {
out += jsx;
last = 'val';
mark();
}
if (eof)
break;
continue;
}
if (isIdStart(c)) {
let j = i + 1;
while (j < n && isId(src[j]))
j++;
if (j >= n && partial) {
// The word may be cut (`ret` of `return`): it does not count until something follows.
eof = true;
i = n;
break;
}
const word = src.slice(i, j);
out += word;
i = j;
last = `id:${word}`;
mark();
continue;
}
if (/[0-9]/.test(c) || (c === '.' && /[0-9]/.test(src[i + 1] ?? ''))) {
let j = i + 1;
while (j < n && /[\w.]/.test(src[j]))
j++;
if (j >= n && partial) {
eof = true;
i = n;
break;
}
out += src.slice(i, j);
i = j;
last = 'val';
mark();
continue;
}
if (c === '?' &&
(src[i + 1] === '?' || (src[i + 1] === '.' && !/[0-9]/.test(src[i + 2] ?? '')))) {
// `??` and `?.`: one token, no conditional to close.
out += src.slice(i, i + 2);
i += 2;
last = '?';
mark(false);
continue;
}
if (c === '?')
ternaries[stack.length] = (ternaries[stack.length] ?? 0) + 1;
else if (c === ':' && (ternaries[stack.length] ?? 0) > 0)
ternaries[stack.length] = (ternaries[stack.length] ?? 0) - 1;
// Whether the code may end right after this bracket: `[` opening an array literal and `(`
// opening a call may (`[]`, `f()`); `v[`, a grouping `(`, `if (` and an empty `()` (arrow
// parameters still to come) may not.
let ok = true;
if (c === '[')
ok = exprStart();
else if (c === '(') {
const keyword = last.startsWith('id:') ? last.slice(3) : '';
ok =
!exprStart() &&
['if', 'for', 'while', 'switch', 'catch', 'function', 'with'].indexOf(keyword) < 0;
}
else if (c === ')')
ok = last !== '(';
if (c === '{' || c === '(' || c === '[') {
stack.push(c);
ternaries[stack.length] = 0;
}
else if (c === '}' || c === ')' || c === ']') {
ternaries.length = stack.length;
stack.pop();
}
out += c;
i++;
last = c;
mark(ok);
}
if (i >= n && partial) {
eof = true;
return finish();
}
if (stopAtBrace)
fail('Unexpected end of input');
return out;
}
return js(false);
}
/**
* A sandbox program as the model writes it, made runnable in the frame: `import` lines dropped (the
* kit, React and its hooks are already in scope) and `export` / `export default` taken off the
* declarations — `export default function App()` is `function App()`.
*/
export function prepareSandboxJsx(source) {
return source
.replace(/^\s*import\s[^;\n]*(?:from\s*['"][^'"\n]*['"])?\s*;?[ \t]*$/gm, '')
.replace(/^(\s*)export\s+default\s+(?=(async\s+)?function|class)/gm, '$1')
.replace(/^(\s*)export\s+default\s+([A-Za-z_$][\w$]*)\s*;?\s*$/gm, '$1')
.replace(/^(\s*)export\s+(?=(const|let|var|function|class|async)\b)/gm, '$1');
}
//# sourceMappingURL=sandbox-jsx.js.map