@cloudflare/codemode 0.0.1 → 0.0.4

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/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # @cloudflare/codemode
2
+
3
+ ## 0.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#771](https://github.com/cloudflare/agents/pull/771) [`87dc96d`](https://github.com/cloudflare/agents/commit/87dc96d19de1d26dbb2badecbb9955a4eb8e9e2e) Thanks [@threepointone](https://github.com/threepointone)! - update dependencies
8
+
9
+ - Updated dependencies [[`cf8a1e7`](https://github.com/cloudflare/agents/commit/cf8a1e7a24ecaac62c2aefca7b0fd5bf1373e8bd), [`87dc96d`](https://github.com/cloudflare/agents/commit/87dc96d19de1d26dbb2badecbb9955a4eb8e9e2e)]:
10
+ - agents@0.3.4
11
+
12
+ ## 0.0.3
13
+
14
+ ### Patch Changes
15
+
16
+ - [`a5d0137`](https://github.com/cloudflare/agents/commit/a5d01379b9ad2d88bc028c50f1858b4e69f106c5) Thanks [@threepointone](https://github.com/threepointone)! - trigger a new release
17
+
18
+ - Updated dependencies [[`a5d0137`](https://github.com/cloudflare/agents/commit/a5d01379b9ad2d88bc028c50f1858b4e69f106c5)]:
19
+ - agents@0.3.3
20
+
21
+ ## 0.0.2
22
+
23
+ ### Patch Changes
24
+
25
+ - [#756](https://github.com/cloudflare/agents/pull/756) [`0c4275f`](https://github.com/cloudflare/agents/commit/0c4275f8f4b71c264c32c3742d151ef705739c2f) Thanks [@threepointone](https://github.com/threepointone)! - feat: split ai-chat and codemode into separate packages
26
+
27
+ Extract @cloudflare/ai-chat and @cloudflare/codemode into their own packages
28
+ with comprehensive READMEs. Update agents README to remove chat-specific
29
+ content and point to new packages. Fix documentation imports to reflect
30
+ new package structure.
31
+
32
+ Maintains backward compatibility, no breaking changes.
33
+
34
+ - Updated dependencies [[`0c4275f`](https://github.com/cloudflare/agents/commit/0c4275f8f4b71c264c32c3742d151ef705739c2f), [`f12553f`](https://github.com/cloudflare/agents/commit/f12553f2fa65912c68d9a7620b9a11b70b8790a2)]:
35
+ - agents@0.3.2
package/README.md ADDED
@@ -0,0 +1,333 @@
1
+ ### 💻 `@cloudflare/codemode` - Code Mode: The Better Way to Use MCP
2
+
3
+ Instead of asking LLMs to call tools directly, Code Mode lets them write executable code that orchestrates multiple operations. **LLMs are better at writing code than calling tools** - they've seen millions of lines of real-world TypeScript but only contrived tool-calling examples.
4
+
5
+ Code Mode converts your tools (especially MCP servers) into TypeScript APIs, enabling complex workflows, error handling, and multi-step operations that are natural in code but difficult with traditional tool calling.
6
+
7
+ Built on Cloudflare's Worker Loader API, Code Mode executes generated code in secure, isolated sandboxes with millisecond startup times.
8
+
9
+ > **⚠️ Experimental Feature**: Code Mode is currently experimental and may have breaking changes in future releases. Use with caution in production environments.
10
+
11
+ ---
12
+
13
+ ### 🌱 Installation
14
+
15
+ ```sh
16
+ npm install @cloudflare/codemode agents ai
17
+ ```
18
+
19
+ ### 📝 Your First Code Mode Agent
20
+
21
+ Transform your tool-calling agent into a code-generating one:
22
+
23
+ #### Before (Traditional Tool Calling)
24
+
25
+ ```ts
26
+ import { streamText } from "ai";
27
+ import { tool } from "ai";
28
+ import { z } from "zod";
29
+
30
+ const result = streamText({
31
+ model: openai("gpt-4o"),
32
+ messages,
33
+ tools: {
34
+ getWeather: tool({
35
+ description: "Get weather for a location",
36
+ inputSchema: z.object({ location: z.string() }),
37
+ execute: async ({ location }) => {
38
+ return `Weather in ${location}: 72°F, sunny`;
39
+ }
40
+ }),
41
+ sendEmail: tool({
42
+ description: "Send an email",
43
+ inputSchema: z.object({
44
+ to: z.string(),
45
+ subject: z.string(),
46
+ body: z.string()
47
+ }),
48
+ execute: async ({ to, subject, body }) => {
49
+ // Send email logic
50
+ return `Email sent to ${to}`;
51
+ }
52
+ })
53
+ }
54
+ });
55
+ ```
56
+
57
+ #### After (With Code Mode)
58
+
59
+ ```ts
60
+ import { experimental_codemode as codemode } from "@cloudflare/codemode/ai";
61
+ import { streamText } from "ai";
62
+ import { tool } from "ai";
63
+ import { z } from "zod";
64
+
65
+ // Define your tools as usual
66
+ const tools = {
67
+ getWeather: tool({
68
+ description: "Get weather for a location",
69
+ inputSchema: z.object({ location: z.string() }),
70
+ execute: async ({ location }) => {
71
+ return `Weather in ${location}: 72°F, sunny`;
72
+ }
73
+ }),
74
+ sendEmail: tool({
75
+ description: "Send an email",
76
+ inputSchema: z.object({
77
+ to: z.string(),
78
+ subject: z.string(),
79
+ body: z.string()
80
+ }),
81
+ execute: async ({ to, subject, body }) => {
82
+ // Send email logic
83
+ return `Email sent to ${to}`;
84
+ }
85
+ })
86
+ };
87
+
88
+ // Configure Code Mode
89
+ const { prompt, tools: wrappedTools } = await codemode({
90
+ prompt: "You are a helpful assistant...",
91
+ tools,
92
+ globalOutbound: env.globalOutbound,
93
+ loader: env.LOADER,
94
+ proxy: this.ctx.exports.CodeModeProxy({
95
+ props: {
96
+ binding: "MyAgent",
97
+ name: this.name,
98
+ callback: "callTool"
99
+ }
100
+ })
101
+ });
102
+
103
+ // Use the wrapped tools - now the LLM will generate code instead!
104
+ const result = streamText({
105
+ model: openai("gpt-4o"),
106
+ system: prompt,
107
+ messages,
108
+ tools: wrappedTools // Single "codemode" tool that generates code
109
+ });
110
+ ```
111
+
112
+ That's it! Your agent now generates executable code that orchestrates your tools.
113
+
114
+ ### 🏰 Configuration
115
+
116
+ Define the required bindings in your `wrangler.toml`:
117
+
118
+ ```jsonc
119
+ {
120
+ "compatibility_flags": ["experimental", "enable_ctx_exports"],
121
+ "worker_loaders": [
122
+ {
123
+ "binding": "LOADER"
124
+ }
125
+ ],
126
+ "services": [
127
+ {
128
+ "binding": "globalOutbound",
129
+ "service": "your-service",
130
+ "entrypoint": "globalOutbound"
131
+ },
132
+ {
133
+ "binding": "CodeModeProxy",
134
+ "service": "your-service",
135
+ "entrypoint": "CodeModeProxy"
136
+ }
137
+ ]
138
+ }
139
+ ```
140
+
141
+ ### 🎭 Agent Integration
142
+
143
+ #### With MCP Servers
144
+
145
+ ```ts
146
+ import { Agent } from "agents";
147
+ import { experimental_codemode as codemode } from "@cloudflare/codemode/ai";
148
+ import { streamText, convertToModelMessages } from "ai";
149
+ import { openai } from "@ai-sdk/openai";
150
+
151
+ export class CodeModeAgent extends Agent<Env> {
152
+ async onChatMessage() {
153
+ const allTools = {
154
+ ...regularTools,
155
+ ...this.mcp.getAITools() // Include MCP tools
156
+ };
157
+
158
+ const { prompt, tools: wrappedTools } = await codemode({
159
+ prompt: "You are a helpful assistant...",
160
+ tools: allTools,
161
+ globalOutbound: env.globalOutbound,
162
+ loader: env.LOADER,
163
+ proxy: this.ctx.exports.CodeModeProxy({
164
+ props: {
165
+ binding: "CodeModeAgent",
166
+ name: this.name,
167
+ callback: "callTool"
168
+ }
169
+ })
170
+ });
171
+
172
+ const result = streamText({
173
+ model: openai("gpt-4o"),
174
+ system: prompt,
175
+ messages: await convertToModelMessages(this.messages),
176
+ tools: wrappedTools
177
+ });
178
+
179
+ return result.toUIMessageStreamResponse();
180
+ }
181
+
182
+ callTool(functionName: string, args: unknown[]) {
183
+ return this.tools[functionName]?.execute?.(args, {
184
+ abortSignal: new AbortController().signal,
185
+ toolCallId: "codemode",
186
+ messages: []
187
+ });
188
+ }
189
+ }
190
+
191
+ export { CodeModeProxy } from "@cloudflare/codemode/ai";
192
+ ```
193
+
194
+ ### 🌊 Generated Code Example
195
+
196
+ Code Mode enables complex workflows that chain multiple operations:
197
+
198
+ ```javascript
199
+ // Example generated code orchestrating multiple MCP servers:
200
+ async function executeTask() {
201
+ const files = await codemode.listFiles({ path: "/projects" });
202
+ const recentProject = files
203
+ .filter((f) => f.type === "directory")
204
+ .sort((a, b) => new Date(b.modified) - new Date(a.modified))[0];
205
+
206
+ const projectStatus = await codemode.queryDatabase({
207
+ query: "SELECT * FROM projects WHERE name = ?",
208
+ params: [recentProject.name]
209
+ });
210
+
211
+ if (projectStatus.length === 0 || projectStatus[0].status === "incomplete") {
212
+ await codemode.createTask({
213
+ title: `Review project: ${recentProject.name}`,
214
+ priority: "high"
215
+ });
216
+ await codemode.sendEmail({
217
+ to: "team@company.com",
218
+ subject: "Project Review Needed"
219
+ });
220
+ }
221
+
222
+ return { success: true, project: recentProject };
223
+ }
224
+ ```
225
+
226
+ ### 🔒 Security
227
+
228
+ Code runs in isolated Workers with millisecond startup times. No network access by default - only through explicit bindings. API keys are hidden in bindings, preventing leaks.
229
+
230
+ ```ts
231
+ export const globalOutbound = {
232
+ fetch: async (input: string | URL | RequestInfo, init?: RequestInit) => {
233
+ const url = new URL(typeof input === "string" ? input : input.toString());
234
+ if (url.hostname === "example.com") {
235
+ return new Response("Not allowed", { status: 403 });
236
+ }
237
+ return fetch(input, init);
238
+ }
239
+ };
240
+ ```
241
+
242
+ ### 🔧 Setup
243
+
244
+ **Required bindings:**
245
+
246
+ - `LOADER`: Worker Loader for code execution
247
+ - `globalOutbound`: Service for network access control
248
+ - `CodeModeProxy`: Service for tool execution proxy
249
+
250
+ **Environment:**
251
+
252
+ ```ts
253
+ export const globalOutbound = {
254
+ fetch: async (input: string | URL | RequestInfo, init?: RequestInit) => {
255
+ // Your security policies
256
+ return fetch(input, init);
257
+ }
258
+ };
259
+
260
+ export { CodeModeProxy } from "@cloudflare/codemode/ai";
261
+ ```
262
+
263
+ **Proxy configuration:**
264
+
265
+ ```ts
266
+ proxy: this.ctx.exports.CodeModeProxy({
267
+ props: {
268
+ binding: "YourAgentClass",
269
+ name: this.name,
270
+ callback: "callTool"
271
+ }
272
+ });
273
+ ```
274
+
275
+ ### 🎯 Real-World Examples
276
+
277
+ Explore these examples to see Code Mode in action:
278
+
279
+ - **Complete Demo**: [`examples/codemode/`](../../examples/codemode/) - Full working example with MCP integration
280
+ - **Documentation**: [`docs/codemode.md`](../../docs/codemode.md) - Detailed guide and examples
281
+ - **Blog Post**: [Code Mode: the better way to use MCP](https://blog.cloudflare.com/code-mode/) - Deep dive into the philosophy and implementation
282
+
283
+ ### 📚 API Reference
284
+
285
+ #### `experimental_codemode(options)`
286
+
287
+ Wraps your tools with Code Mode, converting them into a single code-generating tool.
288
+
289
+ **Options:**
290
+
291
+ - `tools: ToolSet` - Your tool definitions (including MCP tools)
292
+ - `prompt: string` - System prompt for the LLM
293
+ - `globalOutbound: Fetcher` - Service binding for network access control
294
+ - `loader: WorkerLoader` - Worker Loader binding for code execution
295
+ - `proxy: Fetcher<CodeModeProxy>` - Proxy binding for tool execution
296
+
297
+ **Returns:**
298
+
299
+ - `prompt: string` - Enhanced system prompt
300
+ - `tools: ToolSet` - Wrapped tools (single "codemode" tool)
301
+
302
+ #### `CodeModeProxy`
303
+
304
+ Worker entrypoint that routes tool calls back to your agent.
305
+
306
+ **Props:**
307
+
308
+ - `binding: string` - Your agent class name
309
+ - `name: string` - Agent instance name
310
+ - `callback: string` - Method name to call for tool execution
311
+
312
+ ### 🔗 Integration
313
+
314
+ `@cloudflare/codemode` integrates with the [`agents`](../agents/) framework and works with any agent that extends `Agent`, including MCP server integration via `Agent.mcp`.
315
+
316
+ ### 🚀 Limitations
317
+
318
+ - **Experimental**: Subject to breaking changes
319
+ - **Requires Cloudflare Workers**: Uses Worker Loader API (beta)
320
+ - **JavaScript Only**: Python support planned
321
+
322
+ ### Contributing
323
+
324
+ Contributions are welcome! Please:
325
+
326
+ 1. Open an issue to discuss your proposal
327
+ 2. Ensure your changes align with the package's goals
328
+ 3. Include tests for new features
329
+ 4. Update documentation as needed
330
+
331
+ ### License
332
+
333
+ MIT licensed. See the LICENSE file at the root of this repository for details.
package/dist/ai.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { ToolSet } from "ai";
2
+ import { WorkerEntrypoint } from "cloudflare:workers";
3
+
4
+ //#region src/ai.d.ts
5
+ declare class CodeModeProxy extends WorkerEntrypoint<
6
+ Cloudflare.Env,
7
+ {
8
+ binding: string;
9
+ name: string;
10
+ callback: string;
11
+ }
12
+ > {
13
+ callFunction(options: {
14
+ functionName: string;
15
+ args: unknown[];
16
+ }): Promise<any>;
17
+ }
18
+ declare function experimental_codemode(options: {
19
+ tools: ToolSet;
20
+ prompt: string;
21
+ globalOutbound: Fetcher;
22
+ loader: WorkerLoader;
23
+ proxy: Fetcher<CodeModeProxy>;
24
+ }): Promise<{
25
+ prompt: string;
26
+ tools: ToolSet;
27
+ }>;
28
+ //#endregion
29
+ export { CodeModeProxy, experimental_codemode };
30
+ //# sourceMappingURL=ai.d.ts.map
package/dist/ai.js ADDED
@@ -0,0 +1,147 @@
1
+ import { generateObject, tool } from "ai";
2
+ import { openai } from "@ai-sdk/openai";
3
+ import { z } from "zod";
4
+ import { compile } from "json-schema-to-typescript";
5
+ import { createTypeAlias, printNode, zodToTs } from "zod-to-ts";
6
+ import { getAgentByName } from "agents";
7
+ import { WorkerEntrypoint, env } from "cloudflare:workers";
8
+
9
+ //#region src/ai.ts
10
+ function toCamelCase(str) {
11
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()).replace(/^[a-z]/, (letter) => letter.toUpperCase());
12
+ }
13
+ var CodeModeProxy = class extends WorkerEntrypoint {
14
+ async callFunction(options) {
15
+ return (await getAgentByName(env[this.ctx.props.binding], this.ctx.props.name))[this.ctx.props.callback](options.functionName, options.args);
16
+ }
17
+ };
18
+ async function experimental_codemode(options) {
19
+ const generatedTypes = await generateTypes(options.tools);
20
+ return {
21
+ prompt: `You are a helpful assistant. You have access to the "codemode" tool that can do different things:
22
+
23
+ ${getToolDescriptions(options.tools)}
24
+
25
+ If the user asks to do anything that be achieveable by the codemode tool, then simply pass over control to it by giving it a simple function description. Don't be too verbose.
26
+
27
+ `,
28
+ tools: { codemode: tool({
29
+ description: "codemode: a tool that can generate code to achieve a goal",
30
+ inputSchema: z.object({ functionDescription: z.string() }),
31
+ outputSchema: z.object({
32
+ code: z.string(),
33
+ result: z.any()
34
+ }),
35
+ execute: async ({ functionDescription }) => {
36
+ try {
37
+ const response = await generateObject({
38
+ model: openai("gpt-4.1"),
39
+ schema: z.object({ code: z.string() }),
40
+ prompt: `You are a code generating machine.
41
+
42
+ In addition to regular javascript, you can also use the following functions:
43
+
44
+ ${generatedTypes}
45
+
46
+ Respond only with the code, nothing else. Output javascript code.
47
+
48
+ Generate an async function that achieves the goal. This async function doesn't accept any arguments.
49
+
50
+ Here is user input: ${functionDescription}`
51
+ });
52
+ const result = await createEvaluator(response.object.code, {
53
+ proxy: options.proxy,
54
+ loader: options.loader
55
+ })();
56
+ return {
57
+ code: response.object.code,
58
+ result
59
+ };
60
+ } catch (error) {
61
+ console.error("error", error);
62
+ throw error;
63
+ }
64
+ }
65
+ }) }
66
+ };
67
+ }
68
+ function createEvaluator(code, options) {
69
+ return async () => {
70
+ return await options.loader.get(`code-${Math.random()}`, () => {
71
+ return {
72
+ compatibilityDate: "2025-06-01",
73
+ compatibilityFlags: ["nodejs_compat"],
74
+ mainModule: "foo.js",
75
+ modules: { "foo.js": `
76
+ import { env, WorkerEntrypoint } from "cloudflare:workers";
77
+
78
+ export default class CodeModeWorker extends WorkerEntrypoint {
79
+ async evaluate() {
80
+ try {
81
+ const { CodeModeProxy } = env;
82
+ const codemode = new Proxy(
83
+ {},
84
+ {
85
+ get: (target, prop) => {
86
+ return (args) => {
87
+ return CodeModeProxy.callFunction({
88
+ functionName: prop,
89
+ args: args,
90
+ });
91
+ };
92
+ }
93
+ }
94
+ );
95
+
96
+ return await ${code}();
97
+ } catch (err) {
98
+ return {
99
+ err: err.message,
100
+ stack: err.stack
101
+ };
102
+ }
103
+ }
104
+ }
105
+
106
+ ` },
107
+ env: { CodeModeProxy: options.proxy },
108
+ globalOutbound: null
109
+ };
110
+ }).getEntrypoint().evaluate();
111
+ };
112
+ }
113
+ async function generateTypes(tools) {
114
+ let availableTools = "";
115
+ let availableTypes = "";
116
+ for (const [toolName, tool$1] of Object.entries(tools)) {
117
+ const inputJsonType = tool$1.inputSchema.jsonSchema ? await compile(tool$1.inputSchema.jsonSchema, `${toCamelCase(toolName)}Input`, {
118
+ format: false,
119
+ bannerComment: " "
120
+ }) : printNode(createTypeAlias(zodToTs(tool$1.inputSchema, `${toCamelCase(toolName)}Input`).node, `${toCamelCase(toolName)}Input`));
121
+ const outputJsonType = tool$1.outputSchema?.jsonSchema ? await compile(tool$1.outputSchema?.jsonSchema, `${toCamelCase(toolName)}Output`, {
122
+ format: false,
123
+ bannerComment: " "
124
+ }) : tool$1.outputSchema ? printNode(createTypeAlias(zodToTs(tool$1.outputSchema, `${toCamelCase(toolName)}Output`).node, `${toCamelCase(toolName)}Output`)) : `interface ${toCamelCase(toolName)}Output { [key: string]: any }`;
125
+ const InputType = inputJsonType.trim().replace("export interface", "interface");
126
+ const OutputType = outputJsonType.trim().replace("export interface", "interface");
127
+ availableTypes += `\n${InputType}`;
128
+ availableTypes += `\n${OutputType}`;
129
+ availableTools += `\n\t/*\n\t${tool$1.description?.trim()}\n\t*/`;
130
+ availableTools += `\n\t${toolName}: (input: ${toCamelCase(toolName)}Input) => Promise<${toCamelCase(toolName)}Output>;`;
131
+ availableTools += "\n";
132
+ }
133
+ availableTools = `\ndeclare const codemode: {${availableTools}}`;
134
+ return `
135
+ ${availableTypes}
136
+ ${availableTools}
137
+ `;
138
+ }
139
+ function getToolDescriptions(tools) {
140
+ return Object.entries(tools).map(([_toolName, tool$1]) => {
141
+ return `\n- ${tool$1.description?.trim()}`;
142
+ }).join("");
143
+ }
144
+
145
+ //#endregion
146
+ export { CodeModeProxy, experimental_codemode };
147
+ //# sourceMappingURL=ai.js.map
package/dist/ai.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.js","names":["tool","compileJsonSchemaToTs","printNodeZodToTs"],"sources":["../src/ai.ts"],"sourcesContent":["import { generateObject, tool, type ToolSet } from \"ai\";\nimport { openai } from \"@ai-sdk/openai\";\nimport { z } from \"zod\";\nimport { compile as compileJsonSchemaToTs } from \"json-schema-to-typescript\";\nimport {\n zodToTs,\n printNode as printNodeZodToTs,\n createTypeAlias\n} from \"zod-to-ts\";\nimport { getAgentByName } from \"agents\";\nimport { env, WorkerEntrypoint } from \"cloudflare:workers\";\n\nfunction toCamelCase(str: string) {\n return str\n .replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())\n .replace(/^[a-z]/, (letter) => letter.toUpperCase());\n}\n\nexport class CodeModeProxy extends WorkerEntrypoint<\n Cloudflare.Env,\n {\n binding: string;\n name: string;\n callback: string;\n }\n> {\n async callFunction(options: { functionName: string; args: unknown[] }) {\n const stub = (await getAgentByName(\n // @ts-expect-error\n env[this.ctx.props.binding] as DurableObjectNamespace<T>,\n this.ctx.props.name\n )) as DurableObjectStub;\n // @ts-expect-error\n return stub[this.ctx.props.callback](options.functionName, options.args);\n }\n}\n\nexport async function experimental_codemode(options: {\n tools: ToolSet;\n prompt: string;\n globalOutbound: Fetcher;\n loader: WorkerLoader;\n proxy: Fetcher<CodeModeProxy>;\n}): Promise<{\n prompt: string;\n tools: ToolSet;\n}> {\n const generatedTypes = await generateTypes(options.tools);\n const prompt = `You are a helpful assistant. You have access to the \"codemode\" tool that can do different things: \n \n ${getToolDescriptions(options.tools)} \n \n If the user asks to do anything that be achieveable by the codemode tool, then simply pass over control to it by giving it a simple function description. Don't be too verbose.\n \n `;\n\n const codemodeTool = tool({\n description: \"codemode: a tool that can generate code to achieve a goal\",\n inputSchema: z.object({\n functionDescription: z.string()\n }),\n outputSchema: z.object({\n code: z.string(),\n result: z.any()\n }),\n execute: async ({ functionDescription }) => {\n try {\n const response = await generateObject({\n model: openai(\"gpt-4.1\"),\n schema: z.object({\n code: z.string()\n }),\n prompt: `You are a code generating machine.\n\n In addition to regular javascript, you can also use the following functions:\n\n ${generatedTypes} \n\n Respond only with the code, nothing else. Output javascript code.\n\n Generate an async function that achieves the goal. This async function doesn't accept any arguments.\n\n Here is user input: ${functionDescription}` // insert ts types for the tools here\n });\n\n // console.log(\"args\", response.object.args);\n const evaluator = createEvaluator(response.object.code, {\n proxy: options.proxy,\n loader: options.loader\n });\n const result = await evaluator();\n return { code: response.object.code, result: result };\n } catch (error) {\n console.error(\"error\", error);\n throw error;\n // return { code: \"\", result: error };\n }\n }\n });\n\n return { prompt, tools: { codemode: codemodeTool } };\n}\n\nfunction createEvaluator(\n code: string,\n options: {\n loader: WorkerLoader;\n proxy: Fetcher<CodeModeProxy>;\n }\n) {\n return async () => {\n const worker = options.loader.get(`code-${Math.random()}`, () => {\n return {\n compatibilityDate: \"2025-06-01\",\n compatibilityFlags: [\"nodejs_compat\"],\n mainModule: \"foo.js\",\n modules: {\n \"foo.js\": `\nimport { env, WorkerEntrypoint } from \"cloudflare:workers\";\n\nexport default class CodeModeWorker extends WorkerEntrypoint {\n async evaluate() {\n try {\n const { CodeModeProxy } = env;\n const codemode = new Proxy(\n {},\n {\n get: (target, prop) => {\n return (args) => {\n return CodeModeProxy.callFunction({\n functionName: prop,\n args: args, \n });\n };\n }\n }\n );\n\n return await ${code}();\n } catch (err) {\n return {\n err: err.message,\n stack: err.stack\n };\n }\n }\n}\n \n `\n },\n env: {\n // insert keys and bindings to tools/ts functions here\n CodeModeProxy: options.proxy\n },\n globalOutbound: null\n };\n });\n\n // @ts-expect-error TODO: fix this\n return await worker.getEntrypoint().evaluate();\n };\n}\n\nasync function generateTypes(tools: ToolSet) {\n let availableTools = \"\";\n let availableTypes = \"\";\n\n for (const [toolName, tool] of Object.entries(tools)) {\n // @ts-expect-error TODO: fix this\n const inputJsonType = tool.inputSchema.jsonSchema\n ? await compileJsonSchemaToTs(\n // @ts-expect-error TODO: fix this\n tool.inputSchema.jsonSchema,\n `${toCamelCase(toolName)}Input`,\n {\n format: false,\n bannerComment: \" \"\n }\n )\n : printNodeZodToTs(\n createTypeAlias(\n zodToTs(\n // @ts-expect-error TODO: fix this\n tool.inputSchema,\n `${toCamelCase(toolName)}Input`\n ).node,\n `${toCamelCase(toolName)}Input`\n )\n );\n\n const outputJsonType =\n // @ts-expect-error TODO: fix this\n tool.outputSchema?.jsonSchema\n ? await compileJsonSchemaToTs(\n // @ts-expect-error TODO: fix this\n tool.outputSchema?.jsonSchema,\n `${toCamelCase(toolName)}Output`,\n {\n format: false,\n bannerComment: \" \"\n }\n )\n : tool.outputSchema\n ? printNodeZodToTs(\n createTypeAlias(\n zodToTs(\n // @ts-expect-error TODO: fix this\n tool.outputSchema,\n `${toCamelCase(toolName)}Output`\n ).node,\n `${toCamelCase(toolName)}Output`\n )\n )\n : `interface ${toCamelCase(toolName)}Output { [key: string]: any }`;\n\n const InputType = inputJsonType\n .trim()\n .replace(\"export interface\", \"interface\");\n\n const OutputType = outputJsonType\n .trim()\n .replace(\"export interface\", \"interface\");\n\n availableTypes += `\\n${InputType}`;\n availableTypes += `\\n${OutputType}`;\n availableTools += `\\n\\t/*\\n\\t${tool.description?.trim()}\\n\\t*/`;\n availableTools += `\\n\\t${toolName}: (input: ${toCamelCase(toolName)}Input) => Promise<${toCamelCase(toolName)}Output>;`;\n availableTools += \"\\n\";\n }\n\n availableTools = `\\ndeclare const codemode: {${availableTools}}`;\n\n return `\n${availableTypes}\n${availableTools}\n `;\n}\n\nfunction getToolDescriptions(tools: ToolSet) {\n return Object.entries(tools)\n .map(([_toolName, tool]) => {\n return `\\n- ${tool.description?.trim()}`;\n })\n .join(\"\");\n}\n"],"mappings":";;;;;;;;;AAYA,SAAS,YAAY,KAAa;AAChC,QAAO,IACJ,QAAQ,cAAc,GAAG,WAAW,OAAO,aAAa,CAAC,CACzD,QAAQ,WAAW,WAAW,OAAO,aAAa,CAAC;;AAGxD,IAAa,gBAAb,cAAmC,iBAOjC;CACA,MAAM,aAAa,SAAoD;AAOrE,UANc,MAAM,eAElB,IAAI,KAAK,IAAI,MAAM,UACnB,KAAK,IAAI,MAAM,KAChB,EAEW,KAAK,IAAI,MAAM,UAAU,QAAQ,cAAc,QAAQ,KAAK;;;AAI5E,eAAsB,sBAAsB,SASzC;CACD,MAAM,iBAAiB,MAAM,cAAc,QAAQ,MAAM;AAqDzD,QAAO;EAAE,QApDM;;IAEb,oBAAoB,QAAQ,MAAM,CAAC;;;;;EAkDpB,OAAO,EAAE,UA5CL,KAAK;GACxB,aAAa;GACb,aAAa,EAAE,OAAO,EACpB,qBAAqB,EAAE,QAAQ,EAChC,CAAC;GACF,cAAc,EAAE,OAAO;IACrB,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,KAAK;IAChB,CAAC;GACF,SAAS,OAAO,EAAE,0BAA0B;AAC1C,QAAI;KACF,MAAM,WAAW,MAAM,eAAe;MACpC,OAAO,OAAO,UAAU;MACxB,QAAQ,EAAE,OAAO,EACf,MAAM,EAAE,QAAQ,EACjB,CAAC;MACF,QAAQ;;;;QAIV,eAAe;;;;;;4BAMK;MACnB,CAAC;KAOF,MAAM,SAAS,MAJG,gBAAgB,SAAS,OAAO,MAAM;MACtD,OAAO,QAAQ;MACf,QAAQ,QAAQ;MACjB,CAAC,EAC8B;AAChC,YAAO;MAAE,MAAM,SAAS,OAAO;MAAc;MAAQ;aAC9C,OAAO;AACd,aAAQ,MAAM,SAAS,MAAM;AAC7B,WAAM;;;GAIX,CAAC,EAEgD;EAAE;;AAGtD,SAAS,gBACP,MACA,SAIA;AACA,QAAO,YAAY;AAiDjB,SAAO,MAhDQ,QAAQ,OAAO,IAAI,QAAQ,KAAK,QAAQ,UAAU;AAC/D,UAAO;IACL,mBAAmB;IACnB,oBAAoB,CAAC,gBAAgB;IACrC,YAAY;IACZ,SAAS,EACP,UAAU;;;;;;;;;;;;;;;;;;;;;qBAqBC,KAAK;;;;;;;;;;WAWjB;IACD,KAAK,EAEH,eAAe,QAAQ,OACxB;IACD,gBAAgB;IACjB;IACD,CAGkB,eAAe,CAAC,UAAU;;;AAIlD,eAAe,cAAc,OAAgB;CAC3C,IAAI,iBAAiB;CACrB,IAAI,iBAAiB;AAErB,MAAK,MAAM,CAAC,UAAUA,WAAS,OAAO,QAAQ,MAAM,EAAE;EAEpD,MAAM,gBAAgBA,OAAK,YAAY,aACnC,MAAMC,QAEJD,OAAK,YAAY,YACjB,GAAG,YAAY,SAAS,CAAC,QACzB;GACE,QAAQ;GACR,eAAe;GAChB,CACF,GACDE,UACE,gBACE,QAEEF,OAAK,aACL,GAAG,YAAY,SAAS,CAAC,OAC1B,CAAC,MACF,GAAG,YAAY,SAAS,CAAC,OAC1B,CACF;EAEL,MAAM,iBAEJA,OAAK,cAAc,aACf,MAAMC,QAEJD,OAAK,cAAc,YACnB,GAAG,YAAY,SAAS,CAAC,SACzB;GACE,QAAQ;GACR,eAAe;GAChB,CACF,GACDA,OAAK,eACHE,UACE,gBACE,QAEEF,OAAK,cACL,GAAG,YAAY,SAAS,CAAC,QAC1B,CAAC,MACF,GAAG,YAAY,SAAS,CAAC,QAC1B,CACF,GACD,aAAa,YAAY,SAAS,CAAC;EAE3C,MAAM,YAAY,cACf,MAAM,CACN,QAAQ,oBAAoB,YAAY;EAE3C,MAAM,aAAa,eAChB,MAAM,CACN,QAAQ,oBAAoB,YAAY;AAE3C,oBAAkB,KAAK;AACvB,oBAAkB,KAAK;AACvB,oBAAkB,aAAaA,OAAK,aAAa,MAAM,CAAC;AACxD,oBAAkB,OAAO,SAAS,YAAY,YAAY,SAAS,CAAC,oBAAoB,YAAY,SAAS,CAAC;AAC9G,oBAAkB;;AAGpB,kBAAiB,8BAA8B,eAAe;AAE9D,QAAO;EACP,eAAe;EACf,eAAe;;;AAIjB,SAAS,oBAAoB,OAAgB;AAC3C,QAAO,OAAO,QAAQ,MAAM,CACzB,KAAK,CAAC,WAAWA,YAAU;AAC1B,SAAO,OAAOA,OAAK,aAAa,MAAM;GACtC,CACD,KAAK,GAAG"}
package/package.json CHANGED
@@ -1,13 +1,45 @@
1
1
  {
2
2
  "name": "@cloudflare/codemode",
3
- "version": "0.0.1",
4
- "description": "",
5
- "main": "index.js",
3
+ "version": "0.0.4",
4
+ "description": "Code Mode: use LLMs to generate executable code that performs tool calls",
5
+ "repository": {
6
+ "directory": "packages/codemode",
7
+ "type": "git",
8
+ "url": "git+https://github.com/cloudflare/agents.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/cloudflare/agents/issues"
12
+ },
13
+ "dependencies": {
14
+ "zod-to-ts": "^2.0.0"
15
+ },
16
+ "devDependencies": {
17
+ "ai": "^6.0.15",
18
+ "zod": "^4.3.5"
19
+ },
20
+ "peerDependencies": {
21
+ "agents": "^0.3.4",
22
+ "ai": "^6.0.0",
23
+ "zod": "^3.25.0 || ^4.0.0"
24
+ },
25
+ "exports": {
26
+ "./ai": {
27
+ "types": "./dist/ai.d.ts",
28
+ "import": "./dist/ai.js",
29
+ "require": "./dist/ai.js"
30
+ }
31
+ },
6
32
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
33
+ "build": "tsx ./scripts/build.ts"
8
34
  },
9
- "keywords": [],
10
- "author": "",
11
- "license": "ISC",
12
- "type": "commonjs"
35
+ "keywords": [
36
+ "cloudflare",
37
+ "agents",
38
+ "ai",
39
+ "llm",
40
+ "codemode"
41
+ ],
42
+ "author": "Cloudflare Inc.",
43
+ "license": "MIT",
44
+ "type": "module"
13
45
  }
@@ -0,0 +1,26 @@
1
+ import { execSync } from "node:child_process";
2
+ import { build } from "tsdown";
3
+
4
+ async function main() {
5
+ await build({
6
+ clean: true,
7
+ dts: true,
8
+ entry: ["src/ai.ts"],
9
+ skipNodeModulesBundle: true,
10
+ external: ["cloudflare:workers"],
11
+ format: "esm",
12
+ sourcemap: true,
13
+ fixedExtension: false
14
+ });
15
+
16
+ // then run prettier on the generated .d.ts files
17
+ execSync("prettier --write ./dist/*.d.ts");
18
+
19
+ process.exit(0);
20
+ }
21
+
22
+ main().catch((err) => {
23
+ // Build failures should fail
24
+ console.error(err);
25
+ process.exit(1);
26
+ });
package/src/ai.ts ADDED
@@ -0,0 +1,245 @@
1
+ import { generateObject, tool, type ToolSet } from "ai";
2
+ import { openai } from "@ai-sdk/openai";
3
+ import { z } from "zod";
4
+ import { compile as compileJsonSchemaToTs } from "json-schema-to-typescript";
5
+ import {
6
+ zodToTs,
7
+ printNode as printNodeZodToTs,
8
+ createTypeAlias
9
+ } from "zod-to-ts";
10
+ import { getAgentByName } from "agents";
11
+ import { env, WorkerEntrypoint } from "cloudflare:workers";
12
+
13
+ function toCamelCase(str: string) {
14
+ return str
15
+ .replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())
16
+ .replace(/^[a-z]/, (letter) => letter.toUpperCase());
17
+ }
18
+
19
+ export class CodeModeProxy extends WorkerEntrypoint<
20
+ Cloudflare.Env,
21
+ {
22
+ binding: string;
23
+ name: string;
24
+ callback: string;
25
+ }
26
+ > {
27
+ async callFunction(options: { functionName: string; args: unknown[] }) {
28
+ const stub = (await getAgentByName(
29
+ // @ts-expect-error
30
+ env[this.ctx.props.binding] as DurableObjectNamespace<T>,
31
+ this.ctx.props.name
32
+ )) as DurableObjectStub;
33
+ // @ts-expect-error
34
+ return stub[this.ctx.props.callback](options.functionName, options.args);
35
+ }
36
+ }
37
+
38
+ export async function experimental_codemode(options: {
39
+ tools: ToolSet;
40
+ prompt: string;
41
+ globalOutbound: Fetcher;
42
+ loader: WorkerLoader;
43
+ proxy: Fetcher<CodeModeProxy>;
44
+ }): Promise<{
45
+ prompt: string;
46
+ tools: ToolSet;
47
+ }> {
48
+ const generatedTypes = await generateTypes(options.tools);
49
+ const prompt = `You are a helpful assistant. You have access to the "codemode" tool that can do different things:
50
+
51
+ ${getToolDescriptions(options.tools)}
52
+
53
+ If the user asks to do anything that be achieveable by the codemode tool, then simply pass over control to it by giving it a simple function description. Don't be too verbose.
54
+
55
+ `;
56
+
57
+ const codemodeTool = tool({
58
+ description: "codemode: a tool that can generate code to achieve a goal",
59
+ inputSchema: z.object({
60
+ functionDescription: z.string()
61
+ }),
62
+ outputSchema: z.object({
63
+ code: z.string(),
64
+ result: z.any()
65
+ }),
66
+ execute: async ({ functionDescription }) => {
67
+ try {
68
+ const response = await generateObject({
69
+ model: openai("gpt-4.1"),
70
+ schema: z.object({
71
+ code: z.string()
72
+ }),
73
+ prompt: `You are a code generating machine.
74
+
75
+ In addition to regular javascript, you can also use the following functions:
76
+
77
+ ${generatedTypes}
78
+
79
+ Respond only with the code, nothing else. Output javascript code.
80
+
81
+ Generate an async function that achieves the goal. This async function doesn't accept any arguments.
82
+
83
+ Here is user input: ${functionDescription}` // insert ts types for the tools here
84
+ });
85
+
86
+ // console.log("args", response.object.args);
87
+ const evaluator = createEvaluator(response.object.code, {
88
+ proxy: options.proxy,
89
+ loader: options.loader
90
+ });
91
+ const result = await evaluator();
92
+ return { code: response.object.code, result: result };
93
+ } catch (error) {
94
+ console.error("error", error);
95
+ throw error;
96
+ // return { code: "", result: error };
97
+ }
98
+ }
99
+ });
100
+
101
+ return { prompt, tools: { codemode: codemodeTool } };
102
+ }
103
+
104
+ function createEvaluator(
105
+ code: string,
106
+ options: {
107
+ loader: WorkerLoader;
108
+ proxy: Fetcher<CodeModeProxy>;
109
+ }
110
+ ) {
111
+ return async () => {
112
+ const worker = options.loader.get(`code-${Math.random()}`, () => {
113
+ return {
114
+ compatibilityDate: "2025-06-01",
115
+ compatibilityFlags: ["nodejs_compat"],
116
+ mainModule: "foo.js",
117
+ modules: {
118
+ "foo.js": `
119
+ import { env, WorkerEntrypoint } from "cloudflare:workers";
120
+
121
+ export default class CodeModeWorker extends WorkerEntrypoint {
122
+ async evaluate() {
123
+ try {
124
+ const { CodeModeProxy } = env;
125
+ const codemode = new Proxy(
126
+ {},
127
+ {
128
+ get: (target, prop) => {
129
+ return (args) => {
130
+ return CodeModeProxy.callFunction({
131
+ functionName: prop,
132
+ args: args,
133
+ });
134
+ };
135
+ }
136
+ }
137
+ );
138
+
139
+ return await ${code}();
140
+ } catch (err) {
141
+ return {
142
+ err: err.message,
143
+ stack: err.stack
144
+ };
145
+ }
146
+ }
147
+ }
148
+
149
+ `
150
+ },
151
+ env: {
152
+ // insert keys and bindings to tools/ts functions here
153
+ CodeModeProxy: options.proxy
154
+ },
155
+ globalOutbound: null
156
+ };
157
+ });
158
+
159
+ // @ts-expect-error TODO: fix this
160
+ return await worker.getEntrypoint().evaluate();
161
+ };
162
+ }
163
+
164
+ async function generateTypes(tools: ToolSet) {
165
+ let availableTools = "";
166
+ let availableTypes = "";
167
+
168
+ for (const [toolName, tool] of Object.entries(tools)) {
169
+ // @ts-expect-error TODO: fix this
170
+ const inputJsonType = tool.inputSchema.jsonSchema
171
+ ? await compileJsonSchemaToTs(
172
+ // @ts-expect-error TODO: fix this
173
+ tool.inputSchema.jsonSchema,
174
+ `${toCamelCase(toolName)}Input`,
175
+ {
176
+ format: false,
177
+ bannerComment: " "
178
+ }
179
+ )
180
+ : printNodeZodToTs(
181
+ createTypeAlias(
182
+ zodToTs(
183
+ // @ts-expect-error TODO: fix this
184
+ tool.inputSchema,
185
+ `${toCamelCase(toolName)}Input`
186
+ ).node,
187
+ `${toCamelCase(toolName)}Input`
188
+ )
189
+ );
190
+
191
+ const outputJsonType =
192
+ // @ts-expect-error TODO: fix this
193
+ tool.outputSchema?.jsonSchema
194
+ ? await compileJsonSchemaToTs(
195
+ // @ts-expect-error TODO: fix this
196
+ tool.outputSchema?.jsonSchema,
197
+ `${toCamelCase(toolName)}Output`,
198
+ {
199
+ format: false,
200
+ bannerComment: " "
201
+ }
202
+ )
203
+ : tool.outputSchema
204
+ ? printNodeZodToTs(
205
+ createTypeAlias(
206
+ zodToTs(
207
+ // @ts-expect-error TODO: fix this
208
+ tool.outputSchema,
209
+ `${toCamelCase(toolName)}Output`
210
+ ).node,
211
+ `${toCamelCase(toolName)}Output`
212
+ )
213
+ )
214
+ : `interface ${toCamelCase(toolName)}Output { [key: string]: any }`;
215
+
216
+ const InputType = inputJsonType
217
+ .trim()
218
+ .replace("export interface", "interface");
219
+
220
+ const OutputType = outputJsonType
221
+ .trim()
222
+ .replace("export interface", "interface");
223
+
224
+ availableTypes += `\n${InputType}`;
225
+ availableTypes += `\n${OutputType}`;
226
+ availableTools += `\n\t/*\n\t${tool.description?.trim()}\n\t*/`;
227
+ availableTools += `\n\t${toolName}: (input: ${toCamelCase(toolName)}Input) => Promise<${toCamelCase(toolName)}Output>;`;
228
+ availableTools += "\n";
229
+ }
230
+
231
+ availableTools = `\ndeclare const codemode: {${availableTools}}`;
232
+
233
+ return `
234
+ ${availableTypes}
235
+ ${availableTools}
236
+ `;
237
+ }
238
+
239
+ function getToolDescriptions(tools: ToolSet) {
240
+ return Object.entries(tools)
241
+ .map(([_toolName, tool]) => {
242
+ return `\n- ${tool.description?.trim()}`;
243
+ })
244
+ .join("");
245
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "exclude": ["src/tests/**/*.ts", "src/e2e/**/*.ts"],
3
+ "extends": "../../tsconfig.base.json"
4
+ }