@tanstack/ai-isolate-cloudflare 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # @tanstack/ai-isolate-cloudflare
2
+
3
+ Cloudflare Workers driver for TanStack AI Code Mode.
4
+
5
+ This package runs generated JavaScript in a Worker and keeps `external_*` tool execution on your host process through a request/response loop.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add @tanstack/ai-isolate-cloudflare
11
+ ```
12
+
13
+ ## Environment Guidance (Conservative)
14
+
15
+ - **Local development:** supported with the package's Miniflare dev server (`pnpm dev:worker`)
16
+ - **Remote dev:** supported with `wrangler dev --remote`
17
+ - **Production:** evaluate carefully before rollout; dynamic code execution with `unsafe_eval` has platform/security constraints and is often treated as an advanced or enterprise setup
18
+
19
+ If you need a fully local setup without Cloudflare constraints, prefer `@tanstack/ai-isolate-node` or `@tanstack/ai-isolate-quickjs`.
20
+
21
+ ## Quick Start
22
+
23
+ ```typescript
24
+ import { chat, toolDefinition } from '@tanstack/ai'
25
+ import { createCodeMode } from '@tanstack/ai-code-mode'
26
+ import { createCloudflareIsolateDriver } from '@tanstack/ai-isolate-cloudflare'
27
+ import { z } from 'zod'
28
+
29
+ const fetchWeather = toolDefinition({
30
+ name: 'fetchWeather',
31
+ description: 'Get weather for a city',
32
+ inputSchema: z.object({ location: z.string() }),
33
+ outputSchema: z.object({
34
+ temperature: z.number(),
35
+ condition: z.string(),
36
+ }),
37
+ }).server(async ({ location }) => {
38
+ return { temperature: 72, condition: `sunny in ${location}` }
39
+ })
40
+
41
+ const driver = createCloudflareIsolateDriver({
42
+ workerUrl: 'http://localhost:8787', // local dev server URL
43
+ authorization: 'Bearer your-secret-token', // optional
44
+ })
45
+
46
+ const { tool, systemPrompt } = createCodeMode({
47
+ driver,
48
+ tools: [fetchWeather],
49
+ timeout: 30_000,
50
+ })
51
+
52
+ const result = await chat({
53
+ adapter: yourTextAdapter,
54
+ model: 'gpt-4o-mini',
55
+ systemPrompts: ['You are a helpful assistant.', systemPrompt],
56
+ tools: [tool],
57
+ messages: [{ role: 'user', content: 'Compare weather in Tokyo and Paris' }],
58
+ })
59
+ ```
60
+
61
+ ## Worker Setup
62
+
63
+ ### Option 1: Local Miniflare server
64
+
65
+ From this package directory:
66
+
67
+ ```bash
68
+ pnpm dev:worker
69
+ ```
70
+
71
+ This starts a local Worker endpoint (default `http://localhost:8787`) with `UNSAFE_EVAL` configured for local testing.
72
+
73
+ ### Option 2: Wrangler remote dev
74
+
75
+ ```bash
76
+ wrangler dev --remote
77
+ ```
78
+
79
+ This runs through Cloudflare's network and can be useful when validating behavior against the hosted runtime.
80
+
81
+ ## API
82
+
83
+ ### `createCloudflareIsolateDriver(config)`
84
+
85
+ Creates a driver that delegates code execution to a Worker endpoint.
86
+
87
+ - `workerUrl` (required): URL of the Worker endpoint
88
+ - `authorization` (optional): value sent as `Authorization` header
89
+ - `timeout` (optional): request timeout in ms (default: `30000`)
90
+ - `maxToolRounds` (optional): max Worker <-> host tool callback rounds (default: `10`)
91
+
92
+ ## Worker Entry Export
93
+
94
+ The package also exports a Worker entrypoint:
95
+
96
+ ```typescript
97
+ import worker from '@tanstack/ai-isolate-cloudflare/worker'
98
+ ```
99
+
100
+ Use this when you want to bundle or compose the provided worker logic in your own Worker project.
101
+
102
+ ## Security Notes
103
+
104
+ - Protect the worker endpoint if it is reachable outside trusted infrastructure.
105
+ - Validate auth headers server-side if you set `authorization` in the driver.
106
+ - Add rate limiting and request monitoring for untrusted traffic.
107
+ - Treat generated code execution as a high-risk surface; keep strict input and network boundaries.
108
+
109
+ ## Architecture
110
+
111
+ ```
112
+ Host Driver Cloudflare Worker
113
+ ----------- ------------------
114
+ 1) send code + tool schemas -> execute until tool call or completion
115
+ 2) receive tool requests <- need_tools payload
116
+ 3) execute tools locally -> send toolResults
117
+ 4) receive final result <- success/error payload
118
+ ```
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @tanstack/ai-isolate-cloudflare
3
+ *
4
+ * Cloudflare Workers driver for TanStack AI Code Mode.
5
+ * Execute LLM-generated code on Cloudflare's global edge network.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { createCloudflareIsolateDriver } from '@tanstack/ai-isolate-cloudflare'
10
+ *
11
+ * const driver = createCloudflareIsolateDriver({
12
+ * workerUrl: 'https://your-worker.workers.dev',
13
+ * })
14
+ * ```
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+ export { createCloudflareIsolateDriver, type CloudflareIsolateDriverConfig, } from './isolate-driver.js';
19
+ export type { ExecuteRequest, ExecuteResponse, ToolSchema, ToolCallRequest, ToolResultPayload, } from './types.js';
@@ -0,0 +1,5 @@
1
+ import { createCloudflareIsolateDriver } from "./isolate-driver.js";
2
+ export {
3
+ createCloudflareIsolateDriver
4
+ };
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1,72 @@
1
+ import { IsolateDriver } from '@tanstack/ai-code-mode';
2
+ /**
3
+ * Configuration for the Cloudflare Workers isolate driver
4
+ */
5
+ export interface CloudflareIsolateDriverConfig {
6
+ /**
7
+ * URL of the deployed Cloudflare Worker
8
+ * For local development, use: http://localhost:8787
9
+ */
10
+ workerUrl: string;
11
+ /**
12
+ * Optional authorization header value
13
+ * Useful for protecting your Worker endpoint
14
+ */
15
+ authorization?: string;
16
+ /**
17
+ * Default execution timeout in ms (default: 30000)
18
+ */
19
+ timeout?: number;
20
+ /**
21
+ * Maximum number of tool callback rounds (default: 10)
22
+ * Prevents infinite loops
23
+ */
24
+ maxToolRounds?: number;
25
+ }
26
+ /**
27
+ * Create a Cloudflare Workers isolate driver
28
+ *
29
+ * This driver executes code on Cloudflare's global edge network,
30
+ * providing true distributed execution capabilities.
31
+ *
32
+ * Tool calls are handled via a request/response loop:
33
+ * 1. Code is sent to the Worker
34
+ * 2. Worker executes until it needs a tool
35
+ * 3. Tool call is returned to the driver
36
+ * 4. Driver executes the tool locally
37
+ * 5. Result is sent back to the Worker
38
+ * 6. Worker continues execution
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * import { createCloudflareIsolateDriver } from '@tanstack/ai-isolate-cloudflare'
43
+ *
44
+ * // For local development with wrangler
45
+ * const driver = createCloudflareIsolateDriver({
46
+ * workerUrl: 'http://localhost:8787',
47
+ * })
48
+ *
49
+ * // For production
50
+ * const driver = createCloudflareIsolateDriver({
51
+ * workerUrl: 'https://code-mode-worker.your-account.workers.dev',
52
+ * authorization: 'Bearer your-secret-token',
53
+ * })
54
+ *
55
+ * const context = await driver.createContext({
56
+ * bindings: {
57
+ * readFile: {
58
+ * name: 'readFile',
59
+ * description: 'Read a file',
60
+ * inputSchema: { type: 'object', properties: { path: { type: 'string' } } },
61
+ * execute: async ({ path }) => fs.readFile(path, 'utf-8'),
62
+ * },
63
+ * },
64
+ * })
65
+ *
66
+ * const result = await context.execute(`
67
+ * const content = await readFile({ path: './data.json' })
68
+ * return JSON.parse(content)
69
+ * `)
70
+ * ```
71
+ */
72
+ export declare function createCloudflareIsolateDriver(config: CloudflareIsolateDriverConfig): IsolateDriver;
@@ -0,0 +1,174 @@
1
+ function bindingsToSchemas(bindings) {
2
+ return Object.entries(bindings).map(([name, binding]) => ({
3
+ name,
4
+ description: binding.description,
5
+ inputSchema: binding.inputSchema
6
+ }));
7
+ }
8
+ function normalizeError(error) {
9
+ if (error instanceof Error) {
10
+ return { name: error.name, message: error.message };
11
+ }
12
+ if (typeof error === "object" && error !== null) {
13
+ const e = error;
14
+ return {
15
+ name: String(e.name || "Error"),
16
+ message: String(e.message || JSON.stringify(error))
17
+ };
18
+ }
19
+ return { name: "Error", message: String(error) };
20
+ }
21
+ class CloudflareIsolateContext {
22
+ workerUrl;
23
+ authorization;
24
+ timeout;
25
+ maxToolRounds;
26
+ bindings;
27
+ disposed = false;
28
+ constructor(workerUrl, bindings, timeout, maxToolRounds, authorization) {
29
+ this.workerUrl = workerUrl;
30
+ this.bindings = bindings;
31
+ this.timeout = timeout;
32
+ this.maxToolRounds = maxToolRounds;
33
+ this.authorization = authorization;
34
+ }
35
+ async execute(code) {
36
+ if (this.disposed) {
37
+ return {
38
+ success: false,
39
+ error: {
40
+ name: "DisposedError",
41
+ message: "Context has been disposed"
42
+ },
43
+ logs: []
44
+ };
45
+ }
46
+ const tools = bindingsToSchemas(this.bindings);
47
+ let toolResults;
48
+ let allLogs = [];
49
+ let rounds = 0;
50
+ while (rounds < this.maxToolRounds) {
51
+ rounds++;
52
+ const request = {
53
+ code,
54
+ tools,
55
+ toolResults,
56
+ timeout: this.timeout
57
+ };
58
+ try {
59
+ const headers = {
60
+ "Content-Type": "application/json"
61
+ };
62
+ if (this.authorization) {
63
+ headers["Authorization"] = this.authorization;
64
+ }
65
+ const response = await fetch(this.workerUrl, {
66
+ method: "POST",
67
+ headers,
68
+ body: JSON.stringify(request)
69
+ });
70
+ if (!response.ok) {
71
+ const errorText = await response.text();
72
+ return {
73
+ success: false,
74
+ error: {
75
+ name: "WorkerError",
76
+ message: `Worker returned ${response.status}: ${errorText}`
77
+ },
78
+ logs: allLogs
79
+ };
80
+ }
81
+ const result = await response.json();
82
+ if (result.status === "error") {
83
+ return {
84
+ success: false,
85
+ error: result.error,
86
+ logs: allLogs
87
+ };
88
+ }
89
+ if (result.status === "done") {
90
+ allLogs = [...allLogs, ...result.logs];
91
+ return {
92
+ success: result.success,
93
+ value: result.value,
94
+ error: result.error,
95
+ logs: allLogs
96
+ };
97
+ }
98
+ allLogs = [...allLogs, ...result.logs];
99
+ toolResults = {};
100
+ for (const toolCall of result.toolCalls) {
101
+ const binding = this.bindings[toolCall.name];
102
+ if (!binding) {
103
+ toolResults[toolCall.id] = {
104
+ success: false,
105
+ error: `Unknown tool: ${toolCall.name}`
106
+ };
107
+ continue;
108
+ }
109
+ try {
110
+ const toolResult = await binding.execute(toolCall.args);
111
+ toolResults[toolCall.id] = {
112
+ success: true,
113
+ value: toolResult
114
+ };
115
+ } catch (toolError) {
116
+ const err = normalizeError(toolError);
117
+ toolResults[toolCall.id] = {
118
+ success: false,
119
+ error: err.message
120
+ };
121
+ }
122
+ }
123
+ } catch (fetchError) {
124
+ const err = normalizeError(fetchError);
125
+ return {
126
+ success: false,
127
+ error: {
128
+ name: "NetworkError",
129
+ message: `Failed to communicate with Worker: ${err.message}`
130
+ },
131
+ logs: allLogs
132
+ };
133
+ }
134
+ }
135
+ return {
136
+ success: false,
137
+ error: {
138
+ name: "MaxRoundsExceeded",
139
+ message: `Exceeded maximum tool callback rounds (${this.maxToolRounds})`
140
+ },
141
+ logs: allLogs
142
+ };
143
+ }
144
+ dispose() {
145
+ this.disposed = true;
146
+ return Promise.resolve();
147
+ }
148
+ }
149
+ function createCloudflareIsolateDriver(config) {
150
+ const {
151
+ workerUrl,
152
+ authorization,
153
+ timeout: defaultTimeout = 3e4,
154
+ maxToolRounds = 10
155
+ } = config;
156
+ return {
157
+ createContext(isolateConfig) {
158
+ const timeout = isolateConfig.timeout ?? defaultTimeout;
159
+ return Promise.resolve(
160
+ new CloudflareIsolateContext(
161
+ workerUrl,
162
+ isolateConfig.bindings,
163
+ timeout,
164
+ maxToolRounds,
165
+ authorization
166
+ )
167
+ );
168
+ }
169
+ };
170
+ }
171
+ export {
172
+ createCloudflareIsolateDriver
173
+ };
174
+ //# sourceMappingURL=isolate-driver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isolate-driver.js","sources":["../../src/isolate-driver.ts"],"sourcesContent":["import type {\n ExecutionResult,\n IsolateConfig,\n IsolateContext,\n IsolateDriver,\n ToolBinding,\n} from '@tanstack/ai-code-mode'\nimport type {\n ExecuteRequest,\n ExecuteResponse,\n ToolResultPayload,\n ToolSchema,\n} from './types'\n\n/**\n * Configuration for the Cloudflare Workers isolate driver\n */\nexport interface CloudflareIsolateDriverConfig {\n /**\n * URL of the deployed Cloudflare Worker\n * For local development, use: http://localhost:8787\n */\n workerUrl: string\n\n /**\n * Optional authorization header value\n * Useful for protecting your Worker endpoint\n */\n authorization?: string\n\n /**\n * Default execution timeout in ms (default: 30000)\n */\n timeout?: number\n\n /**\n * Maximum number of tool callback rounds (default: 10)\n * Prevents infinite loops\n */\n maxToolRounds?: number\n}\n\n/**\n * Convert tool bindings to schemas for the Worker\n */\nfunction bindingsToSchemas(\n bindings: Record<string, ToolBinding>,\n): Array<ToolSchema> {\n return Object.entries(bindings).map(([name, binding]) => ({\n name,\n description: binding.description,\n inputSchema: binding.inputSchema,\n }))\n}\n\n/**\n * Normalize errors from various sources\n */\nfunction normalizeError(error: unknown): { name: string; message: string } {\n if (error instanceof Error) {\n return { name: error.name, message: error.message }\n }\n if (typeof error === 'object' && error !== null) {\n const e = error as Record<string, unknown>\n return {\n name: String(e.name || 'Error'),\n message: String(e.message || JSON.stringify(error)),\n }\n }\n return { name: 'Error', message: String(error) }\n}\n\n/**\n * IsolateContext implementation using Cloudflare Workers\n */\nclass CloudflareIsolateContext implements IsolateContext {\n private workerUrl: string\n private authorization?: string\n private timeout: number\n private maxToolRounds: number\n private bindings: Record<string, ToolBinding>\n private disposed = false\n\n constructor(\n workerUrl: string,\n bindings: Record<string, ToolBinding>,\n timeout: number,\n maxToolRounds: number,\n authorization?: string,\n ) {\n this.workerUrl = workerUrl\n this.bindings = bindings\n this.timeout = timeout\n this.maxToolRounds = maxToolRounds\n this.authorization = authorization\n }\n\n async execute<T = unknown>(code: string): Promise<ExecutionResult<T>> {\n if (this.disposed) {\n return {\n success: false,\n error: {\n name: 'DisposedError',\n message: 'Context has been disposed',\n },\n logs: [],\n }\n }\n\n const tools = bindingsToSchemas(this.bindings)\n let toolResults: Record<string, ToolResultPayload> | undefined\n let allLogs: Array<string> = []\n let rounds = 0\n\n // Request/response loop for tool callbacks\n while (rounds < this.maxToolRounds) {\n rounds++\n\n const request: ExecuteRequest = {\n code,\n tools,\n toolResults,\n timeout: this.timeout,\n }\n\n try {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n }\n\n if (this.authorization) {\n headers['Authorization'] = this.authorization\n }\n\n const response = await fetch(this.workerUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify(request),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n return {\n success: false,\n error: {\n name: 'WorkerError',\n message: `Worker returned ${response.status}: ${errorText}`,\n },\n logs: allLogs,\n }\n }\n\n const result: ExecuteResponse = await response.json()\n\n if (result.status === 'error') {\n return {\n success: false,\n error: result.error,\n logs: allLogs,\n }\n }\n\n if (result.status === 'done') {\n allLogs = [...allLogs, ...result.logs]\n return {\n success: result.success,\n value: result.value as T,\n error: result.error,\n logs: allLogs,\n }\n }\n\n // status === 'need_tools'\n // Collect logs from this round\n allLogs = [...allLogs, ...result.logs]\n\n // Execute tool calls locally\n toolResults = {}\n\n for (const toolCall of result.toolCalls) {\n const binding = this.bindings[toolCall.name] as\n | ToolBinding\n | undefined\n\n if (!binding) {\n toolResults[toolCall.id] = {\n success: false,\n error: `Unknown tool: ${toolCall.name}`,\n }\n continue\n }\n\n try {\n const toolResult = await binding.execute(toolCall.args)\n toolResults[toolCall.id] = {\n success: true,\n value: toolResult,\n }\n } catch (toolError) {\n const err = normalizeError(toolError)\n toolResults[toolCall.id] = {\n success: false,\n error: err.message,\n }\n }\n }\n\n // Continue loop to send results back to Worker\n } catch (fetchError) {\n const err = normalizeError(fetchError)\n return {\n success: false,\n error: {\n name: 'NetworkError',\n message: `Failed to communicate with Worker: ${err.message}`,\n },\n logs: allLogs,\n }\n }\n }\n\n // Max rounds exceeded\n return {\n success: false,\n error: {\n name: 'MaxRoundsExceeded',\n message: `Exceeded maximum tool callback rounds (${this.maxToolRounds})`,\n },\n logs: allLogs,\n }\n }\n\n dispose(): Promise<void> {\n this.disposed = true\n return Promise.resolve()\n }\n}\n\n/**\n * Create a Cloudflare Workers isolate driver\n *\n * This driver executes code on Cloudflare's global edge network,\n * providing true distributed execution capabilities.\n *\n * Tool calls are handled via a request/response loop:\n * 1. Code is sent to the Worker\n * 2. Worker executes until it needs a tool\n * 3. Tool call is returned to the driver\n * 4. Driver executes the tool locally\n * 5. Result is sent back to the Worker\n * 6. Worker continues execution\n *\n * @example\n * ```typescript\n * import { createCloudflareIsolateDriver } from '@tanstack/ai-isolate-cloudflare'\n *\n * // For local development with wrangler\n * const driver = createCloudflareIsolateDriver({\n * workerUrl: 'http://localhost:8787',\n * })\n *\n * // For production\n * const driver = createCloudflareIsolateDriver({\n * workerUrl: 'https://code-mode-worker.your-account.workers.dev',\n * authorization: 'Bearer your-secret-token',\n * })\n *\n * const context = await driver.createContext({\n * bindings: {\n * readFile: {\n * name: 'readFile',\n * description: 'Read a file',\n * inputSchema: { type: 'object', properties: { path: { type: 'string' } } },\n * execute: async ({ path }) => fs.readFile(path, 'utf-8'),\n * },\n * },\n * })\n *\n * const result = await context.execute(`\n * const content = await readFile({ path: './data.json' })\n * return JSON.parse(content)\n * `)\n * ```\n */\nexport function createCloudflareIsolateDriver(\n config: CloudflareIsolateDriverConfig,\n): IsolateDriver {\n const {\n workerUrl,\n authorization,\n timeout: defaultTimeout = 30000,\n maxToolRounds = 10,\n } = config\n\n return {\n createContext(isolateConfig: IsolateConfig): Promise<IsolateContext> {\n const timeout = isolateConfig.timeout ?? defaultTimeout\n\n return Promise.resolve(\n new CloudflareIsolateContext(\n workerUrl,\n isolateConfig.bindings,\n timeout,\n maxToolRounds,\n authorization,\n ),\n )\n },\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Shared types between the Cloudflare Worker and the driver
3
+ */
4
+ /**
5
+ * Tool schema passed to the worker
6
+ */
7
+ export interface ToolSchema {
8
+ name: string;
9
+ description: string;
10
+ inputSchema: Record<string, unknown>;
11
+ }
12
+ /**
13
+ * Request to execute code in the worker
14
+ */
15
+ export interface ExecuteRequest {
16
+ /** The code to execute */
17
+ code: string;
18
+ /** Tool schemas available for the code to call */
19
+ tools: Array<ToolSchema>;
20
+ /** Results from previous tool calls (for continuation) */
21
+ toolResults?: Record<string, ToolResultPayload>;
22
+ /** Execution timeout in ms */
23
+ timeout?: number;
24
+ }
25
+ /**
26
+ * Tool call requested by the worker
27
+ */
28
+ export interface ToolCallRequest {
29
+ /** Unique ID for this tool call */
30
+ id: string;
31
+ /** Name of the tool to call */
32
+ name: string;
33
+ /** Arguments to pass to the tool */
34
+ args: unknown;
35
+ }
36
+ /**
37
+ * Result of a tool call
38
+ */
39
+ export interface ToolResultPayload {
40
+ /** Whether the tool call succeeded */
41
+ success: boolean;
42
+ /** The result value if successful */
43
+ value?: unknown;
44
+ /** Error message if failed */
45
+ error?: string;
46
+ }
47
+ /**
48
+ * Response from the worker - either done or needs tool calls
49
+ */
50
+ export type ExecuteResponse = {
51
+ status: 'done';
52
+ success: boolean;
53
+ value?: unknown;
54
+ error?: {
55
+ name: string;
56
+ message: string;
57
+ stack?: string;
58
+ };
59
+ logs: Array<string>;
60
+ } | {
61
+ status: 'need_tools';
62
+ toolCalls: Array<ToolCallRequest>;
63
+ logs: Array<string>;
64
+ /** Continuation state to send back with tool results */
65
+ continuationId: string;
66
+ } | {
67
+ status: 'error';
68
+ error: {
69
+ name: string;
70
+ message: string;
71
+ };
72
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Cloudflare Worker for Code Mode execution
3
+ *
4
+ * This Worker executes JavaScript code in a V8 isolate on Cloudflare's edge network.
5
+ * Tool calls are handled via a request/response loop with the driver.
6
+ *
7
+ * Flow:
8
+ * 1. Receive code + tool schemas
9
+ * 2. Execute code, collecting any tool calls
10
+ * 3. If tool calls are needed, return them to the driver
11
+ * 4. Driver executes tools locally, sends results back
12
+ * 5. Re-execute with tool results injected
13
+ * 6. Return final result
14
+ */
15
+ /**
16
+ * UnsafeEval binding type
17
+ * This is only available in local development with wrangler dev
18
+ */
19
+ interface UnsafeEval {
20
+ eval: (code: string) => unknown;
21
+ }
22
+ interface Env {
23
+ /**
24
+ * UnsafeEval binding - provides eval() for local development
25
+ * Configured in wrangler.toml as an unsafe binding
26
+ */
27
+ UNSAFE_EVAL?: UnsafeEval;
28
+ }
29
+ /**
30
+ * Main Worker fetch handler
31
+ */
32
+ declare const _default: {
33
+ fetch(request: Request, env: Env, _ctx: ExecutionContext): Promise<Response>;
34
+ };
35
+ export default _default;
@@ -0,0 +1,132 @@
1
+ import { wrapCode } from "./wrap-code.js";
2
+ async function executeCode(request, env) {
3
+ const { code, tools, toolResults, timeout = 3e4 } = request;
4
+ if (!env.UNSAFE_EVAL) {
5
+ return {
6
+ status: "error",
7
+ error: {
8
+ name: "UnsafeEvalNotAvailable",
9
+ message: "UNSAFE_EVAL binding is not available. This Worker requires the unsafe_eval binding for local development. For production, consider using Workers for Platforms."
10
+ }
11
+ };
12
+ }
13
+ try {
14
+ const wrappedCode = wrapCode(code, tools, toolResults);
15
+ const controller = new AbortController();
16
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
17
+ try {
18
+ const result = await env.UNSAFE_EVAL.eval(wrappedCode);
19
+ clearTimeout(timeoutId);
20
+ if (result.status === "need_tools") {
21
+ return {
22
+ status: "need_tools",
23
+ toolCalls: result.toolCalls || [],
24
+ logs: result.logs,
25
+ continuationId: crypto.randomUUID()
26
+ };
27
+ }
28
+ return {
29
+ status: "done",
30
+ success: result.success ?? false,
31
+ value: result.value,
32
+ error: result.error,
33
+ logs: result.logs
34
+ };
35
+ } catch (evalError) {
36
+ clearTimeout(timeoutId);
37
+ if (controller.signal.aborted) {
38
+ return {
39
+ status: "error",
40
+ error: {
41
+ name: "TimeoutError",
42
+ message: `Execution timed out after ${timeout}ms`
43
+ }
44
+ };
45
+ }
46
+ const error = evalError;
47
+ return {
48
+ status: "done",
49
+ success: false,
50
+ error: {
51
+ name: error.name || "EvalError",
52
+ message: error.message || String(error),
53
+ stack: error.stack
54
+ },
55
+ logs: []
56
+ };
57
+ }
58
+ } catch (error) {
59
+ const err = error;
60
+ return {
61
+ status: "error",
62
+ error: {
63
+ name: err.name || "Error",
64
+ message: err.message || String(err)
65
+ }
66
+ };
67
+ }
68
+ }
69
+ const index = {
70
+ async fetch(request, env, _ctx) {
71
+ if (request.method === "OPTIONS") {
72
+ return new Response(null, {
73
+ headers: {
74
+ "Access-Control-Allow-Origin": "*",
75
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
76
+ "Access-Control-Allow-Headers": "Content-Type, Authorization"
77
+ }
78
+ });
79
+ }
80
+ if (request.method !== "POST") {
81
+ return new Response(JSON.stringify({ error: "Method not allowed" }), {
82
+ status: 405,
83
+ headers: {
84
+ "Content-Type": "application/json",
85
+ "Access-Control-Allow-Origin": "*"
86
+ }
87
+ });
88
+ }
89
+ try {
90
+ const body = await request.json();
91
+ if (!body.code || typeof body.code !== "string") {
92
+ return new Response(JSON.stringify({ error: "Code is required" }), {
93
+ status: 400,
94
+ headers: {
95
+ "Content-Type": "application/json",
96
+ "Access-Control-Allow-Origin": "*"
97
+ }
98
+ });
99
+ }
100
+ const result = await executeCode(body, env);
101
+ return new Response(JSON.stringify(result), {
102
+ status: 200,
103
+ headers: {
104
+ "Content-Type": "application/json",
105
+ "Access-Control-Allow-Origin": "*"
106
+ }
107
+ });
108
+ } catch (error) {
109
+ const err = error;
110
+ return new Response(
111
+ JSON.stringify({
112
+ status: "error",
113
+ error: {
114
+ name: "RequestError",
115
+ message: err.message || "Failed to process request"
116
+ }
117
+ }),
118
+ {
119
+ status: 500,
120
+ headers: {
121
+ "Content-Type": "application/json",
122
+ "Access-Control-Allow-Origin": "*"
123
+ }
124
+ }
125
+ );
126
+ }
127
+ }
128
+ };
129
+ export {
130
+ index as default
131
+ };
132
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../../src/worker/index.ts"],"sourcesContent":["/**\n * Cloudflare Worker for Code Mode execution\n *\n * This Worker executes JavaScript code in a V8 isolate on Cloudflare's edge network.\n * Tool calls are handled via a request/response loop with the driver.\n *\n * Flow:\n * 1. Receive code + tool schemas\n * 2. Execute code, collecting any tool calls\n * 3. If tool calls are needed, return them to the driver\n * 4. Driver executes tools locally, sends results back\n * 5. Re-execute with tool results injected\n * 6. Return final result\n */\n\nimport { wrapCode } from './wrap-code'\nimport type { ExecuteRequest, ExecuteResponse, ToolCallRequest } from '../types'\n\n/**\n * UnsafeEval binding type\n * This is only available in local development with wrangler dev\n */\ninterface UnsafeEval {\n eval: (code: string) => unknown\n}\n\ninterface Env {\n /**\n * UnsafeEval binding - provides eval() for local development\n * Configured in wrangler.toml as an unsafe binding\n */\n UNSAFE_EVAL?: UnsafeEval\n}\n\n/**\n * Execute code in the Worker's V8 isolate\n */\nasync function executeCode(\n request: ExecuteRequest,\n env: Env,\n): Promise<ExecuteResponse> {\n const { code, tools, toolResults, timeout = 30000 } = request\n\n // Check if UNSAFE_EVAL binding is available\n if (!env.UNSAFE_EVAL) {\n return {\n status: 'error',\n error: {\n name: 'UnsafeEvalNotAvailable',\n message:\n 'UNSAFE_EVAL binding is not available. ' +\n 'This Worker requires the unsafe_eval binding for local development. ' +\n 'For production, consider using Workers for Platforms.',\n },\n }\n }\n\n try {\n const wrappedCode = wrapCode(code, tools, toolResults)\n\n // Execute with timeout\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n try {\n // Use UNSAFE_EVAL binding to execute the code\n // This is only available in local development with wrangler dev\n const result = (await env.UNSAFE_EVAL.eval(wrappedCode)) as {\n status: string\n success?: boolean\n value?: unknown\n error?: { name: string; message: string; stack?: string }\n logs: Array<string>\n toolCalls?: Array<ToolCallRequest>\n }\n\n clearTimeout(timeoutId)\n\n if (result.status === 'need_tools') {\n return {\n status: 'need_tools',\n toolCalls: result.toolCalls || [],\n logs: result.logs,\n continuationId: crypto.randomUUID(),\n }\n }\n\n return {\n status: 'done',\n success: result.success ?? false,\n value: result.value,\n error: result.error,\n logs: result.logs,\n }\n } catch (evalError: unknown) {\n clearTimeout(timeoutId)\n\n if (controller.signal.aborted) {\n return {\n status: 'error',\n error: {\n name: 'TimeoutError',\n message: `Execution timed out after ${timeout}ms`,\n },\n }\n }\n\n const error = evalError as Error\n return {\n status: 'done',\n success: false,\n error: {\n name: error.name || 'EvalError',\n message: error.message || String(error),\n stack: error.stack,\n },\n logs: [],\n }\n }\n } catch (error: unknown) {\n const err = error as Error\n return {\n status: 'error',\n error: {\n name: err.name || 'Error',\n message: err.message || String(err),\n },\n }\n }\n}\n\n/**\n * Main Worker fetch handler\n */\nexport default {\n async fetch(\n request: Request,\n env: Env,\n _ctx: ExecutionContext,\n ): Promise<Response> {\n // Handle CORS preflight\n if (request.method === 'OPTIONS') {\n return new Response(null, {\n headers: {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'POST, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n },\n })\n }\n\n // Only accept POST requests\n if (request.method !== 'POST') {\n return new Response(JSON.stringify({ error: 'Method not allowed' }), {\n status: 405,\n headers: {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n },\n })\n }\n\n try {\n const body: ExecuteRequest = await request.json()\n\n // Validate request\n if (!body.code || typeof body.code !== 'string') {\n return new Response(JSON.stringify({ error: 'Code is required' }), {\n status: 400,\n headers: {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n },\n })\n }\n\n // Execute the code\n const result = await executeCode(body, env)\n\n return new Response(JSON.stringify(result), {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n },\n })\n } catch (error: unknown) {\n const err = error as Error\n return new Response(\n JSON.stringify({\n status: 'error',\n error: {\n name: 'RequestError',\n message: err.message || 'Failed to process request',\n },\n }),\n {\n status: 500,\n headers: {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n },\n },\n )\n }\n },\n}\n"],"names":[],"mappings":";AAqCA,eAAe,YACb,SACA,KAC0B;AAC1B,QAAM,EAAE,MAAM,OAAO,aAAa,UAAU,QAAU;AAGtD,MAAI,CAAC,IAAI,aAAa;AACpB,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,OAAO;AAAA,QACL,MAAM;AAAA,QACN,SACE;AAAA,MAAA;AAAA,IAGJ;AAAA,EAEJ;AAEA,MAAI;AACF,UAAM,cAAc,SAAS,MAAM,OAAO,WAAW;AAGrD,UAAM,aAAa,IAAI,gBAAA;AACvB,UAAM,YAAY,WAAW,MAAM,WAAW,MAAA,GAAS,OAAO;AAE9D,QAAI;AAGF,YAAM,SAAU,MAAM,IAAI,YAAY,KAAK,WAAW;AAStD,mBAAa,SAAS;AAEtB,UAAI,OAAO,WAAW,cAAc;AAClC,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,WAAW,OAAO,aAAa,CAAA;AAAA,UAC/B,MAAM,OAAO;AAAA,UACb,gBAAgB,OAAO,WAAA;AAAA,QAAW;AAAA,MAEtC;AAEA,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SAAS,OAAO,WAAW;AAAA,QAC3B,OAAO,OAAO;AAAA,QACd,OAAO,OAAO;AAAA,QACd,MAAM,OAAO;AAAA,MAAA;AAAA,IAEjB,SAAS,WAAoB;AAC3B,mBAAa,SAAS;AAEtB,UAAI,WAAW,OAAO,SAAS;AAC7B,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,OAAO;AAAA,YACL,MAAM;AAAA,YACN,SAAS,6BAA6B,OAAO;AAAA,UAAA;AAAA,QAC/C;AAAA,MAEJ;AAEA,YAAM,QAAQ;AACd,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,OAAO;AAAA,UACL,MAAM,MAAM,QAAQ;AAAA,UACpB,SAAS,MAAM,WAAW,OAAO,KAAK;AAAA,UACtC,OAAO,MAAM;AAAA,QAAA;AAAA,QAEf,MAAM,CAAA;AAAA,MAAC;AAAA,IAEX;AAAA,EACF,SAAS,OAAgB;AACvB,UAAM,MAAM;AACZ,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,OAAO;AAAA,QACL,MAAM,IAAI,QAAQ;AAAA,QAClB,SAAS,IAAI,WAAW,OAAO,GAAG;AAAA,MAAA;AAAA,IACpC;AAAA,EAEJ;AACF;AAKA,MAAA,QAAe;AAAA,EACb,MAAM,MACJ,SACA,KACA,MACmB;AAEnB,QAAI,QAAQ,WAAW,WAAW;AAChC,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,SAAS;AAAA,UACP,+BAA+B;AAAA,UAC/B,gCAAgC;AAAA,UAChC,gCAAgC;AAAA,QAAA;AAAA,MAClC,CACD;AAAA,IACH;AAGA,QAAI,QAAQ,WAAW,QAAQ;AAC7B,aAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,qBAAA,CAAsB,GAAG;AAAA,QACnE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,+BAA+B;AAAA,QAAA;AAAA,MACjC,CACD;AAAA,IACH;AAEA,QAAI;AACF,YAAM,OAAuB,MAAM,QAAQ,KAAA;AAG3C,UAAI,CAAC,KAAK,QAAQ,OAAO,KAAK,SAAS,UAAU;AAC/C,eAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,mBAAA,CAAoB,GAAG;AAAA,UACjE,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,+BAA+B;AAAA,UAAA;AAAA,QACjC,CACD;AAAA,MACH;AAGA,YAAM,SAAS,MAAM,YAAY,MAAM,GAAG;AAE1C,aAAO,IAAI,SAAS,KAAK,UAAU,MAAM,GAAG;AAAA,QAC1C,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,+BAA+B;AAAA,QAAA;AAAA,MACjC,CACD;AAAA,IACH,SAAS,OAAgB;AACvB,YAAM,MAAM;AACZ,aAAO,IAAI;AAAA,QACT,KAAK,UAAU;AAAA,UACb,QAAQ;AAAA,UACR,OAAO;AAAA,YACL,MAAM;AAAA,YACN,SAAS,IAAI,WAAW;AAAA,UAAA;AAAA,QAC1B,CACD;AAAA,QACD;AAAA,UACE,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,+BAA+B;AAAA,UAAA;AAAA,QACjC;AAAA,MACF;AAAA,IAEJ;AAAA,EACF;AACF;"}