INTEGRITY Документация

Маршрутизация

Это руководство объясняет, как запросы направляются к агентам, как работает присвоение имён и какие есть паттерны для организации ваших агентов.

Как работает маршрутизация

Когда поступает запрос, routeAgentRequest() анализирует URL и перенаправляет его на соответствующий экземпляр агента:

https://your-worker.dev/agents/{agent-name}/{instance-name}
                               └────┬────┘   └─────┬─────┘
                               Class name     Unique instance ID
                              (kebab-case)

Примеры URL:

URL Класс Agent Экземпляр
/agents/counter/user-123 Counter user-123
/agents/chat-room/lobby ChatRoom lobby
/agents/my-agent/default MyAgent default

Разрешение имён

Имена классов Agent автоматически преобразуются в kebab-case для URL:

Имя класса URL Path
Counter /agents/counter/...
MyAgent /agents/my-agent/...
ChatRoom /agents/chat-room/...
AIAssistant /agents/ai-assistant/...

Роутер сопоставляет как исходное имя, так и его kebab-case версию, поэтому можно использовать любое из них:

Использование 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) определяет, какой экземпляр агента обрабатывает запрос. Каждое уникальное имя получает собственный изолированный агент с собственным состоянием.

Агенты для каждого пользователя

Каждый пользователь получает собственный экземпляр агента:

// 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

Глобальный синглтон

Один экземпляр для всего приложения:

// 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

Для сложных сценариев, где нужен контроль над структурой URL, можно обойти стандартный /agents/{agent}/{name} шаблоном.

Использование 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
});

Это полезно, когда:

Выбор экземпляра на стороне сервера

При использовании 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);
}

Получение идентичности экземпляра (на стороне клиента)

При использовании basePath, клиент не знает, к какому экземпляру он подключился, пока сервер не вернёт эту информацию. Агент автоматически отправляет свою идентичность при подключении:

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

Обработка изменений идентичности при переподключении

Если при повторном подключении идентификация меняется (например, сессия истекла и пользователь входит под другим аккаунтом), это можно обработать с помощью 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 не задан и идентичность меняется, записывается предупреждение, которое помогает выявить неожиданные изменения сессии.

Отключение идентификации из соображений безопасности

Если имена ваших экземпляров содержат чувствительные данные (идентификаторы сессий, внутренние идентификаторы пользователей), можно отключить отправку идентификатора:

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 };
}

Когда идентификация отключена:

Когда использовать собственную маршрутизацию

Сценарий Подход
Стандартный доступ к агенту По умолчанию /agents/{agent}/{name}
Экземпляр из auth/session 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",
});

Доступные подсказки о местоположении: wnam, enam, sam, weur, eeur, apac, oc, afr, me

Юрисдикция

Для требований к размещению данных:

// 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;
	}
}

При использовании props с routeAgentRequest, одни и те же props передаются любому агенту, который соответствует URL. Это хорошо подходит для универсального контекста, например для аутентификации:

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>;

Для инициализации, специфичной для агента, используйте getAgentByName вместо этого, если вы хотите точно контролировать, какой агент получит props.

Повтор маршрутизации

Используйте routingRetry с getAgentByName() когда серверный код должен повторять попытки при временных сбоях маршрутизации Durable Object:

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>;

Для таких опций, как locationHint, jurisdiction, а также props, см. Параметры маршрутизации.

Подпути и методы 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 });
	}
}

Несколько агентов

В одном проекте может быть несколько классов агентов. Каждый получает собственное пространство имён:

// 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 Request<br/>or WebSocket"] --> B["routeAgentRequest<br/>Parse URL path"]
    B --> C["Find binding in<br/>env by name"]
    C --> D["Get/create DO<br/>by instance ID"]
    D --> E["Agent Instance"]
    E --> F{"Protocol?"}
    F -->|WebSocket| G["onConnect(), onMessage"]
    F -->|HTTP| H["onRequest()"]

Маршрутизация с аутентификацией

Существует несколько способов аутентифицировать запросы до того, как они достигнут вашего агента.

Использование хуков аутентификации

routeAgentRequest() функция предоставляет onBeforeConnect и onBeforeRequest хуки для аутентификации:

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 ↗, авторизуйтесь в middleware перед вызовом агента:

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) см. в Аутентификация между доменами.

Устранение неполадок

Пространство имён Agent не найдено

Сообщение об ошибке перечисляет доступных агентов. Проверьте:

  1. Класс Agent экспортируется из вашей точки входа.
  2. Имя класса в коде соответствует class_name в wrangler.jsonc.
  3. URL использует корректное имя в формате kebab-case.

Запрос возвращает 404

  1. Проверьте шаблон URL: /agents/{agent-name}/{instance-name}.
  2. Проверьте, что routeAgentRequest() вызывается до вашего обработчика 404.
  3. Убедитесь, что ответ от routeAgentRequest() возвращается (а не просто вызывается).

Соединение WebSocket не устанавливается

  1. Не изменяйте ответ от routeAgentRequest() для апгрейдов WebSocket.
  2. Убедитесь, что CORS включён, если подключение выполняется с другого источника.
  3. Фактическую ошибку проверьте в инструментах разработчика браузера.

basePath не работает

  1. Убедитесь, что ваш Worker обрабатывает пользовательский путь и перенаправляет запрос агенту.
  2. Используйте getAgentByName() + agent.fetch(request) чтобы перенаправлять запросы.
  3. agent параметр по-прежнему обязателен, но игнорируется, если basePath задан.
  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> - Ответ при совпадении, undefined, если маршрут агента не найден.

getAgentByName(namespace, name, options?)

Получить экземпляр агента по имени для RPC на стороне сервера или переадресации запросов.

Параметр Тип Описание
namespace DurableObjectNamespace<T> Привязка Agent из 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 Имя класса Agent (обязательно)
name string Имя экземпляра (по умолчанию: "default")
basePath string Полный путь URL: обходит построение URL agent/name
path string Дополнительный путь, добавляемый к URL
onIdentity (name, agent) => void Вызывается, когда сервер отправляет идентичность
onIdentityChange (oldName, newName, oldAgent, newAgent) => void Вызывается при изменении идентичности при переподключении

Свойства возвращаемого значения (React-хук):

Свойство Тип Описание
name string Имя текущего экземпляра (реактивное)
agent string Имя текущего класса агента (реактивное)
identified boolean Получена ли идентичность (реактивно)
ready Promise<void> Разрешается при получении identity

Agent.options (сервер)

Статические параметры конфигурации агента:

Опция Тип По умолчанию Описание
hibernate boolean true Должен ли агент переходить в спящий режим при простое
sendIdentityOnConnect boolean true Нужно ли отправлять идентичность клиентам при подключении
hungScheduleTimeoutSeconds number 30 Тайм-аут, после которого работающее расписание считается зависшим
class SecureAgent extends Agent {
	static options = { sendIdentityOnConnect: false };
}
class SecureAgent extends Agent {
	static options = { sendIdentityOnConnect: false };
}

Следующие шаги

Клиентский SDK

Подключение из браузеров с помощью useAgent и AgentClient.

Аутентификация между доменами

Паттерны аутентификации WebSocket.

Вызываемые методы

RPC от клиентов через WebSocket.

Конфигурация

Настройте привязки агента в wrangler.jsonc.