Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

はじめに

最終更新 Markdown で表示Agent セットアップ

永続メモリ、組み込みファイルツール、ストリーミングを備えたチャットエージェントを、手順どおりに構築します。

Cloudflare Agents が初めてなら、先に エージェントとは で中核の考え方を確認してください。そうでなければ、ここからそのまま進められます。

このチュートリアルの終わりには、次ができる Think エージェントがあります。

  • React チャット UI へ応答をストリーミングする
  • モデルが読み書きできる永続メモリを持つ
  • ワークスペースのファイルツール(read、write、edit、find、grep、delete)を含む
  • カスタムのサーバー側ツールに対応する

前提条件

  • Node.js 24+
  • Workers AI にアクセスできる Cloudflare アカウント
  • TypeScript と Cloudflare Workers の基礎知識

1. プロジェクトを作る

mkdir my-think-agent && cd my-think-agent
npm init -y

依存関係をインストールします。

npm install @cloudflare/think @cloudflare/ai-chat agents ai @cloudflare/shell zod workers-ai-provider react react-dom
npm install -D wrangler @cloudflare/vite-plugin @cloudflare/workers-types @vitejs/plugin-react @tailwindcss/vite tailwindcss typescript vite

2. wrangler を設定する

wrangler.jsonc を作成します。

{
	"name": "my-think-agent",
	"compatibility_date": "2026-01-28",
	"compatibility_flags": ["nodejs_compat"],
	"ai": { "binding": "AI" },
	"assets": {
		"not_found_handling": "single-page-application",
		"run_worker_first": ["/agents/*"]
	},
	"durable_objects": {
		"bindings": [{ "class_name": "MyAgent", "name": "MyAgent" }]
	},
	"migrations": [{ "new_sqlite_classes": ["MyAgent"], "tag": "v1" }],
	"main": "src/server.ts"
}
name = "my-think-agent"
compatibility_date = "2026-01-28"
compatibility_flags = [ "nodejs_compat" ]
main = "src/server.ts"

[ai]
binding = "AI"

[assets]
not_found_handling = "single-page-application"
run_worker_first = [ "/agents/*" ]

[[durable_objects.bindings]]
class_name = "MyAgent"
name = "MyAgent"

[[migrations]]
new_sqlite_classes = [ "MyAgent" ]
tag = "v1"

vite.config.ts を作成します。

import { cloudflare } from "@cloudflare/vite-plugin";
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";

export default defineConfig({
	plugins: [react(), cloudflare(), tailwindcss()],
});
import { cloudflare } from "@cloudflare/vite-plugin";
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";

export default defineConfig({
	plugins: [react(), cloudflare(), tailwindcss()],
});

tsconfig.json を作成します。

{
	"extends": "agents/tsconfig"
}

3. エージェントを定義する

src/server.ts を作成します。

import { Think } from "@cloudflare/think";
import { createWorkersAI } from "workers-ai-provider";
import { routeAgentRequest } from "agents";

export class MyAgent extends Think {
	getModel() {
		return createWorkersAI({ binding: this.env.AI })(
			"@cf/moonshotai/kimi-k2.6",
		);
	}

	getSystemPrompt() {
		return "You are a helpful assistant with access to a workspace filesystem.";
	}
}

export default {
	async fetch(request, env) {
		return (
			(await routeAgentRequest(request, env)) ||
			new Response("Not found", { status: 404 })
		);
	},
};
import { Think } from "@cloudflare/think";
import { createWorkersAI } from "workers-ai-provider";
import { routeAgentRequest } from "agents";

export class MyAgent extends Think<Env> {
	getModel() {
		return createWorkersAI({ binding: this.env.AI })(
			"@cf/moonshotai/kimi-k2.6",
		);
	}

	getSystemPrompt() {
		return "You are a helpful assistant with access to a workspace filesystem.";
	}
}

export default {
	async fetch(request: Request, env: Env) {
		return (
			(await routeAgentRequest(request, env)) ||
			new Response("Not found", { status: 404 })
		);
	},
} satisfies ExportedHandler<Env>;

これで動くエージェントです。Think は次を自動で提供します。

  • WebSocket チャットプロトコル(useAgentChat 互換)
  • SQLite へのメッセージ永続化
  • 再開可能なストリーミング(ページ更新でバッファ済みチャンクを再生)
  • ワークスペースのファイルツール(read、write、edit、list、find、grep、delete)
  • 中止 / キャンセル対応
  • 部分メッセージを残すエラー処理

4. React クライアントをつなぐ

src/client.tsx を作成します。

import { createRoot } from "react-dom/client";
import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";

function Chat() {
	const agent = useAgent({ agent: "MyAgent" });
	const { messages, sendMessage, status } = useAgentChat({ agent });

	return (
		<div>
			<h1>Think Agent</h1>
			{messages.map((msg) => (
				<div key={msg.id}>
					<strong>{msg.role}:</strong>
					{msg.parts.map((part, i) =>
						part.type === "text" ? <span key={i}>{part.text}</span> : null,
					)}
				</div>
			))}

			<form
				onSubmit={(e) => {
					e.preventDefault();
					const input = e.currentTarget.elements.namedItem("input");
					if (!input.value.trim()) return;
					sendMessage({ text: input.value });
					input.value = "";
				}}
			>
				<input name="input" placeholder="Send a message..." />
				<button type="submit">Send</button>
			</form>

			<p>Status: {status}</p>
		</div>
	);
}

const root = document.getElementById("root");
if (root) {
	createRoot(root).render(<Chat />);
}
import { createRoot } from "react-dom/client";
import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";

function Chat() {
	const agent = useAgent({ agent: "MyAgent" });
	const { messages, sendMessage, status } = useAgentChat({ agent });

	return (
		<div>
			<h1>Think Agent</h1>
			{messages.map((msg) => (
				<div key={msg.id}>
					<strong>{msg.role}:</strong>
					{msg.parts.map((part, i) =>
						part.type === "text" ? <span key={i}>{part.text}</span> : null,
					)}
				</div>
			))}

			<form
				onSubmit={(e) => {
					e.preventDefault();
					const input = e.currentTarget.elements.namedItem(
						"input",
					) as HTMLInputElement;
					if (!input.value.trim()) return;
					sendMessage({ text: input.value });
					input.value = "";
				}}
			>
				<input name="input" placeholder="Send a message..." />
				<button type="submit">Send</button>
			</form>

			<p>Status: {status}</p>
		</div>
	);
}

const root = document.getElementById("root");
if (root) {
	createRoot(root).render(<Chat />);
}

index.html を作成します。

<!doctype html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Think Agent</title>
	</head>
	<body>
		<div id="root"></div>
		<script type="module" src="/src/client.tsx"></script>
	</body>
</html>

5. 実行する

npx vite dev

ブラウザを開き、メッセージを送ります。エージェントはテキストをストリーミングで返し、ワークスペースのファイルツールはモデルから自動で使えます。

6. 永続メモリを追加する

configureSession をオーバーライドし、再起動後も残る書き込み可能なメモリをモデルに渡します。

export class MyAgent extends Think {
	getModel() {
		return createWorkersAI({ binding: this.env.AI })(
			"@cf/moonshotai/kimi-k2.6",
		);
	}

	configureSession(session) {
		return session
			.withContext("soul", {
				provider: {
					get: async () =>
						"You are a helpful assistant. Remember important facts about the user.",
				},
			})
			.withContext("memory", {
				description: "Important facts about the user and conversation.",
				maxTokens: 2000,
			})
			.withCachedPrompt();
	}
}
export class MyAgent extends Think<Env> {
	getModel(): LanguageModel {
		return createWorkersAI({ binding: this.env.AI })(
			"@cf/moonshotai/kimi-k2.6",
		);
	}

	configureSession(session: Session) {
		return session
			.withContext("soul", {
				provider: {
					get: async () =>
						"You are a helpful assistant. Remember important facts about the user.",
				},
			})
			.withContext("memory", {
				description: "Important facts about the user and conversation.",
				maxTokens: 2000,
			})
			.withCachedPrompt();
	}
}

これでモデルはシステムプロンプトに MEMORY セクションを見ます。更新には set_context ツールを使います。メモリに書いた事実は SQLite に残り、Durable Object のハイバネーションと再起動を越えます。

configureSession を使うと、システムプロンプトは getSystemPrompt() ではなくコンテキストブロックから組み立てます。上の "soul" ブロックがシステムアイデンティティです。読み取り専用で、常に先頭に出ます。"memory" ブロックは書き込み可能で、役立つ情報を得るとモデルが先回りして更新します。

コンテキストブロック、コンパクション、検索、スキル、複数セッションは Sessions のドキュメント を参照してください。

7. カスタムツールを追加する

getTools() をオーバーライドし、組み込みのワークスペースツールと並べて独自ツールを追加します。

import { tool } from "ai";
import { z } from "zod";

export class MyAgent extends Think {
	getModel() {
		/* ... */
	}
	configureSession(session) {
		/* ... */
	}

	getTools() {
		return {
			getWeather: tool({
				description: "Get the current weather for a city",
				inputSchema: z.object({
					city: z.string().describe("City name"),
				}),
				execute: async ({ city }) => {
					const res = await fetch(
						`https://api.weatherapi.com/v1/current.json?key=${this.env.WEATHER_KEY}&q=${city}`,
					);
					return res.json();
				},
			}),
		};
	}
}
import { tool } from "ai";
import { z } from "zod";

export class MyAgent extends Think<Env> {
	getModel(): LanguageModel {
		/* ... */
	}
	configureSession(session: Session) {
		/* ... */
	}

	getTools(): ToolSet {
		return {
			getWeather: tool({
				description: "Get the current weather for a city",
				inputSchema: z.object({
					city: z.string().describe("City name"),
				}),
				execute: async ({ city }) => {
					const res = await fetch(
						`https://api.weatherapi.com/v1/current.json?key=${this.env.WEATHER_KEY}&q=${city}`,
					);
					return res.json();
				},
			}),
		};
	}
}

Think は複数ソースのツールを自動でマージします。各ターンで、モデルは次にアクセスできます。

  1. ワークスペースツール — read、write、edit、list、find、grep、delete、bash(組み込み)
  2. 独自ツールgetTools() から
  3. 拡張ツール — 読み込んだ拡張から
  4. セッションツール — set_context、load_context、search_context(configureSession から)
  5. スキルツール — activate_skill、read_skill_resource、任意の run_skill_script(getSkills() から)
  6. MCP ツール — 接続中の MCP サーバーから(ある場合)
  7. クライアントツール — ブラウザから(ある場合)

8. ライフサイクルフックを追加する

Think は、エントリパスに関係なく、各ターンで発火するフックを提供します。

export class MyAgent extends Think {
	getModel() {
		/* ... */
	}

	beforeTurn(ctx) {
		console.log(
			`Turn starting: ${Object.keys(ctx.tools).length} tools available`,
		);
	}

	onChatResponse(result) {
		console.log(`Turn ${result.status}: ${result.message.parts.length} parts`);
	}
}
import type {
	TurnContext,
	TurnConfig,
	ChatResponseResult,
} from "@cloudflare/think";

export class MyAgent extends Think<Env> {
	getModel(): LanguageModel {
		/* ... */
	}

	beforeTurn(ctx: TurnContext): TurnConfig | void {
		console.log(
			`Turn starting: ${Object.keys(ctx.tools).length} tools available`,
		);
	}

	onChatResponse(result: ChatResponseResult) {
		console.log(`Turn ${result.status}: ${result.message.parts.length} parts`);
	}
}

全体のリファレンスは ライフサイクルフック を参照してください。

次のステップ

役に立ちましたか?