Browser integration
Use @cloudflare/codemode/browser when your browser owns the tools that the model must orchestrate. For example, these tools might read page state, access browser APIs, or update data held by your application.
Code Mode is useful when the model must call several client tools with loops, conditions, or intermediate results. For a single browser action, use a standard client-side tool instead.
Code Mode presents those tools to the model as typed functions. The model writes one JavaScript async arrow function that can call several tools, combine their results, and apply control flow. IframeSandboxExecutor runs that generated code in a sandboxed iframe on the page.
This integration does not give an agent control of a remote browser. To inspect websites, capture screenshots, or automate pages with the Chrome DevTools Protocol (CDP), refer to Browser tools.
Install the package in your client application:
npm i @cloudflare/codemode yarn add @cloudflare/codemode pnpm add @cloudflare/codemode bun add @cloudflare/codemode The @cloudflare/codemode/browser entry point uses JSON Schema and browser APIs. It does not require the AI SDK or Zod peer dependencies used by @cloudflare/codemode/ai.
The browser creates the Code Mode tool and registers it as a dynamic client tool. The Agent receives the tool schema, but the tool implementation remains in the browser.
-
Define the browser-owned tools with JSON Schema and an
executefunction.src/browser-tools.js export const browserTools = {getPageInfo: {description: "Get information about the current browser page",inputSchema: {type: "object",properties: {},required: [],},execute: async () => ({title: document.title,url: window.location.href,}),},getSelectionText: {description: "Get the user's current text selection",inputSchema: {type: "object",properties: {},required: [],},execute: async () => ({text: window.getSelection()?.toString() ?? "",}),},};src/browser-tools.ts import type { JsonSchemaExecutableToolDescriptors } from "@cloudflare/codemode/browser";export const browserTools: JsonSchemaExecutableToolDescriptors = {getPageInfo: {description: "Get information about the current browser page",inputSchema: {type: "object",properties: {},required: []},execute: async () => ({title: document.title,url: window.location.href})},getSelectionText: {description: "Get the user's current text selection",inputSchema: {type: "object",properties: {},required: []},execute: async () => ({text: window.getSelection()?.toString() ?? ""})}};JSON Schema supplies the types shown to the model.
createBrowserCodeTool()does not use the schema to validate arguments at runtime. Validate untrusted inputs inside eachexecutefunction when required. -
Create the Code Mode descriptor with an iframe executor.
src/codemode-tool.js import {IframeSandboxExecutor,createBrowserCodeTool,} from "@cloudflare/codemode/browser";import { browserTools } from "./browser-tools";export const codemodeTool = createBrowserCodeTool({tools: browserTools,executor: new IframeSandboxExecutor(),});src/codemode-tool.ts import {IframeSandboxExecutor,createBrowserCodeTool} from "@cloudflare/codemode/browser";import { browserTools } from "./browser-tools";export const codemodeTool = createBrowserCodeTool({tools: browserTools,executor: new IframeSandboxExecutor()});createBrowserCodeTool()returns a plain descriptor namedcodemode. Its description includes generated TypeScript definitions for the browser tools. Its input contains the model-generated JavaScript in acodeproperty.The
executoroption is optional. When omitted,createBrowserCodeTool()creates anIframeSandboxExecutorwith default settings. -
Register the descriptor with
useAgentChat()and execute client tool calls.src/client.jsx import { useAgentChat } from "@cloudflare/ai-chat/react";import { useAgent } from "agents/react";import { useMemo } from "react";import { codemodeTool } from "./codemode-tool";function BrowserCodeModeChat() {const agent = useAgent({ agent: "browser-codemode" });const tools = useMemo(() => ({codemode: {description: codemodeTool.description,parameters: codemodeTool.inputSchema,execute: (input) => codemodeTool.execute(input),},}),[],);const { messages, sendMessage } = useAgentChat({agent,tools,onToolCall: async ({ toolCall, addToolOutput }) => {const tool = tools[toolCall.toolName];if (!tool?.execute) return;try {const output = await tool.execute(toolCall.input);addToolOutput({toolCallId: toolCall.toolCallId,output,});} catch (error) {addToolOutput({toolCallId: toolCall.toolCallId,state: "output-error",errorText: error instanceof Error ? error.message : String(error),});}},});// Render messages and call sendMessage() from your chat UI.}src/client.tsx import { useAgentChat, type AITool } from "@cloudflare/ai-chat/react";import { useAgent } from "agents/react";import { useMemo } from "react";import { codemodeTool } from "./codemode-tool";function BrowserCodeModeChat() {const agent = useAgent({ agent: "browser-codemode" });const tools = useMemo<Record<string, AITool>>(() => ({codemode: {description: codemodeTool.description,parameters: codemodeTool.inputSchema,execute: (input) =>codemodeTool.execute(input as { code: string })}}),[]);const { messages, sendMessage } = useAgentChat({agent,tools,onToolCall: async ({ toolCall, addToolOutput }) => {const tool = tools[toolCall.toolName];if (!tool?.execute) return;try {const output = await tool.execute(toolCall.input);addToolOutput({toolCallId: toolCall.toolCallId,output});} catch (error) {addToolOutput({toolCallId: toolCall.toolCallId,state: "output-error",errorText: error instanceof Error ? error.message : String(error)});}}});// Render messages and call sendMessage() from your chat UI.}useAgentChat()sends the registered client tool schema to the Agent. When the model callscodemode,onToolCallexecutes the descriptor in the browser and adds its output to the conversation. -
On the Agent, convert the client schemas into model tools.
src/server.js import { AIChatAgent, createToolsFromClientSchemas } from "@cloudflare/ai-chat";import { convertToModelMessages, stepCountIs, streamText } from "ai";import { createWorkersAI } from "workers-ai-provider";export class BrowserCodemode extends AIChatAgent {async onChatMessage(_onFinish, options) {const workersai = createWorkersAI({ binding: this.env.AI });const result = streamText({model: workersai("@cf/moonshotai/kimi-k2.7-code"),system:"Use the codemode tool to write JavaScript that calls browser-provided tools.",messages: await convertToModelMessages(this.messages),tools: createToolsFromClientSchemas(options?.clientTools),stopWhen: stepCountIs(10),});return result.toUIMessageStreamResponse();}}src/server.ts import { AIChatAgent, createToolsFromClientSchemas } from "@cloudflare/ai-chat";import { convertToModelMessages, stepCountIs, streamText } from "ai";import { createWorkersAI } from "workers-ai-provider";export class BrowserCodemode extends AIChatAgent<Env> {async onChatMessage(_onFinish?: unknown,options?: {clientTools?: Parameters<typeof createToolsFromClientSchemas>[0];}) {const workersai = createWorkersAI({ binding: this.env.AI });const result = streamText({model: workersai("@cf/moonshotai/kimi-k2.7-code"),system:"Use the codemode tool to write JavaScript that calls browser-provided tools.",messages: await convertToModelMessages(this.messages),tools: createToolsFromClientSchemas(options?.clientTools),stopWhen: stepCountIs(10)});return result.toUIMessageStreamResponse();}}The Agent advertises the client-provided schema to the model. It does not run the generated code or the browser tool implementations.
If your browser tool set changes at runtime, create a new Code Mode descriptor and register the updated descriptor with your client tool layer.
IframeSandboxExecutor creates a hidden iframe for each execution. The iframe uses sandbox="allow-scripts" and receives the generated code through postMessage. Tool calls return to the parent page, which runs the matching browser-owned execute function.
Messages are scoped to the current iframe and an execution nonce. The executor removes the iframe and message listener after completion, failure, or timeout.
The executor accepts these options:
| Option | Type | Default | Behavior |
|---|---|---|---|
timeout | number | 30000 | Ends an execution after the specified number of milliseconds. |
csp | string | default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval'; | Sets the Content Security Policy (CSP) for the iframe document. |
The default CSP blocks resources except the inline and evaluated scripts required to execute generated code. Pass a custom policy only when your generated code needs additional iframe capabilities.
Relaxing directives such as connect-src, img-src, or form-action can let generated iframe code communicate with external systems. That code could expose values returned by browser tools. Keep outbound destinations narrow, and do not place secrets in tool results. Browser-owned tools execute separately in the parent page with the capabilities their implementations provide.
createBrowserCodeTool() excludes any tool whose needsApproval value is true or a function. Code Mode does not pause iframe execution to request approval for those tools.
Keep approval-gated actions outside the Code Mode descriptor. Register them as standard tools and use the useAgentChat() approval flow instead.