@cloudflare/codemode 0.1.1 → 0.1.2
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/package.json +7 -3
- package/CHANGELOG.md +0 -109
- package/e2e/codemode.spec.ts +0 -124
- package/e2e/playwright.config.ts +0 -24
- package/e2e/worker.ts +0 -144
- package/e2e/wrangler.jsonc +0 -14
- package/scripts/build.ts +0 -25
- package/src/ai.ts +0 -1
- package/src/executor.ts +0 -170
- package/src/index.ts +0 -13
- package/src/tests/cloudflare-test.d.ts +0 -5
- package/src/tests/executor.test.ts +0 -224
- package/src/tests/schema-conversion.test.ts +0 -1068
- package/src/tests/tool.test.ts +0 -454
- package/src/tests/tsconfig.json +0 -10
- package/src/tests/types.test.ts +0 -446
- package/src/tool.ts +0 -152
- package/src/types.ts +0 -677
- package/tsconfig.json +0 -4
- package/vitest.config.ts +0 -17
- package/wrangler.jsonc +0 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudflare/codemode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Code Mode: use LLMs to generate executable code that performs tool calls",
|
|
5
5
|
"repository": {
|
|
6
6
|
"directory": "packages/codemode",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@playwright/test": "^1.58.2",
|
|
19
|
-
"ai": "^6.0.
|
|
19
|
+
"ai": "^6.0.105",
|
|
20
20
|
"vitest": "3.2.4",
|
|
21
21
|
"zod": "^4.3.6"
|
|
22
22
|
},
|
|
@@ -52,5 +52,9 @@
|
|
|
52
52
|
],
|
|
53
53
|
"author": "Cloudflare Inc.",
|
|
54
54
|
"license": "MIT",
|
|
55
|
-
"type": "module"
|
|
55
|
+
"type": "module",
|
|
56
|
+
"files": [
|
|
57
|
+
"dist",
|
|
58
|
+
"README.md"
|
|
59
|
+
]
|
|
56
60
|
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# @cloudflare/codemode
|
|
2
|
-
|
|
3
|
-
## 0.1.1
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- [#962](https://github.com/cloudflare/agents/pull/962) [`ef46d68`](https://github.com/cloudflare/agents/commit/ef46d68e9c381b7541c4aa803014144abce4fb72) Thanks [@tumberger](https://github.com/tumberger)! - Validate tool arguments against Zod schema before execution in codemode sandbox
|
|
8
|
-
|
|
9
|
-
- [#973](https://github.com/cloudflare/agents/pull/973) [`969fbff`](https://github.com/cloudflare/agents/commit/969fbff702d5702c1f0ea6faaecb3dfd0431a01b) Thanks [@threepointone](https://github.com/threepointone)! - Update dependencies
|
|
10
|
-
|
|
11
|
-
- [#960](https://github.com/cloudflare/agents/pull/960) [`179b8cb`](https://github.com/cloudflare/agents/commit/179b8cbc60bc9e6ac0d2ee26c430d842950f5f08) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Harden JSON Schema to TypeScript converter for production use
|
|
12
|
-
- Add depth and circular reference guards to prevent stack overflows on recursive or deeply nested schemas
|
|
13
|
-
- Add `$ref` resolution for internal JSON Pointers (`#/definitions/...`, `#/$defs/...`, `#`)
|
|
14
|
-
- Add tuple support (`prefixItems` for JSON Schema 2020-12, array `items` for draft-07)
|
|
15
|
-
- Add OpenAPI 3.0 `nullable: true` support across all schema branches
|
|
16
|
-
- Fix string escaping in enum/const values, property names (control chars, U+2028/U+2029), and JSDoc comments (`*/`)
|
|
17
|
-
- Add per-tool error isolation in `generateTypes()` so one malformed schema cannot crash the pipeline
|
|
18
|
-
- Guard missing `inputSchema` in `getAITools()` with a fallback to `{ type: "object" }`
|
|
19
|
-
- Add per-tool error isolation in `getAITools()` so one bad MCP tool does not break the entire tool set
|
|
20
|
-
|
|
21
|
-
- [#961](https://github.com/cloudflare/agents/pull/961) [`f6aa79f`](https://github.com/cloudflare/agents/commit/f6aa79f3bf86922db73b4d33439262aefcbcf817) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Updated default tool prompt to explicitly request JavaScript code from LLMs, preventing TypeScript syntax errors in the Dynamic Worker executor.
|
|
22
|
-
|
|
23
|
-
## 0.1.0
|
|
24
|
-
|
|
25
|
-
### Minor Changes
|
|
26
|
-
|
|
27
|
-
- [#879](https://github.com/cloudflare/agents/pull/879) [`90e54da`](https://github.com/cloudflare/agents/commit/90e54dab21f7c2c783aac117693918765e8b254b) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Remove experimental_codemode() and CodeModeProxy. Replace with createCodeTool() from @cloudflare/codemode/ai which returns a standard AI SDK Tool. The package no longer owns an LLM call or model choice. Users call streamText/generateText with their own model and pass the codemode tool.
|
|
28
|
-
|
|
29
|
-
The AI-dependent export (createCodeTool) is now at @cloudflare/codemode/ai. The root export (@cloudflare/codemode) contains the executor, type generation, and utilities which do not require the ai peer dependency.
|
|
30
|
-
|
|
31
|
-
ToolDispatcher (extends RpcTarget) replaces CodeModeProxy (extends WorkerEntrypoint) for dispatching tool calls from the sandbox back to the host. It is passed as a parameter to the dynamic worker's evaluate() method instead of being injected as an env binding, removing the need for CodeModeProxy and globalOutbound service bindings. Only a WorkerLoader binding is required now. globalOutbound on DynamicWorkerExecutor defaults to null which blocks fetch/connect at the runtime level. New Executor interface (execute(code, fns) => ExecuteResult) allows custom sandbox implementations. DynamicWorkerExecutor is the Cloudflare Workers implementation. Console output captured in ExecuteResult.logs. Configurable execution timeout.
|
|
32
|
-
|
|
33
|
-
AST-based code normalization via acorn replaces regex. sanitizeToolName() exported for converting MCP-style tool names to valid JS identifiers.
|
|
34
|
-
|
|
35
|
-
### Patch Changes
|
|
36
|
-
|
|
37
|
-
- [#954](https://github.com/cloudflare/agents/pull/954) [`943c407`](https://github.com/cloudflare/agents/commit/943c4070992bb836625abb5bf4e3271a6f52f7a2) Thanks [@threepointone](https://github.com/threepointone)! - update dependencies
|
|
38
|
-
|
|
39
|
-
## 0.0.8
|
|
40
|
-
|
|
41
|
-
### Patch Changes
|
|
42
|
-
|
|
43
|
-
- [#916](https://github.com/cloudflare/agents/pull/916) [`24e16e0`](https://github.com/cloudflare/agents/commit/24e16e025b82dbd7b321339a18c6d440b2879136) Thanks [@threepointone](https://github.com/threepointone)! - Widen peer dependency ranges across packages to prevent cascading major bumps during 0.x minor releases. Mark `@cloudflare/ai-chat` and `@cloudflare/codemode` as optional peer dependencies of `agents` to fix unmet peer dependency warnings during installation.
|
|
44
|
-
|
|
45
|
-
## 0.0.7
|
|
46
|
-
|
|
47
|
-
### Patch Changes
|
|
48
|
-
|
|
49
|
-
- [#849](https://github.com/cloudflare/agents/pull/849) [`21a7977`](https://github.com/cloudflare/agents/commit/21a79778f5150aecd890f55a164d397f70db681e) Thanks [@Muhammad-Bin-Ali](https://github.com/Muhammad-Bin-Ali)! - Allow configurable model in `experimental_codemode` instead of hardcoded `gpt-4.1`
|
|
50
|
-
|
|
51
|
-
- [#859](https://github.com/cloudflare/agents/pull/859) [`3de98a3`](https://github.com/cloudflare/agents/commit/3de98a398d55aeca51c7b845ed4c5d6051887d6d) Thanks [@threepointone](https://github.com/threepointone)! - broaden peer deps
|
|
52
|
-
|
|
53
|
-
- [#865](https://github.com/cloudflare/agents/pull/865) [`c3211d0`](https://github.com/cloudflare/agents/commit/c3211d0b0cc36aa294c15569ae650d3afeab9926) Thanks [@threepointone](https://github.com/threepointone)! - update dependencies
|
|
54
|
-
|
|
55
|
-
## 0.0.6
|
|
56
|
-
|
|
57
|
-
### Patch Changes
|
|
58
|
-
|
|
59
|
-
- [#813](https://github.com/cloudflare/agents/pull/813) [`7aebab3`](https://github.com/cloudflare/agents/commit/7aebab369d1bef6c685e05a4a3bd6627edcb87db) Thanks [@threepointone](https://github.com/threepointone)! - update dependencies
|
|
60
|
-
|
|
61
|
-
- [#800](https://github.com/cloudflare/agents/pull/800) [`a54edf5`](https://github.com/cloudflare/agents/commit/a54edf56b462856d1ef4f424c2363ac43a53c46e) Thanks [@threepointone](https://github.com/threepointone)! - Update dependencies
|
|
62
|
-
|
|
63
|
-
- [#818](https://github.com/cloudflare/agents/pull/818) [`7c74336`](https://github.com/cloudflare/agents/commit/7c743360d7e3639e187725391b9d5c114838bd18) Thanks [@threepointone](https://github.com/threepointone)! - update dependencies
|
|
64
|
-
|
|
65
|
-
- Updated dependencies [[`0c3c9bb`](https://github.com/cloudflare/agents/commit/0c3c9bb62ceff66ed38d3bbd90c767600f1f3453), [`0c3c9bb`](https://github.com/cloudflare/agents/commit/0c3c9bb62ceff66ed38d3bbd90c767600f1f3453), [`d1a0c2b`](https://github.com/cloudflare/agents/commit/d1a0c2b73b1119d71e120091753a6bcca0e2faa9), [`6218541`](https://github.com/cloudflare/agents/commit/6218541e9c1e40ccbaa25b2d9d93858c0ad81ffa), [`6218541`](https://github.com/cloudflare/agents/commit/6218541e9c1e40ccbaa25b2d9d93858c0ad81ffa), [`6218541`](https://github.com/cloudflare/agents/commit/6218541e9c1e40ccbaa25b2d9d93858c0ad81ffa), [`6218541`](https://github.com/cloudflare/agents/commit/6218541e9c1e40ccbaa25b2d9d93858c0ad81ffa), [`fd79481`](https://github.com/cloudflare/agents/commit/fd7948180abf066fa3d27911a83ffb4c91b3f099), [`6218541`](https://github.com/cloudflare/agents/commit/6218541e9c1e40ccbaa25b2d9d93858c0ad81ffa), [`0c3c9bb`](https://github.com/cloudflare/agents/commit/0c3c9bb62ceff66ed38d3bbd90c767600f1f3453), [`6218541`](https://github.com/cloudflare/agents/commit/6218541e9c1e40ccbaa25b2d9d93858c0ad81ffa), [`6218541`](https://github.com/cloudflare/agents/commit/6218541e9c1e40ccbaa25b2d9d93858c0ad81ffa), [`e20da53`](https://github.com/cloudflare/agents/commit/e20da5319eb46bac6ac580edf71836b00ac6f8bb), [`f604008`](https://github.com/cloudflare/agents/commit/f604008957f136241815909319a552bad6738b58), [`7aebab3`](https://github.com/cloudflare/agents/commit/7aebab369d1bef6c685e05a4a3bd6627edcb87db), [`a54edf5`](https://github.com/cloudflare/agents/commit/a54edf56b462856d1ef4f424c2363ac43a53c46e), [`7c74336`](https://github.com/cloudflare/agents/commit/7c743360d7e3639e187725391b9d5c114838bd18), [`6218541`](https://github.com/cloudflare/agents/commit/6218541e9c1e40ccbaa25b2d9d93858c0ad81ffa), [`ded8d3e`](https://github.com/cloudflare/agents/commit/ded8d3e8aeba0358ebd4aecb5ba15344b5a21db1)]:
|
|
66
|
-
- agents@0.3.7
|
|
67
|
-
|
|
68
|
-
## 0.0.5
|
|
69
|
-
|
|
70
|
-
### Patch Changes
|
|
71
|
-
|
|
72
|
-
- [#776](https://github.com/cloudflare/agents/pull/776) [`93c613e`](https://github.com/cloudflare/agents/commit/93c613e077e7aa16e78cf9b0b53e285577e92ce5) Thanks [@ShoeBoom](https://github.com/ShoeBoom)! - prepend custom prompt to default assistant text
|
|
73
|
-
|
|
74
|
-
- Updated dependencies [[`395f461`](https://github.com/cloudflare/agents/commit/395f46105d3affb5a2e2ffd28c516a0eefe45bb4), [`f27e62c`](https://github.com/cloudflare/agents/commit/f27e62c24f586abb285843db183198230ddd47ca)]:
|
|
75
|
-
- agents@0.3.6
|
|
76
|
-
|
|
77
|
-
## 0.0.4
|
|
78
|
-
|
|
79
|
-
### Patch Changes
|
|
80
|
-
|
|
81
|
-
- [#771](https://github.com/cloudflare/agents/pull/771) [`87dc96d`](https://github.com/cloudflare/agents/commit/87dc96d19de1d26dbb2badecbb9955a4eb8e9e2e) Thanks [@threepointone](https://github.com/threepointone)! - update dependencies
|
|
82
|
-
|
|
83
|
-
- Updated dependencies [[`cf8a1e7`](https://github.com/cloudflare/agents/commit/cf8a1e7a24ecaac62c2aefca7b0fd5bf1373e8bd), [`87dc96d`](https://github.com/cloudflare/agents/commit/87dc96d19de1d26dbb2badecbb9955a4eb8e9e2e)]:
|
|
84
|
-
- agents@0.3.4
|
|
85
|
-
|
|
86
|
-
## 0.0.3
|
|
87
|
-
|
|
88
|
-
### Patch Changes
|
|
89
|
-
|
|
90
|
-
- [`a5d0137`](https://github.com/cloudflare/agents/commit/a5d01379b9ad2d88bc028c50f1858b4e69f106c5) Thanks [@threepointone](https://github.com/threepointone)! - trigger a new release
|
|
91
|
-
|
|
92
|
-
- Updated dependencies [[`a5d0137`](https://github.com/cloudflare/agents/commit/a5d01379b9ad2d88bc028c50f1858b4e69f106c5)]:
|
|
93
|
-
- agents@0.3.3
|
|
94
|
-
|
|
95
|
-
## 0.0.2
|
|
96
|
-
|
|
97
|
-
### Patch Changes
|
|
98
|
-
|
|
99
|
-
- [#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
|
|
100
|
-
|
|
101
|
-
Extract @cloudflare/ai-chat and @cloudflare/codemode into their own packages
|
|
102
|
-
with comprehensive READMEs. Update agents README to remove chat-specific
|
|
103
|
-
content and point to new packages. Fix documentation imports to reflect
|
|
104
|
-
new package structure.
|
|
105
|
-
|
|
106
|
-
Maintains backward compatibility, no breaking changes.
|
|
107
|
-
|
|
108
|
-
- Updated dependencies [[`0c4275f`](https://github.com/cloudflare/agents/commit/0c4275f8f4b71c264c32c3742d151ef705739c2f), [`f12553f`](https://github.com/cloudflare/agents/commit/f12553f2fa65912c68d9a7620b9a11b70b8790a2)]:
|
|
109
|
-
- agents@0.3.2
|
package/e2e/codemode.spec.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
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
|
-
});
|
package/e2e/playwright.config.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
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
|
-
};
|
package/e2e/wrangler.jsonc
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
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/scripts/build.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
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/index.ts", "src/ai.ts"],
|
|
9
|
-
skipNodeModulesBundle: true,
|
|
10
|
-
external: ["cloudflare:workers"],
|
|
11
|
-
format: "esm",
|
|
12
|
-
sourcemap: true,
|
|
13
|
-
fixedExtension: false
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
// then run oxfmt on the generated .d.ts files
|
|
17
|
-
execSync("oxfmt --write ./dist/*.d.ts");
|
|
18
|
-
|
|
19
|
-
process.exit(0);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
main().catch((err) => {
|
|
23
|
-
console.error(err);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
});
|
package/src/ai.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { createCodeTool, type CreateCodeToolOptions } from "./tool";
|
package/src/executor.ts
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Executor interface and DynamicWorkerExecutor implementation.
|
|
3
|
-
*
|
|
4
|
-
* The Executor interface is the core abstraction — implement it to run
|
|
5
|
-
* LLM-generated code in any sandbox (Workers, QuickJS, Node VM, etc.).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { RpcTarget } from "cloudflare:workers";
|
|
9
|
-
|
|
10
|
-
export interface ExecuteResult {
|
|
11
|
-
result: unknown;
|
|
12
|
-
error?: string;
|
|
13
|
-
logs?: string[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* An executor runs LLM-generated code in a sandbox, making the provided
|
|
18
|
-
* tool functions callable as `codemode.*` inside the sandbox.
|
|
19
|
-
*
|
|
20
|
-
* Implementations should never throw — errors are returned in `ExecuteResult.error`.
|
|
21
|
-
*/
|
|
22
|
-
export interface Executor {
|
|
23
|
-
execute(
|
|
24
|
-
code: string,
|
|
25
|
-
fns: Record<string, (...args: unknown[]) => Promise<unknown>>
|
|
26
|
-
): Promise<ExecuteResult>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// -- ToolDispatcher (RPC target for tool calls from sandboxed Workers) --
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* An RpcTarget that dispatches tool calls from the sandboxed Worker
|
|
33
|
-
* back to the host. Passed via Workers RPC to the dynamic Worker's
|
|
34
|
-
* evaluate() method — no globalOutbound or Fetcher bindings needed.
|
|
35
|
-
*/
|
|
36
|
-
export class ToolDispatcher extends RpcTarget {
|
|
37
|
-
#fns: Record<string, (...args: unknown[]) => Promise<unknown>>;
|
|
38
|
-
|
|
39
|
-
constructor(fns: Record<string, (...args: unknown[]) => Promise<unknown>>) {
|
|
40
|
-
super();
|
|
41
|
-
this.#fns = fns;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async call(name: string, argsJson: string): Promise<string> {
|
|
45
|
-
const fn = this.#fns[name];
|
|
46
|
-
if (!fn) {
|
|
47
|
-
return JSON.stringify({ error: `Tool "${name}" not found` });
|
|
48
|
-
}
|
|
49
|
-
try {
|
|
50
|
-
const args = argsJson ? JSON.parse(argsJson) : {};
|
|
51
|
-
const result = await fn(args);
|
|
52
|
-
return JSON.stringify({ result });
|
|
53
|
-
} catch (err) {
|
|
54
|
-
return JSON.stringify({
|
|
55
|
-
error: err instanceof Error ? err.message : String(err)
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// -- DynamicWorkerExecutor (Cloudflare Workers) --
|
|
62
|
-
|
|
63
|
-
export interface DynamicWorkerExecutorOptions {
|
|
64
|
-
loader: WorkerLoader;
|
|
65
|
-
/**
|
|
66
|
-
* Timeout in milliseconds for code execution. Defaults to 30000 (30s).
|
|
67
|
-
*/
|
|
68
|
-
timeout?: number;
|
|
69
|
-
/**
|
|
70
|
-
* Controls outbound network access from sandboxed code.
|
|
71
|
-
* - `null` (default): fetch() and connect() throw — sandbox is fully isolated.
|
|
72
|
-
* - `undefined`: inherits parent Worker's network access (full internet).
|
|
73
|
-
* - A `Fetcher`: all outbound requests route through this handler.
|
|
74
|
-
*/
|
|
75
|
-
globalOutbound?: Fetcher | null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Executes code in an isolated Cloudflare Worker via WorkerLoader.
|
|
80
|
-
* Tool calls are dispatched via Workers RPC — the host passes a
|
|
81
|
-
* ToolDispatcher (RpcTarget) to the Worker's evaluate() method.
|
|
82
|
-
*
|
|
83
|
-
* External fetch() and connect() are blocked by default via
|
|
84
|
-
* `globalOutbound: null` (runtime-enforced). Pass a Fetcher to
|
|
85
|
-
* `globalOutbound` to allow controlled outbound access.
|
|
86
|
-
*/
|
|
87
|
-
export class DynamicWorkerExecutor implements Executor {
|
|
88
|
-
#loader: WorkerLoader;
|
|
89
|
-
#timeout: number;
|
|
90
|
-
#globalOutbound: Fetcher | null;
|
|
91
|
-
|
|
92
|
-
constructor(options: DynamicWorkerExecutorOptions) {
|
|
93
|
-
this.#loader = options.loader;
|
|
94
|
-
this.#timeout = options.timeout ?? 30000;
|
|
95
|
-
this.#globalOutbound = options.globalOutbound ?? null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async execute(
|
|
99
|
-
code: string,
|
|
100
|
-
fns: Record<string, (...args: unknown[]) => Promise<unknown>>
|
|
101
|
-
): Promise<ExecuteResult> {
|
|
102
|
-
const timeoutMs = this.#timeout;
|
|
103
|
-
|
|
104
|
-
const modulePrefix = [
|
|
105
|
-
'import { WorkerEntrypoint } from "cloudflare:workers";',
|
|
106
|
-
"",
|
|
107
|
-
"export default class CodeExecutor extends WorkerEntrypoint {",
|
|
108
|
-
" async evaluate(dispatcher) {",
|
|
109
|
-
" const __logs = [];",
|
|
110
|
-
' console.log = (...a) => { __logs.push(a.map(String).join(" ")); };',
|
|
111
|
-
' console.warn = (...a) => { __logs.push("[warn] " + a.map(String).join(" ")); };',
|
|
112
|
-
' console.error = (...a) => { __logs.push("[error] " + a.map(String).join(" ")); };',
|
|
113
|
-
" const codemode = new Proxy({}, {",
|
|
114
|
-
" get: (_, toolName) => async (args) => {",
|
|
115
|
-
" const resJson = await dispatcher.call(String(toolName), JSON.stringify(args ?? {}));",
|
|
116
|
-
" const data = JSON.parse(resJson);",
|
|
117
|
-
" if (data.error) throw new Error(data.error);",
|
|
118
|
-
" return data.result;",
|
|
119
|
-
" }",
|
|
120
|
-
" });",
|
|
121
|
-
"",
|
|
122
|
-
" try {",
|
|
123
|
-
" const result = await Promise.race([",
|
|
124
|
-
" ("
|
|
125
|
-
].join("\n");
|
|
126
|
-
|
|
127
|
-
const moduleSuffix = [
|
|
128
|
-
")(),",
|
|
129
|
-
' new Promise((_, reject) => setTimeout(() => reject(new Error("Execution timed out")), ' +
|
|
130
|
-
timeoutMs +
|
|
131
|
-
"))",
|
|
132
|
-
" ]);",
|
|
133
|
-
" return { result, logs: __logs };",
|
|
134
|
-
" } catch (err) {",
|
|
135
|
-
" return { result: undefined, error: err.message, logs: __logs };",
|
|
136
|
-
" }",
|
|
137
|
-
" }",
|
|
138
|
-
"}"
|
|
139
|
-
].join("\n");
|
|
140
|
-
|
|
141
|
-
const executorModule = modulePrefix + code + moduleSuffix;
|
|
142
|
-
|
|
143
|
-
const dispatcher = new ToolDispatcher(fns);
|
|
144
|
-
|
|
145
|
-
const worker = this.#loader.get(`codemode-${crypto.randomUUID()}`, () => ({
|
|
146
|
-
compatibilityDate: "2025-06-01",
|
|
147
|
-
compatibilityFlags: ["nodejs_compat"],
|
|
148
|
-
mainModule: "executor.js",
|
|
149
|
-
modules: {
|
|
150
|
-
"executor.js": executorModule
|
|
151
|
-
},
|
|
152
|
-
globalOutbound: this.#globalOutbound
|
|
153
|
-
}));
|
|
154
|
-
|
|
155
|
-
const entrypoint = worker.getEntrypoint() as unknown as {
|
|
156
|
-
evaluate(dispatcher: ToolDispatcher): Promise<{
|
|
157
|
-
result: unknown;
|
|
158
|
-
error?: string;
|
|
159
|
-
logs?: string[];
|
|
160
|
-
}>;
|
|
161
|
-
};
|
|
162
|
-
const response = await entrypoint.evaluate(dispatcher);
|
|
163
|
-
|
|
164
|
-
if (response.error) {
|
|
165
|
-
return { result: undefined, error: response.error, logs: response.logs };
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return { result: response.result, logs: response.logs };
|
|
169
|
-
}
|
|
170
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
DynamicWorkerExecutor,
|
|
3
|
-
ToolDispatcher,
|
|
4
|
-
type DynamicWorkerExecutorOptions,
|
|
5
|
-
type Executor,
|
|
6
|
-
type ExecuteResult
|
|
7
|
-
} from "./executor";
|
|
8
|
-
export {
|
|
9
|
-
generateTypes,
|
|
10
|
-
sanitizeToolName,
|
|
11
|
-
type ToolDescriptor,
|
|
12
|
-
type ToolDescriptors
|
|
13
|
-
} from "./types";
|