WebSocket Transport
Drive an InteractiveSession over a WebSocket connection with @robota-sdk/agent-transport-ws. The
client sends JSON messages (submit a prompt, run a command, answer a permission prompt) and receives
the session's events as they happen.
Basic setup
createWsTransport() connects one session to one socket you already have. It adds no
authentication: whoever can open a socket on your server can drive the session, so bind the server
to loopback or authenticate the upgrade yourself.
import { InteractiveSession } from '@robota-sdk/agent-framework';
import { AnthropicProvider } from '@robota-sdk/agent-provider-anthropic';
import { createWsTransport } from '@robota-sdk/agent-transport-ws';
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ host: '127.0.0.1', port: 8080 });
const provider = new AnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY });
wss.on('connection', async (ws) => {
const session = new InteractiveSession({ cwd: process.cwd(), provider });
const transport = createWsTransport({
send: (msg) => ws.send(JSON.stringify(msg)),
// Called once if a message cannot be delivered; the transport stops forwarding after it.
onDeliveryError: () => ws.close(1011, 'Outbound delivery failed'),
});
session.attachTransport(transport);
await transport.start();
ws.on('message', (data) => transport.onMessage?.(String(data)));
ws.on('close', () => {
void transport.stop();
void session.shutdown();
});
});transport.onMessage is set by start() and cleared by stop() or a delivery failure, hence the
?..
Message protocol
The main messages are below. The full set is the TClientMessage and TServerMessage types exported
by @robota-sdk/agent-transport.
Client → server
{ "type": "submit", "prompt": "Fix the bug" }
{ "type": "command", "name": "compact", "args": "focus on the API" }
{ "type": "abort" }
{ "type": "cancel-queue" }
{ "type": "get-messages" }
{ "type": "get-context" }
{ "type": "permission-response", "id": "<request id>", "result": true }command runs one of the session's commands. An InteractiveSession has only the commands passed
in its commandModules option; the CLI composes its slash commands this way.
Server → client
{ "type": "text_delta", "delta": "Here is..." }
{ "type": "tool_start", "state": { "toolName": "Read", "isRunning": true } }
{ "type": "complete", "result": { "response": "Done." } }
{ "type": "command_result", "name": "compact", "message": "Context compacted.", "success": true }
{ "type": "permission_request", "event": { "id": "<request id>", "toolName": "Bash", "toolArgs": { "command": "pnpm test" } } }A permission_request waits for a permission-response with the same id. result is true (allow
once), false (deny), "allow-session" or "allow-project".
Advanced: direct handler
createWsTransport() is a thin wrapper. To control the connection yourself, pair the carrier-neutral
session handler with an outbound delivery boundary:
import {
createOutboundDelivery,
createSessionMessageHandler,
type IProtocolSession,
} from '@robota-sdk/agent-transport';
import type { WebSocketServer } from 'ws';
declare const wss: WebSocketServer;
declare const session: IProtocolSession; // for example an InteractiveSession
wss.on('connection', (ws) => {
const deliver = createOutboundDelivery(
(message) => ws.send(JSON.stringify(message)),
() => ws.close(1011, 'Outbound delivery failed'),
);
const { onMessage, cleanup } = createSessionMessageHandler({ session, deliver });
ws.on('message', (data) => onMessage(String(data)));
ws.on('close', cleanup);
});For a ready-made server with token admission, use the package's WsTransport class;
examples/capabilities/multi-surface-deploy
serves one session over WebSocket and HTTP together.
examples/websocket-chat is a complete chat server that
defines its own small message protocol instead.