/** * Build the ES module source that runs inside the dynamic Worker. * * The module exports a `WorkerEntrypoint` subclass with an `evaluate` * method that: * 1. Captures console output into `__logs`. * 2. Creates a recursive `tools` Proxy that dispatches calls via RPC. * 3. Executes the normalised user code with a `Promise.race` timeout. * 4. Returns `{ result, error?, logs }`. * * Tool args cross the dispatcher boundary via Workers RPC, not JSON, so * `Uint8Array` / `ArrayBuffer` survive intact. `Blob` / `File` aren't on * RPC's structured-clone allow-list, so we encode them to a tagged * envelope (`__encodeBinary`) and the host rehydrates them. This is * required for any tool whose upstream wants `multipart/form-data` * (OpenAI Files API, etc.). */ export const buildExecutorModule = (body: string, timeoutMs: number): string => [ 'import { WorkerEntrypoint } from "cloudflare:workers";', "", "export default class CodeExecutor extends WorkerEntrypoint {", " async evaluate(__dispatcher) {", " const __logs = [];", ' console.log = (...a) => { __logs.push(a.map(String).join(" ")); };', ' console.warn = (...a) => { __logs.push("[warn] " + a.map(String).join(" ")); };', ' console.error = (...a) => { __logs.push("[error] " + a.map(String).join(" ")); };', " const __serializeErrorValue = (value) => {", " if (value instanceof Error) {", " return {", " __type: 'Error',", " name: value.name,", " message: value.message,", " ...(typeof value.stack === 'string' && value.stack.length > 0 ? { stack: value.stack } : {}),", " };", " }", " if (value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {", " return value;", " }", " try {", " return JSON.parse(JSON.stringify(value));", " } catch {", " return String(value);", " }", " };", " const __renderMessage = (value) => {", " if (typeof value === 'string') return value;", " if (value && typeof value === 'object' && value.__type === 'Error' && typeof value.message === 'string') {", " return value.message;", " }", " if (value instanceof Error) return value.message;", " if (value && typeof value === 'object' && typeof value.message === 'string') return value.message;", " if (typeof value === 'undefined') return 'Unknown error';", " if (value && typeof value === 'object') {", " try {", " return JSON.stringify(value);", " } catch {", " return String(value);", " }", " }", " return String(value);", " };", " const __serializeThrownError = (err) => {", " const primary = __serializeErrorValue(err);", " return {", " kind: 'unknown',", " message: __renderMessage(primary),", " primary,", " failures: [],", " defects: [],", " interrupted: false,", " };", " };", "", // Blob/File aren't on Workers RPC's structured-clone allow-list, so // we encode them to a tagged ArrayBuffer envelope before the call and // the host rehydrates them — and symmetrically the host encodes // Blob/File results which we rehydrate here. ArrayBuffer / typed // arrays cross natively. " const __isPlainObject = (v) => {", " const p = Object.getPrototypeOf(v);", " return p === Object.prototype || p === null;", " };", " const __isBinaryEnvelope = (v) =>", " v && typeof v === 'object' && v.__executorBinary === 1 &&", " v.buffer instanceof ArrayBuffer && typeof v.type === 'string';", " const __encodeBinary = async (value) => {", " if (value === null || typeof value !== 'object') return value;", " if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) return value;", " if (typeof File !== 'undefined' && value instanceof File) {", " return {", " __executorBinary: 1,", " kind: 'file',", " type: value.type,", " name: value.name,", " lastModified: value.lastModified,", " buffer: await value.arrayBuffer(),", " };", " }", " if (value instanceof Blob) {", " return { __executorBinary: 1, kind: 'blob', type: value.type, buffer: await value.arrayBuffer() };", " }", " if (Array.isArray(value)) return Promise.all(value.map(__encodeBinary));", " if (!__isPlainObject(value)) return value;", " const out = {};", " for (const [k, v] of Object.entries(value)) out[k] = await __encodeBinary(v);", " return out;", " };", " const __decodeBinary = (value) => {", " if (value === null || typeof value !== 'object') return value;", " if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) return value;", " if (__isBinaryEnvelope(value)) {", " if (value.kind === 'file' && typeof value.name === 'string') {", " return new File([value.buffer], value.name, {", " type: value.type,", " ...(typeof value.lastModified === 'number' ? { lastModified: value.lastModified } : {}),", " });", " }", " return new Blob([value.buffer], { type: value.type });", " }", " if (Array.isArray(value)) return value.map(__decodeBinary);", " if (!__isPlainObject(value)) return value;", " const out = {};", " for (const [k, v] of Object.entries(value)) out[k] = __decodeBinary(v);", " return out;", " };", " const __makeToolsProxy = (path = []) => new Proxy(() => undefined, {", " get(_target, prop) {", " if (prop === 'then' || typeof prop === 'symbol') return undefined;", " return __makeToolsProxy([...path, String(prop)]);", " },", " apply(_target, _thisArg, args) {", " const toolPath = path.join('.');", " if (!toolPath) throw new Error('Tool path missing in invocation');", " return (async () => {", " const encoded = await __encodeBinary(args[0]);", " const data = await __dispatcher.call(toolPath, encoded);", " if (!data.ok) {", " throw (data.error && data.error.primary !== null ? data.error.primary : data.error.message);", " }", " return __decodeBinary(data.result);", " })();", " },", " });", " const tools = __makeToolsProxy();", "", " try {", " const result = await Promise.race([", " (async () => {", body, " })(),", " new Promise((_, reject) =>", ` setTimeout(() => reject(new Error("Execution timed out after ${timeoutMs}ms")), ${timeoutMs})`, " ),", " ]);", " return { result, logs: __logs };", " } catch (err) {", " return { result: undefined, error: __serializeThrownError(err), logs: __logs };", " }", " }", "}", ].join("\n");