agents 0.16.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -8
- package/dist/{agent-tool-types-NofdbL9X.d.ts → agent-tool-types-Cd1TZPfB.d.ts} +595 -137
- package/dist/agent-tool-types.d.ts +34 -18
- package/dist/agent-tool-types.js +20 -1
- package/dist/agent-tool-types.js.map +1 -0
- package/dist/agent-tools-BXlsuX0d.js +304 -0
- package/dist/agent-tools-BXlsuX0d.js.map +1 -0
- package/dist/agent-tools-_E8wxUIK.d.ts +119 -0
- package/dist/agent-tools.d.ts +24 -18
- package/dist/agent-tools.js.map +1 -1
- package/dist/ai-chat-agent.d.ts +1 -1
- package/dist/ai-chat-agent.js +1 -2
- package/dist/ai-chat-agent.js.map +1 -1
- package/dist/ai-chat-v5-migration.d.ts +1 -1
- package/dist/ai-chat-v5-migration.js +1 -2
- package/dist/ai-chat-v5-migration.js.map +1 -1
- package/dist/ai-react.d.ts +1 -1
- package/dist/ai-react.js +1 -2
- package/dist/ai-react.js.map +1 -1
- package/dist/ai-types.d.ts +1 -7
- package/dist/ai-types.js +2 -8
- package/dist/ai-types.js.map +1 -1
- package/dist/browser/ai.js +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/chat/index.d.ts +1923 -76
- package/dist/chat/index.js +1784 -278
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +602 -0
- package/dist/chat/react.js +1506 -0
- package/dist/chat/react.js.map +1 -0
- package/dist/chat-sdk/index.d.ts +4 -4
- package/dist/{classPrivateFieldGet2-CZ7QjTXN.js → classPrivateFieldGet2-DZBYAB34.js} +5 -5
- package/dist/{classPrivateMethodInitSpec-D-0__zd9.js → classPrivateMethodInitSpec-qMjJ6sHQ.js} +2 -2
- package/dist/{client-FUizKzj2.js → client-BZ-B3NhC.js} +19 -3
- package/dist/client-BZ-B3NhC.js.map +1 -0
- package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
- package/dist/client.d.ts +76 -57
- package/dist/client.js +33 -5
- package/dist/client.js.map +1 -1
- package/dist/{compaction-helpers-DVcu5lPN.d.ts → compaction-helpers-wUz6M3us.d.ts} +20 -1
- package/dist/{connector-CrKhowfD.js → connector-CdldGF3h.js} +5 -5
- package/dist/{connector-CrKhowfD.js.map → connector-CdldGF3h.js.map} +1 -1
- package/dist/email.js +1 -1
- package/dist/email.js.map +1 -1
- package/dist/experimental/memory/session/index.d.ts +1 -1
- package/dist/experimental/memory/session/index.js +20 -2
- package/dist/experimental/memory/session/index.js.map +1 -1
- package/dist/experimental/memory/utils/index.d.ts +1 -1
- package/dist/{index-B7IbEeze.d.ts → index-CcbnKkNh.d.ts} +188 -14
- package/dist/index.d.ts +91 -71
- package/dist/index.js +562 -24
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts +18 -14
- package/dist/mcp/client.js +1 -1
- package/dist/mcp/index.d.ts +30 -30
- package/dist/mcp/index.js +7 -7
- package/dist/mcp/index.js.map +1 -1
- package/dist/{agent-tools-3zLG7MgA.js → message-builder-BymO4N_D.js} +45 -126
- package/dist/message-builder-BymO4N_D.js.map +1 -0
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +4 -2
- package/dist/observability/index.js.map +1 -1
- package/dist/react.d.ts +123 -110
- package/dist/react.js +44 -11
- package/dist/react.js.map +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/skills/compile.js +1 -1
- package/dist/skills/compile.js.map +1 -1
- package/dist/skills/index.js +5 -5
- package/dist/skills/index.js.map +1 -1
- package/dist/sub-routing.d.ts +6 -6
- package/dist/utils.js +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/vite.js +5 -5
- package/dist/vite.js.map +1 -1
- package/dist/wire-types-nflOzNuU.js +240 -0
- package/dist/wire-types-nflOzNuU.js.map +1 -0
- package/dist/{workflow-types-SrZK_o9p.d.ts → workflow-types-Baz_PO5v.d.ts} +42 -22
- package/dist/workflow-types.d.ts +25 -21
- package/dist/workflow-types.js.map +1 -1
- package/dist/workflows.d.ts +22 -21
- package/dist/workflows.js +31 -7
- package/dist/workflows.js.map +1 -1
- package/docs/adding-to-existing-project.md +450 -0
- package/docs/agent-class.md +503 -0
- package/docs/agent-tools.md +552 -0
- package/docs/browse-the-web.md +430 -0
- package/docs/callable-methods.md +627 -0
- package/docs/chat-agents.md +1687 -0
- package/docs/chat-sdk.md +181 -0
- package/docs/client-sdk.md +520 -0
- package/docs/client-tools-continuation.md +177 -0
- package/docs/codemode.md +440 -0
- package/docs/configuration.md +775 -0
- package/docs/cross-domain-authentication.md +171 -0
- package/docs/durable-execution.md +537 -0
- package/docs/email.md +663 -0
- package/docs/get-current-agent.md +204 -0
- package/docs/getting-started.md +305 -0
- package/docs/http-websockets.md +668 -0
- package/docs/human-in-the-loop.md +661 -0
- package/docs/index.md +151 -0
- package/docs/long-running-agents.md +730 -0
- package/docs/mcp-client.md +620 -0
- package/docs/mcp-servers.md +526 -0
- package/docs/mcp-transports.md +308 -0
- package/docs/migration-to-ai-sdk-v5.md +96 -0
- package/docs/migration-to-ai-sdk-v6.md +163 -0
- package/docs/observability.md +261 -0
- package/docs/push-notifications.md +367 -0
- package/docs/queue.md +329 -0
- package/docs/readonly-connections.md +278 -0
- package/docs/resumable-streaming.md +127 -0
- package/docs/retries.md +444 -0
- package/docs/routing.md +749 -0
- package/docs/scheduling.md +898 -0
- package/docs/securing-mcp-servers.md +359 -0
- package/docs/server-driven-messages.md +477 -0
- package/docs/sessions.md +1024 -0
- package/docs/state.md +512 -0
- package/docs/sub-agents.md +389 -0
- package/docs/webhooks.md +604 -0
- package/docs/workflows.md +877 -0
- package/package.json +47 -15
- package/dist/agent-tools-3zLG7MgA.js.map +0 -1
- package/dist/agent-tools-DLquv-dp.d.ts +0 -14
- package/dist/client-FUizKzj2.js.map +0 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Client-Side Tools and Auto-Continuation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Tools in `AIChatAgent` can be divided into two categories:
|
|
6
|
+
|
|
7
|
+
- **Server tools**: Have an `execute` function on the server. The AI SDK runs them automatically and the LLM continues responding in the same turn.
|
|
8
|
+
- **Client tools**: No `execute` function on the server. The tool call is sent to the client via `onToolCall`, and the client provides the result. By default, this requires a new request to continue.
|
|
9
|
+
|
|
10
|
+
With `autoContinueAfterToolResult`, client tools can behave like server tools -- the LLM calls a tool, the client executes it, and the server automatically continues the conversation in the same turn.
|
|
11
|
+
|
|
12
|
+
## Server Setup
|
|
13
|
+
|
|
14
|
+
Define a tool without an `execute` function. The AI SDK will pause and send `tool-input-available` to the client:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { AIChatAgent } from "@cloudflare/ai-chat";
|
|
18
|
+
import { createWorkersAI } from "workers-ai-provider";
|
|
19
|
+
import { streamText, tool, convertToModelMessages, stepCountIs } from "ai";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
|
|
22
|
+
export class MyAgent extends AIChatAgent {
|
|
23
|
+
async onChatMessage() {
|
|
24
|
+
const workersai = createWorkersAI({ binding: this.env.AI });
|
|
25
|
+
|
|
26
|
+
const result = streamText({
|
|
27
|
+
model: workersai("@cf/moonshotai/kimi-k2.7-code"),
|
|
28
|
+
messages: await convertToModelMessages(this.messages),
|
|
29
|
+
tools: {
|
|
30
|
+
// Client-side tool: no execute function
|
|
31
|
+
getUserLocation: tool({
|
|
32
|
+
description: "Get the user's location from their browser",
|
|
33
|
+
inputSchema: z.object({})
|
|
34
|
+
}),
|
|
35
|
+
|
|
36
|
+
// Server-side tool: has execute, runs automatically
|
|
37
|
+
getWeather: tool({
|
|
38
|
+
description: "Get weather for a city",
|
|
39
|
+
inputSchema: z.object({ city: z.string() }),
|
|
40
|
+
execute: async ({ city }) => fetchWeather(city)
|
|
41
|
+
})
|
|
42
|
+
},
|
|
43
|
+
stopWhen: stepCountIs(5) // Allow multi-step so the LLM can respond after tool results
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return result.toUIMessageStreamResponse();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Client Setup
|
|
52
|
+
|
|
53
|
+
Use `onToolCall` to handle client-side tool execution. Auto-continuation is enabled by default (`autoContinueAfterToolResult: true`), so the server automatically calls `onChatMessage()` again after receiving the tool result, letting the LLM continue in the same assistant message.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { useAgent } from "agents/react";
|
|
57
|
+
import { useAgentChat } from "@cloudflare/ai-chat/react";
|
|
58
|
+
|
|
59
|
+
function Chat() {
|
|
60
|
+
const agent = useAgent({ agent: "MyAgent" });
|
|
61
|
+
|
|
62
|
+
const { messages, sendMessage } = useAgentChat({
|
|
63
|
+
agent,
|
|
64
|
+
// Auto-continuation is enabled by default — no need to set this explicitly
|
|
65
|
+
// autoContinueAfterToolResult: true,
|
|
66
|
+
onToolCall: async ({ toolCall, addToolOutput }) => {
|
|
67
|
+
if (toolCall.toolName === "getUserLocation") {
|
|
68
|
+
const pos = await new Promise((resolve, reject) => {
|
|
69
|
+
navigator.geolocation.getCurrentPosition(resolve, reject);
|
|
70
|
+
});
|
|
71
|
+
addToolOutput({
|
|
72
|
+
toolCallId: toolCall.toolCallId,
|
|
73
|
+
output: {
|
|
74
|
+
lat: pos.coords.latitude,
|
|
75
|
+
lng: pos.coords.longitude
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Render messages...
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## How It Works
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
User: "What's the weather near me?"
|
|
90
|
+
|
|
91
|
+
1. Client sends message → Server calls LLM
|
|
92
|
+
2. LLM decides to call getUserLocation (no server execute)
|
|
93
|
+
3. Stream sends tool-input-available to client
|
|
94
|
+
4. onToolCall fires → client gets geolocation → sends CF_AGENT_TOOL_RESULT
|
|
95
|
+
5. Server receives result with autoContinue: true
|
|
96
|
+
6. Server waits for the original stream to complete
|
|
97
|
+
7. Server calls onChatMessage() again (continuation)
|
|
98
|
+
8. LLM sees the location result, calls getWeather (server execute)
|
|
99
|
+
9. LLM responds: "It's sunny and 72°F near you!"
|
|
100
|
+
10. Continuation parts are merged into the same assistant message
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The user sees a single seamless response, even though it involved a client-side tool call mid-stream.
|
|
104
|
+
|
|
105
|
+
## Without Auto-Continuation
|
|
106
|
+
|
|
107
|
+
When `autoContinueAfterToolResult` is set to `false`, the client must explicitly send a follow-up message after providing the tool result:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
const { messages, sendMessage, addToolOutput } = useAgentChat({
|
|
111
|
+
agent,
|
|
112
|
+
onToolCall: async ({ toolCall, addToolOutput: provide }) => {
|
|
113
|
+
if (toolCall.toolName === "getUserLocation") {
|
|
114
|
+
const pos = await getPosition();
|
|
115
|
+
provide({
|
|
116
|
+
toolCallId: toolCall.toolCallId,
|
|
117
|
+
output: { lat: pos.coords.latitude, lng: pos.coords.longitude }
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
autoContinueAfterToolResult: false, // Disable auto-continuation
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// After tool result is provided, send a follow-up to continue
|
|
125
|
+
// This creates a new assistant message rather than continuing the existing one
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Use this when you want explicit control over when the conversation continues, or when tool results need user review before proceeding.
|
|
129
|
+
|
|
130
|
+
## Combining with `needsApproval`
|
|
131
|
+
|
|
132
|
+
You can use client-side tools and approval together. For example, a tool that needs both user approval and browser execution:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// Server: tool with needsApproval but no execute
|
|
136
|
+
const shareLocation = tool({
|
|
137
|
+
description: "Share the user's location with a third party",
|
|
138
|
+
inputSchema: z.object({ service: z.string() }),
|
|
139
|
+
needsApproval: true
|
|
140
|
+
// No execute - client handles after approval
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
// Client: handle approval, then execute
|
|
146
|
+
const { addToolApprovalResponse } = useAgentChat({
|
|
147
|
+
agent,
|
|
148
|
+
autoContinueAfterToolResult: true,
|
|
149
|
+
onToolCall: async ({ toolCall, addToolOutput }) => {
|
|
150
|
+
if (toolCall.toolName === "shareLocation") {
|
|
151
|
+
const pos = await getPosition();
|
|
152
|
+
addToolOutput({
|
|
153
|
+
toolCallId: toolCall.toolCallId,
|
|
154
|
+
output: { lat: pos.coords.latitude, lng: pos.coords.longitude }
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The flow becomes: LLM calls tool → user approves → client executes → server auto-continues.
|
|
162
|
+
|
|
163
|
+
If the user denies the tool instead, you can provide a custom error message using `addToolOutput` with `state: "output-error"`:
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
// Deny with a reason instead of generic rejection
|
|
167
|
+
addToolOutput({
|
|
168
|
+
toolCallId: toolCall.toolCallId,
|
|
169
|
+
state: "output-error",
|
|
170
|
+
errorText: "User declined to share location"
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Related Docs
|
|
175
|
+
|
|
176
|
+
- [Chat Agents](./chat-agents.md) — Full `AIChatAgent` and `useAgentChat` reference
|
|
177
|
+
- [Human in the Loop](./human-in-the-loop.md) — Approval patterns including `needsApproval`
|
package/docs/codemode.md
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
# Codemode (Experimental)
|
|
2
|
+
|
|
3
|
+
Codemode lets LLMs write and execute code that orchestrates your tools, instead of calling them one at a time. Inspired by [CodeAct](https://machinelearning.apple.com/research/codeact), it works because LLMs are better at writing code than making individual tool calls — they have seen millions of lines of real-world TypeScript but only contrived tool-calling examples.
|
|
4
|
+
|
|
5
|
+
The `@cloudflare/codemode` package converts your tools into typed TypeScript APIs, gives the LLM a single "write code" tool, and executes the generated code in a secure, isolated Worker sandbox.
|
|
6
|
+
|
|
7
|
+
> **Experimental** — this feature may have breaking changes in future releases. Use with caution in production.
|
|
8
|
+
|
|
9
|
+
## When to use Codemode
|
|
10
|
+
|
|
11
|
+
Codemode is most useful when the LLM needs to:
|
|
12
|
+
|
|
13
|
+
- **Chain multiple tool calls** with logic between them (conditionals, loops, error handling)
|
|
14
|
+
- **Compose results** from different tools before returning
|
|
15
|
+
- **Work with MCP servers** that expose many fine-grained operations
|
|
16
|
+
- **Perform multi-step workflows** that would require many round-trips with standard tool calling
|
|
17
|
+
|
|
18
|
+
For simple, single tool calls, standard AI SDK tool calling is simpler and sufficient.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npm install @cloudflare/codemode ai zod
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
### 1. Define your tools
|
|
29
|
+
|
|
30
|
+
Use the standard AI SDK `tool()` function:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { tool } from "ai";
|
|
34
|
+
import { z } from "zod";
|
|
35
|
+
|
|
36
|
+
const tools = {
|
|
37
|
+
getWeather: tool({
|
|
38
|
+
description: "Get weather for a location",
|
|
39
|
+
inputSchema: z.object({ location: z.string() }),
|
|
40
|
+
execute: async ({ location }) => `Weather in ${location}: 72°F, sunny`
|
|
41
|
+
}),
|
|
42
|
+
sendEmail: tool({
|
|
43
|
+
description: "Send an email",
|
|
44
|
+
inputSchema: z.object({
|
|
45
|
+
to: z.string(),
|
|
46
|
+
subject: z.string(),
|
|
47
|
+
body: z.string()
|
|
48
|
+
}),
|
|
49
|
+
execute: async ({ to, subject, body }) => `Email sent to ${to}`
|
|
50
|
+
})
|
|
51
|
+
};
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. Create the codemode tool
|
|
55
|
+
|
|
56
|
+
`createCodeTool` takes your tools and an executor, and returns a single AI SDK tool:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { createCodeTool } from "@cloudflare/codemode/ai";
|
|
60
|
+
import { DynamicWorkerExecutor } from "@cloudflare/codemode";
|
|
61
|
+
|
|
62
|
+
const executor = new DynamicWorkerExecutor({
|
|
63
|
+
loader: env.LOADER
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const codemode = createCodeTool({ tools, executor });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Use it with streamText
|
|
70
|
+
|
|
71
|
+
Pass the codemode tool to `streamText` or `generateText` like any other tool. You choose the model:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { streamText } from "ai";
|
|
75
|
+
|
|
76
|
+
const result = streamText({
|
|
77
|
+
model,
|
|
78
|
+
system: "You are a helpful assistant.",
|
|
79
|
+
messages,
|
|
80
|
+
tools: { codemode }
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
When the LLM decides to use codemode, it writes an async arrow function like:
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
async () => {
|
|
88
|
+
const weather = await codemode.getWeather({ location: "London" });
|
|
89
|
+
if (weather.includes("sunny")) {
|
|
90
|
+
await codemode.sendEmail({
|
|
91
|
+
to: "team@example.com",
|
|
92
|
+
subject: "Nice day!",
|
|
93
|
+
body: `It's ${weather}`
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return { weather, notified: true };
|
|
97
|
+
};
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The code runs in an isolated Worker sandbox, tool calls are dispatched back to the host via Workers RPC, and the result is returned to the LLM.
|
|
101
|
+
|
|
102
|
+
## Configuration
|
|
103
|
+
|
|
104
|
+
### Wrangler bindings
|
|
105
|
+
|
|
106
|
+
Add a `worker_loaders` binding to your `wrangler.jsonc`. This is the only binding required:
|
|
107
|
+
|
|
108
|
+
```jsonc
|
|
109
|
+
// wrangler.jsonc
|
|
110
|
+
{
|
|
111
|
+
"worker_loaders": [{ "binding": "LOADER" }],
|
|
112
|
+
"compatibility_flags": ["nodejs_compat"]
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Vite configuration
|
|
117
|
+
|
|
118
|
+
If you use `zod-to-ts` (which codemode depends on), add a `__filename` define to your Vite config:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
// vite.config.ts
|
|
122
|
+
export default defineConfig({
|
|
123
|
+
plugins: [react(), cloudflare(), tailwindcss()],
|
|
124
|
+
define: {
|
|
125
|
+
__filename: "'index.ts'"
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## How it works
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
┌─────────────┐ ┌──────────────────────────────────────┐
|
|
134
|
+
│ │ │ Dynamic Worker (isolated sandbox) │
|
|
135
|
+
│ Host │ RPC │ │
|
|
136
|
+
│ Worker │◄──────►│ LLM-generated code runs here │
|
|
137
|
+
│ │ │ codemode.myTool() → dispatcher.call()│
|
|
138
|
+
│ ToolDispatcher │ │
|
|
139
|
+
│ holds tool fns │ fetch() blocked by default │
|
|
140
|
+
└─────────────┘ └──────────────────────────────────────┘
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
1. `createCodeTool` generates TypeScript type definitions from your tools and builds a description the LLM can read
|
|
144
|
+
2. The LLM writes an async arrow function that calls `codemode.toolName(args)`
|
|
145
|
+
3. The code is normalized via AST parsing (acorn) and sent to the executor
|
|
146
|
+
4. `DynamicWorkerExecutor` spins up an isolated Worker via `WorkerLoader`
|
|
147
|
+
5. Inside the sandbox, a `Proxy` intercepts `codemode.*` calls and routes them back to the host via Workers RPC (`ToolDispatcher extends RpcTarget`)
|
|
148
|
+
6. Console output (`console.log`, `console.warn`, `console.error`) is captured and returned in the result
|
|
149
|
+
|
|
150
|
+
### Network isolation
|
|
151
|
+
|
|
152
|
+
External `fetch()` and `connect()` are **blocked by default** — enforced at the Workers runtime level via `globalOutbound: null`. Sandboxed code can only interact with the host through `codemode.*` tool calls.
|
|
153
|
+
|
|
154
|
+
To allow controlled outbound access, pass a `Fetcher`:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
const executor = new DynamicWorkerExecutor({
|
|
158
|
+
loader: env.LOADER,
|
|
159
|
+
globalOutbound: null // default — fully isolated
|
|
160
|
+
// globalOutbound: env.MY_OUTBOUND_SERVICE // route through a Fetcher
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Using with an Agent
|
|
165
|
+
|
|
166
|
+
The typical pattern is to create the executor and codemode tool inside an Agent's message handler:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { Agent } from "agents";
|
|
170
|
+
import { createCodeTool } from "@cloudflare/codemode/ai";
|
|
171
|
+
import { DynamicWorkerExecutor } from "@cloudflare/codemode";
|
|
172
|
+
import { streamText, convertToModelMessages, stepCountIs } from "ai";
|
|
173
|
+
|
|
174
|
+
export class MyAgent extends Agent<Env, State> {
|
|
175
|
+
async onChatMessage() {
|
|
176
|
+
const executor = new DynamicWorkerExecutor({
|
|
177
|
+
loader: this.env.LOADER
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const codemode = createCodeTool({
|
|
181
|
+
tools: myTools,
|
|
182
|
+
executor
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const result = streamText({
|
|
186
|
+
model,
|
|
187
|
+
system: "You are a helpful assistant.",
|
|
188
|
+
messages: await convertToModelMessages(this.state.messages),
|
|
189
|
+
tools: { codemode },
|
|
190
|
+
stopWhen: stepCountIs(10)
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Stream response back to client...
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### With MCP tools
|
|
199
|
+
|
|
200
|
+
MCP tools work the same way — merge them into the tool set:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const codemode = createCodeTool({
|
|
204
|
+
tools: {
|
|
205
|
+
...myTools,
|
|
206
|
+
...this.mcp.getAITools()
|
|
207
|
+
},
|
|
208
|
+
executor
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Tool names with hyphens or dots (common in MCP) are automatically sanitized to valid JavaScript identifiers (e.g., `my-server.list-items` becomes `my_server_list_items`).
|
|
213
|
+
|
|
214
|
+
### Browser executor with dynamic client tools
|
|
215
|
+
|
|
216
|
+
If your tools live in the browser instead of the Agent, build codemode from
|
|
217
|
+
those browser-side functions and register it with whatever client-tool layer
|
|
218
|
+
you already use. This keeps the server generic while running generated code in
|
|
219
|
+
an iframe sandbox on the page.
|
|
220
|
+
|
|
221
|
+
**Server:**
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { AIChatAgent, createToolsFromClientSchemas } from "@cloudflare/ai-chat";
|
|
225
|
+
import { createWorkersAI } from "workers-ai-provider";
|
|
226
|
+
import { streamText, convertToModelMessages, stepCountIs } from "ai";
|
|
227
|
+
|
|
228
|
+
export class BrowserCodemodeAgent extends AIChatAgent<Env> {
|
|
229
|
+
async onChatMessage(_onFinish, options) {
|
|
230
|
+
const workersai = createWorkersAI({ binding: this.env.AI });
|
|
231
|
+
|
|
232
|
+
const result = streamText({
|
|
233
|
+
model: workersai("@cf/moonshotai/kimi-k2.7-code"),
|
|
234
|
+
messages: await convertToModelMessages(this.messages),
|
|
235
|
+
tools: createToolsFromClientSchemas(options?.clientTools),
|
|
236
|
+
stopWhen: stepCountIs(10)
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return result.toUIMessageStreamResponse();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Client:**
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
import { useAgent } from "agents/react";
|
|
248
|
+
import { useAgentChat, type AITool } from "@cloudflare/ai-chat/react";
|
|
249
|
+
import {
|
|
250
|
+
IframeSandboxExecutor,
|
|
251
|
+
createBrowserCodeTool
|
|
252
|
+
} from "@cloudflare/codemode/browser";
|
|
253
|
+
|
|
254
|
+
const browserTools = {
|
|
255
|
+
getPageTitle: {
|
|
256
|
+
description: "Get the current page title",
|
|
257
|
+
inputSchema: {
|
|
258
|
+
type: "object",
|
|
259
|
+
properties: {},
|
|
260
|
+
required: []
|
|
261
|
+
},
|
|
262
|
+
execute: async () => ({ title: document.title })
|
|
263
|
+
},
|
|
264
|
+
getSelectionText: {
|
|
265
|
+
description: "Get the user's current text selection",
|
|
266
|
+
inputSchema: {
|
|
267
|
+
type: "object",
|
|
268
|
+
properties: {},
|
|
269
|
+
required: []
|
|
270
|
+
},
|
|
271
|
+
execute: async () => ({
|
|
272
|
+
text: window.getSelection()?.toString() ?? ""
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const codemode = createBrowserCodeTool({
|
|
278
|
+
tools: browserTools,
|
|
279
|
+
executor: new IframeSandboxExecutor()
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
function BrowserCodemodeChat() {
|
|
283
|
+
const agent = useAgent({ agent: "BrowserCodemodeAgent" });
|
|
284
|
+
|
|
285
|
+
const tools: Record<string, AITool> = {
|
|
286
|
+
codemode: {
|
|
287
|
+
description: codemode.description,
|
|
288
|
+
parameters: codemode.inputSchema,
|
|
289
|
+
execute: codemode.execute
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const { messages, sendMessage } = useAgentChat({
|
|
294
|
+
agent,
|
|
295
|
+
tools,
|
|
296
|
+
onToolCall: async ({ toolCall, addToolOutput }) => {
|
|
297
|
+
const tool = tools[toolCall.toolName];
|
|
298
|
+
if (tool?.execute) {
|
|
299
|
+
const output = await tool.execute(toolCall.input);
|
|
300
|
+
addToolOutput({ toolCallId: toolCall.toolCallId, output });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Render your chat UI...
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
This pattern is useful when:
|
|
310
|
+
|
|
311
|
+
- the browser owns the tool surface at runtime
|
|
312
|
+
- your page exposes client-side capabilities that only the browser can run
|
|
313
|
+
- you want codemode's typed code-generation prompt without routing tool
|
|
314
|
+
execution through the server
|
|
315
|
+
|
|
316
|
+
If your browser tools are dynamic, rebuild the codemode descriptor whenever the
|
|
317
|
+
tool set changes and re-register it with your client tool layer. Codemode stays
|
|
318
|
+
agnostic about where those tools come from.
|
|
319
|
+
|
|
320
|
+
If you need approval-gated tools, use the standard `needsApproval` +
|
|
321
|
+
`useAgentChat` approval flow described in
|
|
322
|
+
[Human in the Loop](https://github.com/cloudflare/agents/blob/main/docs/agents/human-in-the-loop.md). Codemode currently excludes tools
|
|
323
|
+
with `needsApproval` instead of pausing execution for approval.
|
|
324
|
+
|
|
325
|
+
## The Executor interface
|
|
326
|
+
|
|
327
|
+
The `Executor` interface is deliberately minimal — implement it to run code in any sandbox:
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
interface Executor {
|
|
331
|
+
execute(
|
|
332
|
+
code: string,
|
|
333
|
+
providersOrFns:
|
|
334
|
+
| ResolvedProvider[]
|
|
335
|
+
| Record<string, (...args: unknown[]) => Promise<unknown>>
|
|
336
|
+
): Promise<ExecuteResult>;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
interface ResolvedProvider {
|
|
340
|
+
name: string;
|
|
341
|
+
fns: Record<string, (...args: unknown[]) => Promise<unknown>>;
|
|
342
|
+
positionalArgs?: boolean;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
interface ExecuteResult {
|
|
346
|
+
result: unknown;
|
|
347
|
+
error?: string;
|
|
348
|
+
logs?: string[];
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
`DynamicWorkerExecutor` is the built-in Cloudflare Workers implementation. You can build your own for Node VM, QuickJS, containers, or any other sandbox.
|
|
353
|
+
|
|
354
|
+
## API reference
|
|
355
|
+
|
|
356
|
+
### `createCodeTool(options)`
|
|
357
|
+
|
|
358
|
+
Returns an AI SDK compatible `Tool`.
|
|
359
|
+
|
|
360
|
+
| Option | Type | Default | Description |
|
|
361
|
+
| ------------- | ---------------------------- | -------------- | ------------------------------------------------------ |
|
|
362
|
+
| `tools` | `ToolSet \| ToolDescriptors` | required | Your tools (AI SDK `tool()` or raw descriptors) |
|
|
363
|
+
| `executor` | `Executor` | required | Where to run the generated code |
|
|
364
|
+
| `description` | `string` | auto-generated | Custom tool description. Use `{{types}}` for type defs |
|
|
365
|
+
|
|
366
|
+
### `DynamicWorkerExecutor`
|
|
367
|
+
|
|
368
|
+
Executes code in an isolated Cloudflare Worker via `WorkerLoader`.
|
|
369
|
+
|
|
370
|
+
| Option | Type | Default | Description |
|
|
371
|
+
| ---------------- | ----------------- | -------- | ------------------------------------------------------------ |
|
|
372
|
+
| `loader` | `WorkerLoader` | required | Worker Loader binding from `env.LOADER` |
|
|
373
|
+
| `timeout` | `number` | `60000` | Execution timeout in ms |
|
|
374
|
+
| `globalOutbound` | `Fetcher \| null` | `null` | Network access control. `null` = blocked, `Fetcher` = routed |
|
|
375
|
+
|
|
376
|
+
### `IframeSandboxExecutor`
|
|
377
|
+
|
|
378
|
+
Executes code in a sandboxed browser iframe. Import it from
|
|
379
|
+
`@cloudflare/codemode/browser`.
|
|
380
|
+
|
|
381
|
+
| Option | Type | Default | Description |
|
|
382
|
+
| --------- | -------- | --------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
383
|
+
| `timeout` | `number` | `30000` | Execution timeout in ms. Cannot preempt tight synchronous browser loops. |
|
|
384
|
+
| `csp` | `string` | `default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval';` | Content Security Policy applied to the sandbox iframe document. |
|
|
385
|
+
|
|
386
|
+
### `generateTypes(tools)`
|
|
387
|
+
|
|
388
|
+
Generates TypeScript type definitions from your tools. Used internally by `createCodeTool` but exported for custom use (e.g., displaying types in a frontend).
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
import { generateTypes } from "@cloudflare/codemode";
|
|
392
|
+
|
|
393
|
+
const types = generateTypes(myTools);
|
|
394
|
+
// Returns:
|
|
395
|
+
// type CreateProjectInput = { name: string; description?: string }
|
|
396
|
+
// declare const codemode: { createProject: (input: CreateProjectInput) => Promise<unknown>; }
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### `sanitizeToolName(name)`
|
|
400
|
+
|
|
401
|
+
Converts tool names into valid JavaScript identifiers.
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
import { sanitizeToolName } from "@cloudflare/codemode";
|
|
405
|
+
|
|
406
|
+
sanitizeToolName("get-weather"); // "get_weather"
|
|
407
|
+
sanitizeToolName("3d-render"); // "_3d_render"
|
|
408
|
+
sanitizeToolName("delete"); // "delete_"
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Security considerations
|
|
412
|
+
|
|
413
|
+
- Code runs in **isolated Worker sandboxes** — each execution gets its own Worker instance
|
|
414
|
+
- External network access (`fetch`, `connect`) is **blocked by default** at the runtime level
|
|
415
|
+
- Tool calls are dispatched via Workers RPC, not network requests
|
|
416
|
+
- Execution has a configurable **timeout** (default 60 seconds)
|
|
417
|
+
- Console output is captured separately and does not leak to the host
|
|
418
|
+
- Browser iframe execution runs in a sandboxed iframe with a restrictive CSP by
|
|
419
|
+
default. It uses nonce-scoped internal messages, but its timeout cannot preempt
|
|
420
|
+
tight synchronous loops like `while (true) {}` because those block the browser
|
|
421
|
+
event loop.
|
|
422
|
+
|
|
423
|
+
## Current limitations
|
|
424
|
+
|
|
425
|
+
- **Tool approval (`needsApproval`) is not supported yet.** Tools with
|
|
426
|
+
`needsApproval: true` or a `needsApproval` function are excluded from codemode
|
|
427
|
+
instead of pausing execution for approval. Support for approval flows within
|
|
428
|
+
codemode is planned. For now, use approval-required tools through standard AI
|
|
429
|
+
SDK tool calling instead.
|
|
430
|
+
- Requires Cloudflare Workers environment for `DynamicWorkerExecutor`
|
|
431
|
+
- Limited to JavaScript execution
|
|
432
|
+
- The `zod-to-ts` dependency bundles the TypeScript compiler, which increases Worker size
|
|
433
|
+
- LLM code quality depends on prompt engineering and model capability
|
|
434
|
+
|
|
435
|
+
## Example
|
|
436
|
+
|
|
437
|
+
See [`examples/codemode/`](https://github.com/cloudflare/agents/tree/main/examples/codemode) for a full working example — a project management assistant that uses codemode to orchestrate tasks, sprints, and comments via SQLite.
|
|
438
|
+
|
|
439
|
+
See [`examples/codemode-browser/`](https://github.com/cloudflare/agents/tree/main/examples/codemode-browser) for a browser
|
|
440
|
+
iframe executor example with dynamic client tools.
|