Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

协议消息

当一个 WebSocket 客户端连接到 Agent 时,框架会自动发送几条 JSON 文本帧 — 包括身份、状态和 MCP 服务器列表。对于无法处理这些消息的客户端,你可以按连接禁用这些协议消息。

概览

每次新连接时,Agent 会发送三条协议消息:

消息类型内容
cf_agent_identityAgent 名称和类
cf_agent_state当前 agent 状态
cf_agent_mcp_servers已连接的 MCP 服务器列表

状态和 MCP 消息在发生变化时,也会广播给所有连接。

对于大多数 Web 客户端来说这没问题 — 客户端 SDKuseAgent hook 会自动消费这些消息。然而,有些客户端无法处理 JSON 文本帧:

  • 仅支持二进制的客户端 — MQTT 设备、IoT 传感器、自定义二进制协议
  • 轻量级客户端 — 拥有最简 WebSocket 栈的嵌入式系统
  • 非浏览器客户端 — 通过 WebSocket 连接的硬件设备

对于这些连接,你可以禁用协议消息,同时保持其他功能(RPC、常规消息、this.broadcast() 广播)正常工作。

禁用协议消息

重写 shouldSendProtocolMessages 来控制哪些连接接收协议消息。返回 false 即可禁用。

JavaScript


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";

  }

}


TypeScript


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";

  }

}


Explain Code

这个 hook 会在 onConnect 期间、消息发送之前运行。当它返回 false 时:

  • 连接时不会发送 cf_agent_identitycf_agent_statecf_agent_mcp_servers 消息
  • 该连接之后也会被排除在状态和 MCP 广播之外
  • RPC 调用、常规 onMessage 处理和 this.broadcast() 仍然正常工作

使用 WebSocket 子协议

你也可以检查 WebSocket 子协议头,这是在 WebSocket 上协商协议的标准方式:

JavaScript


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";

  }

}


TypeScript


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";

  }

}


Explain Code

检查协议状态

使用 isConnectionProtocolEnabled 检查某个连接是否启用了协议消息:

JavaScript


export class MyAgent extends Agent {

  @callable()

  async getConnectionInfo() {

    const { connection } = getCurrentAgent();

    if (!connection) return null;


    return {

      protocolEnabled: this.isConnectionProtocolEnabled(connection),

      readonly: this.isConnectionReadonly(connection),

    };

  }

}


Explain Code

TypeScript


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),

    };

  }

}


Explain Code

哪些功能受影响、哪些不受影响

下表展示了禁用协议消息后,某个连接仍可正常使用的功能:

操作是否可用
连接时接收 cf_agent_identity
连接时和广播时接收 cf_agent_state
连接时和广播时接收 cf_agent_mcp_servers
收发常规 WebSocket 消息
调用 @callable() RPC 方法
接收 this.broadcast() 消息
发送二进制数据
通过 RPC 修改 agent 状态

与 readonly 组合使用

一个连接可以同时是只读和禁用协议消息。这对于只观察、不修改状态的二进制设备很有用:

JavaScript


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()})`;

  }

}


Explain Code

TypeScript


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()})`;

  }

}


Explain Code

两个标志都存储在连接的 WebSocket attachment 中,且对 connection.state 不可见 — 它们之间不会互相干扰,也不会与用户定义的连接状态冲突。

API 参考

shouldSendProtocolMessages

一个可重写的 hook,用于决定连接时该连接是否接收协议消息。

参数类型描述
connectionConnection正在连接的客户端
ctxConnectionContext包含升级请求
返回booleanfalse 表示禁用协议消息

默认值:返回 true(所有连接都接收协议消息)。

这个 hook 在连接时只评估一次。结果会持久化到连接的 WebSocket attachment 中,可以挺过休眠

isConnectionProtocolEnabled

检查某个连接当前是否启用了协议消息。

参数类型描述
connectionConnection要检查的连接
返回booleantrue 表示协议消息已启用

随时可以调用,包括 agent 从休眠中唤醒之后。

工作原理

协议状态以内部标志的形式存储在连接的 WebSocket attachment 中 — 这与只读连接使用的机制相同。这意味着:

  • 挺过休眠 — 标志会被序列化,在 agent 唤醒时恢复
  • 无需清理 — 连接关闭时,连接状态会自动丢弃
  • 零开销 — 不需要数据库表或查询,只用连接内置的 attachment
  • 不受用户代码影响connection.stateconnection.setState() 不会暴露或覆盖该标志

与可以通过 setConnectionReadonly() 动态切换的只读模式不同,协议状态在连接时设置一次,之后不能改变。要更改连接的协议状态,客户端必须断开重连。

相关资源