← Создание агентов на Cloudflare / agents / getting-started
Добавить в существующий проект
Это руководство показывает, как добавить агентов в существующий проект Cloudflare Workers. Если вы начинаете с нуля, обратитесь к Создание чат-агента взамен.
Предварительные требования
- Существующий проект Cloudflare Workers с конфигурационным файлом Wrangler
- Node.js 18 или новее
1. Установите пакет
npm i agentsДля приложений на React дополнительные пакеты не нужны: привязки React включены по умолчанию.
Для приложений на Hono:
npm i agents hono-agents2. Создайте Agent
Создайте новый файл для своего агента (например, src/agents/counter.ts):
import { Agent, callable } from "agents";
export class CounterAgent extends Agent {
initialState = { count: 0 };
@callable()
increment() {
this.setState({ count: this.state.count + 1 });
return this.state.count;
}
@callable()
decrement() {
this.setState({ count: this.state.count - 1 });
return this.state.count;
}
}import { Agent, callable } from "agents";
export type CounterState = {
count: number;
};
export class CounterAgent extends Agent<Env, CounterState> {
initialState: CounterState = { count: 0 };
@callable()
increment() {
this.setState({ count: this.state.count + 1 });
return this.state.count;
}
@callable()
decrement() {
this.setState({ count: this.state.count - 1 });
return this.state.count;
}
}3. Обновите конфигурацию Wrangler
Добавьте привязку Durable Object и миграцию:
{
"name": "my-existing-project",
"main": "src/index.ts",
// Set this to today's date
"compatibility_date": "2026-08-28",
"compatibility_flags": ["nodejs_compat"],
"durable_objects": {
"bindings": [
{
"name": "CounterAgent",
"class_name": "CounterAgent",
},
],
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["CounterAgent"],
},
],
}name = "my-existing-project"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-08-28"
compatibility_flags = [ "nodejs_compat" ]
[[durable_objects.bindings]]
name = "CounterAgent"
class_name = "CounterAgent"
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "CounterAgent" ]Основные моменты:
nameв привязках становится свойствомenv(например,env.CounterAgent)class_nameдолжен точно совпадать с именем экспортируемого вами классаnew_sqlite_classesвключает хранилище SQLite для сохранения состоянияnodejs_compatфлаг обязателен для пакета agents
4. Настройте TypeScript и Vite
Если вы используете @callable() декораторы (как в примере выше), нужны две конфигурации сборки.
tsconfig.json : расширьте agents/tsconfig (или установите "target": "ES2021" вручную):
{
"extends": "agents/tsconfig"
}Если у вас уже есть tsconfig.json с пользовательскими настройками вы можете расширять и переопределять:
{
"extends": "agents/tsconfig",
"compilerOptions": {
"paths": { "~/*": ["./src/*"] }
}
}vite.config.ts : добавьте agents() плагин (обрабатывает трансформации декораторов TC39 для Vite 8):
import agents from "agents/vite";
export default defineConfig({
plugins: [
agents(),
// ... your existing plugins
],
});import agents from "agents/vite";
export default defineConfig({
plugins: [
agents(),
// ... your existing plugins
],
});Если ваш проект не использует Vite, tsconfig.json одного изменения достаточно, но ваш бандлер должен поддерживать декораторы TC39 (stage 3, версия 2023-11).
Подробнее см. в Конфигурация TypeScript и Конфигурация Vite справочник.
5. Экспортируйте класс Agent
Класс вашего агента должен экспортироваться из главной точки входа. Обновите свой src/index.ts:
// Export the agent class (required for Durable Objects)
export { CounterAgent } from "./agents/counter";
// Your existing exports...
export default {
// ...
};// Export the agent class (required for Durable Objects)
export { CounterAgent } from "./agents/counter";
// Your existing exports...
export default {
// ...
} satisfies ExportedHandler<Env>;6. Настройте маршрутизацию
Выберите подход, который соответствует структуре вашего проекта:
Обычные Workers (fetch handler)
import { routeAgentRequest } from "agents";
export { CounterAgent } from "./agents/counter";
export default {
async fetch(request, env, ctx) {
// Try agent routing first
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// Your existing routing logic
const url = new URL(request.url);
if (url.pathname === "/api/hello") {
return Response.json({ message: "Hello!" });
}
return new Response("Not found", { status: 404 });
},
};import { routeAgentRequest } from "agents";
export { CounterAgent } from "./agents/counter";
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
// Try agent routing first
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// Your existing routing logic
const url = new URL(request.url);
if (url.pathname === "/api/hello") {
return Response.json({ message: "Hello!" });
}
return new Response("Not found", { status: 404 });
},
} satisfies ExportedHandler<Env>;Hono
import { Hono } from "hono";
import { agentsMiddleware } from "hono-agents";
export { CounterAgent } from "./agents/counter";
const app = new Hono();
// Add agents middleware - handles WebSocket upgrades and agent HTTP requests
app.use("*", agentsMiddleware());
// Your existing routes continue to work
app.get("/api/hello", (c) => c.json({ message: "Hello!" }));
export default app;import { Hono } from "hono";
import { agentsMiddleware } from "hono-agents";
export { CounterAgent } from "./agents/counter";
const app = new Hono<{ Bindings: Env }>();
// Add agents middleware - handles WebSocket upgrades and agent HTTP requests
app.use("*", agentsMiddleware());
// Your existing routes continue to work
app.get("/api/hello", (c) => c.json({ message: "Hello!" }));
export default app;Со статическими ресурсами
Если вы обслуживаете статические ресурсы вместе с агентами, по умолчанию в первую очередь обслуживаются статические ресурсы. Код Worker выполняется только для путей, которые не соответствуют ни одному статическому ресурсу:
import { routeAgentRequest } from "agents";
export { CounterAgent } from "./agents/counter";
export default {
async fetch(request, env, ctx) {
// Static assets are served automatically before this runs
// This only handles non-asset requests
// Route to agents
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
return new Response("Not found", { status: 404 });
},
};import { routeAgentRequest } from "agents";
export { CounterAgent } from "./agents/counter";
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
// Static assets are served automatically before this runs
// This only handles non-asset requests
// Route to agents
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
return new Response("Not found", { status: 404 });
},
} satisfies ExportedHandler<Env>;Настройте ресурсы в конфигурационном файле Wrangler:
{
"assets": {
"directory": "./public",
},
}[assets]
directory = "./public"7. Сгенерируйте типы TypeScript
Не пишите вручную свой Env интерфейс. Выполните wrangler types чтобы сгенерировать файл определения типов, соответствующий вашей конфигурации Wrangler. Это позволяет находить несоответствия между конфигурацией и кодом на этапе компиляции, а не на этапе развёртывания.
Перезапустите wrangler types каждый раз, когда вы добавляете или переименовываете привязку.
npx wrangler typesЭто создаёт файл с определениями типов, в котором типизированы все ваши bindings, включая пространства имён Durable Object вашего агента. Этот Agent класс по умолчанию использует сгенерированный Env тип, поэтому его не нужно передавать как параметр типа: extends Agent достаточно, если вам не нужно передавать второй параметр типа для состояния (например, Agent<Env, CounterState>).
См. Конфигурация для дополнительных подробностей о генерации типов.
8. Подключитесь со стороны фронтенда
React
import { useState } from "react";
import { useAgent } from "agents/react";
function CounterWidget() {
const [count, setCount] = useState(0);
const agent = useAgent({
agent: "CounterAgent",
onStateUpdate: (state) => setCount(state.count),
});
return (
<>
{count}
<button onClick={() => agent.stub.increment()}>+</button>
<button onClick={() => agent.stub.decrement()}>-</button>
</>
);
}import { useState } from "react";
import { useAgent } from "agents/react";
import type { CounterAgent, CounterState } from "./agents/counter";
function CounterWidget() {
const [count, setCount] = useState(0);
const agent = useAgent<CounterAgent, CounterState>({
agent: "CounterAgent",
onStateUpdate: (state) => setCount(state.count),
});
return (
<>
{count}
<button onClick={() => agent.stub.increment()}>+</button>
<button onClick={() => agent.stub.decrement()}>-</button>
</>
);
}Основные моменты:
useAgentподключается к вашему агенту через WebSocketonStateUpdateсрабатывает при каждом изменении состояния агентаagent.stub.methodName()вызывает методы, помеченные@callable()в вашем Agent
Vanilla JavaScript
import { AgentClient } from "agents/client";
const agent = new AgentClient({
agent: "CounterAgent",
name: "user-123", // Optional: unique instance name
onStateUpdate: (state) => {
document.getElementById("count").textContent = state.count;
},
});
// Call methods
document.getElementById("increment").onclick = () => agent.call("increment");import { AgentClient } from "agents/client";
const agent = new AgentClient({
agent: "CounterAgent",
name: "user-123", // Optional: unique instance name
onStateUpdate: (state) => {
document.getElementById("count").textContent = state.count;
},
});
// Call methods
document.getElementById("increment").onclick = () => agent.call("increment");Как это работает
Когда вы нажали кнопку:
- Клиент называется
agent.stub.increment()через WebSocket - Agent выполнил
increment(), обновлённое состояние сsetState() - Состояние автоматически сохраняется в SQLite
- Рассылка отправленный всем подключённым клиентам
- React обновляется через
onStateUpdate
flowchart LR
A["Browser<br/>(React)"] <-->|WebSocket| B["Agent<br/>(Counter)"]
B --> C["SQLite<br/>(State)"]
Основные понятия
| Понятие | Что это значит |
|---|---|
| Экземпляр Agent | Каждое уникальное имя получает собственного агента. CounterAgent:user-123 отделён от CounterAgent:user-456 |
| Постоянное состояние | Состояние переживает перезапуски, повторные развёртывания и гибернацию. Оно хранится в SQLite |
| Синхронизация в реальном времени | Все клиенты, подключённые к одному и тому же агенту, мгновенно получают обновления состояния |
| Гибернация | Когда ни один клиент не подключён, агент переходит в спящий режим (бесплатно). Он просыпается при следующем запросе |
Deploy to Cloudflare
npm run deployТеперь ваш агент работает в глобальной сети Cloudflare, максимально близко к вашим пользователям.
Типичные паттерны интеграции
Agents за аутентификацией
Перед маршрутизацией к агентам проверьте аутентификацию:
export default {
async fetch(request, env) {
// Check auth for agent routes
if (request.url.includes("/agents/")) {
const authResult = await checkAuth(request, env);
if (!authResult.valid) {
return new Response("Unauthorized", { status: 401 });
}
}
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// ... rest of routing
},
};export default {
async fetch(request: Request, env: Env) {
// Check auth for agent routes
if (request.url.includes("/agents/")) {
const authResult = await checkAuth(request, env);
if (!authResult.valid) {
return new Response("Unauthorized", { status: 401 });
}
}
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// ... rest of routing
},
} satisfies ExportedHandler<Env>;Пользовательский префикс пути агента
По умолчанию агенты маршрутизируются по адресу /agents/{agent-name}/{instance-name}. Вы можете настроить это:
import { routeAgentRequest } from "agents";
const agentResponse = await routeAgentRequest(request, env, {
prefix: "/api/agents", // Now routes at /api/agents/{agent-name}/{instance-name}
});import { routeAgentRequest } from "agents";
const agentResponse = await routeAgentRequest(request, env, {
prefix: "/api/agents", // Now routes at /api/agents/{agent-name}/{instance-name}
});См. Маршрутизация для дополнительных опций, включая CORS, пользовательское именование инстансов и подсказки о местоположении.
Доступ к агентам из серверного кода
Вы можете взаимодействовать с агентами прямо из кода Worker:
import { getAgentByName } from "agents";
export default {
async fetch(request, env) {
if (request.url.endsWith("/api/increment")) {
// Get a specific agent instance
const counter = await getAgentByName(env.CounterAgent, "shared-counter");
const newCount = await counter.increment();
return Response.json({ count: newCount });
}
// ...
},
};import { getAgentByName } from "agents";
export default {
async fetch(request: Request, env: Env) {
if (request.url.endsWith("/api/increment")) {
// Get a specific agent instance
const counter = await getAgentByName(env.CounterAgent, "shared-counter");
const newCount = await counter.increment();
return Response.json({ count: newCount });
}
// ...
},
} satisfies ExportedHandler<Env>;Добавление нескольких агентов
Добавьте больше агентов, расширив конфигурацию:
// src/agents/chat.ts
export class Chat extends Agent {
// ...
}
// src/agents/scheduler.ts
export class Scheduler extends Agent {
// ...
}// src/agents/chat.ts
export class Chat extends Agent {
// ...
}
// src/agents/scheduler.ts
export class Scheduler extends Agent {
// ...
}Обновите файл конфигурации Wrangler:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"durable_objects": {
"bindings": [
{
"name": "CounterAgent",
"class_name": "CounterAgent"
},
{
"name": "Chat",
"class_name": "Chat"
},
{
"name": "Scheduler",
"class_name": "Scheduler"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": [
"CounterAgent",
"Chat",
"Scheduler"
]
}
]
}[[durable_objects.bindings]]
name = "CounterAgent"
class_name = "CounterAgent"
[[durable_objects.bindings]]
name = "Chat"
class_name = "Chat"
[[durable_objects.bindings]]
name = "Scheduler"
class_name = "Scheduler"
[[migrations]]
tag = "v1"
new_sqlite_classes = ["CounterAgent", "Chat", "Scheduler"]Экспортируйте все агенты из точки входа:
export { CounterAgent } from "./agents/counter";
export { Chat } from "./agents/chat";
export { Scheduler } from "./agents/scheduler";export { CounterAgent } from "./agents/counter";
export { Chat } from "./agents/chat";
export { Scheduler } from "./agents/scheduler";Устранение неполадок
Agent не найден, или ошибки 404
- Проверьте экспорт - класс Agent должен экспортироваться из главной точки входа.
- Проверьте привязку -
class_nameв конфигурационном файле Wrangler должно точно совпадать с именем экспортируемого класса. - Проверьте маршрут - маршрут по умолчанию:
/agents/{'{agent-name}'}/{'{instance-name}'}. Имя агента на клиенте совпадает с именем класса (без учёта регистра).
Ошибка «No such Durable Object class»
Добавьте миграцию в файл конфигурации Wrangler:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": [
"YourAgentClass"
]
}
]
}[[migrations]]
tag = "v1"
new_sqlite_classes = ["YourAgentClass"]Соединение WebSocket не устанавливается
Убедитесь, что ваша маршрутизация передаёт ответ без изменений:
// Correct - return the response directly
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// Wrong - this breaks WebSocket connections
if (agentResponse) return new Response(agentResponse.body);// Correct - return the response directly
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// Wrong - this breaks WebSocket connections
if (agentResponse) return new Response(agentResponse.body);Состояние не сохраняется
Проверьте, что:
- Вы вызываете
this.setState(), а не изменяяthis.stateнапрямую. - Класс агента находится в
new_sqlite_classesв миграциях. - Вы подключаетесь к тому же имени экземпляра агента.
-
onStateUpdateколбэк подключён в вашем клиенте. - Соединение WebSocket установлено (проверьте инструменты разработчика в браузере).
ошибки вида «Method X is not callable»
Убедитесь, что ваши методы декорированы @callable():
import { Agent, callable } from "agents";
export class MyAgent extends Agent {
@callable()
increment() {
// ...
}
}import { Agent, callable } from "agents";
export class MyAgent extends Agent {
@callable()
increment() {
// ...
}
}Ошибки типов с agent.stub
Добавьте параметры типов агента и состояния:
import { useAgent } from "agents/react";
// Pass the agent and state types to useAgent
const agent = useAgent({
agent: "CounterAgent",
onStateUpdate: (state) => setCount(state.count),
});
// Now agent.stub is fully typed
agent.stub.increment();import { useAgent } from "agents/react";
import type { CounterAgent, CounterState } from "./server";
// Pass the agent and state types to useAgent
const agent = useAgent<CounterAgent, CounterState>({
agent: "CounterAgent",
onStateUpdate: (state) => setCount(state.count),
});
// Now agent.stub is fully typed
agent.stub.increment();SyntaxError: Invalid or unexpected token с @callable()
Если ваш dev-сервер завершается ошибкой с SyntaxError: Invalid or unexpected token, задайте "target": "ES2021" в вашем tsconfig.json. Это гарантирует, что транспилятор esbuild в Vite преобразует декораторы TC39 в более раннюю версию синтаксиса, вместо того чтобы передавать их как нативный синтаксис.
{
"compilerOptions": {
"target": "ES2021"
}
}Следующие шаги
Теперь, когда у вас есть работающий агент, изучите следующие темы:
Типичные следующие шаги
| Узнайте, как | См. |
|---|---|
| Добавьте возможности ИИ/LLM | Использование ИИ-моделей |
| Предоставление инструментов через MCP | MCP-серверы |
| Запуск фоновых задач | Планирование задач |
| Обрабатывайте письма | Маршрутизация электронной почты |
| Используйте Cloudflare Workflows | Запуск Workflows |