TypeBox 作為協議的單一真實來源
最後更新:2026-01-10
TypeBox 是一個 TypeScript 優先的 Schema 函式庫。我們用它定義 Gateway WebSocket 協議(握手、請求/回應、伺服器事件)。這些 Schema 驅動執行期間驗證、JSON Schema 匯出,以及 macOS App 的 Swift 程式碼生成。單一真實來源;其他一切都是自動產生的。
如果你想了解更高層級的協議脈絡,從 Gateway architecture 開始。
心智模型(30 秒版本)
每個 Gateway WS 訊息都是三種 Frame 之一:
- Request:
{ type: "req", id, method, params } - Response:
{ type: "res", id, ok, payload | error } - Event:
{ type: "event", event, payload, seq?, stateVersion? }
第一個 Frame 必須是 connect 請求。之後用戶端可以呼叫方法(如 health、send、chat.send)並訂閱事件(如 presence、tick、agent)。
連線流程(最簡化):
Client Gateway
|---- req:connect -------->|
|<---- res:hello-ok --------|
|<---- event:tick ----------|
|---- req:health ---------->|
|<---- res:health ----------|
常用方法 + 事件:
| 類別 | 範例 | 備註 |
|---|---|---|
| 核心 | connect、health、status | connect 必須是第一個 |
| 訊息 | send、poll、agent、agent.wait | 有副作用的需要 idempotencyKey |
| 對話 | chat.history、chat.send、chat.abort、chat.inject | WebChat 使用這些 |
| Session | sessions.list、sessions.patch、sessions.delete | Session 管理 |
| Node | node.list、node.invoke、node.pair.* | Gateway WS + Node 操作 |
| 事件 | tick、presence、agent、chat、health、shutdown | 伺服器推送 |
權威清單位於 src/gateway/server.ts(METHODS、EVENTS)。
Schema 的位置
- 來源:
src/gateway/protocol/schema.ts - 執行期間驗證器(AJV):
src/gateway/protocol/index.ts - 伺服器握手 + 方法分派:
src/gateway/server.ts - Node 用戶端:
src/gateway/client.ts - 產生的 JSON Schema:
dist/protocol.schema.json - 產生的 Swift 模型:
apps/macos/Sources/OpenClawProtocol/GatewayModels.swift
目前的流水線
pnpm protocol:gen- 將 JSON Schema(draft-07)寫入
dist/protocol.schema.json
- 將 JSON Schema(draft-07)寫入
pnpm protocol:gen:swift- 產生 Swift Gateway 模型
pnpm protocol:check- 執行兩個產生器並驗證輸出已提交
Schema 在執行期間的使用方式
- 伺服器端:每個收到的 Frame 都會以 AJV 驗證。握手只接受 params 符合
ConnectParams的connect請求。 - 用戶端:JS 用戶端在使用前會驗證事件和回應 Frame。
- 方法介面:Gateway 在
hello-ok中宣告支援的methods和events。
Frame 範例
Connect(第一則訊息):
{
"type": "req",
"id": "c1",
"method": "connect",
"params": {
"minProtocol": 2,
"maxProtocol": 2,
"client": {
"id": "openclaw-macos",
"displayName": "macos",
"version": "1.0.0",
"platform": "macos 15.1",
"mode": "ui",
"instanceId": "A1B2"
}
}
}
Hello-ok 回應:
{
"type": "res",
"id": "c1",
"ok": true,
"payload": {
"type": "hello-ok",
"protocol": 2,
"server": { "version": "dev", "connId": "ws-1" },
"features": { "methods": ["health"], "events": ["tick"] },
"snapshot": {
"presence": [],
"health": {},
"stateVersion": { "presence": 0, "health": 0 },
"uptimeMs": 0
},
"policy": { "maxPayload": 1048576, "maxBufferedBytes": 1048576, "tickIntervalMs": 30000 }
}
}
請求 + 回應:
{ "type": "req", "id": "r1", "method": "health" }
{ "type": "res", "id": "r1", "ok": true, "payload": { "ok": true } }
事件:
{ "type": "event", "event": "tick", "payload": { "ts": 1730000000 }, "seq": 12 }
最小用戶端(Node.js)
最精簡的實用流程:connect + health。
import { WebSocket } from "ws";
const ws = new WebSocket("ws://127.0.0.1:18789");
ws.on("open", () => {
ws.send(
JSON.stringify({
type: "req",
id: "c1",
method: "connect",
params: {
minProtocol: 3,
maxProtocol: 3,
client: {
id: "cli",
displayName: "example",
version: "dev",
platform: "node",
mode: "cli",
},
},
}),
);
});
ws.on("message", (data) => {
const msg = JSON.parse(String(data));
if (msg.type === "res" && msg.id === "c1" && msg.ok) {
ws.send(JSON.stringify({ type: "req", id: "h1", method: "health" }));
}
if (msg.type === "res" && msg.id === "h1") {
console.log("health:", msg.payload);
ws.close();
}
});
完整範例:從頭到尾新增一個方法
範例:新增一個 system.echo 請求,回傳 { ok: true, text }。
- Schema(單一真實來源)
在 src/gateway/protocol/schema.ts 中新增:
export const SystemEchoParamsSchema = Type.Object(
{ text: NonEmptyString },
{ additionalProperties: false },
);
export const SystemEchoResultSchema = Type.Object(
{ ok: Type.Boolean(), text: NonEmptyString },
{ additionalProperties: false },
);
將兩者加入 ProtocolSchemas 並匯出型別:
SystemEchoParams: SystemEchoParamsSchema,
SystemEchoResult: SystemEchoResultSchema,
export type SystemEchoParams = Static<typeof SystemEchoParamsSchema>;
export type SystemEchoResult = Static<typeof SystemEchoResultSchema>;
- 驗證
在 src/gateway/protocol/index.ts 中匯出 AJV 驗證器:
export const validateSystemEchoParams = ajv.compile<SystemEchoParams>(SystemEchoParamsSchema);
- 伺服器行為
在 src/gateway/server-methods/system.ts 中新增處理器:
export const systemHandlers: GatewayRequestHandlers = {
"system.echo": ({ params, respond }) => {
const text = String(params.text ?? "");
respond(true, { ok: true, text });
},
};
在 src/gateway/server-methods.ts 中註冊(已自動合併 systemHandlers),然後在 src/gateway/server.ts 的 METHODS 中加入 "system.echo"。
- 重新產生
pnpm protocol:check
- 測試 + 文件
在 src/gateway/server.*.test.ts 中新增伺服器測試,並在文件中記錄該方法。
Swift 程式碼生成行為
Swift 產生器會產出:
GatewayFrame列舉,包含req、res、event和unknown的 case- 強型別的 payload 結構體/列舉
ErrorCode值和GATEWAY_PROTOCOL_VERSION
未知的 Frame 類型會保留為原始 payload,確保向前相容性。
版本控制 + 相容性
PROTOCOL_VERSION位於src/gateway/protocol/schema.ts。- 用戶端傳送
minProtocol+maxProtocol;伺服器拒絕不符合的版本。 - Swift 模型保留未知 Frame 類型以避免破壞舊版用戶端。
Schema 模式與慣例
- 大多數物件使用
additionalProperties: false以嚴格限制 payload。 NonEmptyString是 ID 和方法/事件名稱的預設型別。- 頂層
GatewayFrame使用type作為鑑別器(discriminator)。 - 有副作用的方法通常要求 params 中包含
idempotencyKey(例如:send、poll、agent、chat.send)。 agent接受選填的internalEvents用於執行期間產生的協作上下文(例如子 Agent/Cron 任務完成後的交接);請將此視為內部 API 介面。
線上 Schema JSON
產生的 JSON Schema 位於倉庫中的 dist/protocol.schema.json。已發布的原始檔案通常可在以下位置取得:
修改 Schema 時
- 更新 TypeBox Schema。
- 執行
pnpm protocol:check。 - 提交重新產生的 Schema + Swift 模型。