← Создание агентов на Cloudflare / agents / communication-channels / chat
Клиентский SDK
Подключайтесь к агентам из любой среды выполнения JavaScript: в браузере, Node.js, Deno, Bun или edge-функциях, используя WebSockets или HTTP. SDK обеспечивает синхронизацию состояния в реальном времени, вызовы методов RPC и потоковую передачу ответов.
Обзор
Клиентский SDK предлагает два способа подключения через WebSocket и один способ отправки HTTP-запросов.
| Клиент | Вариант использования |
|---|---|
useAgent |
React-хук с автоматическим переподключением и управлением состоянием |
AgentClient |
Класс Vanilla JavaScript/TypeScript для любой среды |
agentFetch |
HTTP-запросы, когда WebSocket не требуется |
Все клиенты предоставляют:
- Двунаправленная синхронизация состояния - Отправка и получение обновлений состояния в реальном времени
- RPC-вызовы - вызывайте методы агента с типизированными аргументами и возвращаемыми значениями
- Потоковая передача - Обрабатывайте чанкированные ответы для AI completions
- Автопереподключение - автоматическое переподключение с экспоненциальной задержкой
быстрый старт
React
import { useAgent } from "agents/react";
function Chat() {
const agent = useAgent({
agent: "ChatAgent",
name: "room-123",
onStateUpdate: (state) => {
console.log("New state:", state);
},
});
const sendMessage = async () => {
const response = await agent.call("sendMessage", ["Hello!"]);
console.log("Response:", response);
};
return <button onClick={sendMessage}>Send</button>;
}import { useAgent } from "agents/react";
function Chat() {
const agent = useAgent({
agent: "ChatAgent",
name: "room-123",
onStateUpdate: (state) => {
console.log("New state:", state);
},
});
const sendMessage = async () => {
const response = await agent.call("sendMessage", ["Hello!"]);
console.log("Response:", response);
};
return <button onClick={sendMessage}>Send</button>;
}Vanilla JavaScript
import { AgentClient } from "agents/client";
const client = new AgentClient({
agent: "ChatAgent",
name: "room-123",
host: "your-worker.your-subdomain.workers.dev",
onStateUpdate: (state) => {
console.log("New state:", state);
},
});
// Call a method
const response = await client.call("sendMessage", ["Hello!"]);import { AgentClient } from "agents/client";
const client = new AgentClient({
agent: "ChatAgent",
name: "room-123",
host: "your-worker.your-subdomain.workers.dev",
onStateUpdate: (state) => {
console.log("New state:", state);
},
});
// Call a method
const response = await client.call("sendMessage", ["Hello!"]);Подключение к агентам
Именование Agent
agent параметр, это имя класса вашего агента. Оно автоматически преобразуется из camelCase в kebab-case для URL:
// These are equivalent:
useAgent({ agent: "ChatAgent" }); // → /agents/chat-agent/...
useAgent({ agent: "MyCustomAgent" }); // → /agents/my-custom-agent/...
useAgent({ agent: "LOUD_AGENT" }); // → /agents/loud-agent/...// These are equivalent:
useAgent({ agent: "ChatAgent" }); // → /agents/chat-agent/...
useAgent({ agent: "MyCustomAgent" }); // → /agents/my-custom-agent/...
useAgent({ agent: "LOUD_AGENT" }); // → /agents/loud-agent/...Имена экземпляров
name параметр определяет конкретный экземпляр агента. Если его не указать, используется значение по умолчанию "default":
// Connect to a specific chat room
useAgent({ agent: "ChatAgent", name: "room-123" });
// Connect to a user's personal agent
useAgent({ agent: "UserAgent", name: userId });
// Uses "default" instance
useAgent({ agent: "ChatAgent" });// Connect to a specific chat room
useAgent({ agent: "ChatAgent", name: "room-123" });
// Connect to a user's personal agent
useAgent({ agent: "UserAgent", name: userId });
// Uses "default" instance
useAgent({ agent: "ChatAgent" });Варианты подключения
Оба useAgent и AgentClient принимают параметры соединения:
useAgent({
agent: "ChatAgent",
name: "room-123",
// Connection settings
host: "my-worker.workers.dev", // Custom host (defaults to current origin)
path: "/custom/path", // Custom path prefix
// Query parameters (sent on connection)
query: {
token: "abc123",
version: "2",
},
// Event handlers
onOpen: () => console.log("Connected"),
onClose: () => console.log("Disconnected"),
onError: (error) => console.error("Error:", error),
});useAgent({
agent: "ChatAgent",
name: "room-123",
// Connection settings
host: "my-worker.workers.dev", // Custom host (defaults to current origin)
path: "/custom/path", // Custom path prefix
// Query parameters (sent on connection)
query: {
token: "abc123",
version: "2",
},
// Event handlers
onOpen: () => console.log("Connected"),
onClose: () => console.log("Disconnected"),
onError: (error) => console.error("Error:", error),
});Параметры асинхронного запроса
Для токенов аутентификации или других асинхронных данных передайте функцию, которая возвращает Promise:
useAgent({
agent: "ChatAgent",
name: "room-123",
// Async query - called before connecting
query: async () => {
const token = await getAuthToken();
return { token };
},
// Dependencies that trigger re-fetching the query
queryDeps: [userId],
// Cache TTL for the query result (default: 5 minutes)
cacheTtl: 60 * 1000, // 1 minute
});useAgent({
agent: "ChatAgent",
name: "room-123",
// Async query - called before connecting
query: async () => {
const token = await getAuthToken();
return { token };
},
// Dependencies that trigger re-fetching the query
queryDeps: [userId],
// Cache TTL for the query result (default: 5 minutes)
cacheTtl: 60 * 1000, // 1 minute
});Функция query кэшируется и повторно вызывается только тогда, когда:
queryDepsизменениеcacheTtlистекает- Соединение WebSocket закрывается (автоматическая инвалидация кеша)
- Компонент перемонтируется
Синхронизация состояния
Agents могут поддерживать состояние, которое синхронизируется в обоих направлениях со всеми подключёнными клиентами.
Чтение текущего состояния
Оба useAgent и AgentClient предоставляет state свойство, которое отражает текущее состояние агента. Изначально оно равно undefined пока с сервера не будет получено первое сообщение о состоянии.
const agent = useAgent({ agent: "GameAgent", name: "game-123" });
// Read the current state at any time
console.log("Current score:", agent.state?.score);const agent = useAgent({ agent: "GameAgent", name: "game-123" });
// Read the current state at any time
console.log("Current score:", agent.state?.score);С useAgent, обновления состояния запускают повторный рендер React, поэтому agent.state всегда отражает последнее значение в вашем JSX. При использовании AgentClient, state поле обновляется синхронно при каждой входящей трансляции с сервера или setState вызов.
Получение обновлений состояния
const agent = useAgent({
agent: "GameAgent",
name: "game-123",
onStateUpdate: (state, source) => {
// state: The new state from the agent
// source: "server" (agent pushed) or "client" (you pushed)
console.log(`State updated from ${source}:`, state);
setGameState(state);
},
});const agent = useAgent({
agent: "GameAgent",
name: "game-123",
onStateUpdate: (state, source) => {
// state: The new state from the agent
// source: "server" (agent pushed) or "client" (you pushed)
console.log(`State updated from ${source}:`, state);
setGameState(state);
},
});Отправка обновлений состояния
// Update the agent's state from the client
agent.setState({ score: 100, level: 5 });// Update the agent's state from the client
agent.setState({ score: 100, level: 5 });Когда вы вызываете setState():
- Состояние отправляется агенту через WebSocket
- Агента
onStateChanged()метод вызывается - Агент рассылает новое состояние всем подключённым клиентам
- Ваш
onStateUpdateколбэк срабатывает сsource: "client"
Поток состояния
sequenceDiagram
participant Client
participant Agent
Client->>Agent: setState()
Agent-->>Client: onStateUpdate (broadcast)
Вызов методов агента (RPC)
Вызывайте методы вашего агента, декорированные @callable().
Использование call()
// Basic call
const result = await agent.call("getUser", [userId]);
// Call with multiple arguments
const result = await agent.call("createPost", [title, content, tags]);
// Call with no arguments
const result = await agent.call("getStats");// Basic call
const result = await agent.call("getUser", [userId]);
// Call with multiple arguments
const result = await agent.call("createPost", [title, content, tags]);
// Call with no arguments
const result = await agent.call("getStats");Использование прокси-заглушки
stub свойство обеспечивает более простой синтаксис для вызова методов:
// Instead of:
const user = await agent.call("getUser", ["user-123"]);
// You can write:
const user = await agent.stub.getUser("user-123");
// Multiple arguments work naturally:
const post = await agent.stub.createPost(title, content, tags);// Instead of:
const user = await agent.call("getUser", ["user-123"]);
// You can write:
const user = await agent.stub.getUser("user-123");
// Multiple arguments work naturally:
const post = await agent.stub.createPost(title, content, tags);Интеграция с TypeScript
Для полной типовой безопасности передайте свой класс Agent как параметр типа:
const agent = useAgent({
agent: "MyAgent",
name: "instance-1",
});
// Now stub methods are fully typed
const result = await agent.stub.processData({ input: "test" });import type { MyAgent } from "./agents/my-agent";
const agent = useAgent<MyAgent>({
agent: "MyAgent",
name: "instance-1",
});
// Now stub methods are fully typed
const result = await agent.stub.processData({ input: "test" });Потоковые ответы
Пометить вызываемый метод как потоковый. Фреймворк передаёт StreamingResponse в качестве первого аргумента:
// Agent-side
import { Agent, callable } from "agents";
class MyAgent extends Agent {
@callable({ streaming: true })
async generateText(stream, prompt) {
for await (const chunk of llm.stream(prompt)) {
stream.send(chunk);
}
stream.end();
}
}
// Client-side
await agent.call("generateText", [prompt], {
onChunk: (chunk) => {
// Called for each chunk
appendToOutput(chunk);
},
onDone: (finalResult) => {
// Called when stream completes
console.log("Complete:", finalResult);
},
onError: (error) => {
// Called if streaming fails
console.error("Stream error:", error);
},
});// Agent-side
import { Agent, callable, type StreamingResponse } from "agents";
class MyAgent extends Agent {
@callable({ streaming: true })
async generateText(stream: StreamingResponse, prompt: string) {
for await (const chunk of llm.stream(prompt)) {
stream.send(chunk);
}
stream.end();
}
}
// Client-side
await agent.call("generateText", [prompt], {
onChunk: (chunk) => {
// Called for each chunk
appendToOutput(chunk);
},
onDone: (finalResult) => {
// Called when stream completes
console.log("Complete:", finalResult);
},
onError: (error) => {
// Called if streaming fails
console.error("Stream error:", error);
},
});HTTP-запросы с помощью agentFetch
Для разовых запросов без поддержания соединения WebSocket:
import { agentFetch } from "agents/client";
// GET request
const response = await agentFetch({
agent: "DataAgent",
name: "instance-1",
host: "my-worker.workers.dev",
});
const data = await response.json();
// POST request with body
const response = await agentFetch(
{
agent: "DataAgent",
name: "instance-1",
host: "my-worker.workers.dev",
},
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "process" }),
},
);import { agentFetch } from "agents/client";
// GET request
const response = await agentFetch({
agent: "DataAgent",
name: "instance-1",
host: "my-worker.workers.dev",
});
const data = await response.json();
// POST request with body
const response = await agentFetch(
{
agent: "DataAgent",
name: "instance-1",
host: "my-worker.workers.dev",
},
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "process" }),
},
);Когда использовать agentFetch в сравнении с WebSocket:
Используйте agentFetch |
Используйте useAgent/AgentClient |
|---|---|
| Одноразовые запросы | Требуются обновления в реальном времени |
| Межсерверные вызовы | Двунаправленная связь |
| Простой API в стиле REST | Синхронизация состояния |
| Постоянное соединение не требуется | Несколько вызовов RPC |
Интеграция MCP-сервера
Если ваш агент использует серверы MCP (Model Context Protocol), можно получать обновления об их состоянии:
const agent = useAgent({
agent: "AssistantAgent",
name: "session-123",
onMcpUpdate: (mcpServers) => {
// mcpServers is a record of server states
for (const [serverId, server] of Object.entries(mcpServers)) {
console.log(`${serverId}: ${server.connectionState}`);
console.log(`Tools: ${server.tools?.map((t) => t.name).join(", ")}`);
}
},
});const agent = useAgent({
agent: "AssistantAgent",
name: "session-123",
onMcpUpdate: (mcpServers) => {
// mcpServers is a record of server states
for (const [serverId, server] of Object.entries(mcpServers)) {
console.log(`${serverId}: ${server.connectionState}`);
console.log(`Tools: ${server.tools?.map((t) => t.name).join(", ")}`);
}
},
});Обработка ошибок
Ошибки подключения
const agent = useAgent({
agent: "MyAgent",
onError: (error) => {
console.error("WebSocket error:", error);
},
onClose: () => {
console.log("Connection closed, will auto-reconnect...");
},
});const agent = useAgent({
agent: "MyAgent",
onError: (error) => {
console.error("WebSocket error:", error);
},
onClose: () => {
console.log("Connection closed, will auto-reconnect...");
},
});Ошибки RPC
try {
const result = await agent.call("riskyMethod", [data]);
} catch (error) {
// Error thrown by the agent method
console.error("RPC failed:", error.message);
}try {
const result = await agent.call("riskyMethod", [data]);
} catch (error) {
// Error thrown by the agent method
console.error("RPC failed:", error.message);
}Ошибки потоковой передачи
await agent.call("streamingMethod", [data], {
onChunk: (chunk) => handleChunk(chunk),
onError: (errorMessage) => {
// Stream-specific error handling
console.error("Stream error:", errorMessage);
},
});await agent.call("streamingMethod", [data], {
onChunk: (chunk) => handleChunk(chunk),
onError: (errorMessage) => {
// Stream-specific error handling
console.error("Stream error:", errorMessage);
},
});Рекомендации
1. Используйте типизированные стабы
// Prefer this:
const user = await agent.stub.getUser(id);
// Over this:
const user = await agent.call("getUser", [id]);// Prefer this:
const user = await agent.stub.getUser(id);
// Over this:
const user = await agent.call("getUser", [id]);2. Переподключение выполняется автоматически
Клиент автоматически переподключается, а агент при каждом подключении автоматически отправляет текущее состояние. Ваш onStateUpdate колбэк сработает с последним состоянием: ручная повторная синхронизация не требуется. Если вы используете асинхронный query функцию для аутентификации, кэш автоматически сбрасывается при отключении, что гарантирует получение свежих токенов при повторном подключении.
3. Оптимизируйте кэширование запросов
// For auth tokens that expire hourly:
useAgent({
query: async () => ({ token: await getToken() }),
cacheTtl: 55 * 60 * 1000, // Refresh 5 min before expiry
queryDeps: [userId], // Refresh if user changes
});// For auth tokens that expire hourly:
useAgent({
query: async () => ({ token: await getToken() }),
cacheTtl: 55 * 60 * 1000, // Refresh 5 min before expiry
queryDeps: [userId], // Refresh if user changes
});4. Очистите соединения
В чистом JS закрывайте соединения по завершении:
const client = new AgentClient({ agent: "MyAgent", host: "..." });
// When done:
client.close();const client = new AgentClient({ agent: "MyAgent", host: "..." });
// When done:
client.close();React useAgent автоматически выполняет очистку при размонтировании.
Справочник по React-хуку
UseAgentOptions
type UseAgentOptions<State> = {
// Required
agent: string; // Agent class name
// Optional
name?: string; // Instance name (default: "default")
host?: string; // Custom host
path?: string; // Custom path prefix
// Query parameters
query?: Record<string, string> | (() => Promise<Record<string, string>>);
queryDeps?: unknown[]; // Dependencies for async query
cacheTtl?: number; // Query cache TTL in ms (default: 5 min)
// Callbacks
onStateUpdate?: (state: State, source: "server" | "client") => void;
onMcpUpdate?: (mcpServers: MCPServersState) => void;
onOpen?: () => void;
onClose?: () => void;
onError?: (error: Event) => void;
onMessage?: (message: MessageEvent) => void;
};Возвращаемое значение
useAgent хук возвращает объект со следующими свойствами и методами:
| Свойство/Метод | Тип | Описание |
|---|---|---|
agent |
string |
Имя агента в формате kebab-case |
name |
string |
Имя экземпляра |
setState(state) |
void |
Отправка состояния агенту |
call(method, args?, options?) |
Promise |
Вызов метода агента |
stub |
Proxy |
Типизированные вызовы методов |
send(data) |
void |
Отправить необработанное сообщение WebSocket |
close() |
void |
Закрыть соединение |
reconnect() |
void |
Принудительное переподключение |
Справочник по Vanilla JS
AgentClientOptions
type AgentClientOptions<State> = {
// Required
agent: string; // Agent class name
host: string; // Worker host
// Optional
name?: string; // Instance name (default: "default")
path?: string; // Custom path prefix
query?: Record<string, string>;
// Callbacks
onStateUpdate?: (state: State, source: "server" | "client") => void;
};Методы AgentClient
| Свойство/Метод | Тип | Описание |
|---|---|---|
agent |
string |
Имя агента в формате kebab-case |
name |
string |
Имя экземпляра |
setState(state) |
void |
Отправка состояния агенту |
call(method, args?, options?) |
Promise |
Вызов метода агента |
send(data) |
void |
Отправить необработанное сообщение WebSocket |
close() |
void |
Закрыть соединение |
reconnect() |
void |
Принудительное переподключение |
Клиент также поддерживает слушатели событий WebSocket:
client.addEventListener("open", () => {});
client.addEventListener("close", () => {});
client.addEventListener("error", () => {});
client.addEventListener("message", () => {});client.addEventListener("open", () => {});
client.addEventListener("close", () => {});
client.addEventListener("error", () => {});
client.addEventListener("message", () => {});События агентных инструментов
Если ваш UI чата отображает сохранённые дочерние запуски из Agents как инструменты, используйте useAgentToolEvents() наряду с useAgent() и useAgentChat(). Hook подписывается на родительское соединение, воспроизводит сохранённые временные шкалы дочерних агентов и группирует запуски по идентификатору родительского вызова инструмента.
import { useAgent, useAgentToolEvents } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";
const agent = useAgent({ agent: "Assistant", name: userId });
const { messages } = useAgentChat({ agent });
const agentTools = useAgentToolEvents({ agent });import { useAgent, useAgentToolEvents } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";
const agent = useAgent({ agent: "Assistant", name: userId });
const { messages } = useAgentChat({ agent });
const agentTools = useAgentToolEvents({ agent });