58 lines · 3.0 KB · TypeScript
RawDownload
// ---------------------------------------------------------------------------// Shared helpers for the Codex plugin surfaces that run through `node_repl`.//// Two Codex plugins ship no MCP server of their own and are driven by// executing JavaScript in Codex's Node REPL: Computer Use (`@oai/sky`) and// Chrome (`browser-client.mjs`). Both surfaces project that REPL as typed MCP// tools, and both therefore have to embed caller arguments into a JS source// text and read one JSON value back out. That encoding is the genuinely// shared part, and it is the part that must be exactly right.// --------------------------------------------------------------------------- /** A JS string literal. For values that ARE strings and are ours (a resolved *  module path), where the prototype hazard below cannot apply. *//** * An expression that evaluates to `value` inside the REPL. * * `JSON.parse` of a string, NOT a bare object literal. JSON is almost a subset * of JS, but the two differ in exactly the way that matters here: `__proto__` * as a key sets an object's PROTOTYPE in a literal while being an ordinary own * key under `JSON.parse`. Embedding caller arguments as a literal therefore let * a caller move data onto the prototype chain — an argument named `__proto__` * would vanish from `args.__proto__` and reappear as inherited properties on * every lookup the generated program makes. Parsing keeps arguments data. * * The JSON is then escaped as a JS string. U+2028 and U+2029 are legal raw * inside a JSON string but are literal line terminators in a JS source text, so * a value containing one would end the statement early. */export const jsString = (value: string): string =>  JSON.stringify(value).replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029"); export const jsLiteral = (value: unknown): string =>  `JSON.parse(${JSON.stringify(JSON.stringify(value ?? null))    .replaceAll("\u2028", "\\u2028")    .replaceAll("\u2029", "\\u2029")})`; /** * Wrap a program body so it runs in its own scope and reports one JSON value. * * The scope is not cosmetic. A REPL session is persistent and pooled * connections now reuse it across calls, so a program that declared its * working variables at top level would redeclare the same `const` on every * call — which the REPL answers with a warning per variable, prepended to the * result a caller then has to read around. Everything per-call lives in here; * only deliberate caches (the imported runtime) are left on `globalThis`. * * The REPL returns values only through `nodeRepl.write`, and only as text; * `undefined` (every action method) becomes `null` so a caller always gets a * well-formed JSON body rather than an empty string. */export const writeJsonResult = (body: readonly string[], expression: string): string =>  [    "await (async () => {",    ...body.map((line) => `  ${line}`),    `  const result = ${expression};`,    "  nodeRepl.write(JSON.stringify(result ?? null));",    "})();",  ].join("\n");