TypeBox là nguồn chân lý của protocol

Cập nhật lần cuối: 10/01/2026

TypeBox là một thư viện schema ưu tiên TypeScript. Mình dùng nó để định nghĩa Gateway WebSocket protocol (handshake, request/response, server events). Các schema này điều khiển runtime validation, JSON Schema export, và Swift codegen cho ứng dụng macOS. Một nguồn chân lý duy nhất; mọi thứ khác đều được tự động sinh ra.

Nếu các bạn muốn hiểu ngữ cảnh protocol ở mức cao hơn, hãy bắt đầu với Kiến trúc Gateway.

Mô hình tư duy (30 giây)

Mỗi message Gateway WS là một trong ba loại frame:

  • Request: { type: "req", id, method, params }
  • Response: { type: "res", id, ok, payload | error }
  • Event: { type: "event", event, payload, seq?, stateVersion? }

Frame đầu tiên bắt buộc phải là request connect. Sau đó, client có thể gọi các method (ví dụ health, send, chat.send) và subscribe vào các event (ví dụ presence, tick, agent).

Luồng kết nối (tối thiểu):

Client                    Gateway
  |---- req:connect -------->|
  |<---- res:hello-ok --------|
  |<---- event:tick ----------|
  |---- req:health ---------->|
  |<---- res:health ----------|

Các method + event phổ biến:

Danh mụcVí dụGhi chú
Coreconnect, health, statusconnect phải là đầu tiên
Messagingsend, poll, agent, agent.waitside-effects cần idempotencyKey
Chatchat.history, chat.send, chat.abort, chat.injectWebChat dùng những cái này
Sessionssessions.list, sessions.patch, sessions.deletequản trị session
Nodesnode.list, node.invoke, node.pair.*Gateway WS + node actions
Eventstick, presence, agent, chat, health, shutdownserver push

Danh sách chính thức nằm trong src/gateway/server.ts (METHODS, EVENTS).

Vị trí các schema

  • Source: src/gateway/protocol/schema.ts
  • Runtime validators (AJV): src/gateway/protocol/index.ts
  • Server handshake + method dispatch: src/gateway/server.ts
  • Node client: src/gateway/client.ts
  • Generated JSON Schema: dist/protocol.schema.json
  • Generated Swift models: apps/macos/Sources/OpenClawProtocol/GatewayModels.swift

Pipeline hiện tại

  • pnpm protocol:gen
    • ghi JSON Schema (draft‑07) vào dist/protocol.schema.json
  • pnpm protocol:gen:swift
    • sinh ra Swift gateway models
  • pnpm protocol:check
    • chạy cả hai generator và xác minh output đã được commit

Cách các schema được dùng lúc runtime

  • Server side: mọi inbound frame đều được validate bằng AJV. Handshake chỉ chấp nhận request connect có params khớp với ConnectParams.
  • Client side: JS client validate event và response frames trước khi dùng chúng.
  • Method surface: Gateway quảng cáo các methodsevents được hỗ trợ trong hello-ok.

Ví dụ các frame

Connect (message đầu tiên):

{
  "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 response:

{
  "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 }
  }
}

Request + response:

{ "type": "req", "id": "r1", "method": "health" }
{ "type": "res", "id": "r1", "ok": true, "payload": { "ok": true } }

Event:

{ "type": "event", "event": "tick", "payload": { "ts": 1730000000 }, "seq": 12 }

Client tối thiểu (Node.js)

Luồng nhỏ nhất có ích: 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();
  }
});

Ví dụ chi tiết: thêm method từ đầu đến cuối

Ví dụ: thêm request system.echo mới trả về { ok: true, text }.

  1. Schema (nguồn chân lý)

Thêm vào 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 },
);

Thêm cả hai vào ProtocolSchemas và export types:

  SystemEchoParams: SystemEchoParamsSchema,
  SystemEchoResult: SystemEchoResultSchema,
export type SystemEchoParams = Static<typeof SystemEchoParamsSchema>;
export type SystemEchoResult = Static<typeof SystemEchoResultSchema>;
  1. Validation

Trong src/gateway/protocol/index.ts, export một AJV validator:

export const validateSystemEchoParams = ajv.compile<SystemEchoParams>(SystemEchoParamsSchema);
  1. Server behavior

Thêm handler trong src/gateway/server-methods/system.ts:

export const systemHandlers: GatewayRequestHandlers = {
  "system.echo": ({ params, respond }) => {
    const text = String(params.text ?? "");
    respond(true, { ok: true, text });
  },
};

Đăng ký nó trong src/gateway/server-methods.ts (đã merge systemHandlers rồi), sau đó thêm "system.echo" vào METHODS trong src/gateway/server.ts.

  1. Regenerate
pnpm protocol:check
  1. Tests + docs

Thêm server test trong src/gateway/server.*.test.ts và ghi chú method trong docs.

Hành vi Swift codegen

Swift generator sinh ra:

  • GatewayFrame enum với các case req, res, event, và unknown
  • Strongly typed payload structs/enums
  • Giá trị ErrorCodeGATEWAY_PROTOCOL_VERSION

Các loại frame không xác định được giữ lại dưới dạng raw payloads để tương thích về sau.

Versioning + compatibility

  • PROTOCOL_VERSION nằm trong src/gateway/protocol/schema.ts.
  • Client gửi minProtocol + maxProtocol; server từ chối nếu không khớp.
  • Swift models giữ lại các loại frame không xác định để tránh làm hỏng các client cũ hơn.

Các pattern và quy ước của schema

  • Hầu hết các object dùng additionalProperties: false cho strict payloads.
  • NonEmptyString là mặc định cho ID và tên method/event.
  • GatewayFrame cấp cao nhất dùng discriminator trên type.
  • Các method có side effects thường yêu cầu idempotencyKey trong params (ví dụ: send, poll, agent, chat.send).

Live schema JSON

Generated JSON Schema nằm trong repo tại dist/protocol.schema.json. File raw được publish thường có sẵn tại:

Khi các bạn thay đổi schema

  1. Cập nhật TypeBox schemas.
  2. Chạy pnpm protocol:check.
  3. Commit schema + Swift models đã được regenerate.