INTEGRITY Dokumentace

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

  1. Agent je aktivní a obsluhuje připojení
  2. Po období nečinnosti bez zpráv agent hibernuje (uspí se)
  3. Připojení WebSocket zůstávají otevřená (řeší Cloudflare)
  4. Když dorazí zpráva, agent se probudí
  5. onMessage se 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:

Viz Client SDK pro úplnou dokumentaci.

Další kroky

Synchronizace stavu

Synchronizuje stav mezi agenty a klienty.

Volatelné metody

RPC přes WebSockets pro volání metod.

Autentizace mezi doménami

Zabezpečte WebSocket připojení napříč doménami.