壹 · 连接
WebSocket 客户端
ctx.agentWs
仅 wss:// 的 WebSocket 客户端:可刷新的鉴权请求头、心跳、指数退避重连、并发与背压控制;鉴权失败或指定关闭码时进入 failed 状态并可慢速重试。
接口
const conn = ctx.agentWs.connect<Frame>({
url: 'wss://example.com/stream',
headers: async () => ({ 'X-Token': await getToken() }), // 也接受静态对象
parse: (value) => parseFrame(value), // 可选,把 JsonValue 转成业务类型;抛错则丢弃该帧
onMessage: async (frame, { signal }) => {},
onError: (err, frame) => {}, // 可选,默认只记日志
onStateChange: (state) => {}, // connecting / open / reconnecting / failed / closed
fatalCloseCodes: [4001],
concurrency: 1,
})
conn.state // 当前状态
await conn.whenOpen({ signal }) // 等待连接打开
await conn.send(frame) // 写入 socket 缓冲区后 resolve
await conn.close()connect() 返回的连接随调用方插件一起管理:插件卸载时连接以 1001 关闭,正在执行的 onMessage 收到的 signal 被中止,状态变为 closed。
传输与鉴权
- 只允许
wss://;ws://仅在主机为localhost或127.0.0.1时允许,用于测试。 - 不允许关闭证书校验,不跟随重定向,避免鉴权头随重定向泄露。
headers为函数时,每次连接和重连前都会调用,用于刷新会过期的令牌。请求头的值会登记为脱敏密钥,不会出现在日志和错误中。
帧处理
- 只收发 JSON 文本帧。无法解析为 JSON、二进制帧、
parse抛错的帧会被丢弃,日志只记录长度与哈希,连接不受影响。 - 超过
maxPayloadBytes的帧会导致断开,并按普通断线重连。
并发与背压
- 同时最多
concurrency个onMessage在执行,默认 1,也就是按到达顺序串行。 - 待处理消息超过
maxPendingMessages时暂停读取 socket,回落到阈值以下后恢复。已进入接收缓冲区的帧仍会被处理,所以暂停时待处理数可能略高于阈值。 onMessage抛错交给onError;没有提供onError时只记日志,不断开连接。
发送
send() 在连接不处于 open 时立即以 not_open 失败(可重试),本包不做内部排队;是否重发由业务包决定。
重连、心跳与失败
- 断线后按指数退避加抖动自动重连:第 n 次等待
min(maxDelayMs, initialDelayMs × 2^n) × (1 − jitter × random)。抖动只向下,保证不超过maxDelayMs。 - 连接稳定保持
stableResetMs后,退避时间重置为初始值。 - 客户端定时发送 ping;超过
readTimeoutMs没有收到任何帧(包括 pong)就主动断开并重连。背压暂停期间读超时暂停计时。 - 握手返回 401 / 403,或连接以
fatalCloseCodes中的关闭码断开时,视为鉴权或配置错误:状态变为failed,health()返回failed,并触发agent-kit/service-failed事件。 fatalRetryDelayMs大于 0 时按该间隔慢速重试(重试前重新获取headers);为 0 时不再重试,由进程托管或人工恢复。onStateChange在connect()时先收到connecting,之后例如open → reconnecting → open → closed;fatal 后慢速重试为failed → connecting → open。
配置
在 Profile 的 cordis.patch.yml 中按 id agent-kit-ws 启用并给出配置。按 id 修改 config 时整段替换,未给出的字段使用默认值。下表由本包源码中的配置 schema 生成。
- id: agent-kit-ws
disabled: false
config:
# 只写需要改的字段,其余使用下表的默认值 | 字段 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|
pingIntervalMs | 30000 | ≥ 1000 | WS ping 间隔 |
readTimeoutMs | 75000 | ≥ 2000 | 读超时,需 ≥ 2 × pingIntervalMs |
reconnect.initialDelayMs | 1000 | ≥ 100 | 首次重连前的等待时间,之后按指数增长。 |
reconnect.maxDelayMs | 60000 | ≥ 100 | 重连等待时间的上限。 |
reconnect.jitter | 0.2 | [0, 1] | 抖动比例:每次等待时间向下随机减少最多该比例。 |
stableResetMs | 60000 | ≥ 0 | 连接稳定保持多久后重置退避 |
fatalRetryDelayMs | 300000 | ≥ 0 | fatal 后慢速重试间隔,0 表示不重试 |
maxPayloadBytes | 1048576 | [1024, 104857600] | 单帧最大字节数,超过时断开并按普通断线重连。 |
maxPendingMessages | 100 | ≥ 1 | 待处理消息超过该值时暂停读取 socket(背压)。 |
约束与说明
- readTimeoutMs 必须 ≥ 2 × pingIntervalMs。
- reconnect.maxDelayMs 必须 ≥ reconnect.initialDelayMs。
- fatalRetryDelayMs 为 0(fatal 后不再重试)或 ≥ 10000。
- connect() 参数中的 fatalCloseCodes(默认空)与 concurrency(默认 1)由业务包按对端约定给出。
错误码
所有错误都是 KitError:用 isKitError(e) 判断,按 code 与 retryable 决定是否重试。
| code | 可重试 | 含义 |
|---|---|---|
not_open | 是 | 连接未处于 open 状态时调用 send();不做内部排队。 |
closed | 否 | 连接已关闭(主动 close 或调用方插件卸载)。 |
aborted | 否 | whenOpen() 的 signal 被中止。 |
invalid_url | 否 | URL 非法,或使用了非本机地址的 ws://。 |
invalid_options | 否 | connect() 参数非法,或 send() 的帧无法序列化为 JSON。 |
invalid_config | 否 | 配置或环境变量非法,Service 启动失败。 |