← Создание агентов на Cloudflare / agents / runtime / communication
Сообщения протокола
Когда клиент WebSocket подключается к Agent, фреймворк автоматически отправляет несколько текстовых JSON-фреймов: данные идентификации, состояние и списки MCP-серверов. Эти служебные сообщения протокола, привязанные к конкретному соединению, можно отключить для клиентов, которые не умеют их обрабатывать.
Обзор
При каждом новом подключении Agent отправляет три сообщения протокола:
| Тип сообщения | Content |
|---|---|
cf_agent_identity |
Имя и класс Agent |
cf_agent_state |
Текущее состояние агента |
cf_agent_mcp_servers |
Список подключённых MCP-серверов |
Сообщения состояния и MCP также рассылаются всем подключениям при каждом изменении.
Для большинства веб-клиентов это не проблема: Клиентский SDK и useAgent хук автоматически обрабатывают эти сообщения. Однако некоторые клиенты не умеют обрабатывать текстовые фреймы JSON:
- Клиенты, работающие только с двоичными данными : устройства MQTT, датчики IoT, собственные двоичные протоколы
- Лёгкие клиенты : встраиваемые системы с минимальным стеком WebSocket
- Небраузерные клиенты : аппаратные устройства, подключающиеся через WebSocket
Для таких подключений можно подавить протокольные сообщения, сохранив всё остальное (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:
- Нет
cf_agent_identity,cf_agent_state, илиcf_agent_mcp_serversсообщения отправляются при подключении - С этого момента соединение исключается из состояния и рассылок MCP
- RPC-вызовы, обычные
onMessageобработку иthis.broadcast()по-прежнему работают в обычном режиме
Использование субпротокола 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-подключения: тот же механизм используется подключения только для чтения. Это означает:
- Переживает гибернацию : флаг сериализуется и восстанавливается при пробуждении агента
- Очистка не требуется : состояние соединения автоматически удаляется при закрытии соединения
- Нулевые накладные расходы : ни таблиц базы данных, ни запросов, только встроенное вложение соединения
- Защищено от пользовательского кода,
connection.stateиconnection.setState()никогда не раскрывайте и не перезаписывайте флаг
В отличие от только для чтения который можно динамически переключать с помощью setConnectionReadonly(), статус протокола устанавливается один раз при подключении и впоследствии измениться не может. Чтобы изменить статус протокола соединения, клиент должен отключиться и подключиться заново.