Created
March 27, 2026 21:49
-
-
Save ConProgramming/b436377fd71de605765497ab52e899cf to your computer and use it in GitHub Desktop.
AI SDK codemode + Secure Exec in Trigger.dev — createCodeModeTool, executor, network adapter, test task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * SecureExecExecutor — runs LLM-generated code in a Secure Exec V8 isolate. | |
| * | |
| * Two-file pattern: the LLM writes a full ESM module with | |
| * `export default async (codemode) => { ... }`. The executor writes three | |
| * VFS files per execution: | |
| * 1. _codemode_{port}.mjs — RPC proxy that exports the `codemode` object | |
| * 2. user-code-{port}.mjs — LLM's code, written AS-IS | |
| * 3. entry-{port}.mjs — static entrypoint that imports both and | |
| * captures __RESULT__/__ERROR__ | |
| * | |
| * Tool calls from sandboxed code are routed back to the host via a local | |
| * HTTP RPC server. The sandbox only has network access to localhost. | |
| */ | |
| import { | |
| NodeRuntime, | |
| createNodeDriver, | |
| createNodeRuntimeDriverFactory, | |
| createInMemoryFileSystem, | |
| } from "secure-exec"; | |
| import type { VirtualFileSystem } from "secure-exec"; | |
| import type { FsAccessRequest, NetworkAccessRequest, PermissionDecision } from "@secure-exec/core"; | |
| import { createServer, type Server } from "node:http"; | |
| import { existsSync } from "node:fs"; | |
| import { dirname, join } from "node:path"; | |
| import { outdent } from "outdent"; | |
| import { forked__createDefaultNetworkAdapter } from "./network-adapter"; | |
| import { serializeError } from "serialize-error"; | |
| const findNodeModulesRoot = (): string => { | |
| let dir = process.cwd(); | |
| for (;;) { | |
| if (existsSync(join(dir, "node_modules"))) return dir; | |
| const parent = dirname(dir); | |
| if (parent === dir) return process.cwd(); | |
| dir = parent; | |
| } | |
| }; | |
| export interface ExecuteResult { | |
| result: unknown; | |
| error?: unknown; | |
| logs?: string[]; | |
| } | |
| export interface SecureExecExecutorOptions { | |
| memoryLimit?: number; | |
| cpuTimeLimitMs?: number; | |
| } | |
| export const DEFAULT_SECURE_EXEC_MEMORY_LIMIT = 64; | |
| export const DEFAULT_SECURE_EXEC_CPU_TIME_LIMIT_MS = 30_000; | |
| const ALLOWED_FS_PREFIXES = ["/root"]; | |
| export class SecureExecExecutor { | |
| #runtime: NodeRuntime; | |
| #filesystem: VirtualFileSystem; | |
| #server: Server | null = null; | |
| get filesystem(): VirtualFileSystem { | |
| return this.#filesystem; | |
| } | |
| constructor(options: SecureExecExecutorOptions = {}) { | |
| this.#filesystem = createInMemoryFileSystem(); | |
| this.#runtime = new NodeRuntime({ | |
| systemDriver: createNodeDriver({ | |
| filesystem: this.#filesystem, | |
| networkAdapter: forked__createDefaultNetworkAdapter(), | |
| moduleAccess: { cwd: findNodeModulesRoot() }, | |
| permissions: { | |
| fs: this.#checkFs, | |
| network: this.#checkNetwork, | |
| }, | |
| }), | |
| runtimeDriverFactory: createNodeRuntimeDriverFactory(), | |
| onStdio: (event) => { | |
| console.log("[onStdio] event", event); | |
| }, | |
| memoryLimit: options.memoryLimit ?? DEFAULT_SECURE_EXEC_MEMORY_LIMIT, | |
| cpuTimeLimitMs: options.cpuTimeLimitMs ?? DEFAULT_SECURE_EXEC_CPU_TIME_LIMIT_MS, | |
| }); | |
| } | |
| async execute( | |
| code: string, | |
| fns: Record<string, (...args: unknown[]) => Promise<unknown>>, | |
| ): Promise<ExecuteResult> { | |
| const { server, port } = await this.#startRpcServer(fns); | |
| try { | |
| await this.#filesystem.mkdir("/root/app"); | |
| await this.#writeCodemodeModule(Object.keys(fns), port); | |
| await this.#writeUserCode(code, port); | |
| const entryCode = await this.#getEntryModule(port); | |
| const execResult = await this.#runtime.run<{ result: Error | unknown }>( | |
| entryCode, | |
| `/root/app/entry-${port}.mjs`, | |
| ); | |
| if (execResult.exports?.result instanceof Error) { | |
| console.error("Error executing code", serializeError(execResult.exports?.result)); | |
| return { | |
| result: undefined, | |
| error: serializeError(execResult.exports?.result), | |
| }; | |
| } | |
| if (execResult.code !== 0) { | |
| return { | |
| result: undefined, | |
| error: { message: execResult.errorMessage ?? `Exit code ${execResult.code}` }, | |
| }; | |
| } | |
| return { | |
| result: execResult.exports?.result, | |
| error: undefined, | |
| }; | |
| } catch (err) { | |
| console.error("Error executing code", err); | |
| return { | |
| result: undefined, | |
| error: serializeError(err), | |
| }; | |
| } finally { | |
| server.close(); | |
| } | |
| } | |
| dispose(): void { | |
| this.#runtime.dispose(); | |
| this.#server?.close(); | |
| } | |
| async #writeCodemodeModule(toolNames: string[], port: number): Promise<void> { | |
| const proxyMethods = toolNames | |
| .map((name) => ` ${name}: (input) => rpc('${name}', input)`) | |
| .join(",\n"); | |
| const moduleCode = outdent` | |
| async function rpc(toolName, args) { | |
| try { | |
| const res = await fetch('http://127.0.0.1:${port}', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ tool: toolName, args }), | |
| }); | |
| const data = JSON.parse(typeof res.text === 'function' ? await res.text() : res.body); | |
| if (data.error) throw new Error(data.error); | |
| return data.result; | |
| } catch (err) { | |
| return { error: err.toString() }; | |
| } | |
| } | |
| export const codemode = { | |
| ${proxyMethods} | |
| }; | |
| `; | |
| await this.#filesystem.writeFile(`/root/app/_codemode-${port}.mjs`, moduleCode); | |
| } | |
| async #writeUserCode(userCode: string, port: number): Promise<void> { | |
| const cleaned = userCode | |
| .trim() | |
| .replace(/^```(?:js|javascript|typescript|ts|tsx|jsx)?\s*\n/, "") | |
| .replace(/\n?```\s*$/, "") | |
| .trim(); | |
| await this.#filesystem.writeFile(`/root/app/user-code-${port}.mjs`, cleaned); | |
| } | |
| async #getEntryModule(port: number): Promise<string> { | |
| const entryCode = outdent` | |
| import { codemode } from "./_codemode-${port}.mjs"; | |
| import { default as run } from "./user-code-${port}.mjs"; | |
| export const result = await run(codemode).catch((err) => new Error("Failed to execute code", { cause: err })); | |
| `; | |
| return entryCode; | |
| } | |
| #startRpcServer( | |
| fns: Record<string, (...args: unknown[]) => Promise<unknown>>, | |
| ): Promise<{ server: Server; port: number }> { | |
| return new Promise((resolve) => { | |
| const server = createServer(async (req, res) => { | |
| let body = ""; | |
| for await (const chunk of req) body += chunk; | |
| try { | |
| const { tool: name, args } = JSON.parse(body); | |
| const fn = fns[name]; | |
| if (!fn) { | |
| res.writeHead(400, { "Content-Type": "application/json" }); | |
| res.end(JSON.stringify({ error: `Unknown tool: ${name}` })); | |
| return; | |
| } | |
| const result = await fn(args); | |
| res.writeHead(200, { "Content-Type": "application/json" }); | |
| res.end(JSON.stringify({ result })); | |
| } catch (err) { | |
| res.writeHead(500, { "Content-Type": "application/json" }); | |
| res.end( | |
| JSON.stringify({ | |
| error: err instanceof Error ? err.message : String(err), | |
| }), | |
| ); | |
| } | |
| }); | |
| server.listen(0, "127.0.0.1", () => { | |
| const addr = server.address() as { port: number }; | |
| resolve({ server, port: addr.port }); | |
| }); | |
| }); | |
| } | |
| #checkFs = (req: FsAccessRequest): PermissionDecision => { | |
| const allowed = ALLOWED_FS_PREFIXES.some((prefix) => req.path.startsWith(prefix)); | |
| // eslint-disable-next-line no-console | |
| console.log(`[sandbox-fs] ${allowed ? "ALLOW" : "DENY"} ${req.op} ${req.path}`); | |
| return { allow: allowed }; | |
| }; | |
| #checkNetwork = (req: NetworkAccessRequest): PermissionDecision => { | |
| // eslint-disable-next-line no-console | |
| console.log(`[sandbox-net] ALLOW ${req.op} ${req.hostname ?? ""} ${req.url ?? ""}`); | |
| return { allow: true }; | |
| }; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Creates a "codemode" tool that lets the LLM write JavaScript ESM modules | |
| * orchestrating multiple tool calls in a single sandbox execution. | |
| * | |
| * The LLM writes: `export default async (codemode) => { ... }` | |
| * The executor writes a static entrypoint that imports and calls it. | |
| */ | |
| import { tool, type Tool } from "ai"; | |
| import { z } from "zod"; | |
| import { logger } from "@trigger.dev/sdk"; | |
| import { outdent } from "outdent"; | |
| import { SecureExecExecutor, type SecureExecExecutorOptions } from "./executor.js"; | |
| export { SecureExecExecutor } from "./executor.js"; | |
| export type { ExecuteResult, SecureExecExecutorOptions } from "./executor.js"; | |
| export type { VirtualFileSystem } from "secure-exec"; | |
| export type { CodeModeToolOptions }; | |
| interface CodeModeToolOptions { | |
| memoryLimit?: number; | |
| cpuTimeLimitMs?: number; | |
| } | |
| export const createCodeModeTool = ( | |
| tools: Record<string, Tool>, | |
| options: SecureExecExecutorOptions = {}, | |
| ) => { | |
| const executor = new SecureExecExecutor(options); | |
| const toolEntries = Object.entries(tools) | |
| .filter(([, t]) => typeof t.execute === "function") | |
| .map( | |
| ([name, t]) => ` /** ${t.description ?? name} */\n ${name}: (input) => Promise<unknown>;`, | |
| ) | |
| .join("\n"); | |
| const codeToolDescription = outdent` | |
| Execute JavaScript in a sandboxed V8 isolate by writing a full ESM module. | |
| The sandbox has an in-memory virtual filesystem (import * as fs from "fs") and | |
| access to npm packages from the host. Files placed in /root/workspace/ persist | |
| across codemode calls within the same session. | |
| Write a JavaScript ESM module. Export a default async function that receives | |
| ${"`codemode`"} as its argument. Use import statements at the top for npm packages | |
| and skill helpers. Return the result from the function. | |
| npm packages MUST use namespace imports: import * as pkg from "pkg" | |
| CORRECT FORMAT: | |
| import * as JSZip from "jszip"; | |
| import { validateDocx } from "/root/skills/docx/helpers/validate-docx.mjs"; | |
| export default async (codemode) => { | |
| const a = await codemode.toolA({ param: "value" }); | |
| const b = await codemode.toolB({ param: "value" }); | |
| return { a, b }; | |
| }; | |
| WRONG (require/eval — use import instead): | |
| const JSZip = require("jszip"); | |
| eval(fs.readFileSync(...)); | |
| WRONG (default/named imports for CJS npm packages — use import * as): | |
| import JSZip from "jszip"; | |
| import { Document } from "docx"; | |
| WRONG (bare statements, no export default): | |
| const a = await codemode.toolA({ param: "value" }); | |
| return a; | |
| Available codemode API: | |
| declare const codemode: { | |
| ${toolEntries} | |
| } | |
| `; | |
| const codemodeTool = tool({ | |
| description: codeToolDescription, | |
| inputSchema: z.object({ | |
| code: z | |
| .string() | |
| .describe("A JavaScript ESM module with export default async (codemode) => { ... }"), | |
| }), | |
| execute: async ({ code }) => { | |
| logger.info("codemode received generated code", { | |
| codeLength: code.length, | |
| codePreview: code.slice(0, 1000), | |
| }); | |
| const fns: Record<string, (...args: unknown[]) => Promise<unknown>> = {}; | |
| for (const [name, t] of Object.entries(tools)) { | |
| if (typeof t.execute === "function") { | |
| fns[name] = t.execute as (...args: unknown[]) => Promise<unknown>; | |
| } | |
| } | |
| const result = await executor.execute(code, fns); | |
| logger.info("codemode sandbox execution completed", { | |
| hasError: Boolean(result.error), | |
| logCount: result.logs?.length ?? 0, | |
| }); | |
| if (result.error) throw new Error(result.error); | |
| return result; | |
| }, | |
| }); | |
| return { | |
| tool: codemodeTool, | |
| filesystem: executor.filesystem, | |
| dispose: () => executor.dispose(), | |
| }; | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import * as dns from "node:dns"; | |
| import * as net from "node:net"; | |
| import * as http from "node:http"; | |
| import * as https from "node:https"; | |
| import * as zlib from "node:zlib"; | |
| import type { NetworkAdapter } from "@secure-exec/core"; | |
| export interface DefaultNetworkAdapterOptions { | |
| /** Pre-seed loopback ports that should bypass SSRF checks (e.g. host-managed servers). */ | |
| initialExemptPorts?: Iterable<number>; | |
| } | |
| interface LoopbackAwareNetworkAdapter extends NetworkAdapter { | |
| __setLoopbackPortChecker?(checker: (hostname: string, port: number) => boolean): void; | |
| } | |
| /** Check whether an IP address falls in a private/reserved range (SSRF protection). */ | |
| export function isPrivateIp(ip: string): boolean { | |
| // Normalize IPv4-mapped IPv6 (::ffff:a.b.c.d → a.b.c.d) | |
| const normalized = ip.startsWith("::ffff:") ? ip.slice(7) : ip; | |
| if (net.isIPv4(normalized)) { | |
| const parts = normalized.split(".").map(Number); | |
| const [a, b] = parts; | |
| return ( | |
| a === 10 || | |
| (a === 172 && b >= 16 && b <= 31) || | |
| (a === 192 && b === 168) || | |
| a === 127 || | |
| (a === 169 && b === 254) || | |
| a === 0 || | |
| (a >= 224 && a <= 239) || | |
| a >= 240 | |
| ); | |
| } | |
| if (net.isIPv6(normalized)) { | |
| const lower = normalized.toLowerCase(); | |
| return ( | |
| lower === "::1" || | |
| lower === "::" || | |
| lower.startsWith("fc") || | |
| lower.startsWith("fd") || | |
| lower.startsWith("fe80") || | |
| lower.startsWith("ff") | |
| ); | |
| } | |
| return false; | |
| } | |
| /** Check whether a hostname is a loopback address (127.x.x.x, ::1, localhost). */ | |
| function isLoopbackHost(hostname: string): boolean { | |
| const bare = | |
| hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname; | |
| if (bare === "localhost" || bare === "::1") return true; | |
| if (net.isIPv4(bare) && bare.startsWith("127.")) return true; | |
| return false; | |
| } | |
| function getUrlPort(parsed: URL): number { | |
| return parsed.port ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80; | |
| } | |
| /** | |
| * Resolve hostname to IP and block private/reserved ranges (SSRF protection). | |
| * | |
| * Loopback requests are allowed only when an explicit exemption or the | |
| * runtime-provided kernel listener checker claims the requested port. | |
| */ | |
| async function assertNotPrivateHost( | |
| url: string, | |
| allowLoopbackPort?: (hostname: string, port: number) => boolean, | |
| ): Promise<void> { | |
| // WARNING: This is a temporary fix to allow loopback requests to the RPC server. | |
| // TODO: Remove this once we receive a proper fix from the Secure Exec team. | |
| return false; | |
| const parsed = new URL(url); | |
| if (parsed.protocol === "data:" || parsed.protocol === "blob:") return; | |
| const hostname = parsed.hostname; | |
| const bare = | |
| hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname; | |
| if (isLoopbackHost(hostname)) { | |
| const port = getUrlPort(parsed); | |
| if (allowLoopbackPort?.(hostname, port)) { | |
| return; | |
| } | |
| } | |
| if (net.isIP(bare)) { | |
| if (isPrivateIp(bare)) { | |
| throw new Error(`SSRF blocked: ${hostname} resolves to private IP`); | |
| } | |
| return; | |
| } | |
| const address = await new Promise<string>((resolve, reject) => { | |
| dns.lookup(bare, (err, addr) => { | |
| if (err) reject(err); | |
| else resolve(addr); | |
| }); | |
| }); | |
| if (isPrivateIp(address)) { | |
| throw new Error(`SSRF blocked: ${hostname} resolves to private IP ${address}`); | |
| } | |
| } | |
| const MAX_REDIRECTS = 20; | |
| /** | |
| * Create a Node.js network adapter that provides real fetch, DNS, and HTTP | |
| * client support. Binary responses are base64-encoded with an | |
| * `x-body-encoding` header so the bridge can decode them. | |
| */ | |
| export function forked__createDefaultNetworkAdapter( | |
| options?: DefaultNetworkAdapterOptions, | |
| ): NetworkAdapter { | |
| const upgradeSockets = new Map<number, import("stream").Duplex>(); | |
| const initialExemptPorts = new Set<number>(options?.initialExemptPorts); | |
| let nextUpgradeSocketId = 1; | |
| let onUpgradeSocketData: ((socketId: number, dataBase64: string) => void) | null = null; | |
| let onUpgradeSocketEnd: ((socketId: number) => void) | null = null; | |
| let dynamicLoopbackPortChecker: ((hostname: string, port: number) => boolean) | undefined; | |
| const allowLoopbackPort = (hostname: string, port: number): boolean => { | |
| if (initialExemptPorts.has(port)) return true; | |
| if (dynamicLoopbackPortChecker?.(hostname, port)) return true; | |
| return false; | |
| }; | |
| const adapter: LoopbackAwareNetworkAdapter = { | |
| __setLoopbackPortChecker(checker) { | |
| dynamicLoopbackPortChecker = checker; | |
| }, | |
| upgradeSocketWrite(socketId, dataBase64) { | |
| const socket = upgradeSockets.get(socketId); | |
| if (socket && !socket.destroyed) { | |
| socket.write(Buffer.from(dataBase64, "base64")); | |
| } | |
| }, | |
| upgradeSocketEnd(socketId) { | |
| const socket = upgradeSockets.get(socketId); | |
| if (socket && !socket.destroyed) { | |
| socket.end(); | |
| } | |
| }, | |
| upgradeSocketDestroy(socketId) { | |
| const socket = upgradeSockets.get(socketId); | |
| if (socket) { | |
| socket.destroy(); | |
| upgradeSockets.delete(socketId); | |
| } | |
| }, | |
| setUpgradeSocketCallbacks(callbacks) { | |
| onUpgradeSocketData = callbacks.onData; | |
| onUpgradeSocketEnd = callbacks.onEnd; | |
| }, | |
| async fetch(url, requestOptions) { | |
| let currentUrl = url; | |
| let redirected = false; | |
| for (let i = 0; i <= MAX_REDIRECTS; i++) { | |
| await assertNotPrivateHost(currentUrl, allowLoopbackPort); | |
| const response = await fetch(currentUrl, { | |
| method: requestOptions?.method || "GET", | |
| headers: requestOptions?.headers, | |
| body: requestOptions?.body, | |
| redirect: "manual", | |
| }); | |
| const status = response.status; | |
| if ( | |
| status === 301 || | |
| status === 302 || | |
| status === 303 || | |
| status === 307 || | |
| status === 308 | |
| ) { | |
| const location = response.headers.get("location"); | |
| if (!location) break; | |
| currentUrl = new URL(location, currentUrl).href; | |
| redirected = true; | |
| if (status === 301 || status === 302 || status === 303) { | |
| requestOptions = { ...requestOptions, method: "GET", body: undefined }; | |
| } | |
| continue; | |
| } | |
| const headers: Record<string, string> = {}; | |
| response.headers.forEach((value, key) => { | |
| headers[key] = value; | |
| }); | |
| delete headers["content-encoding"]; | |
| const contentType = response.headers.get("content-type") || ""; | |
| const isBinary = | |
| contentType.includes("octet-stream") || | |
| contentType.includes("gzip") || | |
| currentUrl.endsWith(".tgz"); | |
| let body: string; | |
| if (isBinary) { | |
| const buffer = await response.arrayBuffer(); | |
| body = Buffer.from(buffer).toString("base64"); | |
| headers["x-body-encoding"] = "base64"; | |
| } else { | |
| body = await response.text(); | |
| } | |
| return { | |
| ok: response.ok, | |
| status: response.status, | |
| statusText: response.statusText, | |
| headers, | |
| body, | |
| url: currentUrl, | |
| redirected, | |
| }; | |
| } | |
| throw new Error("Too many redirects"); | |
| }, | |
| async dnsLookup(hostname) { | |
| return new Promise((resolve) => { | |
| dns.lookup(hostname, (err, address, family) => { | |
| if (err) { | |
| resolve({ error: err.message, code: err.code || "ENOTFOUND" }); | |
| } else { | |
| resolve({ address, family }); | |
| } | |
| }); | |
| }); | |
| }, | |
| async httpRequest(url, requestOptions) { | |
| await assertNotPrivateHost(url, allowLoopbackPort); | |
| type HttpRequestResult = Awaited<ReturnType<NetworkAdapter["httpRequest"]>> & { | |
| rawHeaders?: string[]; | |
| }; | |
| return new Promise<HttpRequestResult>((resolve, reject) => { | |
| const urlObj = new URL(url); | |
| const isHttps = urlObj.protocol === "https:"; | |
| const transport = isHttps ? https : http; | |
| const reqOptions: https.RequestOptions = { | |
| hostname: urlObj.hostname, | |
| port: urlObj.port || (isHttps ? 443 : 80), | |
| path: urlObj.pathname + urlObj.search, | |
| method: requestOptions?.method || "GET", | |
| headers: requestOptions?.headers || {}, | |
| // Keep host-side pooling disabled so sandbox http.Agent semantics | |
| // are controlled entirely by the bridge layer. | |
| agent: false, | |
| ...(isHttps && | |
| requestOptions?.rejectUnauthorized !== undefined && { | |
| rejectUnauthorized: requestOptions.rejectUnauthorized, | |
| }), | |
| }; | |
| const req = transport.request(reqOptions, (res) => { | |
| const chunks: Buffer[] = []; | |
| res.on("data", (chunk: Buffer) => chunks.push(chunk)); | |
| res.on("end", async () => { | |
| let buffer: Buffer = Buffer.concat(chunks); | |
| const contentEncoding = res.headers["content-encoding"]; | |
| if (contentEncoding === "gzip" || contentEncoding === "deflate") { | |
| try { | |
| buffer = await new Promise((responseResolve, responseReject) => { | |
| const decompress = contentEncoding === "gzip" ? zlib.gunzip : zlib.inflate; | |
| decompress(buffer, (err, result) => { | |
| if (err) responseReject(err); | |
| else responseResolve(result); | |
| }); | |
| }); | |
| } catch { | |
| // Preserve the original buffer when decompression fails. | |
| } | |
| } | |
| const contentType = res.headers["content-type"] || ""; | |
| const isBinary = | |
| contentType.includes("octet-stream") || | |
| contentType.includes("gzip") || | |
| url.endsWith(".tgz"); | |
| const headers: Record<string, string> = {}; | |
| const rawHeaders = [...res.rawHeaders]; | |
| Object.entries(res.headers).forEach(([key, value]) => { | |
| if (typeof value === "string") headers[key] = value; | |
| else if (Array.isArray(value)) headers[key] = value.join(", "); | |
| }); | |
| delete headers["content-encoding"]; | |
| const trailers: Record<string, string> = {}; | |
| if (res.trailers) { | |
| Object.entries(res.trailers).forEach(([key, value]) => { | |
| if (typeof value === "string") trailers[key] = value; | |
| }); | |
| } | |
| const hasTrailers = Object.keys(trailers).length > 0; | |
| const base = { | |
| status: res.statusCode || 200, | |
| statusText: res.statusMessage || "OK", | |
| headers, | |
| rawHeaders, | |
| url, | |
| ...(hasTrailers ? { trailers } : {}), | |
| }; | |
| if (isBinary) { | |
| headers["x-body-encoding"] = "base64"; | |
| resolve({ ...base, body: buffer.toString("base64") }); | |
| } else { | |
| resolve({ ...base, body: buffer.toString("utf-8") }); | |
| } | |
| }); | |
| res.on("error", reject); | |
| }); | |
| req.on("upgrade", (res, socket, head) => { | |
| const headers: Record<string, string> = {}; | |
| const rawHeaders = [...res.rawHeaders]; | |
| Object.entries(res.headers).forEach(([key, value]) => { | |
| if (typeof value === "string") headers[key] = value; | |
| else if (Array.isArray(value)) headers[key] = value.join(", "); | |
| }); | |
| const socketId = nextUpgradeSocketId++; | |
| upgradeSockets.set(socketId, socket); | |
| socket.on("data", (chunk) => { | |
| if (onUpgradeSocketData) { | |
| onUpgradeSocketData(socketId, chunk.toString("base64")); | |
| } | |
| }); | |
| socket.on("close", () => { | |
| if (onUpgradeSocketEnd) { | |
| onUpgradeSocketEnd(socketId); | |
| } | |
| upgradeSockets.delete(socketId); | |
| }); | |
| resolve({ | |
| status: res.statusCode || 101, | |
| statusText: res.statusMessage || "Switching Protocols", | |
| headers, | |
| rawHeaders, | |
| body: head.toString("base64"), | |
| url, | |
| upgradeSocketId: socketId, | |
| }); | |
| }); | |
| req.on("connect", (res, socket, head) => { | |
| const headers: Record<string, string> = {}; | |
| const rawHeaders = [...res.rawHeaders]; | |
| Object.entries(res.headers).forEach(([key, value]) => { | |
| if (typeof value === "string") headers[key] = value; | |
| else if (Array.isArray(value)) headers[key] = value.join(", "); | |
| }); | |
| const socketId = nextUpgradeSocketId++; | |
| upgradeSockets.set(socketId, socket); | |
| socket.on("data", (chunk) => { | |
| if (onUpgradeSocketData) { | |
| onUpgradeSocketData(socketId, chunk.toString("base64")); | |
| } | |
| }); | |
| socket.on("close", () => { | |
| if (onUpgradeSocketEnd) { | |
| onUpgradeSocketEnd(socketId); | |
| } | |
| upgradeSockets.delete(socketId); | |
| }); | |
| resolve({ | |
| status: res.statusCode || 200, | |
| statusText: res.statusMessage || "Connection established", | |
| headers, | |
| rawHeaders, | |
| body: head.toString("base64"), | |
| url, | |
| upgradeSocketId: socketId, | |
| }); | |
| }); | |
| req.on("error", reject); | |
| if (requestOptions?.body) req.write(requestOptions.body); | |
| req.end(); | |
| }); | |
| }, | |
| }; | |
| return adapter; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Code Mode with Secure Exec | |
| * | |
| * Same pattern as @cloudflare/codemode, but using Secure Exec as the sandbox | |
| * instead of Cloudflare Workers. The LLM writes code that calls tools via | |
| * `codemode.*`, executed safely in a V8 isolate. | |
| * | |
| * See: https://blog.cloudflare.com/code-mode/ | |
| */ | |
| import { logger, task } from "@trigger.dev/sdk"; | |
| import { generateText, tool, stepCountIs } from "ai"; | |
| import { createAIProvider } from "@govsignals/ai/providers"; | |
| import { z } from "zod"; | |
| import { createCodeModeTool } from "../lib/create-codemode-tool"; | |
| const tools = { | |
| getWeather: tool({ | |
| description: | |
| "Get current weather for a city. Returns { temp_f: number, condition: string, humidity: number }.", | |
| inputSchema: z.object({ | |
| city: z.string().describe("City name"), | |
| }), | |
| execute: async ({ city }) => { | |
| const data: Record<string, { temp_f: number; condition: string; humidity: number }> = { | |
| "San Francisco": { temp_f: 62, condition: "Foggy", humidity: 78 }, | |
| "New York": { temp_f: 84, condition: "Sunny", humidity: 55 }, | |
| London: { temp_f: 58, condition: "Rainy", humidity: 88 }, | |
| Tokyo: { temp_f: 91, condition: "Humid", humidity: 72 }, | |
| }; | |
| const output = data[city] ?? { temp_f: 70, condition: "Clear", humidity: 50 }; | |
| logger.info("getWeather output", { output }); | |
| return output; | |
| }, | |
| }), | |
| }; | |
| export const testCodeMode = task({ | |
| id: "test-code-mode", | |
| retry: { maxAttempts: 1 }, | |
| run: async () => { | |
| const { tool: codemodeTool, dispose } = createCodeModeTool(tools, { | |
| cpuTimeLimitMs: 10_000, | |
| }); | |
| try { | |
| const AI = await createAIProvider(); | |
| logger.info("test-code-mode starting", { | |
| model: "thinking", | |
| }); | |
| const { text } = await generateText({ | |
| model: AI.languageModel("thinking"), | |
| prompt: | |
| "Compare the weather in San Francisco and Tokyo. Calculate the temperature difference in both Fahrenheit and Celsius.", | |
| stopWhen: stepCountIs(10), | |
| tools: { | |
| codemode: codemodeTool, | |
| }, | |
| experimental_telemetry: { isEnabled: true }, | |
| }); | |
| logger.info("Code mode test completed", { | |
| textLength: text.length, | |
| textPreview: text.slice(0, 500), | |
| }); | |
| return { text }; | |
| } finally { | |
| dispose(); | |
| } | |
| }, | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment