Skip to content

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

ルーティング

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

このガイドでは、リクエストがエージェントへどうルーティングされるか、命名の仕組み、エージェントを整理するパターンを説明します。

ルーティングの仕組み

リクエストが届くと、routeAgentRequest() は URL を調べ、適切なエージェントインスタンスへルーティングします。

https://your-worker.dev/agents/{agent-name}/{instance-name}
                               └────┬────┘   └─────┬─────┘
                               クラス名     一意なインスタンス ID
                              (kebab-case)

URL の例:

URL エージェントクラス インスタンス
/agents/counter/user-123 Counter user-123
/agents/chat-room/lobby ChatRoom lobby
/agents/my-agent/default MyAgent default

名前の解決

エージェントクラス名は、URL 向けに自動で kebab-case に変換されます。

クラス名 URL パス
Counter /agents/counter/...
MyAgent /agents/my-agent/...
ChatRoom /agents/chat-room/...
AIAssistant /agents/ai-assistant/...

ルーターは元の名前と kebab-case 版の両方に一致するため、どちらでも使えます。

  • useAgent({ agent: "Counter" })/agents/counter/...
  • useAgent({ agent: "counter" })/agents/counter/...

routeAgentRequest() を使う

routeAgentRequest() 関数は、エージェントルーティングの主なエントリポイントです。

import { routeAgentRequest } from "agents";

export default {
	async fetch(request, env, ctx) {
		// Route to agents - returns Response or undefined
		const agentResponse = await routeAgentRequest(request, env);

		if (agentResponse) {
			return agentResponse;
		}

		// No agent matched - handle other routes
		return new Response("Not found", { status: 404 });
	},
};
import { routeAgentRequest } from "agents";

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext) {
		// Route to agents - returns Response or undefined
		const agentResponse = await routeAgentRequest(request, env);

		if (agentResponse) {
			return agentResponse;
		}

		// No agent matched - handle other routes
		return new Response("Not found", { status: 404 });
	},
} satisfies ExportedHandler<Env>;

エージェント URL を組み立てる

既知のエージェント ID 向けのパス名を作るには、buildAgentPath() を使います。この関数はルート経路と、入れ子の各 /sub/ 経路を処理します。

import { buildAgentPath, buildAgentUrl } from "agents";

const address = [
	{ className: "Inbox", name: userId },
	{ className: "Chat", name: chatId },
];

buildAgentPath(address, { leafPath: "/callbacks/job" });
// /agents/inbox/{userId}/sub/chat/{chatId}/callbacks/job

buildAgentUrl("https://app.example.com", address, {
	leafPath: "/callbacks/job",
});
// URL("https://app.example.com/agents/inbox/...")
import { buildAgentPath, buildAgentUrl } from "agents";

const address = [
	{ className: "Inbox", name: userId },
	{ className: "Chat", name: chatId },
];

buildAgentPath(address, { leafPath: "/callbacks/job" });
// /agents/inbox/{userId}/sub/chat/{chatId}/callbacks/job

buildAgentUrl("https://app.example.com", address, {
	leafPath: "/callbacks/job",
});
// URL("https://app.example.com/agents/inbox/...")

エージェント内では、this.selfPath がルート優先の ID を提供します。ルート Durable Object のバインディング名がクラス名と異なる場合は、rootBinding としてバインディング名を渡します。パス名は HTTP リクエストと WebSocket 接続の両方に対応します。

カスタムのルートプレフィックスを使う場合は、同じ prefixbuildAgentPath()routeAgentRequest() に渡します。コールバックと webhook の例は サブエージェント を参照してください。

インスタンス命名パターン

インスタンス名(URL の最後の部分)は、どのエージェントインスタンスがリクエストを処理するかを決めます。一意な名前ごとに、独自の状態を持つ隔離されたエージェントが作られます。

ユーザーごとのエージェント

各ユーザーが自身のエージェントインスタンスを持ちます。

// Client
const agent = useAgent({
	agent: "UserProfile",
	name: `user-${userId}`, // e.g., "user-abc123"
});
// Client
const agent = useAgent({
	agent: "UserProfile",
	name: `user-${userId}`, // e.g., "user-abc123"
});
/agents/user-profile/user-abc123 → User abc123's agent
/agents/user-profile/user-xyz789 → User xyz789's agent (separate instance)

共有ルーム

複数ユーザーが同じエージェントインスタンスを共有します。

// Client
const agent = useAgent({
	agent: "ChatRoom",
	name: roomId, // e.g., "general" or "room-42"
});
// Client
const agent = useAgent({
	agent: "ChatRoom",
	name: roomId, // e.g., "general" or "room-42"
});
/agents/chat-room/general → All users in "general" share this agent

グローバルシングルトン

アプリケーション全体で 1 つのインスタンスです。

// Client
const agent = useAgent({
	agent: "AppConfig",
	name: "default", // Or any consistent name
});
// Client
const agent = useAgent({
	agent: "AppConfig",
	name: "default", // Or any consistent name
});

動的な命名

コンテキストに基づいてインスタンス名を生成します。

// Per-session
const agent = useAgent({
	agent: "Session",
	name: sessionId,
});

// Per-document
const agent = useAgent({
	agent: "Document",
	name: `doc-${documentId}`,
});

// Per-game
const agent = useAgent({
	agent: "Game",
	name: `game-${gameId}-${Date.now()}`,
});
// Per-session
const agent = useAgent({
	agent: "Session",
	name: sessionId,
});

// Per-document
const agent = useAgent({
	agent: "Document",
	name: `doc-${documentId}`,
});

// Per-game
const agent = useAgent({
	agent: "Game",
	name: `game-${gameId}-${Date.now()}`,
});

カスタム URL ルーティング

デフォルトの /agents/{agent}/{name} パターンを迂回し、URL 構造を制御する必要がある高度な用途では、次を使えます。

basePath を使う(クライアント側)

basePath オプションを使うと、クライアントは任意の URL パスに接続できます。

// Client connects to /user instead of /agents/user-agent/...
const agent = useAgent({
	agent: "UserAgent", // Required but ignored when basePath is set
	basePath: "user", // → connects to /user
});
// Client connects to /user instead of /agents/user-agent/...
const agent = useAgent({
	agent: "UserAgent", // Required but ignored when basePath is set
	basePath: "user", // → connects to /user
});

次のときに便利です。

  • /agents/ プレフィックスなしのきれいな URL が欲しい
  • インスタンス名をサーバー側で決める(認証 / セッションなど)
  • 既存の URL 構造と連携する

サーバー側のインスタンス選択

basePath を使うとき、サーバーがルーティングを処理する必要があります。getAgentByName() でエージェントインスタンスを取得し、fetch() でリクエストを転送します。

export default {
	async fetch(request, env) {
		const url = new URL(request.url);

		// Custom routing - server determines instance from session
		if (url.pathname.startsWith("/user/")) {
			const session = await getSession(request);
			const agent = await getAgentByName(env.UserAgent, session.userId);
			return agent.fetch(request); // Forward request directly to agent
		}

		// Default routing for standard /agents/... paths
		return (
			(await routeAgentRequest(request, env)) ??
			new Response("Not found", { status: 404 })
		);
	},
};
export default {
	async fetch(request: Request, env: Env) {
		const url = new URL(request.url);

		// Custom routing - server determines instance from session
		if (url.pathname.startsWith("/user/")) {
			const session = await getSession(request);
			const agent = await getAgentByName(env.UserAgent, session.userId);
			return agent.fetch(request); // Forward request directly to agent
		}

		// Default routing for standard /agents/... paths
		return (
			(await routeAgentRequest(request, env)) ??
			new Response("Not found", { status: 404 })
		);
	},
} satisfies ExportedHandler<Env>;

動的インスタンス付きのカスタムパス

異なるパスを異なるインスタンスへルーティングします。

// Route /chat/{room} to ChatRoom agent
if (url.pathname.startsWith("/chat/")) {
	const roomId = url.pathname.replace("/chat/", "");
	const agent = await getAgentByName(env.ChatRoom, roomId);
	return agent.fetch(request);
}

// Route /doc/{id} to Document agent
if (url.pathname.startsWith("/doc/")) {
	const docId = url.pathname.replace("/doc/", "");
	const agent = await getAgentByName(env.Document, docId);
	return agent.fetch(request);
}
// Route /chat/{room} to ChatRoom agent
if (url.pathname.startsWith("/chat/")) {
	const roomId = url.pathname.replace("/chat/", "");
	const agent = await getAgentByName(env.ChatRoom, roomId);
	return agent.fetch(request);
}

// Route /doc/{id} to Document agent
if (url.pathname.startsWith("/doc/")) {
	const docId = url.pathname.replace("/doc/", "");
	const agent = await getAgentByName(env.Document, docId);
	return agent.fetch(request);
}

インスタンス ID を受け取る(クライアント側)

basePath を使うとき、サーバーがこの情報を返すまで、クライアントはどのインスタンスに接続したかを知りません。エージェントは接続時に自身の ID を自動送信します。

const agent = useAgent({
	agent: "UserAgent",
	basePath: "user",
	onIdentity: (name, agentType) => {
		console.log(`Connected to ${agentType} instance: ${name}`);
		// e.g., "Connected to user-agent instance: user-123"
	},
});

// Reactive state - re-renders when identity is received
return (
	<div>
		{agent.identified ? `Connected to: ${agent.name}` : "Connecting..."}
	</div>
);
const agent = useAgent({
	agent: "UserAgent",
	basePath: "user",
	onIdentity: (name, agentType) => {
		console.log(`Connected to ${agentType} instance: ${name}`);
		// e.g., "Connected to user-agent instance: user-123"
	},
});

// Reactive state - re-renders when identity is received
return (
	<div>
		{agent.identified ? `Connected to: ${agent.name}` : "Connecting..."}
	</div>
);

AgentClient の場合:

const agent = new AgentClient({
	agent: "UserAgent",
	basePath: "user",
	host: "example.com",
	onIdentity: (name, agentType) => {
		// Update UI with actual instance name
		setInstanceName(name);
	},
});

// Wait for identity before proceeding
await agent.ready;
console.log(agent.name); // Now has the server-determined name
const agent = new AgentClient({
	agent: "UserAgent",
	basePath: "user",
	host: "example.com",
	onIdentity: (name, agentType) => {
		// Update UI with actual instance name
		setInstanceName(name);
	},
});

// Wait for identity before proceeding
await agent.ready;
console.log(agent.name); // Now has the server-determined name

再接続時の ID 変更を扱う

再接続時に ID が変わる場合(セッション期限切れ後、別ユーザーとしてログインするなど)は、onIdentityChange で扱えます。

const agent = useAgent({
	agent: "UserAgent",
	basePath: "user",
	onIdentityChange: (oldName, newName, oldAgent, newAgent) => {
		console.log(`Session changed: ${oldName} → ${newName}`);
		// Refresh state, show notification, etc.
	},
});
const agent = useAgent({
	agent: "UserAgent",
	basePath: "user",
	onIdentityChange: (oldName, newName, oldAgent, newAgent) => {
		console.log(`Session changed: ${oldName} → ${newName}`);
		// Refresh state, show notification, etc.
	},
});

onIdentityChange がなく、ID が変わった場合は、想定外のセッション変更を見つけるために警告がログされます。

セキュリティのために ID を無効にする

インスタンス名に機微データ(セッション ID、内部ユーザー ID)が含まれる場合、ID の送信を無効にできます。

class SecureAgent extends Agent {
	// Do not expose instance names to clients
	static options = { sendIdentityOnConnect: false };
}
class SecureAgent extends Agent {
	// Do not expose instance names to clients
	static options = { sendIdentityOnConnect: false };
}

ID が無効なとき:

  • agent.identifiedfalse のままです
  • agent.ready は解決しません(代わりに状態更新を使います)
  • onIdentityonIdentityChange は呼ばれません

カスタムルーティングを使うタイミング

シナリオ アプローチ
標準のエージェントアクセス デフォルトの /agents/{agent}/{name}
認証 / セッションからのインスタンス basePath + getAgentByName + fetch
きれいな URL(/agents/ プレフィックスなし) basePath + カスタムルーティング
レガシー URL 構造 basePath + カスタムルーティング
複雑なルーティングロジック Worker 内のカスタムルーティング

ルーティングオプション

routeAgentRequest()getAgentByName() はどちらも、ルーティング動作をカスタマイズするオプションを受け付けます。

CORS

クロスオリジンリクエスト(フロントエンドが別ドメインにある場合など)では:

const response = await routeAgentRequest(request, env, {
	cors: true, // Enable default CORS headers
});
const response = await routeAgentRequest(request, env, {
	cors: true, // Enable default CORS headers
});

またはカスタム CORS ヘッダー付き:

const response = await routeAgentRequest(request, env, {
	cors: {
		"Access-Control-Allow-Origin": "https://myapp.com",
		"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
		"Access-Control-Allow-Headers": "Content-Type, Authorization",
	},
});
const response = await routeAgentRequest(request, env, {
	cors: {
		"Access-Control-Allow-Origin": "https://myapp.com",
		"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
		"Access-Control-Allow-Headers": "Content-Type, Authorization",
	},
});

ロケーションヒント

レイテンシに敏感なアプリケーションでは、エージェントの実行場所をヒントとして渡せます。

// With getAgentByName
const agent = await getAgentByName(env.MyAgent, "instance-name", {
	locationHint: "enam", // Eastern North America
});

// With routeAgentRequest (applies to all matched agents)
const response = await routeAgentRequest(request, env, {
	locationHint: "enam",
});
// With getAgentByName
const agent = await getAgentByName(env.MyAgent, "instance-name", {
	locationHint: "enam", // Eastern North America
});

// With routeAgentRequest (applies to all matched agents)
const response = await routeAgentRequest(request, env, {
	locationHint: "enam",
});

利用可能なロケーションヒント: wnamenamsamweureeurapacocafrme

管轄

データ所在地の要件がある場合:

// With getAgentByName
const agent = await getAgentByName(env.MyAgent, "instance-name", {
	jurisdiction: "eu", // EU jurisdiction
});

// With routeAgentRequest (applies to all matched agents)
const response = await routeAgentRequest(request, env, {
	jurisdiction: "eu",
});
// With getAgentByName
const agent = await getAgentByName(env.MyAgent, "instance-name", {
	jurisdiction: "eu", // EU jurisdiction
});

// With routeAgentRequest (applies to all matched agents)
const response = await routeAgentRequest(request, env, {
	jurisdiction: "eu",
});

Props

エージェントは直接コンストラクトされるのではなくランタイムがインスタンス化するため、初期化引数を渡すには props を使います。

const agent = await getAgentByName(env.MyAgent, "instance-name", {
	props: {
		userId: session.userId,
		config: { maxRetries: 3 },
	},
});
const agent = await getAgentByName(env.MyAgent, "instance-name", {
	props: {
		userId: session.userId,
		config: { maxRetries: 3 },
	},
});

Props はエージェントの onStart ライフサイクルメソッドに渡されます。

class MyAgent extends Agent {
	userId;
	config;

	async onStart(props) {
		this.userId = props?.userId;
		this.config = props?.config;
	}
}
class MyAgent extends Agent<Env, State> {
	private userId?: string;
	private config?: { maxRetries: number };

	async onStart(props?: { userId: string; config: { maxRetries: number } }) {
		this.userId = props?.userId;
		this.config = props?.config;
	}
}

routeAgentRequestprops を使うと、URL に一致したエージェントへ同じ props が渡されます。認証などの共通コンテキストに向いています。

export default {
	async fetch(request, env) {
		const session = await getSession(request);
		return routeAgentRequest(request, env, {
			props: { userId: session.userId, role: session.role },
		});
	},
};
export default {
	async fetch(request, env) {
		const session = await getSession(request);
		return routeAgentRequest(request, env, {
			props: { userId: session.userId, role: session.role },
		});
	},
} satisfies ExportedHandler<Env>;

エージェント固有の初期化では、どのエージェントが props を受け取るかを制御できる getAgentByName を使います。

ルーティングの再試行

サーバー側コードが一時的な Durable Object ルーティング失敗を再試行すべきときは、getAgentByName()routingRetry を使います。

const agent = await getAgentByName(env.MyAgent, "instance-name", {
	routingRetry: {
		maxAttempts: 3,
	},
});
const agent = await getAgentByName(env.MyAgent, "instance-name", {
	routingRetry: {
		maxAttempts: 3,
	},
});

このオプションは、短時間のルーティング失敗を呼び出し元へエラーを返す前に再試行すべき、リクエスト転送と RPC 経路に便利です。

フック

routeAgentRequest は、リクエストがエージェントに届く前に傍受するフックに対応します。

const response = await routeAgentRequest(request, env, {
	onBeforeConnect: (req, lobby) => {
		// Called before WebSocket connections
		// Return a Response to reject, Request to modify, or void to continue
	},
	onBeforeRequest: (req, lobby) => {
		// Called before HTTP requests
		// Return a Response to reject, Request to modify, or void to continue
	},
});
const response = await routeAgentRequest(request, env, {
	onBeforeConnect: (req, lobby) => {
		// Called before WebSocket connections
		// Return a Response to reject, Request to modify, or void to continue
	},
	onBeforeRequest: (req, lobby) => {
		// Called before HTTP requests
		// Return a Response to reject, Request to modify, or void to continue
	},
});

これらのフックは認証と検証に便利です。詳細な例は クロスドメイン認証 を参照してください。

サーバー側のエージェントアクセス

Worker コードから getAgentByName() を使い、RPC 呼び出しのためにエージェントにアクセスできます。

import { getAgentByName, routeAgentRequest } from "agents";

export default {
	async fetch(request, env) {
		const url = new URL(request.url);

		// API endpoint that interacts with an agent
		if (url.pathname === "/api/increment") {
			const counter = await getAgentByName(env.Counter, "global-counter");
			const newCount = await counter.increment();
			return Response.json({ count: newCount });
		}

		// Regular agent routing
		return (
			(await routeAgentRequest(request, env)) ??
			new Response("Not found", { status: 404 })
		);
	},
};
import { getAgentByName, routeAgentRequest } from "agents";

export default {
	async fetch(request: Request, env: Env) {
		const url = new URL(request.url);

		// API endpoint that interacts with an agent
		if (url.pathname === "/api/increment") {
			const counter = await getAgentByName(env.Counter, "global-counter");
			const newCount = await counter.increment();
			return Response.json({ count: newCount });
		}

		// Regular agent routing
		return (
			(await routeAgentRequest(request, env)) ??
			new Response("Not found", { status: 404 })
		);
	},
} satisfies ExportedHandler<Env>;

locationHintjurisdictionprops などのオプションは ルーティングオプション を参照してください。

サブパスと HTTP メソッド

リクエストは、インスタンス名のあとにサブパスを含められます。これらはエージェントの onRequest() ハンドラーに渡されます。

/agents/api/v1/users     → agent: "api", instance: "v1", path: "/users"
/agents/api/v1/users/123 → agent: "api", instance: "v1", path: "/users/123"

エージェント内でサブパスを処理します。

export class API extends Agent {
	async onRequest(request) {
		const url = new URL(request.url);

		// url.pathname contains the full path including /agents/api/v1/...
		// Extract the sub-path after your agent's base path
		const path = url.pathname.replace(/^\/agents\/api\/[^/]+/, "");

		if (request.method === "GET" && path === "/users") {
			return Response.json(await this.getUsers());
		}

		if (request.method === "POST" && path === "/users") {
			const data = await request.json();
			return Response.json(await this.createUser(data));
		}

		return new Response("Not found", { status: 404 });
	}
}
export class API extends Agent {
	async onRequest(request: Request): Promise<Response> {
		const url = new URL(request.url);

		// url.pathname contains the full path including /agents/api/v1/...
		// Extract the sub-path after your agent's base path
		const path = url.pathname.replace(/^\/agents\/api\/[^/]+/, "");

		if (request.method === "GET" && path === "/users") {
			return Response.json(await this.getUsers());
		}

		if (request.method === "POST" && path === "/users") {
			const data = await request.json();
			return Response.json(await this.createUser(data));
		}

		return new Response("Not found", { status: 404 });
	}
}

複数エージェント

1 つのプロジェクトに複数のエージェントクラスを持てます。それぞれが独自の名前空間を持ちます。

// server.ts
export { Counter } from "./agents/counter";
export { ChatRoom } from "./agents/chat-room";
export { UserProfile } from "./agents/user-profile";

export default {
	async fetch(request, env) {
		return (
			(await routeAgentRequest(request, env)) ??
			new Response("Not found", { status: 404 })
		);
	},
};
// server.ts
export { Counter } from "./agents/counter";
export { ChatRoom } from "./agents/chat-room";
export { UserProfile } from "./agents/user-profile";

export default {
	async fetch(request: Request, env: Env) {
		return (
			(await routeAgentRequest(request, env)) ??
			new Response("Not found", { status: 404 })
		);
	},
} satisfies ExportedHandler<Env>;
{
	"durable_objects": {
		"bindings": [
			{ "name": "Counter", "class_name": "Counter" },
			{ "name": "ChatRoom", "class_name": "ChatRoom" },
			{ "name": "UserProfile", "class_name": "UserProfile" },
		],
	},
	"migrations": [
		{
			"tag": "v1",
			"new_sqlite_classes": ["Counter", "ChatRoom", "UserProfile"],
		},
	],
}
[[durable_objects.bindings]]
name = "Counter"
class_name = "Counter"

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

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

[[migrations]]
tag = "v1"
new_sqlite_classes = [ "Counter", "ChatRoom", "UserProfile" ]

各エージェントは独自のパス経由でアクセスします。

/agents/counter/...
/agents/chat-room/...
/agents/user-profile/...

リクエストフロー

リクエストがシステムを流れる様子は次のとおりです。

flowchart TD
    A["HTTP リクエスト<br/>または WebSocket"] --> B["routeAgentRequest<br/>URL パスを解析"]
    B --> C["名前で env 内の<br/>バインディングを探す"]
    C --> D["インスタンス ID で<br/>DO を取得 / 作成"]
    D --> E["エージェントインスタンス"]
    E --> F{"プロトコル?"}
    F -->|WebSocket| G["onConnect()、onMessage"]
    F -->|HTTP| H["onRequest()"]

認証付きルーティング

リクエストがエージェントに届く前に認証する方法はいくつかあります。

認証フックを使う

routeAgentRequest() 関数は、認証向けに onBeforeConnectonBeforeRequest フックを提供します。

import { Agent, routeAgentRequest } from "agents";

export default {
	async fetch(request, env) {
		return (
			(await routeAgentRequest(request, env, {
				// Run before WebSocket connections
				onBeforeConnect: async (request) => {
					const token = new URL(request.url).searchParams.get("token");
					if (!(await verifyToken(token, env))) {
						// Return a response to reject the connection
						return new Response("Unauthorized", { status: 401 });
					}
					// Return nothing to allow the connection
				},
				// Run before HTTP requests
				onBeforeRequest: async (request) => {
					const auth = request.headers.get("Authorization");
					if (!auth || !(await verifyAuth(auth, env))) {
						return new Response("Unauthorized", { status: 401 });
					}
				},
				// Optional: prepend a prefix to agent instance names
				prefix: "user-",
			})) ?? new Response("Not found", { status: 404 })
		);
	},
};
import { Agent, routeAgentRequest } from "agents";

export default {
	async fetch(request: Request, env: Env) {
		return (
			(await routeAgentRequest(request, env, {
				// Run before WebSocket connections
				onBeforeConnect: async (request) => {
					const token = new URL(request.url).searchParams.get("token");
					if (!(await verifyToken(token, env))) {
						// Return a response to reject the connection
						return new Response("Unauthorized", { status: 401 });
					}
					// Return nothing to allow the connection
				},
				// Run before HTTP requests
				onBeforeRequest: async (request) => {
					const auth = request.headers.get("Authorization");
					if (!auth || !(await verifyAuth(auth, env))) {
						return new Response("Unauthorized", { status: 401 });
					}
				},
				// Optional: prepend a prefix to agent instance names
				prefix: "user-",
			})) ?? new Response("Not found", { status: 404 })
		);
	},
} satisfies ExportedHandler<Env>;

手動認証

routeAgentRequest() を呼ぶ前に認証を確認します。

export default {
	async fetch(request, env) {
		const url = new URL(request.url);

		// Protect agent routes
		if (url.pathname.startsWith("/agents/")) {
			const user = await authenticate(request, env);
			if (!user) {
				return new Response("Unauthorized", { status: 401 });
			}

			// Optionally, enforce that users can only access their own agents
			const instanceName = url.pathname.split("/")[3];
			if (instanceName !== `user-${user.id}`) {
				return new Response("Forbidden", { status: 403 });
			}
		}

		return (
			(await routeAgentRequest(request, env)) ??
			new Response("Not found", { status: 404 })
		);
	},
};
export default {
	async fetch(request: Request, env: Env) {
		const url = new URL(request.url);

		// Protect agent routes
		if (url.pathname.startsWith("/agents/")) {
			const user = await authenticate(request, env);
			if (!user) {
				return new Response("Unauthorized", { status: 401 });
			}

			// Optionally, enforce that users can only access their own agents
			const instanceName = url.pathname.split("/")[3];
			if (instanceName !== `user-${user.id}`) {
				return new Response("Forbidden", { status: 403 });
			}
		}

		return (
			(await routeAgentRequest(request, env)) ??
			new Response("Not found", { status: 404 })
		);
	},
} satisfies ExportedHandler<Env>;

フレームワークを使う(Hono)

Hono などのフレームワークを使う場合は、エージェントを呼ぶ前にミドルウェアで認証します。

import { Agent, getAgentByName } from "agents";
import { Hono } from "hono";

const app = new Hono();

// Authentication middleware
app.use("/agents/*", async (c, next) => {
	const token = c.req.header("Authorization")?.replace("Bearer ", "");
	if (!token || !(await verifyToken(token, c.env))) {
		return c.json({ error: "Unauthorized" }, 401);
	}
	await next();
});

// Route to a specific agent
app.all("/agents/code-review/:id/*", async (c) => {
	const id = c.req.param("id");
	const agent = await getAgentByName(c.env.CodeReviewAgent, id);
	return agent.fetch(c.req.raw);
});

export default app;
import { Agent, getAgentByName } from "agents";
import { Hono } from "hono";

const app = new Hono<{ Bindings: Env }>();

// Authentication middleware
app.use("/agents/*", async (c, next) => {
	const token = c.req.header("Authorization")?.replace("Bearer ", "");
	if (!token || !(await verifyToken(token, c.env))) {
		return c.json({ error: "Unauthorized" }, 401);
	}
	await next();
});

// Route to a specific agent
app.all("/agents/code-review/:id/*", async (c) => {
	const id = c.req.param("id");
	const agent = await getAgentByName(c.env.CodeReviewAgent, id);
	return agent.fetch(c.req.raw);
});

export default app;

WebSocket 認証パターン(URL 内のトークン、JWT 更新)は、クロスドメイン認証 を参照してください。

トラブルシューティング

エージェント名前空間が見つからない

エラーメッセージは利用可能なエージェントを一覧します。次を確認します。

  1. エージェントクラスがエントリポイントからエクスポートされていること。
  2. コード内のクラス名が wrangler.jsoncclass_name と一致すること。
  3. URL が正しい kebab-case 名を使っていること。

リクエストが 404 を返す

  1. URL パターン /agents/{agent-name}/{instance-name} を確認します。
  2. 404 ハンドラーの前に routeAgentRequest() が呼ばれていることを確認します。
  3. routeAgentRequest() からの応答が返されていること(呼ばれただけではないこと)を確認します。

WebSocket 接続が失敗する

  1. WebSocket アップグレードでは、routeAgentRequest() からの応答を変更しないでください。
  2. 別オリジンから接続する場合は、CORS が有効であることを確認します。
  3. 実際のエラーはブラウザーの開発者ツールで確認します。

basePath が動かない

  1. Worker がカスタムパスを処理し、エージェントへ転送していることを確認します。
  2. リクエストの転送には getAgentByName() + agent.fetch(request) を使います。
  3. basePath が設定されているとき、agent パラメーターは必須ですが無視されます。
  4. サーバー側ルートがクライアントの basePath と一致することを確認します。

API リファレンス

routeAgentRequest(request, env, options?)

リクエストを適切なエージェントへルーティングします。

パラメーター 説明
request Request 受信リクエスト
env Env エージェントバインディング付きの環境
options.cors boolean | HeadersInit CORS ヘッダーを有効にする
options.props Record<string, unknown> リクエストを処理するエージェントへ渡す props
options.locationHint string エージェントインスタンスの優先ロケーション
options.jurisdiction string エージェントインスタンスのデータ管轄
options.onBeforeConnect Function WebSocket 接続前のコールバック
options.onBeforeRequest Function HTTP リクエスト前のコールバック

戻り値: Promise<Response | undefined> — 一致した場合は Response、エージェントルートがない場合は undefined。

getAgentByName(namespace, name, options?)

サーバー側 RPC またはリクエスト転送のために、名前でエージェントインスタンスを取得します。

パラメーター 説明
namespace DurableObjectNamespace<T> env からのエージェントバインディング
name string インスタンス名
options.locationHint string 優先ロケーション
options.jurisdiction string データ管轄
options.props Record<string, unknown> onStart 向けの初期化プロパティ
options.routingRetry object 一時的な Durable Object ルーティング失敗向けの再試行設定

戻り値: Promise<DurableObjectStub<T>> — エージェントメソッドの呼び出しまたはリクエスト転送用の型付きスタブ。

useAgent(options) / AgentClient のオプション

カスタムルーティング向けのクライアント接続オプションです。

オプション 説明
agent string エージェントクラス名(必須)
name string インスタンス名(デフォルト: "default"
basePath string 完全な URL パス。agent/name の URL 組み立てを迂回します
path string URL に追加するパス
onIdentity (name, agent) => void サーバーが ID を送ったときに呼ばれます
onIdentityChange (oldName, newName, oldAgent, newAgent) => void 再接続時に ID が変わったときに呼ばれます

戻り値のプロパティ(React フック):

プロパティ 説明
name string 現在のインスタンス名(リアクティブ)
agent string 現在のエージェントクラス名(リアクティブ)
identified boolean ID を受け取ったかどうか(リアクティブ)
ready Promise<void> ID を受け取ると解決します

Agent.options(サーバー)

エージェント設定の静的オプションです。

オプション デフォルト 説明
hibernate boolean true 非アクティブ時にエージェントをハイバネートするかどうか
sendIdentityOnConnect boolean true 接続時にクライアントへ ID を送るかどうか
hungScheduleTimeoutSeconds number 30 実行中のスケジュールがハングと見なされるまでのタイムアウト
class SecureAgent extends Agent {
	static options = { sendIdentityOnConnect: false };
}
class SecureAgent extends Agent {
	static options = { sendIdentityOnConnect: false };
}

次のステップ

設定

wrangler.jsonc でエージェントバインディングを設定します。

役に立ちましたか?