@tanstack/ai-isolate-cloudflare 0.1.0 → 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tanner Linsley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -15,5 +15,6 @@
15
15
  *
16
16
  * @packageDocumentation
17
17
  */
18
- export { createCloudflareIsolateDriver, type CloudflareIsolateDriverConfig, } from './isolate-driver.js';
19
- export type { ExecuteRequest, ExecuteResponse, ToolSchema, ToolCallRequest, ToolResultPayload, } from './types.js';
18
+ export { createCloudflareIsolateDriver, type CloudflareIsolateDriverConfig, } from './isolate-driver';
19
+ export type { ExecuteRequest, ExecuteResponse, ToolSchema, ToolCallRequest, ToolResultPayload, } from './types';
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACL,6BAA6B,EAC7B,KAAK,6BAA6B,GACnC,MAAM,kBAAkB,CAAA;AAEzB,YAAY,EACV,cAAc,EACd,eAAe,EACf,UAAU,EACV,eAAe,EACf,iBAAiB,GAClB,MAAM,SAAS,CAAA"}
package/dist/esm/index.js CHANGED
@@ -1,5 +1,18 @@
1
- import { createCloudflareIsolateDriver } from "./isolate-driver.js";
2
- export {
3
- createCloudflareIsolateDriver
4
- };
5
- //# sourceMappingURL=index.js.map
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, } from './isolate-driver';
@@ -1,4 +1,4 @@
1
- import { IsolateDriver } from '@tanstack/ai-code-mode';
1
+ import type { IsolateDriver } from '@tanstack/ai-code-mode';
2
2
  /**
3
3
  * Configuration for the Cloudflare Workers isolate driver
4
4
  */
@@ -70,3 +70,4 @@ export interface CloudflareIsolateDriverConfig {
70
70
  * ```
71
71
  */
72
72
  export declare function createCloudflareIsolateDriver(config: CloudflareIsolateDriverConfig): IsolateDriver;
73
+ //# sourceMappingURL=isolate-driver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isolate-driver.d.ts","sourceRoot":"","sources":["../../src/isolate-driver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,aAAa,EAEd,MAAM,wBAAwB,CAAA;AAQ/B;;GAEG;AACH,MAAM,WAAW,6BAA6B;IAC5C;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAA;IAEjB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAsMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,6BAA6B,GACpC,aAAa,CAuBf"}
@@ -1,174 +1,220 @@
1
+ /**
2
+ * Convert tool bindings to schemas for the Worker
3
+ */
1
4
  function bindingsToSchemas(bindings) {
2
- return Object.entries(bindings).map(([name, binding]) => ({
3
- name,
4
- description: binding.description,
5
- inputSchema: binding.inputSchema
6
- }));
5
+ return Object.entries(bindings).map(([name, binding]) => ({
6
+ name,
7
+ description: binding.description,
8
+ inputSchema: binding.inputSchema,
9
+ }));
7
10
  }
11
+ /**
12
+ * Normalize errors from various sources
13
+ */
8
14
  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) };
15
+ if (error instanceof Error) {
16
+ return { name: error.name, message: error.message };
17
+ }
18
+ if (typeof error === 'object' && error !== null) {
19
+ const e = error;
20
+ return {
21
+ name: String(e.name || 'Error'),
22
+ message: String(e.message || JSON.stringify(error)),
23
+ };
24
+ }
25
+ return { name: 'Error', message: String(error) };
20
26
  }
27
+ /**
28
+ * IsolateContext implementation using Cloudflare Workers
29
+ */
21
30
  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
- };
31
+ workerUrl;
32
+ authorization;
33
+ timeout;
34
+ maxToolRounds;
35
+ bindings;
36
+ disposed = false;
37
+ constructor(workerUrl, bindings, timeout, maxToolRounds, authorization) {
38
+ this.workerUrl = workerUrl;
39
+ this.bindings = bindings;
40
+ this.timeout = timeout;
41
+ this.maxToolRounds = maxToolRounds;
42
+ this.authorization = authorization;
45
43
  }
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
44
+ async execute(code) {
45
+ if (this.disposed) {
46
+ return {
47
+ success: false,
48
+ error: {
49
+ name: 'DisposedError',
50
+ message: 'Context has been disposed',
51
+ },
52
+ logs: [],
114
53
  };
115
- } catch (toolError) {
116
- const err = normalizeError(toolError);
117
- toolResults[toolCall.id] = {
118
- success: false,
119
- error: err.message
54
+ }
55
+ const tools = bindingsToSchemas(this.bindings);
56
+ let toolResults;
57
+ let allLogs = [];
58
+ let rounds = 0;
59
+ // Request/response loop for tool callbacks
60
+ while (rounds < this.maxToolRounds) {
61
+ rounds++;
62
+ const request = {
63
+ code,
64
+ tools,
65
+ toolResults,
66
+ timeout: this.timeout,
120
67
  };
121
- }
68
+ try {
69
+ const headers = {
70
+ 'Content-Type': 'application/json',
71
+ };
72
+ if (this.authorization) {
73
+ headers['Authorization'] = this.authorization;
74
+ }
75
+ const response = await fetch(this.workerUrl, {
76
+ method: 'POST',
77
+ headers,
78
+ body: JSON.stringify(request),
79
+ });
80
+ if (!response.ok) {
81
+ const errorText = await response.text();
82
+ return {
83
+ success: false,
84
+ error: {
85
+ name: 'WorkerError',
86
+ message: `Worker returned ${response.status}: ${errorText}`,
87
+ },
88
+ logs: allLogs,
89
+ };
90
+ }
91
+ const result = await response.json();
92
+ if (result.status === 'error') {
93
+ return {
94
+ success: false,
95
+ error: result.error,
96
+ logs: allLogs,
97
+ };
98
+ }
99
+ if (result.status === 'done') {
100
+ allLogs = [...allLogs, ...result.logs];
101
+ return {
102
+ success: result.success,
103
+ value: result.value,
104
+ error: result.error,
105
+ logs: allLogs,
106
+ };
107
+ }
108
+ // status === 'need_tools'
109
+ // Collect logs from this round
110
+ allLogs = [...allLogs, ...result.logs];
111
+ // Execute tool calls locally
112
+ toolResults = {};
113
+ for (const toolCall of result.toolCalls) {
114
+ const binding = this.bindings[toolCall.name];
115
+ if (!binding) {
116
+ toolResults[toolCall.id] = {
117
+ success: false,
118
+ error: `Unknown tool: ${toolCall.name}`,
119
+ };
120
+ continue;
121
+ }
122
+ try {
123
+ const toolResult = await binding.execute(toolCall.args);
124
+ toolResults[toolCall.id] = {
125
+ success: true,
126
+ value: toolResult,
127
+ };
128
+ }
129
+ catch (toolError) {
130
+ const err = normalizeError(toolError);
131
+ toolResults[toolCall.id] = {
132
+ success: false,
133
+ error: err.message,
134
+ };
135
+ }
136
+ }
137
+ // Continue loop to send results back to Worker
138
+ }
139
+ catch (fetchError) {
140
+ const err = normalizeError(fetchError);
141
+ return {
142
+ success: false,
143
+ error: {
144
+ name: 'NetworkError',
145
+ message: `Failed to communicate with Worker: ${err.message}`,
146
+ },
147
+ logs: allLogs,
148
+ };
149
+ }
122
150
  }
123
- } catch (fetchError) {
124
- const err = normalizeError(fetchError);
151
+ // Max rounds exceeded
125
152
  return {
126
- success: false,
127
- error: {
128
- name: "NetworkError",
129
- message: `Failed to communicate with Worker: ${err.message}`
130
- },
131
- logs: allLogs
153
+ success: false,
154
+ error: {
155
+ name: 'MaxRoundsExceeded',
156
+ message: `Exceeded maximum tool callback rounds (${this.maxToolRounds})`,
157
+ },
158
+ logs: allLogs,
132
159
  };
133
- }
134
160
  }
161
+ dispose() {
162
+ this.disposed = true;
163
+ return Promise.resolve();
164
+ }
165
+ }
166
+ /**
167
+ * Create a Cloudflare Workers isolate driver
168
+ *
169
+ * This driver executes code on Cloudflare's global edge network,
170
+ * providing true distributed execution capabilities.
171
+ *
172
+ * Tool calls are handled via a request/response loop:
173
+ * 1. Code is sent to the Worker
174
+ * 2. Worker executes until it needs a tool
175
+ * 3. Tool call is returned to the driver
176
+ * 4. Driver executes the tool locally
177
+ * 5. Result is sent back to the Worker
178
+ * 6. Worker continues execution
179
+ *
180
+ * @example
181
+ * ```typescript
182
+ * import { createCloudflareIsolateDriver } from '@tanstack/ai-isolate-cloudflare'
183
+ *
184
+ * // For local development with wrangler
185
+ * const driver = createCloudflareIsolateDriver({
186
+ * workerUrl: 'http://localhost:8787',
187
+ * })
188
+ *
189
+ * // For production
190
+ * const driver = createCloudflareIsolateDriver({
191
+ * workerUrl: 'https://code-mode-worker.your-account.workers.dev',
192
+ * authorization: 'Bearer your-secret-token',
193
+ * })
194
+ *
195
+ * const context = await driver.createContext({
196
+ * bindings: {
197
+ * readFile: {
198
+ * name: 'readFile',
199
+ * description: 'Read a file',
200
+ * inputSchema: { type: 'object', properties: { path: { type: 'string' } } },
201
+ * execute: async ({ path }) => fs.readFile(path, 'utf-8'),
202
+ * },
203
+ * },
204
+ * })
205
+ *
206
+ * const result = await context.execute(`
207
+ * const content = await readFile({ path: './data.json' })
208
+ * return JSON.parse(content)
209
+ * `)
210
+ * ```
211
+ */
212
+ export function createCloudflareIsolateDriver(config) {
213
+ const { workerUrl, authorization, timeout: defaultTimeout = 30000, maxToolRounds = 10, } = config;
135
214
  return {
136
- success: false,
137
- error: {
138
- name: "MaxRoundsExceeded",
139
- message: `Exceeded maximum tool callback rounds (${this.maxToolRounds})`
140
- },
141
- logs: allLogs
215
+ createContext(isolateConfig) {
216
+ const timeout = isolateConfig.timeout ?? defaultTimeout;
217
+ return Promise.resolve(new CloudflareIsolateContext(workerUrl, isolateConfig.bindings, timeout, maxToolRounds, authorization));
218
+ },
142
219
  };
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
220
  }
171
- export {
172
- createCloudflareIsolateDriver
173
- };
174
- //# sourceMappingURL=isolate-driver.js.map
@@ -70,3 +70,4 @@ export type ExecuteResponse = {
70
70
  message: string;
71
71
  };
72
72
  };
73
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,kDAAkD;IAClD,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;IACxB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;IAC/C,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,mCAAmC;IACnC,EAAE,EAAE,MAAM,CAAA;IACV,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,oCAAoC;IACpC,IAAI,EAAE,OAAO,CAAA;CACd;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAA;IAChB,qCAAqC;IACrC,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GACvB;IACE,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAA;QACZ,OAAO,EAAE,MAAM,CAAA;QACf,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;IACD,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;CACpB,GACD;IACE,MAAM,EAAE,YAAY,CAAA;IACpB,SAAS,EAAE,KAAK,CAAC,eAAe,CAAC,CAAA;IACjC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IACnB,wDAAwD;IACxD,cAAc,EAAE,MAAM,CAAA;CACvB,GACD;IACE,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAA;QACZ,OAAO,EAAE,MAAM,CAAA;KAChB,CAAA;CACF,CAAA"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Shared types between the Cloudflare Worker and the driver
3
+ */
4
+ export {};
@@ -33,3 +33,4 @@ declare const _default: {
33
33
  fetch(request: Request, env: Env, _ctx: ExecutionContext): Promise<Response>;
34
34
  };
35
35
  export default _default;
36
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/worker/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH;;;GAGG;AACH,UAAU,UAAU;IAClB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;CAChC;AAED,UAAU,GAAG;IACX;;;OAGG;IACH,WAAW,CAAC,EAAE,UAAU,CAAA;CACzB;AAmGD;;GAEG;;mBAGU,OAAO,OACX,GAAG,QACF,gBAAgB,GACrB,OAAO,CAAC,QAAQ,CAAC;;AALtB,wBAwEC"}
@@ -1,132 +1,158 @@
1
- import { wrapCode } from "./wrap-code.js";
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
+ import { wrapCode } from './wrap-code';
16
+ /**
17
+ * Execute code in the Worker's V8 isolate
18
+ */
2
19
  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") {
20
+ const { code, tools, toolResults, timeout = 30000 } = request;
21
+ // Check if UNSAFE_EVAL binding is available
22
+ if (!env.UNSAFE_EVAL) {
21
23
  return {
22
- status: "need_tools",
23
- toolCalls: result.toolCalls || [],
24
- logs: result.logs,
25
- continuationId: crypto.randomUUID()
24
+ status: 'error',
25
+ error: {
26
+ name: 'UnsafeEvalNotAvailable',
27
+ message: 'UNSAFE_EVAL binding is not available. ' +
28
+ 'This Worker requires the unsafe_eval binding for local development. ' +
29
+ 'For production, consider using Workers for Platforms.',
30
+ },
26
31
  };
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) {
32
+ }
33
+ try {
34
+ const wrappedCode = wrapCode(code, tools, toolResults);
35
+ // Execute with timeout
36
+ const controller = new AbortController();
37
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
38
+ try {
39
+ // Use UNSAFE_EVAL binding to execute the code
40
+ // This is only available in local development with wrangler dev
41
+ const result = (await env.UNSAFE_EVAL.eval(wrappedCode));
42
+ clearTimeout(timeoutId);
43
+ if (result.status === 'need_tools') {
44
+ return {
45
+ status: 'need_tools',
46
+ toolCalls: result.toolCalls || [],
47
+ logs: result.logs,
48
+ continuationId: crypto.randomUUID(),
49
+ };
50
+ }
51
+ return {
52
+ status: 'done',
53
+ success: result.success ?? false,
54
+ value: result.value,
55
+ error: result.error,
56
+ logs: result.logs,
57
+ };
58
+ }
59
+ catch (evalError) {
60
+ clearTimeout(timeoutId);
61
+ if (controller.signal.aborted) {
62
+ return {
63
+ status: 'error',
64
+ error: {
65
+ name: 'TimeoutError',
66
+ message: `Execution timed out after ${timeout}ms`,
67
+ },
68
+ };
69
+ }
70
+ const error = evalError;
71
+ return {
72
+ status: 'done',
73
+ success: false,
74
+ error: {
75
+ name: error.name || 'EvalError',
76
+ message: error.message || String(error),
77
+ stack: error.stack,
78
+ },
79
+ logs: [],
80
+ };
81
+ }
82
+ }
83
+ catch (error) {
84
+ const err = error;
38
85
  return {
39
- status: "error",
40
- error: {
41
- name: "TimeoutError",
42
- message: `Execution timed out after ${timeout}ms`
43
- }
86
+ status: 'error',
87
+ error: {
88
+ name: err.name || 'Error',
89
+ message: err.message || String(err),
90
+ },
44
91
  };
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
92
  }
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
93
  }
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"
94
+ /**
95
+ * Main Worker fetch handler
96
+ */
97
+ export default {
98
+ async fetch(request, env, _ctx) {
99
+ // Handle CORS preflight
100
+ if (request.method === 'OPTIONS') {
101
+ return new Response(null, {
102
+ headers: {
103
+ 'Access-Control-Allow-Origin': '*',
104
+ 'Access-Control-Allow-Methods': 'POST, OPTIONS',
105
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
106
+ },
107
+ });
77
108
  }
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": "*"
109
+ // Only accept POST requests
110
+ if (request.method !== 'POST') {
111
+ return new Response(JSON.stringify({ error: 'Method not allowed' }), {
112
+ status: 405,
113
+ headers: {
114
+ 'Content-Type': 'application/json',
115
+ 'Access-Control-Allow-Origin': '*',
116
+ },
117
+ });
86
118
  }
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": "*"
119
+ try {
120
+ const body = await request.json();
121
+ // Validate request
122
+ if (!body.code || typeof body.code !== 'string') {
123
+ return new Response(JSON.stringify({ error: 'Code is required' }), {
124
+ status: 400,
125
+ headers: {
126
+ 'Content-Type': 'application/json',
127
+ 'Access-Control-Allow-Origin': '*',
128
+ },
129
+ });
130
+ }
131
+ // Execute the code
132
+ const result = await executeCode(body, env);
133
+ return new Response(JSON.stringify(result), {
134
+ status: 200,
135
+ headers: {
136
+ 'Content-Type': 'application/json',
137
+ 'Access-Control-Allow-Origin': '*',
138
+ },
139
+ });
106
140
  }
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
- }
141
+ catch (error) {
142
+ const err = error;
143
+ return new Response(JSON.stringify({
144
+ status: 'error',
145
+ error: {
146
+ name: 'RequestError',
147
+ message: err.message || 'Failed to process request',
148
+ },
149
+ }), {
150
+ status: 500,
151
+ headers: {
152
+ 'Content-Type': 'application/json',
153
+ 'Access-Control-Allow-Origin': '*',
154
+ },
155
+ });
124
156
  }
125
- );
126
- }
127
- }
128
- };
129
- export {
130
- index as default
157
+ },
131
158
  };
132
- //# sourceMappingURL=index.js.map
@@ -1,4 +1,8 @@
1
- import { ToolResultPayload, ToolSchema } from '../types.js';
1
+ /**
2
+ * Code wrapping utilities for the Cloudflare Worker.
3
+ * Extracted for testability without UNSAFE_EVAL.
4
+ */
5
+ import type { ToolResultPayload, ToolSchema } from '../types';
2
6
  /**
3
7
  * Generate tool wrapper code that collects calls or returns cached results.
4
8
  *
@@ -11,3 +15,4 @@ export declare function generateToolWrappers(tools: Array<ToolSchema>, toolResul
11
15
  * Wrap user code in an async IIFE with tool wrappers
12
16
  */
13
17
  export declare function wrapCode(code: string, tools: Array<ToolSchema>, toolResults?: Record<string, ToolResultPayload>): string;
18
+ //# sourceMappingURL=wrap-code.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap-code.d.ts","sourceRoot":"","sources":["../../../src/worker/wrap-code.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE7D;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,EACxB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC9C,MAAM,CA+BR;AAED;;GAEG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,EACxB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC9C,MAAM,CAmER"}
@@ -1,8 +1,19 @@
1
- function generateToolWrappers(tools, toolResults) {
2
- const wrappers = [];
3
- for (const tool of tools) {
4
- if (toolResults) {
5
- wrappers.push(`
1
+ /**
2
+ * Code wrapping utilities for the Cloudflare Worker.
3
+ * Extracted for testability without UNSAFE_EVAL.
4
+ */
5
+ /**
6
+ * Generate tool wrapper code that collects calls or returns cached results.
7
+ *
8
+ * Tool calls are identified by a sequential index (__toolCallIdx) rather than
9
+ * by hashing the input. This avoids mismatches when re-executing code whose
10
+ * inputs contain non-deterministic values (e.g. random UUIDs).
11
+ */
12
+ export function generateToolWrappers(tools, toolResults) {
13
+ const wrappers = [];
14
+ for (const tool of tools) {
15
+ if (toolResults) {
16
+ wrappers.push(`
6
17
  async function ${tool.name}(input) {
7
18
  const callId = 'tc_' + (__toolCallIdx++);
8
19
  const result = __toolResults[callId];
@@ -16,22 +27,26 @@ function generateToolWrappers(tools, toolResults) {
16
27
  return result.value;
17
28
  }
18
29
  `);
19
- } else {
20
- wrappers.push(`
30
+ }
31
+ else {
32
+ wrappers.push(`
21
33
  async function ${tool.name}(input) {
22
34
  const callId = 'tc_' + (__toolCallIdx++);
23
35
  __pendingToolCalls.push({ id: callId, name: '${tool.name}', args: input });
24
36
  throw new __ToolCallNeeded(callId);
25
37
  }
26
38
  `);
39
+ }
27
40
  }
28
- }
29
- return wrappers.join("\n");
41
+ return wrappers.join('\n');
30
42
  }
31
- function wrapCode(code, tools, toolResults) {
32
- const toolWrappers = generateToolWrappers(tools, toolResults);
33
- const toolResultsJson = toolResults ? JSON.stringify(toolResults) : "{}";
34
- return `
43
+ /**
44
+ * Wrap user code in an async IIFE with tool wrappers
45
+ */
46
+ export function wrapCode(code, tools, toolResults) {
47
+ const toolWrappers = generateToolWrappers(tools, toolResults);
48
+ const toolResultsJson = toolResults ? JSON.stringify(toolResults) : '{}';
49
+ return `
35
50
  (async function() {
36
51
  // Tool call tracking (sequential index for stable IDs across re-executions)
37
52
  let __toolCallIdx = 0;
@@ -95,8 +110,3 @@ function wrapCode(code, tools, toolResults) {
95
110
  })()
96
111
  `;
97
112
  }
98
- export {
99
- generateToolWrappers,
100
- wrapCode
101
- };
102
- //# sourceMappingURL=wrap-code.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-isolate-cloudflare",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Cloudflare Workers driver for TanStack AI Code Mode - execute code on the edge",
5
5
  "author": "",
6
6
  "license": "MIT",
@@ -9,9 +9,6 @@
9
9
  "url": "git+https://github.com/TanStack/ai.git",
10
10
  "directory": "packages/typescript/ai-isolate-cloudflare"
11
11
  },
12
- "publishConfig": {
13
- "access": "public"
14
- },
15
12
  "type": "module",
16
13
  "module": "./dist/esm/index.js",
17
14
  "types": "./dist/esm/index.d.ts",
@@ -35,18 +32,6 @@
35
32
  "worker",
36
33
  "wrangler.toml"
37
34
  ],
38
- "scripts": {
39
- "build": "vite build",
40
- "clean": "premove ./build ./dist",
41
- "dev:worker": "node dev-server.mjs",
42
- "deploy:worker": "wrangler deploy",
43
- "lint:fix": "eslint ./src --fix",
44
- "test:build": "publint --strict",
45
- "test:eslint": "eslint ./src",
46
- "test:lib": "vitest --passWithNoTests",
47
- "test:lib:dev": "pnpm test:lib --watch",
48
- "test:types": "tsc"
49
- },
50
35
  "keywords": [
51
36
  "ai",
52
37
  "tanstack",
@@ -57,7 +42,7 @@
57
42
  "isolate"
58
43
  ],
59
44
  "dependencies": {
60
- "@tanstack/ai-code-mode": "workspace:*"
45
+ "@tanstack/ai-code-mode": "0.1.1"
61
46
  },
62
47
  "devDependencies": {
63
48
  "@cloudflare/workers-types": "^4.20241230.0",
@@ -65,5 +50,17 @@
65
50
  "esbuild": "^0.25.12",
66
51
  "miniflare": "^4.20260305.0",
67
52
  "wrangler": "^4.19.1"
53
+ },
54
+ "scripts": {
55
+ "build": "vite build",
56
+ "clean": "premove ./build ./dist",
57
+ "dev:worker": "node dev-server.mjs",
58
+ "deploy:worker": "wrangler deploy",
59
+ "lint:fix": "eslint ./src --fix",
60
+ "test:build": "publint --strict",
61
+ "test:eslint": "eslint ./src",
62
+ "test:lib": "vitest --passWithNoTests",
63
+ "test:lib:dev": "pnpm test:lib --watch",
64
+ "test:types": "tsc"
68
65
  }
69
66
  }