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

Сообщения протокола

Когда клиент WebSocket подключается к Agent, фреймворк автоматически отправляет несколько текстовых JSON-фреймов: данные идентификации, состояние и списки MCP-серверов. Эти служебные сообщения протокола, привязанные к конкретному соединению, можно отключить для клиентов, которые не умеют их обрабатывать.

Обзор

При каждом новом подключении Agent отправляет три сообщения протокола:

Тип сообщения Content
cf_agent_identity Имя и класс Agent
cf_agent_state Текущее состояние агента
cf_agent_mcp_servers Список подключённых MCP-серверов

Сообщения состояния и MCP также рассылаются всем подключениям при каждом изменении.

Для большинства веб-клиентов это не проблема: Клиентский SDK и useAgent хук автоматически обрабатывают эти сообщения. Однако некоторые клиенты не умеют обрабатывать текстовые фреймы JSON:

Для таких подключений можно подавить протокольные сообщения, сохранив всё остальное (RPC, обычные сообщения, широковещательные рассылки через this.broadcast()) работает нормально.

Подавление сообщений протокола

Переопределить shouldSendProtocolMessages чтобы управлять тем, какие соединения получают сообщения протокола. Верните false чтобы подавить их.

import { Agent } from "agents";

export class IoTAgent extends Agent {
	shouldSendProtocolMessages(connection, ctx) {
		const url = new URL(ctx.request.url);
		return url.searchParams.get("protocol") !== "false";
	}
}
import { Agent, type Connection, type ConnectionContext } from "agents";

export class IoTAgent extends Agent<Env, State> {
	shouldSendProtocolMessages(
		connection: Connection,
		ctx: ConnectionContext,
	): boolean {
		const url = new URL(ctx.request.url);
		return url.searchParams.get("protocol") !== "false";
	}
}

Этот хук выполняется во время onConnect, ещё до отправки каких-либо сообщений. Когда она возвращает false:

Использование субпротокола WebSocket

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

export class MqttAgent extends Agent {
	shouldSendProtocolMessages(connection, ctx) {
		// MQTT-over-WebSocket clients negotiate via subprotocol
		const subprotocol = ctx.request.headers.get("Sec-WebSocket-Protocol");
		return subprotocol !== "mqtt";
	}
}
export class MqttAgent extends Agent<Env, State> {
	shouldSendProtocolMessages(
		connection: Connection,
		ctx: ConnectionContext,
	): boolean {
		// MQTT-over-WebSocket clients negotiate via subprotocol
		const subprotocol = ctx.request.headers.get("Sec-WebSocket-Protocol");
		return subprotocol !== "mqtt";
	}
}

Проверка статуса протокола

Используйте isConnectionProtocolEnabled чтобы проверить, включены ли у соединения сообщения протокола:

export class MyAgent extends Agent {
	@callable()
	async getConnectionInfo() {
		const { connection } = getCurrentAgent();
		if (!connection) return null;

		return {
			protocolEnabled: this.isConnectionProtocolEnabled(connection),
			readonly: this.isConnectionReadonly(connection),
		};
	}
}
export class MyAgent extends Agent<Env, State> {
	@callable()
	async getConnectionInfo() {
		const { connection } = getCurrentAgent();
		if (!connection) return null;

		return {
			protocolEnabled: this.isConnectionProtocolEnabled(connection),
			readonly: this.isConnectionReadonly(connection),
		};
	}
}

Что подавляется, а что нет

В следующей таблице показано, что продолжает работать, если сообщения протокола для соединения подавлены:

Действие Работает?
Получайте cf_agent_identity при подключении Нет
Получайте cf_agent_state при подключении и рассылает Нет
Получайте cf_agent_mcp_servers при подключении и рассылает Нет
Отправляйте и получайте обычные сообщения WebSocket Да
Вызовите @callable() RPC-методы Да
Получайте this.broadcast() сообщения Да
Отправляйте бинарные данные Да
Изменение состояния агента через RPC Да

Сочетание с readonly

Соединение может быть одновременно readonly и с подавлением протокола. Это полезно для бинарных устройств, которые должны наблюдать за состоянием, но не изменять его:

export class SensorHub extends Agent {
	shouldSendProtocolMessages(connection, ctx) {
		const url = new URL(ctx.request.url);
		// Binary sensors don't handle JSON protocol frames
		return url.searchParams.get("type") !== "sensor";
	}

	shouldConnectionBeReadonly(connection, ctx) {
		const url = new URL(ctx.request.url);
		// Sensors can only report data via RPC, not modify shared state
		return url.searchParams.get("type") === "sensor";
	}

	@callable()
	async reportReading(sensorId, value) {
		// This RPC still works for readonly+no-protocol connections
		// because it writes to SQL, not agent state
		this
			.sql`INSERT INTO readings (sensor_id, value, ts) VALUES (${sensorId}, ${value}, ${Date.now()})`;
	}
}
export class SensorHub extends Agent<Env, SensorState> {
	shouldSendProtocolMessages(
		connection: Connection,
		ctx: ConnectionContext,
	): boolean {
		const url = new URL(ctx.request.url);
		// Binary sensors don't handle JSON protocol frames
		return url.searchParams.get("type") !== "sensor";
	}

	shouldConnectionBeReadonly(
		connection: Connection,
		ctx: ConnectionContext,
	): boolean {
		const url = new URL(ctx.request.url);
		// Sensors can only report data via RPC, not modify shared state
		return url.searchParams.get("type") === "sensor";
	}

	@callable()
	async reportReading(sensorId: string, value: number) {
		// This RPC still works for readonly+no-protocol connections
		// because it writes to SQL, not agent state
		this
			.sql`INSERT INTO readings (sensor_id, value, ts) VALUES (${sensorId}, ${value}, ${Date.now()})`;
	}
}

Оба флага хранятся во вложении WebSocket соединения и скрыты от connection.state : они не мешают друг другу и не вмешиваются в определённое пользователем состояние соединения.

Справочник по API

shouldSendProtocolMessages

Переопределяемый хук, который определяет, должно ли подключение получать протокольные сообщения в момент подключения.

Параметр Тип Описание
connection Connection Подключающийся клиент
ctx ConnectionContext Содержит запрос Upgrade
Возвращает boolean false чтобы подавить сообщения протокола

По умолчанию возвращает true (все соединения получают сообщения протокола).

Этот хук вычисляется один раз при подключении. Результат устойчиво сохраняется во вложении WebSocket данного подключения и переживает гибернация.

isConnectionProtocolEnabled

Проверяет, включены ли для соединения сообщения протокола.

Параметр Тип Описание
connection Connection Соединение для проверки
Возвращает boolean true если включены сообщения протокола

Можно безопасно вызывать в любой момент, в том числе после выхода агента из гибернации.

Как это работает

Статус протокола хранится в виде внутреннего флага в attachment WebSocket-подключения: тот же механизм используется подключения только для чтения. Это означает:

В отличие от только для чтения который можно динамически переключать с помощью setConnectionReadonly(), статус протокола устанавливается один раз при подключении и впоследствии измениться не может. Чтобы изменить статус протокола соединения, клиент должен отключиться и подключиться заново.