@cloudflare/codemode 0.0.8 → 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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-B9g5T2nd.js","names":["printNodeZodToTs"],"sources":["../src/types.ts"],"sourcesContent":["import {\n zodToTs,\n printNode as printNodeZodToTs,\n createTypeAlias,\n createAuxiliaryTypeStore\n} from \"zod-to-ts\";\nimport type { ZodType } from \"zod\";\nimport type { ToolSet } from \"ai\";\n\nconst JS_RESERVED = new Set([\n \"abstract\",\n \"arguments\",\n \"await\",\n \"boolean\",\n \"break\",\n \"byte\",\n \"case\",\n \"catch\",\n \"char\",\n \"class\",\n \"const\",\n \"continue\",\n \"debugger\",\n \"default\",\n \"delete\",\n \"do\",\n \"double\",\n \"else\",\n \"enum\",\n \"eval\",\n \"export\",\n \"extends\",\n \"false\",\n \"final\",\n \"finally\",\n \"float\",\n \"for\",\n \"function\",\n \"goto\",\n \"if\",\n \"implements\",\n \"import\",\n \"in\",\n \"instanceof\",\n \"int\",\n \"interface\",\n \"let\",\n \"long\",\n \"native\",\n \"new\",\n \"null\",\n \"package\",\n \"private\",\n \"protected\",\n \"public\",\n \"return\",\n \"short\",\n \"static\",\n \"super\",\n \"switch\",\n \"synchronized\",\n \"this\",\n \"throw\",\n \"throws\",\n \"transient\",\n \"true\",\n \"try\",\n \"typeof\",\n \"undefined\",\n \"var\",\n \"void\",\n \"volatile\",\n \"while\",\n \"with\",\n \"yield\"\n]);\n\n/**\n * Sanitize a tool name into a valid JavaScript identifier.\n * Replaces hyphens, dots, and spaces with `_`, strips other invalid chars,\n * prefixes digit-leading names with `_`, and appends `_` to JS reserved words.\n */\nexport function sanitizeToolName(name: string): string {\n if (!name) return \"_\";\n\n // Replace common separators with underscores\n let sanitized = name.replace(/[-.\\s]/g, \"_\");\n\n // Strip any remaining non-identifier characters\n sanitized = sanitized.replace(/[^a-zA-Z0-9_$]/g, \"\");\n\n if (!sanitized) return \"_\";\n\n // Prefix with _ if starts with a digit\n if (/^[0-9]/.test(sanitized)) {\n sanitized = \"_\" + sanitized;\n }\n\n // Append _ to reserved words\n if (JS_RESERVED.has(sanitized)) {\n sanitized = sanitized + \"_\";\n }\n\n return sanitized;\n}\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\n/**\n * Extract field descriptions from a Zod object schema's `.shape`, if available.\n * Returns an array of `@param input.fieldName - description` lines.\n */\nfunction extractParamDescriptions(schema: ZodType): string[] {\n const descriptions: string[] = [];\n const shape = (schema as { shape?: Record<string, ZodType> }).shape;\n if (!shape || typeof shape !== \"object\") return descriptions;\n\n for (const [fieldName, fieldSchema] of Object.entries(shape)) {\n const desc = (fieldSchema as { description?: string }).description;\n if (desc) {\n descriptions.push(`@param input.${fieldName} - ${desc}`);\n }\n }\n return descriptions;\n}\n\nexport interface ToolDescriptor {\n description?: string;\n inputSchema: ZodType;\n outputSchema?: ZodType;\n execute?: (args: unknown) => Promise<unknown>;\n}\n\nexport type ToolDescriptors = Record<string, ToolDescriptor>;\n\n/**\n * Generate TypeScript type definitions from tool descriptors or an AI SDK ToolSet.\n * These types can be included in tool descriptions to help LLMs write correct code.\n */\nexport function generateTypes(tools: ToolDescriptors | ToolSet): string {\n let availableTools = \"\";\n let availableTypes = \"\";\n\n const auxiliaryTypeStore = createAuxiliaryTypeStore();\n\n for (const [toolName, tool] of Object.entries(tools)) {\n // Handle both our ToolDescriptor and AI SDK Tool types\n const inputSchema =\n \"inputSchema\" in tool ? tool.inputSchema : tool.parameters;\n const outputSchema = \"outputSchema\" in tool ? tool.outputSchema : undefined;\n const description = tool.description;\n\n const safeName = sanitizeToolName(toolName);\n\n const inputType = printNodeZodToTs(\n createTypeAlias(\n zodToTs(inputSchema as ZodType, { auxiliaryTypeStore }).node,\n `${toCamelCase(safeName)}Input`\n )\n );\n\n const outputType = outputSchema\n ? printNodeZodToTs(\n createTypeAlias(\n zodToTs(outputSchema as ZodType, { auxiliaryTypeStore }).node,\n `${toCamelCase(safeName)}Output`\n )\n )\n : `type ${toCamelCase(safeName)}Output = unknown`;\n\n availableTypes += `\\n${inputType.trim()}`;\n availableTypes += `\\n${outputType.trim()}`;\n\n // Build JSDoc comment with description and param descriptions\n const paramDescs = extractParamDescriptions(inputSchema as ZodType);\n const jsdocLines: string[] = [];\n if (description?.trim()) {\n jsdocLines.push(description.trim());\n } else {\n jsdocLines.push(toolName);\n }\n for (const pd of paramDescs) {\n jsdocLines.push(pd);\n }\n\n const jsdocBody = jsdocLines.map((l) => `\\t * ${l}`).join(\"\\n\");\n availableTools += `\\n\\t/**\\n${jsdocBody}\\n\\t */`;\n availableTools += `\\n\\t${safeName}: (input: ${toCamelCase(safeName)}Input) => Promise<${toCamelCase(safeName)}Output>;`;\n availableTools += \"\\n\";\n }\n\n availableTools = `\\ndeclare const codemode: {${availableTools}}`;\n\n return `\n${availableTypes}\n${availableTools}\n `.trim();\n}\n"],"mappings":";;;AASA,MAAM,cAAc,IAAI,IAAI;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;AAOF,SAAgB,iBAAiB,MAAsB;AACrD,KAAI,CAAC,KAAM,QAAO;CAGlB,IAAI,YAAY,KAAK,QAAQ,WAAW,IAAI;AAG5C,aAAY,UAAU,QAAQ,mBAAmB,GAAG;AAEpD,KAAI,CAAC,UAAW,QAAO;AAGvB,KAAI,SAAS,KAAK,UAAU,CAC1B,aAAY,MAAM;AAIpB,KAAI,YAAY,IAAI,UAAU,CAC5B,aAAY,YAAY;AAG1B,QAAO;;AAGT,SAAS,YAAY,KAAa;AAChC,QAAO,IACJ,QAAQ,cAAc,GAAG,WAAW,OAAO,aAAa,CAAC,CACzD,QAAQ,WAAW,WAAW,OAAO,aAAa,CAAC;;;;;;AAOxD,SAAS,yBAAyB,QAA2B;CAC3D,MAAM,eAAyB,EAAE;CACjC,MAAM,QAAS,OAA+C;AAC9D,KAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAEhD,MAAK,MAAM,CAAC,WAAW,gBAAgB,OAAO,QAAQ,MAAM,EAAE;EAC5D,MAAM,OAAQ,YAAyC;AACvD,MAAI,KACF,cAAa,KAAK,gBAAgB,UAAU,KAAK,OAAO;;AAG5D,QAAO;;;;;;AAgBT,SAAgB,cAAc,OAA0C;CACtE,IAAI,iBAAiB;CACrB,IAAI,iBAAiB;CAErB,MAAM,qBAAqB,0BAA0B;AAErD,MAAK,MAAM,CAAC,UAAU,SAAS,OAAO,QAAQ,MAAM,EAAE;EAEpD,MAAM,cACJ,iBAAiB,OAAO,KAAK,cAAc,KAAK;EAClD,MAAM,eAAe,kBAAkB,OAAO,KAAK,eAAe;EAClE,MAAM,cAAc,KAAK;EAEzB,MAAM,WAAW,iBAAiB,SAAS;EAE3C,MAAM,YAAYA,UAChB,gBACE,QAAQ,aAAwB,EAAE,oBAAoB,CAAC,CAAC,MACxD,GAAG,YAAY,SAAS,CAAC,OAC1B,CACF;EAED,MAAM,aAAa,eACfA,UACE,gBACE,QAAQ,cAAyB,EAAE,oBAAoB,CAAC,CAAC,MACzD,GAAG,YAAY,SAAS,CAAC,QAC1B,CACF,GACD,QAAQ,YAAY,SAAS,CAAC;AAElC,oBAAkB,KAAK,UAAU,MAAM;AACvC,oBAAkB,KAAK,WAAW,MAAM;EAGxC,MAAM,aAAa,yBAAyB,YAAuB;EACnE,MAAM,aAAuB,EAAE;AAC/B,MAAI,aAAa,MAAM,CACrB,YAAW,KAAK,YAAY,MAAM,CAAC;MAEnC,YAAW,KAAK,SAAS;AAE3B,OAAK,MAAM,MAAM,WACf,YAAW,KAAK,GAAG;EAGrB,MAAM,YAAY,WAAW,KAAK,MAAM,QAAQ,IAAI,CAAC,KAAK,KAAK;AAC/D,oBAAkB,YAAY,UAAU;AACxC,oBAAkB,OAAO,SAAS,YAAY,YAAY,SAAS,CAAC,oBAAoB,YAAY,SAAS,CAAC;AAC9G,oBAAkB;;AAGpB,kBAAiB,8BAA8B,eAAe;AAE9D,QAAO;EACP,eAAe;EACf,eAAe;IACb,MAAM"}
@@ -0,0 +1,124 @@
1
+ import { test, expect } from "@playwright/test";
2
+
3
+ /**
4
+ * E2E tests for @cloudflare/codemode with a real AI binding.
5
+ *
6
+ * These verify the full pipeline:
7
+ * user prompt → LLM generates code via createCodeTool → DynamicWorkerExecutor
8
+ * runs the code in an isolated Worker → tool functions called via RPC → result returned.
9
+ *
10
+ * Uses Workers AI (@cf/zai-org/glm-4.7-flash) — no API key needed.
11
+ */
12
+
13
+ async function runChat(
14
+ request: import("@playwright/test").APIRequestContext,
15
+ baseURL: string,
16
+ userMessage: string
17
+ ): Promise<string> {
18
+ const res = await request.post(`${baseURL}/run`, {
19
+ headers: { "Content-Type": "application/json" },
20
+ data: {
21
+ messages: [
22
+ {
23
+ id: `msg-${crypto.randomUUID()}`,
24
+ role: "user",
25
+ parts: [{ type: "text", text: userMessage }]
26
+ }
27
+ ]
28
+ },
29
+ timeout: 45_000
30
+ });
31
+ expect(res.ok()).toBe(true);
32
+ return res.text();
33
+ }
34
+
35
+ test.describe("codemode e2e (Workers AI)", () => {
36
+ test.setTimeout(45_000);
37
+
38
+ test("LLM generates and executes code that calls addNumbers tool", async ({
39
+ request,
40
+ baseURL
41
+ }) => {
42
+ const response = await runChat(
43
+ request,
44
+ baseURL!,
45
+ "What is 17 + 25? Use the codemode tool with the addNumbers function to calculate this."
46
+ );
47
+
48
+ // The response stream should contain the answer 42 somewhere
49
+ // (either in the tool result or the LLM's text response)
50
+ expect(response).toContain("42");
51
+ });
52
+
53
+ test("LLM generates and executes code that calls getWeather tool", async ({
54
+ request,
55
+ baseURL
56
+ }) => {
57
+ const response = await runChat(
58
+ request,
59
+ baseURL!,
60
+ "What is the weather in London? Use the codemode tool with the getWeather function."
61
+ );
62
+
63
+ // The getWeather tool returns { city: "London", temperature: 22, condition: "Sunny" }
64
+ // The LLM should mention London or the weather data in its response
65
+ const lower = response.toLowerCase();
66
+ expect(
67
+ lower.includes("london") ||
68
+ lower.includes("22") ||
69
+ lower.includes("sunny")
70
+ ).toBe(true);
71
+ });
72
+
73
+ test("LLM generates and executes code that calls listProjects tool", async ({
74
+ request,
75
+ baseURL
76
+ }) => {
77
+ const response = await runChat(
78
+ request,
79
+ baseURL!,
80
+ "List all projects using the codemode tool with the listProjects function."
81
+ );
82
+
83
+ // listProjects returns Alpha and Beta
84
+ const lower = response.toLowerCase();
85
+ expect(lower.includes("alpha") || lower.includes("beta")).toBe(true);
86
+ });
87
+
88
+ test("LLM generates code with multiple tool calls", async ({
89
+ request,
90
+ baseURL
91
+ }) => {
92
+ const response = await runChat(
93
+ request,
94
+ baseURL!,
95
+ "Using the codemode tool, first get the weather in Paris, then add the numbers 10 and 5. Return both results."
96
+ );
97
+
98
+ // Should contain evidence of both tool calls completing
99
+ const lower = response.toLowerCase();
100
+ expect(
101
+ lower.includes("paris") ||
102
+ lower.includes("22") ||
103
+ lower.includes("15") ||
104
+ lower.includes("sunny")
105
+ ).toBe(true);
106
+ });
107
+
108
+ test("generateTypes returns valid type definitions", async ({
109
+ request,
110
+ baseURL
111
+ }) => {
112
+ const res = await request.get(`${baseURL}/types`);
113
+ expect(res.ok()).toBe(true);
114
+
115
+ const data = await res.json();
116
+ const types = data.types as string;
117
+
118
+ expect(types).toContain("declare const codemode");
119
+ expect(types).toContain("addNumbers");
120
+ expect(types).toContain("getWeather");
121
+ expect(types).toContain("createProject");
122
+ expect(types).toContain("listProjects");
123
+ });
124
+ });
@@ -0,0 +1,24 @@
1
+ import { defineConfig } from "@playwright/test";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const PORT = 8798;
6
+ const e2eDir = dirname(fileURLToPath(import.meta.url));
7
+ const configPath = join(e2eDir, "wrangler.jsonc");
8
+
9
+ export default defineConfig({
10
+ testDir: e2eDir,
11
+ testMatch: "*.spec.ts",
12
+ timeout: 60_000,
13
+ retries: 2,
14
+ workers: 1,
15
+ use: {
16
+ baseURL: `http://localhost:${PORT}`
17
+ },
18
+ webServer: {
19
+ command: `lsof -ti tcp:${PORT} | xargs kill -9 2>/dev/null; npx wrangler dev --config ${configPath} --port ${PORT}`,
20
+ port: PORT,
21
+ reuseExistingServer: !process.env.CI,
22
+ timeout: 30_000
23
+ }
24
+ });
package/e2e/worker.ts ADDED
@@ -0,0 +1,144 @@
1
+ import { Agent, routeAgentRequest, getAgentByName } from "agents";
2
+ import {
3
+ streamText,
4
+ convertToModelMessages,
5
+ stepCountIs,
6
+ tool,
7
+ type UIMessage
8
+ } from "ai";
9
+ import { createWorkersAI } from "workers-ai-provider";
10
+ import { z } from "zod";
11
+ import { createCodeTool } from "../src/ai";
12
+ import { DynamicWorkerExecutor, generateTypes } from "../src/index";
13
+
14
+ type Env = {
15
+ AI: Ai;
16
+ LOADER: WorkerLoader;
17
+ CodemodeAgent: DurableObjectNamespace<CodemodeAgent>;
18
+ };
19
+
20
+ const pmTools = {
21
+ createProject: tool({
22
+ description: "Create a new project",
23
+ inputSchema: z.object({
24
+ name: z.string().describe("Project name"),
25
+ description: z.string().optional().describe("Project description")
26
+ }),
27
+ execute: async ({ name, description }) => ({
28
+ id: crypto.randomUUID(),
29
+ name,
30
+ description: description ?? ""
31
+ })
32
+ }),
33
+
34
+ listProjects: tool({
35
+ description: "List all projects",
36
+ inputSchema: z.object({}),
37
+ execute: async () => [
38
+ { id: "proj-1", name: "Alpha", description: "First project" },
39
+ { id: "proj-2", name: "Beta", description: "Second project" }
40
+ ]
41
+ }),
42
+
43
+ addNumbers: tool({
44
+ description: "Add two numbers together",
45
+ inputSchema: z.object({
46
+ a: z.number().describe("First number"),
47
+ b: z.number().describe("Second number")
48
+ }),
49
+ execute: async ({ a, b }) => ({ result: a + b })
50
+ }),
51
+
52
+ getWeather: tool({
53
+ description: "Get the current weather for a city",
54
+ inputSchema: z.object({
55
+ city: z.string().describe("The city name")
56
+ }),
57
+ execute: async ({ city }) => ({
58
+ city,
59
+ temperature: 22,
60
+ condition: "Sunny"
61
+ })
62
+ })
63
+ };
64
+
65
+ export class CodemodeAgent extends Agent<Env> {
66
+ observability = undefined;
67
+
68
+ async onRequest(request: Request): Promise<Response> {
69
+ const url = new URL(request.url);
70
+
71
+ if (url.pathname.endsWith("/chat") && request.method === "POST") {
72
+ return this.handleChat(request);
73
+ }
74
+
75
+ if (url.pathname.endsWith("/generate-types")) {
76
+ return Response.json({ types: generateTypes(pmTools) });
77
+ }
78
+
79
+ return new Response("Not found", { status: 404 });
80
+ }
81
+
82
+ async handleChat(request: Request): Promise<Response> {
83
+ const body = (await request.json()) as { messages: UIMessage[] };
84
+
85
+ const workersai = createWorkersAI({ binding: this.env.AI });
86
+ const model = workersai("@cf/zai-org/glm-4.7-flash");
87
+
88
+ const executor = new DynamicWorkerExecutor({
89
+ loader: this.env.LOADER
90
+ });
91
+
92
+ const codemode = createCodeTool({
93
+ tools: pmTools,
94
+ executor
95
+ });
96
+
97
+ const result = streamText({
98
+ model,
99
+ system: `You are a helpful assistant with access to a codemode tool.
100
+ When asked to perform operations, use the codemode tool to write JavaScript code that calls the available functions on the \`codemode\` object.
101
+ Keep responses very short (1-2 sentences max).
102
+ When asked to add numbers, use the addNumbers tool via codemode.
103
+ When asked about weather, use the getWeather tool via codemode.
104
+ When asked about projects, use createProject or listProjects via codemode.`,
105
+ messages: await convertToModelMessages(body.messages),
106
+ tools: { codemode },
107
+ stopWhen: stepCountIs(5)
108
+ });
109
+
110
+ return result.toTextStreamResponse();
111
+ }
112
+ }
113
+
114
+ export default {
115
+ async fetch(request: Request, env: Env, _ctx: ExecutionContext) {
116
+ const url = new URL(request.url);
117
+
118
+ if (url.pathname.startsWith("/agents/")) {
119
+ return (
120
+ (await routeAgentRequest(request, env)) ||
121
+ new Response("Not found", { status: 404 })
122
+ );
123
+ }
124
+
125
+ if (url.pathname === "/run" && request.method === "POST") {
126
+ const agent = await getAgentByName(env.CodemodeAgent, "e2e-test");
127
+ const agentUrl = new URL(request.url);
128
+ agentUrl.pathname = "/chat";
129
+ return agent.fetch(
130
+ new Request(agentUrl.toString(), {
131
+ method: "POST",
132
+ headers: request.headers,
133
+ body: request.body
134
+ })
135
+ );
136
+ }
137
+
138
+ if (url.pathname === "/types") {
139
+ return Response.json({ types: generateTypes(pmTools) });
140
+ }
141
+
142
+ return new Response("OK");
143
+ }
144
+ };
@@ -0,0 +1,14 @@
1
+ {
2
+ "ai": { "binding": "AI", "remote": true },
3
+ "compatibility_date": "2026-01-28",
4
+ "compatibility_flags": ["nodejs_compat"],
5
+ "define": {
6
+ "__filename": "'index.ts'"
7
+ },
8
+ "durable_objects": {
9
+ "bindings": [{ "class_name": "CodemodeAgent", "name": "CodemodeAgent" }]
10
+ },
11
+ "main": "worker.ts",
12
+ "migrations": [{ "new_sqlite_classes": ["CodemodeAgent"], "tag": "v1" }],
13
+ "worker_loaders": [{ "binding": "LOADER" }]
14
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudflare/codemode",
3
- "version": "0.0.8",
3
+ "version": "0.1.0",
4
4
  "description": "Code Mode: use LLMs to generate executable code that performs tool calls",
5
5
  "repository": {
6
6
  "directory": "packages/codemode",
@@ -11,18 +11,26 @@
11
11
  "url": "https://github.com/cloudflare/agents/issues"
12
12
  },
13
13
  "dependencies": {
14
+ "acorn": "^8.16.0",
14
15
  "zod-to-ts": "^2.0.0"
15
16
  },
16
17
  "devDependencies": {
17
- "ai": "^6.0.86",
18
+ "@playwright/test": "^1.58.2",
19
+ "ai": "^6.0.94",
20
+ "vitest": "3.2.4",
18
21
  "zod": "^4.3.6"
19
22
  },
20
23
  "peerDependencies": {
21
- "agents": "^0.5.0",
22
24
  "ai": "^6.0.0",
23
25
  "zod": "^3.25.0 || ^4.0.0"
24
26
  },
27
+ "types": "dist/index.d.ts",
25
28
  "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js",
32
+ "require": "./dist/index.js"
33
+ },
26
34
  "./ai": {
27
35
  "types": "./dist/ai.d.ts",
28
36
  "import": "./dist/ai.js",
@@ -30,7 +38,10 @@
30
38
  }
31
39
  },
32
40
  "scripts": {
33
- "build": "tsx ./scripts/build.ts"
41
+ "build": "tsx ./scripts/build.ts",
42
+ "test": "vitest run",
43
+ "test:e2e": "npx playwright test --config e2e/playwright.config.ts",
44
+ "test:watch": "vitest"
34
45
  },
35
46
  "keywords": [
36
47
  "cloudflare",
package/scripts/build.ts CHANGED
@@ -5,7 +5,7 @@ async function main() {
5
5
  await build({
6
6
  clean: true,
7
7
  dts: true,
8
- entry: ["src/ai.ts"],
8
+ entry: ["src/index.ts", "src/ai.ts"],
9
9
  skipNodeModulesBundle: true,
10
10
  external: ["cloudflare:workers"],
11
11
  format: "esm",
@@ -20,7 +20,6 @@ async function main() {
20
20
  }
21
21
 
22
22
  main().catch((err) => {
23
- // Build failures should fail
24
23
  console.error(err);
25
24
  process.exit(1);
26
25
  });
package/src/ai.ts CHANGED
@@ -1,247 +1 @@
1
- import { generateObject, tool, type ToolSet, type LanguageModel } from "ai";
2
- import { z } from "zod";
3
- import { compile as compileJsonSchemaToTs } from "json-schema-to-typescript";
4
- import {
5
- zodToTs,
6
- printNode as printNodeZodToTs,
7
- createTypeAlias
8
- } from "zod-to-ts";
9
- import { getAgentByName } from "agents";
10
- import { env, WorkerEntrypoint } from "cloudflare:workers";
11
- import { openai } from "@ai-sdk/openai";
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
- model?: LanguageModel;
45
- }): Promise<{
46
- prompt: string;
47
- tools: ToolSet;
48
- }> {
49
- const generatedTypes = await generateTypes(options.tools);
50
- const prompt = `${options.prompt}
51
- You are a helpful assistant. You have access to the "codemode" tool that can do different things:
52
-
53
- ${getToolDescriptions(options.tools)}
54
-
55
- 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.
56
-
57
- `;
58
-
59
- const codemodeTool = tool({
60
- description: "codemode: a tool that can generate code to achieve a goal",
61
- inputSchema: z.object({
62
- functionDescription: z.string()
63
- }),
64
- outputSchema: z.object({
65
- code: z.string(),
66
- result: z.any()
67
- }),
68
- execute: async ({ functionDescription }) => {
69
- try {
70
- const response = await generateObject({
71
- model: options.model ? options.model : openai("gpt-4.1"),
72
- schema: z.object({
73
- code: z.string()
74
- }),
75
- prompt: `You are a code generating machine.
76
-
77
- In addition to regular javascript, you can also use the following functions:
78
-
79
- ${generatedTypes}
80
-
81
- Respond only with the code, nothing else. Output javascript code.
82
-
83
- Generate an async function that achieves the goal. This async function doesn't accept any arguments.
84
-
85
- Here is user input: ${functionDescription}` // insert ts types for the tools here
86
- });
87
-
88
- // console.log("args", response.object.args);
89
- const evaluator = createEvaluator(response.object.code, {
90
- proxy: options.proxy,
91
- loader: options.loader
92
- });
93
- const result = await evaluator();
94
- return { code: response.object.code, result: result };
95
- } catch (error) {
96
- console.error("error", error);
97
- throw error;
98
- // return { code: "", result: error };
99
- }
100
- }
101
- });
102
-
103
- return { prompt, tools: { codemode: codemodeTool } };
104
- }
105
-
106
- function createEvaluator(
107
- code: string,
108
- options: {
109
- loader: WorkerLoader;
110
- proxy: Fetcher<CodeModeProxy>;
111
- }
112
- ) {
113
- return async () => {
114
- const worker = options.loader.get(`code-${Math.random()}`, () => {
115
- return {
116
- compatibilityDate: "2025-06-01",
117
- compatibilityFlags: ["nodejs_compat"],
118
- mainModule: "foo.js",
119
- modules: {
120
- "foo.js": `
121
- import { env, WorkerEntrypoint } from "cloudflare:workers";
122
-
123
- export default class CodeModeWorker extends WorkerEntrypoint {
124
- async evaluate() {
125
- try {
126
- const { CodeModeProxy } = env;
127
- const codemode = new Proxy(
128
- {},
129
- {
130
- get: (target, prop) => {
131
- return (args) => {
132
- return CodeModeProxy.callFunction({
133
- functionName: prop,
134
- args: args,
135
- });
136
- };
137
- }
138
- }
139
- );
140
-
141
- return await ${code}();
142
- } catch (err) {
143
- return {
144
- err: err.message,
145
- stack: err.stack
146
- };
147
- }
148
- }
149
- }
150
-
151
- `
152
- },
153
- env: {
154
- // insert keys and bindings to tools/ts functions here
155
- CodeModeProxy: options.proxy
156
- },
157
- globalOutbound: null
158
- };
159
- });
160
-
161
- // @ts-expect-error TODO: fix this
162
- return await worker.getEntrypoint().evaluate();
163
- };
164
- }
165
-
166
- async function generateTypes(tools: ToolSet) {
167
- let availableTools = "";
168
- let availableTypes = "";
169
-
170
- for (const [toolName, tool] of Object.entries(tools)) {
171
- // @ts-expect-error TODO: fix this
172
- const inputJsonType = tool.inputSchema.jsonSchema
173
- ? await compileJsonSchemaToTs(
174
- // @ts-expect-error TODO: fix this
175
- tool.inputSchema.jsonSchema,
176
- `${toCamelCase(toolName)}Input`,
177
- {
178
- format: false,
179
- bannerComment: " "
180
- }
181
- )
182
- : printNodeZodToTs(
183
- createTypeAlias(
184
- zodToTs(
185
- // @ts-expect-error TODO: fix this
186
- tool.inputSchema,
187
- `${toCamelCase(toolName)}Input`
188
- ).node,
189
- `${toCamelCase(toolName)}Input`
190
- )
191
- );
192
-
193
- const outputJsonType =
194
- // @ts-expect-error TODO: fix this
195
- tool.outputSchema?.jsonSchema
196
- ? await compileJsonSchemaToTs(
197
- // @ts-expect-error TODO: fix this
198
- tool.outputSchema?.jsonSchema,
199
- `${toCamelCase(toolName)}Output`,
200
- {
201
- format: false,
202
- bannerComment: " "
203
- }
204
- )
205
- : tool.outputSchema
206
- ? printNodeZodToTs(
207
- createTypeAlias(
208
- zodToTs(
209
- // @ts-expect-error TODO: fix this
210
- tool.outputSchema,
211
- `${toCamelCase(toolName)}Output`
212
- ).node,
213
- `${toCamelCase(toolName)}Output`
214
- )
215
- )
216
- : `interface ${toCamelCase(toolName)}Output { [key: string]: any }`;
217
-
218
- const InputType = inputJsonType
219
- .trim()
220
- .replace("export interface", "interface");
221
-
222
- const OutputType = outputJsonType
223
- .trim()
224
- .replace("export interface", "interface");
225
-
226
- availableTypes += `\n${InputType}`;
227
- availableTypes += `\n${OutputType}`;
228
- availableTools += `\n\t/*\n\t${tool.description?.trim()}\n\t*/`;
229
- availableTools += `\n\t${toolName}: (input: ${toCamelCase(toolName)}Input) => Promise<${toCamelCase(toolName)}Output>;`;
230
- availableTools += "\n";
231
- }
232
-
233
- availableTools = `\ndeclare const codemode: {${availableTools}}`;
234
-
235
- return `
236
- ${availableTypes}
237
- ${availableTools}
238
- `;
239
- }
240
-
241
- function getToolDescriptions(tools: ToolSet) {
242
- return Object.entries(tools)
243
- .map(([_toolName, tool]) => {
244
- return `\n- ${tool.description?.trim()}`;
245
- })
246
- .join("");
247
- }
1
+ export { createCodeTool, type CreateCodeToolOptions } from "./tool";