Tools (3)·续
export class MyAgent extends Think<Env> { getModel() { / ... / }
getTools() { return { execute: createExecuteTool(this, { tools: createAITools({ workspace: this.workspace }), }), }; } }
Setup checklist:
```jsonc
{
"$schema": "./node_modules/wrangler/config-schema.json",
"worker_loaders": [
{
"binding": "LOADER"
}
],
"browser": {
"binding": "BROWSER"
}
}
[[worker_loaders]]
binding = "LOADER"
[browser]
binding = "BROWSER" # optional — enables cdp.*
// worker entry — the runtime lives in a Durable Object facet, so the class
// must be exported (the @cloudflare/codemode/vite plugin does this
// automatically; the Think framework's generated entry already includes it)
export { CodemodeRuntime } from "@cloudflare/codemode";
// worker entry — the runtime lives in a Durable Object facet, so the class
// must be exported (the @cloudflare/codemode/vite plugin does this
// automatically; the Think framework's generated entry already includes it)
export { CodemodeRuntime } from "@cloudflare/codemode";
Each missing piece fails with an error naming the step.
Inside the sandbox the model sees typed namespaces plus the platform SDK:
tools.*— your AI SDK tools (object args, validated against their schemas). Only tools with anexecutefunction are exposed — client-side tools cannot run in the sandbox.cdp.*— the browser, when a Browser Run binding is configured. The execute tool defaults tosession: { mode: "dynamic" }: sessions are per-execution unless the model promotes one withcdp.startSession().codemode.search/codemode.describe/codemode.step/codemode.run— discovery, side-effect boundaries, and saved snippets.
Or pass fully explicit options (no agent inference):
createExecuteTool({
ctx: this.ctx,
tools: myDomainTools,
browser: this.env.BROWSER,
loader: this.env.LOADER,
});
createExecuteTool({
ctx: this.ctx,
tools: myDomainTools,
browser: this.env.BROWSER,
loader: this.env.LOADER,
});
Approvals (human-in-the-loop)
An AI SDK tool with needsApproval does not run immediately inside the sandbox — calling it pauses the run durably. The pause comes back as a normal tool output ({ status: "paused", executionId, pending }), the model tells the user what it needs, and the turn ends. This differs from the client-side approval flow for plain getTools() tools: inside the sandbox a function-valued needsApproval cannot be evaluated against the call's arguments ahead of time, so it conservatively always requires approval. Think ships built-in callables to resolve it:
approveExecution(executionId)— resumes the run where it stopped. Already-done work is replayed, not re-executed. The outcome replaces the paused output in the transcript and the chat auto-continues.rejectExecution(executionId, reason?)— ends the run with{ status: "rejected", reason }so the model can adapt.pendingExecutions()— pending actions (with full args) for rendering approval UI.
Note
Render approval cards from pendingExecutions(), not the transcript. The pending array in the paused tool output is a truncated preview — args are bounded (\~2 KB each) so they do not blow up model context, but the full args (up to 1 MB) are what actually execute on approve. A human approving a gated call must see the authoritative args, so fetch them via pendingExecutions(executionId) before enabling the Approve button.
For a working approval card, refer to the assistant example ↗︎.
The runtime handle
createExecuteRuntime returns the moving parts when the host needs more than the tool — and the handle is also assigned to this.codemode when created from an agent:
import { createExecuteRuntime } from "@cloudflare/think/tools/execute";
const { runtime, connectors, tool } = createExecuteRuntime(this);
await runtime.executions(); // audit trail
await runtime.expirePaused(); // reclaim stale never-approved pauses (call from a scheduled task)
await runtime.saveSnippet("name", { executionId }); // promote a script for reuse
import { createExecuteRuntime } from "@cloudflare/think/tools/execute";
const { runtime, connectors, tool } = createExecuteRuntime(this);
await runtime.executions(); // audit trail
await runtime.expirePaused(); // reclaim stale never-approved pauses (call from a scheduled task)
await runtime.saveSnippet("name", { executionId }); // promote a script for reuse
Browser tools
Give your agent access to the Chrome DevTools Protocol (CDP) for web page inspection, scraping, screenshots, and debugging. Requires @cloudflare/codemode and a Browser Run binding.
import { Think } from "@cloudflare/think";
import { createBrowserTools } from "@cloudflare/think/tools/browser";
export class MyAgent extends Think {
getModel() {
/* ... */
}
getTools() {
return {
...createBrowserTools({
ctx: this.ctx,
browser: this.env.BROWSER,
loader: this.env.LOADER,
}),
};
}
}
import { Think } from "@cloudflare/think";
import { createBrowserTools } from "@cloudflare/think/tools/browser";
export class MyAgent extends Think<Env> {
getModel() {
/* ... */
}
getTools() {
return {
...createBrowserTools({
ctx: this.ctx,
browser: this.env.BROWSER,
loader: this.env.LOADER,
}),
};
}
}
{
"$schema": "./node_modules/wrangler/config-schema.json",
"browser": {
"binding": "BROWSER"
},
"worker_loaders": [
{
"binding": "LOADER"
}
]
}
[browser]
binding = "BROWSER"
[[worker_loaders]]
binding = "LOADER"
This adds the durable CDP tool plus stateless Quick Action tools when a browser binding is present:
| Tool | Description |
|---|---|
browser_execute |
Run JavaScript against a live browser over CDP (screenshots, DOM reads, JS evaluation). |
browser_markdown |
Read a page or raw HTML as Markdown. |
browser_extract |
Extract structured data from a page with AI. |
browser_links |
List links on a page. |
browser_scrape |
Scrape specific elements by CSS selector. |
Pass quickActions: false to keep only browser_execute, or pass quickActions: { actions, maxChars, options } to configure the stateless tools. The Quick Action tools share the browser binding, need no Worker Loader, and resolve ctx from the current Agent automatically. To use only the stateless tools, import createQuickActionTools from @cloudflare/think/tools/browser.
The tool is backed by a Code Mode runtime with the cdp connector: the model writes async arrow functions that run in a sandboxed Worker isolate, with cdp.send(), cdp.attachToTarget(), cdp.spec() (the live, normalized protocol description), session helpers (cdp.startSession(), cdp.sessionInfo(), cdp.closeSession()), and debug-log helpers. Executions are recorded for abort-and-replay, so browser sessions survive approval pauses.
By default each execution gets a fresh browser session (one-shot), torn down when the run ends. Pass session: { mode: "dynamic" } to let the model promote a session with cdp.startSession() so later executions continue in the same browser, or session: { mode: "reuse", key } for a named long-lived session. Stale sessions are reclaimed by the connector's sweep() — call it from a scheduled task.
Note
The simplest setup is the unified execute tool in Code execution tool: createExecuteTool(this) already includes cdp.* alongside tools.* when env.BROWSER is bound — one tool, one durable history. Use createBrowserTools when you want a separate, browser-only tool.
For a custom Chrome endpoint, pass cdpUrl instead of browser:
createBrowserTools({
ctx: this.ctx,
cdpUrl: "http://localhost:9222",
loader: this.env.LOADER,
});
createBrowserTools({
ctx: this.ctx,
cdpUrl: "http://localhost:9222",
loader: this.env.LOADER,
});
For the full CDP connector API, refer to Browse the web.
Extensions
Extensions are dynamically loaded sandboxed Workers that add tools at runtime. The LLM can write extension source code, load it, and use the new tools on the next turn.
Extensions require a worker_loaders binding:
import { Think } from "@cloudflare/think";
export class MyAgent extends Think {
extensionLoader = this.env.LOADER;
getModel() {
/* ... */
}
}
import { Think } from "@cloudflare/think";
export class MyAgent extends Think<Env> {
extensionLoader = this.env.LOADER;
getModel() {
/* ... */
}
}
Static extensions
Define extensions that load at startup:
export class MyAgent extends Think {
extensionLoader = this.env.LOADER;
getModel() {
/* ... */
}
getExtensions() {
return [
{
manifest: {
name: "math",
version: "1.0.0",
permissions: { network: false },
},
source: `({
tools: {
add: {
description: "Add two numbers",
parameters: { a: { type: "number" }, b: { type: "number" } },
execute: async ({ a, b }) => ({ result: a + b })
}
}
})`,
},
];
}
}
export class MyAgent extends Think<Env> {
extensionLoader = this.env.LOADER;
getModel() {
/* ... */
}
getExtensions() {
return [
{
manifest: {
name: "math",
version: "1.0.0",
permissions: { network: false },
},
source: `({
tools: {
add: {
description: "Add two numbers",
parameters: { a: { type: "number" }, b: { type: "number" } },
execute: async ({ a, b }) => ({ result: a + b })
}
}
})`,
},
];
}
}
Extension tools are namespaced — a math extension with an add tool becomes math_add in the model's tool set.
LLM-driven extensions
Give the model createExtensionTools so it can load extensions dynamically:
import { createExtensionTools } from "@cloudflare/think/tools/extensions";
export class MyAgent extends Think {
extensionLoader = this.env.LOADER;
getModel() {
/* ... */
}
getTools() {
return {
...createExtensionTools({ manager: this.extensionManager }),
...this.extensionManager.getTools(),
};
}
}
import { createExtensionTools } from "@cloudflare/think/tools/extensions";
export class MyAgent extends Think<Env> {
extensionLoader = this.env.LOADER;
getModel() {
/* ... */
}
getTools() {
return {
...createExtensionTools({ manager: this.extensionManager! }),
...this.extensionManager!.getTools(),
};
}
}
This gives the model two tools:
load_extension— load a new extension from JavaScript sourcelist_extensions— list currently loaded extensions
Extension context blocks
Extensions can declare context blocks in their manifest. These are automatically registered with the Session:
getExtensions() {
return [{
manifest: {
name: "notes",
version: "1.0.0",
permissions: { network: false },
context: [
{ label: "scratchpad", description: "Extension scratch space", maxTokens: 500 },
],
},
source: `({ tools: { /* ... */ } })`,
}];
}
The context block is registered as notes_scratchpad (namespaced by extension name).
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/agents/harnesses/think/tools/#page","headline":"Tools","description":"Built-in workspace tools, custom tools, approvals, MCP tools, code execution, browser tools, and extensions for Think agents.","url":"https://developers.cloudflare.com/agents/harnesses/think/tools/","inLanguage":"en","image":"https://developers.cloudflare.com/agents/harnesses/think/tools/og.png?v=95e31e7f8e5f6bfc","dateModified":"2026-10-20","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Run a durable model-driven reasoning step inside a Cloudflare Workflow with ThinkWorkflow and step.prompt(), including structured output and timeouts. title: Workflows image: https://developers.cloudflare.com/agents/harnesses/think/workflows/og.png?v=d1e47ab935cc53a2
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/agents/llms.txt
Use this file to discover all available pages before exploring further.
Docs