WebSocket 客户端连接到 Agent 时,框架会自动发送若干 JSON 文本帧 — 身份、状态与 MCP server 列表。对无法处理这些消息的客户端,可按连接 suppress 协议消息。
每次新连接时 Agent 发送三种协议消息:
| 消息类型 | 内容 |
|---|---|
cf_agent_identity |
Agent 名称与 class |
cf_agent_state |
当前 agent 状态 |
cf_agent_mcp_servers |
已连接 MCP 服务器列表 |
状态与 MCP 消息在变更时也会广播到所有连接。
对大多数 Web 客户端这没问题 — 客户端 SDK 与 useAgent hook 自动消费这些消息。但部分客户端无法处理 JSON 文本帧:
- 仅 binary 客户端 — MQTT 设备、IoT 传感器、自定义 binary 协议
- 轻量客户端 — 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";
}
}此 hook 在 onConnect 期间、发送任何消息前运行。返回 false 时:
- 连接时不发送
cf_agent_identity、cf_agent_state、cf_agent_mcp_servers - 该连接被排除在后续状态与 MCP 广播之外
- RPC、
onMessage与this.broadcast()仍正常
也可检查 WebSocket subprotocol 头,这是经 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 变更 agent 状态 | 是 |
连接可同时 readonly 与 suppress protocol。适用于应观察但不修改 state 的 binary 设备:
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 attachment 中,对 connection.state 隐藏 — 互不干扰,也不干扰用户定义的 connection state。
可 override 的 hook,决定连接建立时是否接收协议消息。
| 参数 | 类型 | 描述 |
|---|---|---|
connection |
Connection |
正在连接的客户端 |
ctx |
ConnectionContext |
含 upgrade request |
| 返回值 | boolean |
false 即 suppress 协议消息 |
默认:返回 true(所有连接接收协议消息)。
此钩子在连接时评估一次。结果持久化在 WebSocket 附件中,经受 休眠。
检查连接当前是否启用协议消息。
| 参数 | 类型 | 描述 |
|---|---|---|
connection |
Connection |
要检查的连接 |
| 返回值 | boolean |
true 表示启用协议消息 |
随时可安全调用,包括 agent 从休眠唤醒后。
协议状态作为内部标志存在连接的 WebSocket 附件中 — 与 只读连接 相同机制。因此:
- 经受休眠 — 标志序列化并在唤醒时恢复
- 无需清理 — 连接关闭时自动丢弃连接状态
- 零开销 — 无数据库表或查询,仅用连接内置附件
- 用户代码安全 —
connection.state与connection.setState()不会暴露或覆盖标志
与可用 setConnectionReadonly() 动态切换的 只读 不同,协议状态在连接时设定一次,之后不可改。要变更协议状态,客户端须断开并重连。