← Vytvářejte agenty na Cloudflare / agents / runtime / communication
WebSockets
Agents podporují připojení WebSocket pro obousměrnou komunikaci v reálném čase. Tato stránka se věnuje zpracování WebSocket na straně serveru. Ohledně připojení na straně klienta si přečtěte Client SDK.
Hooky životního cyklu
Agents mají několik hooků životního cyklu, které se aktivují v různých okamžicích:
| Hook | Při zavolání |
|---|---|
onStart(props?) |
Jednou při prvním spuštění agenta (před jakýmkoli připojením) |
onRequest(request) |
Když je přijat požadavek HTTP (nikoli WebSocket) |
onConnect(connection, ctx) |
Když se naváže nové připojení WebSocket |
onMessage(connection, message) |
Když je přijata zpráva WebSocket |
onClose(connection, code, reason, wasClean) |
Když se připojení WebSocket uzavře |
onError(connection, error) |
Když u připojení dojde k chybě WebSocket |
onError(error) |
Když dojde k chybě na úrovni serveru (nesouvisející s konkrétním připojením) |
shouldSendProtocolMessages(connection, ctx) |
Zda tomuto připojení odesílat protokolové zprávy (identita, stav, MCP). Výchozí hodnota: true |
onStart
onStart() se volá jednou při prvním spuštění agenta, ještě před navázáním jakýchkoli připojení:
export class MyAgent extends Agent {
async onStart() {
// Initialize resources
console.log(`Agent ${this.name} starting...`);
// Load data from storage
const savedData = this.sql`SELECT * FROM cache`;
for (const row of savedData) {
// Rebuild in-memory state from persistent storage
}
}
onConnect(connection) {
// By the time connections arrive, onStart has completed
}
}export class MyAgent extends Agent {
async onStart() {
// Initialize resources
console.log(`Agent ${this.name} starting...`);
// Load data from storage
const savedData = this.sql`SELECT * FROM cache`;
for (const row of savedData) {
// Rebuild in-memory state from persistent storage
}
}
onConnect(connection: Connection) {
// By the time connections arrive, onStart has completed
}
}Zpracování spojení
Definujte onConnect a onMessage metody na vašem Agentovi, aby přijímal připojení WebSocket:
import { Agent, Connection, ConnectionContext, WSMessage } from "agents";
export class ChatAgent extends Agent {
async onConnect(connection, ctx) {
// Connections are automatically accepted
// Access the original request for auth, headers, cookies
const url = new URL(ctx.request.url);
const token = url.searchParams.get("token");
if (!token) {
connection.close(4001, "Unauthorized");
return;
}
// Store user info on this connection
connection.setState({ authenticated: true });
}
async onMessage(connection, message) {
if (typeof message === "string") {
// Handle text message
const data = JSON.parse(message);
connection.send(JSON.stringify({ received: data }));
}
}
}import { Agent, Connection, ConnectionContext, WSMessage } from "agents";
export class ChatAgent extends Agent {
async onConnect(connection: Connection, ctx: ConnectionContext) {
// Connections are automatically accepted
// Access the original request for auth, headers, cookies
const url = new URL(ctx.request.url);
const token = url.searchParams.get("token");
if (!token) {
connection.close(4001, "Unauthorized");
return;
}
// Store user info on this connection
connection.setState({ authenticated: true });
}
async onMessage(connection: Connection, message: WSMessage) {
if (typeof message === "string") {
// Handle text message
const data = JSON.parse(message);
connection.send(JSON.stringify({ received: data }));
}
}
}Objekt připojení
Každý připojený klient má jedinečný Connection objekt:
| Vlastnost/metoda | Typ | Popis |
|---|---|---|
id |
string |
Jedinečný identifikátor tohoto připojení |
uri |
string | null |
URL adresa původního požadavku na upgrade WebSocketu. Zachovává se i po hibernaci |
state |
State |
Objekt stavu pro jednotlivá připojení |
setState(state) |
void |
Aktualizace stavu připojení |
send(message) |
void |
Odeslat zprávu tomuto klientovi |
close(code?, reason?) |
void |
Uzavřít připojení |
tags |
readonly string[] |
Štítky přiřazené prostřednictvím getConnectionTags. Vždy zahrnuje ID připojení jako první tag |
server |
string |
Název instance agenta (stejný jako this.name na Agentovi) |
Stav pro jednotlivá připojení
Ukládejte data specifická pro každé připojení (informace o uživateli, předvolby atd.):
export class ChatAgent extends Agent {
async onConnect(connection, ctx) {
const userId = new URL(ctx.request.url).searchParams.get("userId");
connection.setState({
userId: userId || "anonymous",
role: "user",
joinedAt: Date.now(),
});
}
async onMessage(connection, message) {
// Access connection-specific state
console.log(`Message from ${connection.state.userId}`);
}
}interface ConnectionState {
userId: string;
role: "admin" | "user";
joinedAt: number;
}
export class ChatAgent extends Agent {
async onConnect(
connection: Connection<ConnectionState>,
ctx: ConnectionContext,
) {
const userId = new URL(ctx.request.url).searchParams.get("userId");
connection.setState({
userId: userId || "anonymous",
role: "user",
joinedAt: Date.now(),
});
}
async onMessage(connection: Connection<ConnectionState>, message: WSMessage) {
// Access connection-specific state
console.log(`Message from ${connection.state.userId}`);
}
}Vysílání všem klientům
Použijte this.broadcast() k odeslání zprávy všem připojeným klientům:
export class ChatAgent extends Agent {
async onMessage(connection, message) {
// Broadcast to all connected clients
this.broadcast(
JSON.stringify({
from: connection.id,
message: message,
timestamp: Date.now(),
}),
);
}
// Broadcast from any method
async notifyAll(event, data) {
this.broadcast(JSON.stringify({ event, data }));
}
}export class ChatAgent extends Agent {
async onMessage(connection: Connection, message: WSMessage) {
// Broadcast to all connected clients
this.broadcast(
JSON.stringify({
from: connection.id,
message: message,
timestamp: Date.now(),
}),
);
}
// Broadcast from any method
async notifyAll(event: string, data: unknown) {
this.broadcast(JSON.stringify({ event, data }));
}
}Vyloučení připojení
Předejte pole ID připojení, která se mají vyloučit z broadcastu:
// Broadcast to everyone except the sender
this.broadcast(
JSON.stringify({ type: "user-typing", userId: "123" }),
[connection.id], // Do not send to the originator
);// Broadcast to everyone except the sender
this.broadcast(
JSON.stringify({ type: "user-typing", userId: "123" }),
[connection.id], // Do not send to the originator
);Štítky připojení
Označte připojení štítky pro snadné filtrování. Přepište getConnectionTags() pro přiřazení štítků při navázání připojení:
export class ChatAgent extends Agent {
getConnectionTags(connection, ctx) {
const url = new URL(ctx.request.url);
const role = url.searchParams.get("role");
const tags = [];
if (role === "admin") tags.push("admin");
if (role === "moderator") tags.push("moderator");
return tags; // Up to 9 tags, max 256 chars each
}
// Later, broadcast only to admins
notifyAdmins(message) {
for (const conn of this.getConnections("admin")) {
conn.send(message);
}
}
}export class ChatAgent extends Agent {
getConnectionTags(connection: Connection, ctx: ConnectionContext): string[] {
const url = new URL(ctx.request.url);
const role = url.searchParams.get("role");
const tags: string[] = [];
if (role === "admin") tags.push("admin");
if (role === "moderator") tags.push("moderator");
return tags; // Up to 9 tags, max 256 chars each
}
// Later, broadcast only to admins
notifyAdmins(message: string) {
for (const conn of this.getConnections("admin")) {
conn.send(message);
}
}
}Metody správy připojení
| Metoda | Podpis | Popis |
|---|---|---|
getConnections |
(tag?: string) => Iterable<Connection> |
Získat všechna připojení, volitelně podle tagu |
getConnection |
(id: string) => Connection | undefined |
Získat připojení podle ID |
getConnectionTags |
(connection, ctx) => string[] |
Přepište, abyste tagovali připojení |
broadcast |
(message, without?: string[]) => void |
Odeslat všem připojením |
isConnectionReadonly |
(connection) => boolean |
Zkontroluje, zda je připojení pouze pro čtení |
isConnectionProtocolEnabled |
(connection) => boolean |
Zkontroluje, zda jsou pro toto připojení povoleny zprávy protokolu |
Zpracování binárních dat
Zprávy mohou být řetězce nebo binární data (ArrayBuffer / ArrayBufferView):
export class FileAgent extends Agent {
async onMessage(connection, message) {
if (message instanceof ArrayBuffer) {
// Handle binary upload
const bytes = new Uint8Array(message);
await this.processFile(bytes);
connection.send(
JSON.stringify({ status: "received", size: bytes.length }),
);
} else if (typeof message === "string") {
// Handle text command
const command = JSON.parse(message);
// ...
}
}
}export class FileAgent extends Agent {
async onMessage(connection: Connection, message: WSMessage) {
if (message instanceof ArrayBuffer) {
// Handle binary upload
const bytes = new Uint8Array(message);
await this.processFile(bytes);
connection.send(
JSON.stringify({ status: "received", size: bytes.length }),
);
} else if (typeof message === "string") {
// Handle text command
const command = JSON.parse(message);
// ...
}
}
}Zpracování chyb a uzavření
Zpracovávejte chyby spojení a odpojení. Tento onError metoda má dvě přetížení: jedno pro chyby připojení WebSocket a jedno pro chyby na úrovni serveru:
export class ChatAgent extends Agent {
// WebSocket connection error
// Server-level error (not tied to a specific connection)
onError(connectionOrError, error) {
if (error) {
console.error(`Connection ${connectionOrError.id} error:`, error);
} else {
console.error("Server error:", connectionOrError);
}
}
async onClose(connection, code, reason, wasClean) {
console.log(`Connection ${connection.id} closed: ${code} ${reason}`);
this.broadcast(
JSON.stringify({
event: "user-left",
userId: connection.state?.userId,
}),
);
}
}export class ChatAgent extends Agent {
// WebSocket connection error
onError(connection: Connection, error: unknown): void;
// Server-level error (not tied to a specific connection)
onError(error: unknown): void;
onError(connectionOrError: Connection | unknown, error?: unknown) {
if (error) {
console.error(
`Connection ${(connectionOrError as Connection).id} error:`,
error,
);
} else {
console.error("Server error:", connectionOrError);
}
}
async onClose(
connection: Connection,
code: number,
reason: string,
wasClean: boolean,
) {
console.log(`Connection ${connection.id} closed: ${code} ${reason}`);
this.broadcast(
JSON.stringify({
event: "user-left",
userId: connection.state?.userId,
}),
);
}
}Výchozí onError implementace chybu zaznamená do logu a znovu ji vyvolá. Přepište ji, chcete-li přidat vlastní zpracování chyb, hlášení nebo logiku obnovy.
Typy zpráv
| Typ | Popis |
|---|---|
string |
Textová zpráva (obvykle JSON) |
ArrayBuffer |
Binární data |
ArrayBufferView |
Typované zobrazení binárních dat pomocí pole |
Hibernace
Agents podporují hibernaci: při nečinnosti mohou usnout a probudit se, jakmile je potřeba. Tím se šetří prostředky a zároveň se zachovávají připojení WebSocket.
Povolování hibernace
Hibernace je ve výchozím nastavení povolená. Chcete-li ji vypnout:
export class AlwaysOnAgent extends Agent {
static options = { hibernate: false };
}export class AlwaysOnAgent extends Agent {
static options = { hibernate: false };
}Jak funguje hibernace
- Agent je aktivní a obsluhuje připojení
- Po období nečinnosti bez zpráv agent hibernuje (uspí se)
- Připojení WebSocket zůstávají otevřená (řeší Cloudflare)
- Když dorazí zpráva, agent se probudí
onMessagese volá běžným způsobem
Co zůstává zachováno při hibernaci
| Ukládá | Nepřetrvává |
|---|---|
this.state (stav agenta) |
Proměnné v paměti |
connection.state |
Časovače/intervaly |
Data SQLite (this.sql) |
Nedokončené Promises |
| Metadata připojení | Lokální mezipaměti |
Ukládejte důležitá data do this.state nebo SQLite, nikoli ve vlastnostech třídy:
export class MyAgent extends Agent {
initialState = { counter: 0 };
// Do not do this - lost on hibernation
localCounter = 0;
onMessage(connection, message) {
// Persists across hibernation
this.setState({ counter: this.state.counter + 1 });
// Lost after hibernation
this.localCounter++;
}
}export class MyAgent extends Agent<Env, { counter: number }> {
initialState = { counter: 0 };
// Do not do this - lost on hibernation
private localCounter = 0;
onMessage(connection: Connection, message: WSMessage) {
// Persists across hibernation
this.setState({ counter: this.state.counter + 1 });
// Lost after hibernation
this.localCounter++;
}
}Běžné vzory
Sledování přítomnosti
Sledujte, kdo je online, pomocí stavu jednotlivých připojení. Stav připojení se po odpojení uživatelů automaticky vyčistí:
export class PresenceAgent extends Agent {
onConnect(connection, ctx) {
const url = new URL(ctx.request.url);
const name = url.searchParams.get("name") || "Anonymous";
connection.setState({
name,
joinedAt: Date.now(),
lastSeen: Date.now(),
});
// Send current presence to new user
connection.send(
JSON.stringify({
type: "presence",
users: this.getPresence(),
}),
);
// Notify others that someone joined
this.broadcastPresence();
}
onClose(connection) {
// No manual cleanup needed - connection state is automatically gone
this.broadcastPresence();
}
onMessage(connection, message) {
if (message === "ping") {
connection.setState((prev) => ({
...prev,
lastSeen: Date.now(),
}));
connection.send("pong");
}
}
getPresence() {
const users = {};
for (const conn of this.getConnections()) {
if (conn.state) {
users[conn.id] = {
name: conn.state.name,
lastSeen: conn.state.lastSeen,
};
}
}
return users;
}
broadcastPresence() {
this.broadcast(
JSON.stringify({
type: "presence",
users: this.getPresence(),
}),
);
}
}type UserState = {
name: string;
joinedAt: number;
lastSeen: number;
};
export class PresenceAgent extends Agent {
onConnect(connection: Connection<UserState>, ctx: ConnectionContext) {
const url = new URL(ctx.request.url);
const name = url.searchParams.get("name") || "Anonymous";
connection.setState({
name,
joinedAt: Date.now(),
lastSeen: Date.now(),
});
// Send current presence to new user
connection.send(
JSON.stringify({
type: "presence",
users: this.getPresence(),
}),
);
// Notify others that someone joined
this.broadcastPresence();
}
onClose(connection: Connection) {
// No manual cleanup needed - connection state is automatically gone
this.broadcastPresence();
}
onMessage(connection: Connection<UserState>, message: WSMessage) {
if (message === "ping") {
connection.setState((prev) => ({
...prev!,
lastSeen: Date.now(),
}));
connection.send("pong");
}
}
private getPresence() {
const users: Record<string, { name: string; lastSeen: number }> = {};
for (const conn of this.getConnections<UserState>()) {
if (conn.state) {
users[conn.id] = {
name: conn.state.name,
lastSeen: conn.state.lastSeen,
};
}
}
return users;
}
private broadcastPresence() {
this.broadcast(
JSON.stringify({
type: "presence",
users: this.getPresence(),
}),
);
}
}Chatovací místnost s vysíláním
export class ChatRoom extends Agent {
onConnect(connection, ctx) {
const url = new URL(ctx.request.url);
const username = url.searchParams.get("username") || "Anonymous";
connection.setState({ username });
// Notify others
this.broadcast(
JSON.stringify({
type: "join",
user: username,
timestamp: Date.now(),
}),
[connection.id], // Do not send to the joining user
);
}
onMessage(connection, message) {
if (typeof message !== "string") return;
const { username } = connection.state;
this.broadcast(
JSON.stringify({
type: "message",
user: username,
text: message,
timestamp: Date.now(),
}),
);
}
onClose(connection) {
const { username } = connection.state || {};
if (username) {
this.broadcast(
JSON.stringify({
type: "leave",
user: username,
timestamp: Date.now(),
}),
);
}
}
}type Message = {
type: "message" | "join" | "leave";
user: string;
text?: string;
timestamp: number;
};
export class ChatRoom extends Agent {
onConnect(connection: Connection, ctx: ConnectionContext) {
const url = new URL(ctx.request.url);
const username = url.searchParams.get("username") || "Anonymous";
connection.setState({ username });
// Notify others
this.broadcast(
JSON.stringify({
type: "join",
user: username,
timestamp: Date.now(),
} satisfies Message),
[connection.id], // Do not send to the joining user
);
}
onMessage(connection: Connection, message: WSMessage) {
if (typeof message !== "string") return;
const { username } = connection.state as { username: string };
this.broadcast(
JSON.stringify({
type: "message",
user: username,
text: message,
timestamp: Date.now(),
} satisfies Message),
);
}
onClose(connection: Connection) {
const { username } = (connection.state as { username: string }) || {};
if (username) {
this.broadcast(
JSON.stringify({
type: "leave",
user: username,
timestamp: Date.now(),
} satisfies Message),
);
}
}
}Potlačení protokolových zpráv
Ve výchozím nastavení agenti odesílají textové rámce JSON (identita, synchronizace stavu, seznamy serverů MCP) každému připojení. Přepište shouldSendProtocolMessages a potlačit je u konkrétních spojení, například u klientů podporujících pouze binární data, kteří neumí zpracovat textové JSON rámce:
export class IoTAgent extends Agent {
shouldSendProtocolMessages(connection, ctx) {
const url = new URL(ctx.request.url);
return url.searchParams.get("protocol") !== "binary";
}
}export class IoTAgent extends Agent {
shouldSendProtocolMessages(
connection: Connection,
ctx: ConnectionContext,
): boolean {
const url = new URL(ctx.request.url);
return url.searchParams.get("protocol") !== "binary";
}
}Když toto vrátí false, připojení nedostává rámce s identitou, stavem ani seznamem serverů MCP, a to ani při připojení, ani prostřednictvím vysílání. Připojení může nadále odesílat a přijímat běžné zprávy, používat RPC a účastnit se veškeré komunikace mimo tento protokol.
Použijte isConnectionProtocolEnabled(connection) pro kontrolu stavu libovolného připojení za běhu.
Vlastnosti agenta
Tyto vlastnosti jsou k dispozici na this uvnitř jakékoli metody agenta:
| Vlastnost | Typ | Popis |
|---|---|---|
this.name |
string |
Název instance tohoto agenta |
this.state |
State |
Aktuální stav agenta (líně načtený ze SQLite) |
this.env |
Env |
Vazby prostředí Workeru |
this.ctx |
DurableObjectState |
Kontext Durable Object (úložiště, alarmy atd.) |
this.sql |
šablonový tag | Šablonová značka SQL pro spouštění dotazů nad úložištěm SQLite agenta |
this.mcp |
MCPClientManager |
Správce klienta MCP pro připojování k externím serverům MCP |
Připojování z klientů
Pro připojení z prohlížeče použijte Agents client SDK:
- Vanilla JS:
AgentClientzagents/client - React:
useAgenthook zagents/react
Viz Client SDK pro úplnou dokumentaci.