Ir al contenido

TypeBox como fuente de verdad del protocolo

Seguro que has pasado por esto: cambias un campo en el backend y de repente el cliente móvil deja de funcionar porque los tipos no coinciden. Mantener la sincronización manual entre la documentación, el servidor y las aplicaciones es una tarea pesada que suele terminar en errores de validación difíciles de encontrar.

Tener que escribir el mismo esquema para TypeScript, JSON Schema y modelos en otros lenguajes como Swift quita tiempo y genera inconsistencias. Lo ideal es definir el protocolo una sola vez y que todo lo demás se genere automáticamente.

Para trabajar con este sistema, necesitas conocer estas ubicaciones:

  • Source: src/gateway/protocol/schema.ts
  • Validators en runtime (AJV): src/gateway/protocol/index.ts
  • Handshake del servidor: src/gateway/server.ts
  • Cliente Node.js: src/gateway/client.ts
  • JSON Schema generado: dist/protocol.schema.json
  • Modelos Swift generados: apps/macos/Sources/OpenClawProtocol/GatewayModels.swift

El protocolo del Gateway usa TypeBox para definir frames de WebSocket. Cada mensaje es uno de estos cuatro tipos de frame:

  • Request: { type: "req", id, method, params }
  • Response: { type: "res", id, ok, payload | error }
  • Event: { type: "event", event, payload, seq?, stateVersion? }
  • Unknown: Frames no reconocidos (útil para compatibilidad)

El flujo mínimo de conexión funciona así:

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

Este es el flujo más pequeño para conectar y revisar el estado:

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

Si quieres añadir un método como system.echo, sigue estos pasos:

  1. Define el esquema en 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 },
);
  1. Registra y exporta los tipos:
SystemEchoParams: SystemEchoParamsSchema,
SystemEchoResult: SystemEchoResultSchema,
export type SystemEchoParams = Static\<typeof SystemEchoParamsSchema\>;
export type SystemEchoResult = Static\<typeof SystemEchoResultSchema\>;
  1. Crea el validador en src/gateway/protocol/index.ts:
export const validateSystemEchoParams = ajv.compile<SystemEchoParams>(SystemEchoParamsSchema);
  1. Implementa la lógica en el servidor: En src/gateway/server-methods/system.ts:
export const systemHandlers: GatewayRequestHandlers = {
"system.echo": ({ params, respond }) => {
const text = String(params.text ?? "");
respond(true, { ok: true, text });
},
};
  1. Actualiza los archivos generados:
Ventana de terminal
pnpm protocol:check

Para mantener todo al día, usamos estos comandos:

  • pnpm protocol:gen: Escribe el JSON Schema en dist/.
  • pnpm protocol:gen:swift: Genera los modelos para la app de macOS.
  • pnpm protocol:check: Ejecuta los generadores y verifica que los cambios estén en el commit.
  • Validación continua: El servidor usa AJV para validar cada frame entrante.
  • Error en el handshake: Recuerda que el primer frame debe ser siempre una solicitud connect. Si envías otro método antes, el Gateway rechazará la conexión.
  • Versión de protocolo: Si el servidor devuelve un error de versión, revisa que minProtocol y maxProtocol coincidan con lo que el Gateway espera.
  • Campos extra: La mayoría de los esquemas usan additionalProperties: false. Si envías campos que no están definidos en el esquema de TypeBox, la validación fallará.

¿Necesitas ayuda con la integración? Prueba el AI Setup Assistant.

OpenClaw

OpenClaw Expert

Sigues atascado?

Si esta pagina no resolvio tu caso, pregunta a OpenClaw Expert para pasos concretos.