From a69510cab33ec6341f95d9caae93acead52174e0 Mon Sep 17 00:00:00 2001 From: aron <263346377+aron-cf@users.noreply.github.com> Date: Fri, 7 Aug 2026 14:24:31 +0000 Subject: [PATCH] examples/think, examples/tutorial, docs: Remove Think adapter use Configure Think agents with the Computer AI tools and workspace.fs instead of the removed root-level compatibility methods. Replace the tutorial's bash tool with exec and update the public integration docs. --- docs/01_vfs.md | 17 ++++------ examples/think/src/agent.ts | 13 ++++---- examples/tutorial/README.md | 60 ++++++++++++++++++++++------------ examples/tutorial/src/index.ts | 37 ++++++++++++++------- packages/computer/README.md | 6 ++-- 5 files changed, 81 insertions(+), 52 deletions(-) diff --git a/docs/01_vfs.md b/docs/01_vfs.md index b348fe4b..d33139b6 100644 --- a/docs/01_vfs.md +++ b/docs/01_vfs.md @@ -34,16 +34,13 @@ Workspace where `fs` works against the local SQLite store but `shell` throws. `WorkspaceOptions` includes the storage handle, optional backends, -clock, session id, mounts, observer, git identity, assets, artifacts, -and `useThink`. There is no `root` or `sandbox` field on the host -facade — sandbox wiring lives behind a `WorkspaceBackend`. - -Set `useThink: true` when assigning the Workspace to -`Think.workspace`. This adds Think's string-oriented filesystem -compatibility methods (`readFile`, `readFileBytes`, `writeFile`, -`readDir`, `rm`, `glob`, `mkdir`, and `stat`) to that Workspace and to -clients returned by `getWorkspace()`, while leaving the primary API on -`workspace.fs`. +clock, session id, mounts, observer, git identity, assets, and +artifacts. There is no `root` or `sandbox` field on the host facade — +sandbox wiring lives behind a `WorkspaceBackend`. + +Agent integrations consume the filesystem through `workspace.fs`. +Use `createAITools` from `@cloudflare/computer/tools` for the standard +AI SDK file tools. Illustrative layout (nothing below `/` is auto-created beyond `ROOT_INODE` itself): diff --git a/examples/think/src/agent.ts b/examples/think/src/agent.ts index e9ae6122..b04b14af 100644 --- a/examples/think/src/agent.ts +++ b/examples/think/src/agent.ts @@ -18,15 +18,13 @@ * a CloudflareContainerBackend (`"container"`) for full Linux * userland through computerd. This mirrors examples/container while * keeping the chat surface unchanged. - * - `useThink: true` adds the string-based compatibility surface - * Think expects; the cast promotes it from optional to present. - * `workspaceBash` is off because `@cloudflare/computer/tools` - * provides the `exec` tool. + * - Think consumes `workspace.fs` directly, while + * `@cloudflare/computer/tools` provides the complete file and + * `exec` tool set. */ import { type DurableObjectStorageLike, - type ThinkWorkspaceCompatibility, Workspace, WorkspaceProxy, WorkspaceServiceProxy, @@ -88,6 +86,8 @@ export class Assistant extends withWorkspaceContainer(AssistantBase) { * shell. Passing `{ backend: "container" }` routes a call to computerd in * the Cloudflare Container. */ + // The installed Think release still types workspace as its legacy + // root-level filesystem shape. Runtime tools use workspace.fs. override workspace = new Workspace({ storage: this.ctx.storage as unknown as DurableObjectStorageLike, backends: [ @@ -99,8 +99,7 @@ export class Assistant extends withWorkspaceContainer(AssistantBase) { }), this.#containerBackend, ], - useThink: true, - }) as Workspace & ThinkWorkspaceCompatibility; + }) as Workspace & AssistantBase["workspace"]; /** Forwarded by WorkspaceProxy for computerd's outbound /ws upgrade. */ override async fetch(request: Request): Promise { diff --git a/examples/tutorial/README.md b/examples/tutorial/README.md index 79f08e76..65e95d06 100644 --- a/examples/tutorial/README.md +++ b/examples/tutorial/README.md @@ -16,7 +16,7 @@ with a link to the PDF. POST /prompt ──► RecipeAgent │ fetch_url https://openstove.org/... (host) │ write /workspace/card.md (host) - │ bash pandoc card.md -o card.pdf (container) + │ exec pandoc card.md -o card.pdf (container) ▼ R2 ──► signed link, good for a day ``` @@ -26,7 +26,7 @@ of the workspace. The `write` tool runs on the host, in the durable object, and writes through the `Workspace` into durable object storage. The container sees the same file on its FUSE mount at `/workspace`, so `pandoc` reads it as an ordinary file. The PDF `pandoc` writes syncs -back the other way when the `bash` call finishes, so the finished file +back the other way when the `exec` call finishes, so the finished file can be published straight from the workspace. The finished code is [one file](src/index.ts). The rest of this page @@ -86,11 +86,12 @@ wrangler r2 bucket create recipe-cards npm install @cloudflare/computer @cloudflare/think agents ai zod ``` -- `@cloudflare/computer` is the filesystem, the container backend, and - the assets client that publishes to R2. -- `@cloudflare/think` provides the agent loop and its file, shell, and - fetch tools. `agents` provides `getAgentByName` for reaching an - instance by name; `ai` and `zod` satisfy Think's peer dependencies. +- `@cloudflare/computer` provides the filesystem, container backend, + AI SDK file and execution tools, and the assets client that publishes + to R2. +- `@cloudflare/think` provides the agent loop and fetch tools. `agents` + provides `getAgentByName` for reaching an instance by name; `ai` and + `zod` satisfy Think's peer dependencies. ## 3. Create the Dockerfile @@ -132,9 +133,9 @@ works in both places. ## 4. Give Think a Computer workspace The durable object owns a `CloudflareContainerBackend`, which is the -container the workspace is mounted in. The `Workspace` instance enables -Computer's Think-compatible methods so Think's built-in tools use the -same filesystem as the container. +container the workspace is mounted in. Think receives the Computer +`Workspace`; agent tools use its `workspace.fs` and `workspace.runtime` +facades directly. ```ts import { @@ -144,7 +145,6 @@ import { import { Think } from "@cloudflare/think"; import { type DurableObjectStorageLike, - type ThinkWorkspaceCompatibility, Workspace, } from "@cloudflare/computer"; @@ -152,6 +152,7 @@ class RecipeBase extends Think {} export class RecipeAgent extends withWorkspaceContainer(RecipeBase) { readonly #backend = new CloudflareContainerBackend({ + id: "container", container: () => this, workspace: { binding: "RecipeAgent", id: this.ctx.id.toString() }, }); @@ -159,8 +160,7 @@ export class RecipeAgent extends withWorkspaceContainer(RecipeBase) { override workspace = new Workspace({ storage: this.ctx.storage as unknown as DurableObjectStorageLike, backends: [this.#backend], - useThink: true, - }) as Workspace & ThinkWorkspaceCompatibility; + }); override async fetch(request: Request): Promise { return new URL(request.url).pathname === "/ws" @@ -178,13 +178,17 @@ hand `/ws` to the backend before the base class sees it. ## 5. Hook the workspace up to the agent -Think already has file and shell tools. The Computer workspace makes -those tools use the same filesystem as the container: -`write` calls Computer's host-side filesystem, while `bash` calls the -container shell. Think's fetch tool gets an allowlist of one host, so -the agent can read openstove.org and nothing else. +Disable Think's legacy shell tool and return Computer's complete tool +set from `getTools()`. `write` calls the host-side filesystem, while +`exec` runs against the container backend. Think's fetch tool gets an +allowlist of one host, so the agent can read openstove.org and nothing +else. ```ts +import { createAITools } from "@cloudflare/computer/tools"; +import type { ToolSet } from "ai"; + +override workspaceBash = false; override maxSteps = 10; override fetchTools = { allowlist: ["https://openstove.org/**"], @@ -196,6 +200,20 @@ override getModel() { return "@cf/zai-org/glm-5.2"; } +override getTools(): ToolSet { + return createAITools({ + workspace: this.workspace, + shell: { + defaultBackend: "container", + backends: { + container: { + description: "Cloudflare Container with full Linux userland, including pandoc and typst.", + }, + }, + }, + }); +} + override getSystemPrompt() { return [ "You turn a cooking request into a one-page PDF recipe card.", @@ -209,13 +227,13 @@ override getSystemPrompt() { " and numbered Method steps. End with the source page URL spelled out,", " not a markdown link: the card gets printed, and a link prints as its", " text alone.", - "3. Convert it with `bash`: `pandoc /workspace/card.md -o /workspace/card.pdf --pdf-engine=typst`.", + "3. Convert it with `exec`: `pandoc /workspace/card.md -o /workspace/card.pdf --pdf-engine=typst`.", "4. Reply with one sentence naming the recipe you picked.", ].join("\n"); } ``` -Nothing copies files between `write` and `bash`: the write goes into +Nothing copies files between `write` and `exec`: the write goes into durable object storage and the container reads it back out of the mount, and the PDF `pandoc` leaves behind travels the same road in reverse. @@ -351,7 +369,7 @@ curl -X POST http://localhost:8787/prompt \ ``` The first request may be slow: the container has to boot before the -first `bash` runs. The link points at R2 rather than at the worker, so +first `exec` runs. The link points at R2 rather than at the worker, so it works the same whether the worker runs locally or deployed. `wrangler deploy` works against any account with Workers AI and diff --git a/examples/tutorial/src/index.ts b/examples/tutorial/src/index.ts index 6e86bf2c..139b0f8b 100644 --- a/examples/tutorial/src/index.ts +++ b/examples/tutorial/src/index.ts @@ -6,30 +6,27 @@ // POST /prompt ──► RecipeAgent (Think + Computer) // │ fetch_url openstove.org (host) // │ write /workspace/card.md (host) -// │ bash pandoc card.md -o card.pdf (container) +// │ exec pandoc card.md -o card.pdf (container) // ▼ // R2 ──► signed link, good for a day // // The write and the pandoc run touch one filesystem: the host writes // through the Workspace, the container sees the same bytes on its // FUSE mount, and the PDF the container produces is readable back on -// the host once the shell command finishes. +// the host once the exec command finishes. // // README.md walks through building this file from an empty directory. -import { - type DurableObjectStorageLike, - type ThinkWorkspaceCompatibility, - Workspace, - WorkspaceProxy, -} from "@cloudflare/computer"; +import { type DurableObjectStorageLike, Workspace, WorkspaceProxy } from "@cloudflare/computer"; import { createAssets } from "@cloudflare/computer/assets"; import { CloudflareContainerBackend, withWorkspaceContainer, } from "@cloudflare/computer/backends/container"; +import { createAITools } from "@cloudflare/computer/tools"; import { Think } from "@cloudflare/think"; import { getAgentByName } from "agents"; +import type { ToolSet } from "ai"; // Carries container egress back to the durable object. The runtime // binds it by name, so it has to appear in the worker's module graph. @@ -38,6 +35,7 @@ export { WorkspaceProxy }; class RecipeBase extends Think {} export class RecipeAgent extends withWorkspaceContainer(RecipeBase) { + override workspaceBash = false; override maxSteps = 10; override fetchTools = { allowlist: ["https://openstove.org/**"], @@ -46,15 +44,17 @@ export class RecipeAgent extends withWorkspaceContainer(RecipeBase) { }; readonly #backend = new CloudflareContainerBackend({ + id: "container", container: () => this, workspace: { binding: "RecipeAgent", id: this.ctx.id.toString() }, }); + // The installed Think release still types workspace as its legacy + // root-level filesystem shape. Runtime tools use workspace.fs. override workspace = new Workspace({ storage: this.ctx.storage as unknown as DurableObjectStorageLike, backends: [this.#backend], - useThink: true, - }) as Workspace & ThinkWorkspaceCompatibility; + }) as Workspace & RecipeBase["workspace"]; override getModel() { return "@cf/zai-org/glm-5.2"; @@ -73,11 +73,26 @@ export class RecipeAgent extends withWorkspaceContainer(RecipeBase) { " and numbered Method steps. End with the source page URL spelled out,", " not a markdown link: the card gets printed, and a link prints as its", " text alone.", - "3. Convert it with `bash`: `pandoc /workspace/card.md -o /workspace/card.pdf --pdf-engine=typst`.", + "3. Convert it with `exec`: `pandoc /workspace/card.md -o /workspace/card.pdf --pdf-engine=typst`.", "4. Reply with one sentence naming the recipe you picked.", ].join("\n"); } + override getTools(): ToolSet { + return createAITools({ + workspace: this.workspace, + shell: { + defaultBackend: "container", + backends: { + container: { + description: + "Cloudflare Container with full Linux userland, including pandoc and typst.", + }, + }, + }, + }); + } + override async fetch(request: Request): Promise { return new URL(request.url).pathname === "/ws" ? this.#backend.handleFetch(request) diff --git a/packages/computer/README.md b/packages/computer/README.md index adf6136e..a4040162 100644 --- a/packages/computer/README.md +++ b/packages/computer/README.md @@ -461,9 +461,9 @@ await ws.ready(); const stub = ws.stub(); // crosses the Workers-RPC boundary ``` -When assigning a workspace to a Think agent's `workspace`, pass -`useThink: true` so Think's compatibility methods are added alongside -`workspace.fs` and `workspace.runtime`. +Agent frameworks should consume files through `workspace.fs`. Use +`createAITools` from `@cloudflare/computer/tools` for the standard AI +SDK file tools. ### Durable pending-sync retries