Skip to main content

WebSocketConnector

Native browser WebSocket adapter. The connector serialises every OutgoingMessage to JSON; incoming text frames are parsed as JSON and treated as IncomingMessage. Plain text frames are wrapped in a text message as a fallback.

import { WebSocketConnector } from "@chativa/connector-websocket";
import { ConnectorRegistry, chatStore } from "@chativa/core";

ConnectorRegistry.register(
new WebSocketConnector({
url: "wss://my-server.example.com/chat",
reconnect: true,
reconnectDelay: 2000,
maxReconnectAttempts: 5,
})
);
chatStore.getState().setConnector("websocket");

Tool calls, GenUI and human-in-the-loop chips work over this connector via the shared JSON frame vocabulary — see Rich frames.

Options

Schema: schemas/connectors/websocket.schema.json.

FieldDefaultDescription
url (required)ws:// or wss:// endpoint.
protocols[]Sub-protocol(s) passed to the WebSocket constructor.
reconnecttrueAuto-reconnect on socket close.
reconnectDelay2000Milliseconds between reconnect attempts.
maxReconnectAttempts5Stop reconnecting after this many failed attempts.

Wire format

Inbound (server → client)

Each text frame must be a JSON-encoded IncomingMessage. See schemas/messages/incoming-message.schema.json.

{ "id": "1", "type": "text", "from": "bot", "data": { "text": "Hello!" }, "timestamp": 1735689600000 }

Plain text frames are tolerated — the connector wraps them as { type: "text", data: { text } }.

Beyond plain messages, these rich frames are routed to their own handlers instead of the transcript:

{ "type": "typing", "isTyping": true }
{ "type": "tool_call", "data": { "id": "c1", "name": "get_weather", "status": "running" } }
{ "type": "genui", "streamId": "s1", "chunk": { "type": "ui", "component": "weather", "props": { "temp": 18 }, "id": 1 }, "done": true }
{ "type": "text", "id": "m1", "data": { "text": "Deploy?" }, "actions": [{ "label": "Approve" }] }

Outbound (client → server)

OutgoingMessage JSON, plus the special survey frame:

{ "type": "survey", "rating": 5, "comment": "Great", "kind": 1 }

…and the GenUI component event, sent when a mounted component fires one:

{ "type": "genui_event", "streamId": "s1", "eventType": "form_submit", "payload": { "email": "[email protected]" } }

Server example

A Node server using ws — an echo bot that shows a tool call, streams a GenUI card, then asks for approval:

import { WebSocketServer } from "ws";

const wss = new WebSocketServer({ port: 8080 });

wss.on("connection", (socket) => {
const send = (frame) => socket.send(JSON.stringify(frame));

socket.on("message", async (raw) => {
const msg = JSON.parse(raw.toString());

// A GenUI component event coming back from the client.
if (msg.type === "genui_event") {
console.log("component event:", msg.eventType, msg.payload);
return;
}

send({ type: "typing", isTyping: true });

// A tool call: same id, re-sent as it advances.
send({ type: "tool_call", data: { id: "c1", name: "get_weather", status: "running", params: { city: "Ankara" } } });
send({ type: "tool_call", data: { id: "c1", name: "get_weather", status: "completed", result: "18°C" } });

// Stream a GenUI card into the bubble.
send({ type: "genui", streamId: "s1", chunk: { type: "text", content: "Here's the forecast:", id: 1 }, done: false });
send({ type: "genui", streamId: "s1", chunk: { type: "ui", component: "weather", props: { city: "Ankara", temp: 18 }, id: 2 }, done: true });

send({ type: "typing", isTyping: false });

// Ask a question — the chips are the human-in-the-loop interrupt.
send({
type: "text",
id: "m1",
from: "bot",
data: { text: "Send this forecast by email?" },
actions: [{ label: "Yes", value: "send_email" }, { label: "No" }],
});
});
});

The chip the user taps arrives as an ordinary user message ("send_email"), so there's no special resume channel to implement.

Limitations

This connector is deliberately thin. It doesn't implement loadHistory, sendFile or onMessageStatus — if you need those, either fork it or write a custom connector.

It does handle tool calls, GenUI streaming, typing and HITL chips — see Rich frames.