run402-mcp
Version:
MCP server for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.
625 lines (617 loc) • 27.6 kB
JavaScript
/**
* The chain proxy: how a `run` snippet reaches the SDK without the SDK ever
* running inside the sandbox.
*
* Inside the sandbox, `r` is a recorder. A property access appends a `get`
* segment and a call appends a `call` segment with its arguments; nothing
* happens until the chain is awaited (its `then`, `catch`, or `finally` is
* read), at which point the chain crosses to the host as JSON. The host
* ({@link ChainHost}) replays it against the real client, built with
* `surface: "sandbox"`, awaiting every intermediate that is a promise:
*
* r.project("prj_1").functions.list()
* → get project → call ("prj_1") → get functions → get list → call ()
*
* The terminal value crosses back as JSON when it is JSON data. Anything else
* (a `ScopedRun402` from `await r.project()`, a deploy operation) crosses as an
* opaque handle that the recorder wraps again, so the snippet keeps chaining
* from it; handles die with the run.
*
* Arguments cross into the host only when they are structured-cloneable data
* (JSON values, `undefined`, `Date`, bytes, `Map`, `Set`, `BigInt`) or a handle.
* A function, a symbol, a class instance, a cyclic structure, or an un-awaited
* `r` chain is refused in the sandbox before anything is sent
* (`RUN_ARGUMENT_NOT_CLONEABLE`), which is also why a filesystem source such as
* a `LocalDirRef` cannot be expressed: `dir()` does not exist inside.
*
* The replay walks only the SDK's public surface. A property the SDK declares
* `private` or `protected` (read from the SDK's own declaration files), a name
* that starts with `_`, `constructor` / `prototype` / `__proto__`, and anything
* that resolves on a JavaScript intrinsic (`Object.prototype`,
* `Function.prototype`, `Array.prototype`, …) is refused, so a chain can reach
* neither the host's `Function` constructor nor the kernel client and its
* credential provider. Plain data returned by a call is navigable by its own
* properties only.
*
* Every dispatched chain is one entry in `calls[]` with its dotted path, its
* duration, and its outcome. An SDK error thrown during the replay crosses
* back with its own `code`, `message`, and `next_actions`, plus an
* `error_ref` that lets the `run` tool report the original error unchanged.
*/
import { readdirSync, readFileSync, statSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { isRun402Error } from "../sdk/dist/index.js";
/** `calls[]` entries retained per run. */
export const CHAIN_MAX_CALLS = 200;
/** The largest intermediate value one chain may carry back into the sandbox. */
export const CHAIN_MAX_TRANSFER_BYTES = 16 * 1024 * 1024;
/**
* The in-sandbox half: defines `r` and keeps the host's `dispatch` in its
* closure. Evaluated once per run by `runInSandbox`.
*/
const PROXY_INSTALL = String.raw `(function (host) {
"use strict";
const stringify = JSON.stringify;
const parse = JSON.parse;
const chains = new WeakMap();
const TAG = "__r402";
const label = (base, segments) => {
let out = base.path;
for (const s of segments) {
if (s.op === "get") out = out ? out + "." + s.key : s.key;
else out += "()";
}
return out || "r";
};
const notCloneable = (where, what) => {
const e = new TypeError(
"Argument " + where + " cannot cross into the host: " + what +
". Pass structured-cloneable data to r (JSON values, undefined, Date, Uint8Array, Map, Set, BigInt) or a handle.",
);
e.code = "RUN_ARGUMENT_NOT_CLONEABLE";
return e;
};
const encode = (v, where, seen) => {
if (v === null || typeof v === "string" || typeof v === "boolean") return v;
if (typeof v === "number") return Number.isFinite(v) ? v : { [TAG]: { t: "number", v: String(v) } };
if (v === undefined) return { [TAG]: { t: "undefined" } };
if (typeof v === "bigint") return { [TAG]: { t: "bigint", v: String(v) } };
if (typeof v === "symbol") throw notCloneable(where, "a symbol");
if (typeof v === "function") {
const chain = chains.get(v);
if (!chain) throw notCloneable(where, "a function");
if (chain.segments.length > 0) throw notCloneable(where, "an r call that was not awaited (" + label(chain.base, chain.segments) + ")");
if (chain.base.id === "root") throw notCloneable(where, "r itself");
return { [TAG]: { t: "handle", v: chain.base.id } };
}
if (seen.has(v)) throw notCloneable(where, "a cyclic structure");
seen.add(v);
try {
if (Array.isArray(v)) return v.map((x, i) => encode(x, where + "[" + i + "]", seen));
if (v instanceof Date) return { [TAG]: { t: "date", v: v.getTime() } };
if (v instanceof ArrayBuffer) return { [TAG]: { t: "bytes", v: Array.from(new Uint8Array(v)) } };
if (ArrayBuffer.isView(v)) return { [TAG]: { t: "bytes", v: Array.from(new Uint8Array(v.buffer, v.byteOffset, v.byteLength)) } };
if (v instanceof Map) return { [TAG]: { t: "map", v: Array.from(v, ([k, x], i) => [encode(k, where + ".key" + i, seen), encode(x, where + ".get(" + i + ")", seen)]) } };
if (v instanceof Set) return { [TAG]: { t: "set", v: Array.from(v, (x, i) => encode(x, where + ".item" + i, seen)) } };
const proto = Object.getPrototypeOf(v);
if (proto !== Object.prototype && proto !== null) {
const name = proto && proto.constructor && typeof proto.constructor.name === "string" ? proto.constructor.name : "an object";
throw notCloneable(where, "an instance of " + name);
}
const out = {};
for (const k of Object.keys(v)) out[k] = encode(v[k], where + "." + k, seen);
return out;
} finally {
seen.delete(v);
}
};
const decode = (v) => {
if (Array.isArray(v)) return v.map(decode);
if (v && typeof v === "object") {
const keys = Object.keys(v);
if (keys.length === 1 && keys[0] === TAG) {
const t = v[TAG];
if (t.t === "bytes") return new Uint8Array(t.v);
if (t.t === "handle") return makeChain({ id: t.v, path: t.path }, []);
return undefined;
}
const out = {};
for (const k of keys) out[k] = decode(v[k]);
return out;
}
return v;
};
const makeError = (e) => {
const err = new Error(String(e.message));
err.name = String(e.name || "Error");
for (const k of ["code", "kind", "status", "next_actions", "details", "retryable", "error_ref"]) {
if (e[k] !== undefined && e[k] !== null) err[k] = e[k];
}
return err;
};
const send = (base, segments) =>
host.dispatch(stringify({ base: base.id, segments })).then((raw) => {
const res = parse(raw);
if (res.ok) return decode(res.value);
throw makeError(res.error);
});
const makeChain = (base, segments) => {
let sent = null;
const proxy = new Proxy(function () {}, {
get(_target, key) {
if (typeof key === "symbol") {
if (key === Symbol.toPrimitive) return () => "[r " + label(base, segments) + "]";
return undefined;
}
if (key === "then" || key === "catch" || key === "finally") {
// A bare handle is a value, not a promise.
if (segments.length === 0) return undefined;
if (!sent) sent = send(base, segments);
return (...a) => sent[key](...a);
}
if (key === "toJSON") return undefined;
return makeChain(base, [...segments, { op: "get", key }]);
},
apply(_target, _receiver, args) {
const where = label(base, segments);
const seen = new Set();
const encoded = args.map((a, i) => encode(a, where + " argument " + (i + 1), seen));
return makeChain(base, [...segments, { op: "call", args: encoded }]);
},
set() { return false; },
defineProperty() { return false; },
deleteProperty() { return false; },
});
chains.set(proxy, { base, segments });
return proxy;
};
Object.defineProperty(globalThis, "r", {
value: makeChain({ id: "root", path: "" }, []),
writable: false,
configurable: false,
enumerable: false,
});
})`;
// ─── what the replay may touch ───────────────────────────────────────────────
const ALWAYS_REFUSED = new Set(["constructor", "prototype", "__proto__", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "caller", "callee", "arguments"]);
/** JavaScript's own prototypes: a property that resolves on one of these is never reachable. */
const INTRINSIC_PROTOTYPES = new Set([
Object.prototype, Function.prototype, Array.prototype, String.prototype, Number.prototype, Boolean.prototype,
Symbol.prototype, BigInt.prototype, Date.prototype, RegExp.prototype, Error.prototype, Promise.prototype,
Map.prototype, Set.prototype, WeakMap.prototype, WeakSet.prototype, ArrayBuffer.prototype,
Object.getPrototypeOf(Uint8Array.prototype), Uint8Array.prototype, DataView.prototype,
Object.getPrototypeOf(async function () { }), Object.getPrototypeOf(function* () { }),
Object.getPrototypeOf(async function* () { }),
Object.getPrototypeOf(Object.getPrototypeOf((function* () { })())),
Object.getPrototypeOf(Object.getPrototypeOf((async function* () { })())),
Object.getPrototypeOf((async function* () { })()),
Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())),
]);
let privateMembers = null;
/**
* Every `private` / `protected` member name the SDK declares, by class name,
* read once from the SDK's declaration files (TypeScript's `private` is not
* enforced at runtime, so the declaration is the only record of it).
*/
export function sdkPrivateMembers() {
if (privateMembers)
return privateMembers;
const map = new Map();
const root = join(dirname(fileURLToPath(import.meta.url)), "..", "sdk", "dist");
const visit = (dir) => {
for (const name of readdirSync(dir)) {
const full = join(dir, name);
if (statSync(full).isDirectory())
visit(full);
else if (name.endsWith(".d.ts")) {
let current = null;
for (const line of readFileSync(full, "utf8").split("\n")) {
const cls = /^(?:export )?declare (?:abstract )?class (\w+)/.exec(line);
if (cls) {
current = cls[1];
continue;
}
if (line === "}") {
current = null;
continue;
}
if (!current)
continue;
const member = /^ {4}(?:private|protected)(?: static)?(?: readonly)?(?: async)?(?: get| set)? (\w+)/.exec(line);
if (member) {
const set = map.get(current) ?? new Set();
set.add(member[1]);
map.set(current, set);
}
}
}
}
};
visit(root);
privateMembers = map;
return map;
}
function isPlainData(value) {
if (Array.isArray(value))
return true;
const proto = Object.getPrototypeOf(value);
return proto === Object.prototype || proto === null;
}
class RefusedProperty extends TypeError {
}
/**
* A member the SDK object does not have. Carries the closest public members,
* so the run's error can say "did you mean" instead of "is not a function".
*/
export class UnknownMember extends TypeError {
path;
suggestions;
members;
code = "RUN_UNKNOWN_MEMBER";
/**
* @param path the missing member as a snippet writes it, e.g. `r.project(…).sql`
* @param suggestions full paths, as a snippet writes them, that do exist
* @param members the parent's public members
*/
constructor(path, suggestions, members) {
super(suggestions.length > 0
? `${path} is not part of the SDK. Did you mean ${suggestions.join(" or ")}?`
: `${path} is not part of the SDK. Its parent has: ${members.slice(0, 20).join(", ")}${members.length > 20 ? ", …" : ""}.`);
this.path = path;
this.suggestions = suggestions;
this.members = members;
this.name = "UnknownMember";
}
}
/** True for an SDK namespace object worth searching: an object, not plain data, not a function. */
function isNamespace(v) {
return typeof v === "object" && v !== null && !isPlainData(v);
}
function hasMember(holder, key) {
return publicMembers(holder).includes(key);
}
/**
* Where else `key` lives, for a member the parent lacks: the parent's own
* namespaces first (`r.project(…).projects.sql`), then the root and the root's
* namespaces (`r.projects.sql`). Reads only public members of SDK objects.
*/
function findElsewhere(key, holder, parentDisplay, root) {
const found = [];
const add = (p) => { if (!found.includes(p))
found.push(p); };
for (const m of publicMembers(holder)) {
const child = holder[m];
if (isNamespace(child) && hasMember(child, key))
add(`${parentDisplay}.${m}.${key}`);
}
if (isNamespace(root) && root !== holder) {
if (hasMember(root, key))
add(`r.${key}`);
for (const m of publicMembers(root)) {
const child = root[m];
if (child !== holder && isNamespace(child) && hasMember(child, key))
add(`r.${m}.${key}`);
}
}
return found.slice(0, 3);
}
/** The public members an SDK object offers: own and inherited, minus private, internal, and intrinsic ones. */
function publicMembers(holder) {
const out = new Set();
const privates = sdkPrivateMembers();
for (let p = holder; p && !INTRINSIC_PROTOTYPES.has(p); p = Object.getPrototypeOf(p)) {
const className = typeof p === "function" ? null : p.constructor?.name;
const hidden = typeof className === "string" ? privates.get(className) : undefined;
for (const key of Object.getOwnPropertyNames(p)) {
if (ALWAYS_REFUSED.has(key) || key.startsWith("_") || key.startsWith("#") || hidden?.has(key))
continue;
if (typeof holder === "function" && (key === "length" || key === "name" || key === "arguments" || key === "caller"))
continue;
out.add(key);
}
}
return [...out].sort();
}
/** Edit distance with adjacent transpositions (optimal string alignment). */
function editDistance(a, b) {
const d = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]);
for (let j = 1; j <= b.length; j++)
d[0][j] = j;
for (let i = 1; i <= a.length; i++) {
for (let j = 1; j <= b.length; j++) {
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1])
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
}
}
return d[a.length][b.length];
}
/** The members closest to `key`: case-insensitive match first, then small edit distance, then containment. At most three. */
export function closestMembers(key, members) {
const lower = key.toLowerCase();
const scored = [];
for (const m of members) {
const ml = m.toLowerCase();
let score = null;
if (ml === lower)
score = 0;
else {
const dist = editDistance(lower, ml);
if (dist <= Math.max(2, Math.floor(key.length / 3)))
score = dist;
else if (lower.length >= 3 && (ml.includes(lower) || lower.includes(ml)))
score = 10;
}
if (score !== null)
scored.push({ m, score });
}
return scored.sort((x, y) => x.score - y.score || x.m.localeCompare(y.m)).slice(0, 3).map((s) => s.m);
}
/** Read `key` from `holder` if the SDK's public surface allows it; throw otherwise. */
function readAllowed(holder, key, path, where = { display: path ? `r.${path}` : "r", root: undefined }) {
if (holder === null || holder === undefined) {
throw new TypeError(`cannot read property '${key}' of ${String(holder)} (${path})`);
}
if (typeof holder !== "object" && typeof holder !== "function") {
throw new RefusedProperty(`${path}.${key} is not part of the SDK surface (a ${typeof holder} has no SDK members)`);
}
if (ALWAYS_REFUSED.has(key) || key.startsWith("_") || key.startsWith("#")) {
throw new RefusedProperty(`${path ? `${path}.` : ""}${key} is not part of the SDK surface`);
}
const obj = holder;
// Plain data (what a call returned): its own properties only.
if (typeof holder === "object" && isPlainData(holder)) {
return Object.prototype.hasOwnProperty.call(obj, key) ? obj[key] : undefined;
}
// An SDK object or function: find where the property lives.
let owner = holder;
while (owner && !Object.prototype.hasOwnProperty.call(owner, key))
owner = Object.getPrototypeOf(owner);
if (!owner) {
// An SDK object has no such member: name the closest ones rather than
// letting the next call fail as "is not a function".
const members = publicMembers(holder);
const near = closestMembers(key, members).map((m) => `${where.display}.${m}`);
const suggestions = near.length > 0 ? near : findElsewhere(key, holder, where.display, where.root);
throw new UnknownMember(`${where.display}.${key}`, suggestions, members);
}
if (INTRINSIC_PROTOTYPES.has(owner)) {
throw new RefusedProperty(`${path ? `${path}.` : ""}${key} is not part of the SDK surface`);
}
const privates = sdkPrivateMembers();
for (let p = typeof holder === "function" ? null : Object.getPrototypeOf(holder); p && !INTRINSIC_PROTOTYPES.has(p); p = Object.getPrototypeOf(p)) {
const className = p.constructor?.name;
if (typeof className === "string" && privates.get(className)?.has(key)) {
throw new RefusedProperty(`${path ? `${path}.` : ""}${key} is private to the SDK`);
}
}
return obj[key];
}
function isThenable(v) {
return (typeof v === "object" || typeof v === "function") && v !== null && typeof v.then === "function";
}
/** JSON data all the way down (the terminal-value test): no functions, no class instances. */
function isJsonData(v, depth = 0) {
if (v === null || typeof v === "string" || typeof v === "boolean")
return true;
if (typeof v === "number")
return true;
if (v === undefined)
return true;
if (typeof v !== "object" || depth > 200)
return false;
if (Array.isArray(v))
return v.every((x) => isJsonData(x, depth + 1));
const proto = Object.getPrototypeOf(v);
if (proto !== Object.prototype && proto !== null)
return false;
return Object.values(v).every((x) => isJsonData(x, depth + 1));
}
function describeError(err) {
if (isRun402Error(err)) {
const e = err;
return {
name: e.name,
message: e.message,
...(e.code ? { code: e.code } : {}),
...(e.kind ? { kind: e.kind } : {}),
status: e.status,
...(e.nextActions !== undefined ? { next_actions: e.nextActions } : {}),
...(e.details !== undefined ? { details: e.details } : {}),
...(e.retryable !== undefined ? { retryable: e.retryable } : {}),
};
}
if (err instanceof UnknownMember)
return { name: err.name, message: err.message, code: err.code };
if (err instanceof Error)
return { name: err.name, message: err.message };
return { name: "Error", message: String(err) };
}
// ─── the host half ───────────────────────────────────────────────────────────
/**
* Replays recorded chains against `root` (the real SDK client) for one run.
* Holds that run's handles, its `calls[]` trace, and the original errors, and
* forgets all of them at {@link dispose}.
*/
export class ChainHost {
calls = [];
callsDropped = 0;
#root;
#handles = new Map();
#errors = new Map();
#nextHandle = 1;
#nextError = 1;
#inflight = 0;
constructor(root) {
this.#root = root;
}
/** The sandbox extension that installs `r` and routes its chains here. */
extension() {
return { install: PROXY_INSTALL, async: { dispatch: (raw) => this.dispatch(raw ?? "{}") } };
}
/** The original error behind an `error_ref` the sandbox reported, if it came from a replay. */
errorFor(ref) {
return ref ? this.#errors.get(ref) : undefined;
}
/** How many chains are replaying right now. */
get inflight() {
return this.#inflight;
}
dispose() {
this.#handles.clear();
this.#errors.clear();
}
async dispatch(raw) {
const started = Date.now();
let req;
try {
req = JSON.parse(raw);
}
catch {
return JSON.stringify({ ok: false, error: { name: "TypeError", message: "malformed chain" } });
}
let value;
let receiver;
let path;
if (req.base === "root") {
value = this.#root;
receiver = undefined;
path = "";
}
else {
const handle = this.#handles.get(req.base);
if (!handle) {
return JSON.stringify({ ok: false, error: { name: "TypeError", message: "that handle belongs to another run" } });
}
value = handle.value;
receiver = handle.receiver;
path = handle.path;
}
for (const s of req.segments)
if (s.op === "get")
path = path ? `${path}.${s.key}` : s.key;
const callPath = path || "r";
let display = "r";
this.#inflight++;
try {
let walked = req.base === "root" ? "" : (this.#handles.get(req.base)?.path ?? "");
display = req.base === "root" ? "r" : (this.#handles.get(req.base)?.display ?? "r");
for (const segment of req.segments) {
if (segment.op === "get") {
if (typeof segment.key !== "string")
throw new RefusedProperty("only string property names cross into the host");
const next = readAllowed(value, segment.key, walked, { display, root: this.#root });
receiver = value;
value = next;
walked = walked ? `${walked}.${segment.key}` : segment.key;
display = `${display}.${segment.key}`;
}
else {
display = `${display}(…)`;
if (typeof value !== "function")
throw new TypeError(`${walked || "r"} is not a function`);
const args = (segment.args ?? []).map((a) => this.#decodeArg(a));
value = Reflect.apply(value, receiver, args);
receiver = undefined;
}
if (isThenable(value))
value = await value;
}
}
catch (err) {
const ref = `err_${this.#nextError++}`;
this.#errors.set(ref, err);
const described = describeError(err);
const attempt = err?.paymentAttemptId;
this.#record({
path: callPath,
duration_ms: Date.now() - started,
ok: false,
code: described.code ?? described.name,
...(typeof attempt === "string" ? { payment_attempt_id: attempt } : {}),
});
return JSON.stringify({ ok: false, error: { ...described, error_ref: ref } });
}
finally {
this.#inflight--;
}
this.#record({ path: callPath, duration_ms: Date.now() - started, ok: true });
return this.#encodeResult(value, receiver, callPath, display);
}
#encodeResult(value, receiver, path, display) {
if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : value;
if (bytes.byteLength > CHAIN_MAX_TRANSFER_BYTES / 4)
return this.#tooLarge(path, bytes.byteLength);
return JSON.stringify({ ok: true, value: { __r402: { t: "bytes", v: Array.from(bytes) } } });
}
if (isJsonData(value)) {
const json = value === undefined ? undefined : JSON.stringify(value);
if (json !== undefined && json.length > CHAIN_MAX_TRANSFER_BYTES)
return this.#tooLarge(path, json.length);
return json === undefined ? JSON.stringify({ ok: true }) : `{"ok":true,"value":${json}}`;
}
const id = `h${this.#nextHandle++}`;
this.#handles.set(id, { value, receiver, path, display });
return JSON.stringify({ ok: true, value: { __r402: { t: "handle", v: id, path } } });
}
#tooLarge(path, bytes) {
return JSON.stringify({
ok: false,
error: {
name: "RangeError",
code: "RUN_VALUE_TOO_LARGE",
message: `${path} returned ${bytes} bytes, over the ${CHAIN_MAX_TRANSFER_BYTES}-byte transfer cap. Ask the SDK for less (a filter, a limit, a page).`,
},
});
}
#record(call) {
if (this.calls.length >= CHAIN_MAX_CALLS) {
this.callsDropped++;
return;
}
this.calls.push(call);
}
#decodeArg(v) {
if (Array.isArray(v))
return v.map((x) => this.#decodeArg(x));
if (v && typeof v === "object") {
const keys = Object.keys(v);
if (keys.length === 1 && keys[0] === "__r402") {
const tag = v.__r402;
switch (tag.t) {
case "undefined":
return undefined;
case "number":
return Number(tag.v);
case "bigint":
return BigInt(String(tag.v));
case "date":
return new Date(Number(tag.v));
case "bytes":
return Uint8Array.from(tag.v);
case "map":
return new Map(tag.v.map(([k, x]) => [this.#decodeArg(k), this.#decodeArg(x)]));
case "set":
return new Set(tag.v.map((x) => this.#decodeArg(x)));
case "handle": {
const entry = this.#handles.get(String(tag.v));
if (!entry)
throw new TypeError("that handle belongs to another run");
return entry.value;
}
default:
throw new TypeError(`unknown argument tag ${String(tag.t)}`);
}
}
const out = {};
for (const k of keys)
out[k] = this.#decodeArg(v[k]);
return out;
}
return v;
}
}
//# sourceMappingURL=sandbox-proxy.js.map