163 lines · 7.4 KB · TypeScript
RawDownload
/** * 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,",    "        };",    "      }",    "      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, seen = new WeakSet()) => {",    "      if (value === null || typeof value !== 'object') return value;",    "      if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) return value;",    "      if (seen.has(value)) throw new Error('Tool RPC payload contains a circular reference');",    "      seen.add(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((item) => __encodeBinary(item, seen)));",    "      if (!__isPlainObject(value)) return value;",    "      const out = {};",    "      for (const [k, v] of Object.entries(value)) out[k] = await __encodeBinary(v, seen);",    "      return out;",    "    };",    "    const __decodeBinary = (value, seen = new WeakSet()) => {",    "      if (value === null || typeof value !== 'object') return value;",    "      if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) return value;",    "      if (seen.has(value)) throw new Error('Tool RPC payload contains a circular reference');",    "      seen.add(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((item) => __decodeBinary(item, seen));",    "      if (!__isPlainObject(value)) return value;",    "      const out = {};",    "      for (const [k, v] of Object.entries(value)) out[k] = __decodeBinary(v, seen);",    "      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 new Error(data.error && data.error.message ? data.error.message : 'Tool execution failed');",    "          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");