WebSocket Streaming
ClawDesk supports real-time bidirectional communication via WebSocket. This is used by the Tauri desktop app and can be consumed by any WebSocket client.
Connection
Endpoint
ws://localhost:1420/ws
Connection Example
const ws = new WebSocket("ws://localhost:1420/ws");
ws.onopen = () => {
console.log("Connected to ClawDesk");
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
handleMessage(msg);
};
ws.onclose = (event) => {
console.log(`Disconnected: ${event.code} ${event.reason}`);
};
ws.onerror = (error) => {
console.error("WebSocket error:", error);
};
Connection Parameters
| Parameter | Type | Description |
|---|---|---|
token | query string | Authentication token (if auth enabled) |
session_id | query string | Resume an existing session |
ws://localhost:1420/ws?token=abc123&session_id=sess_def456
Message Protocol
All WebSocket messages are JSON objects with a type field:
{
"type": "message_type",
"data": { ... }
}
Client → Server Messages
chat.message
Send a chat message to the agent pipeline.
{
"type": "chat.message",
"data": {
"session_id": "sess_abc123",
"content": "What is Rust's ownership model?",
"metadata": {}
}
}
chat.cancel
Cancel a streaming response in progress.
{
"type": "chat.cancel",
"data": {
"session_id": "sess_abc123",
"message_id": "msg_def456"
}
}
session.create
Create a new session.
{
"type": "session.create",
"data": {
"channel_id": "desktop",
"model": "claude-sonnet-4-20250514"
}
}
session.switch
Switch to a different session.
{
"type": "session.switch",
"data": {
"session_id": "sess_abc123"
}
}
ping
Keep-alive ping.
{
"type": "ping",
"data": {
"timestamp": 1708167000
}
}
Server → Client Messages
chat.stream.start
Indicates the start of a streaming response.
{
"type": "chat.stream.start",
"data": {
"message_id": "msg_def456",
"session_id": "sess_abc123",
"model": "claude-sonnet-4-20250514"
}
}
chat.stream.delta
A chunk of streaming content.
{
"type": "chat.stream.delta",
"data": {
"message_id": "msg_def456",
"content": "Rust's ownership model ",
"index": 3
}
}
chat.stream.end
The streaming response is complete.
{
"type": "chat.stream.end",
"data": {
"message_id": "msg_def456",
"finish_reason": "stop",
"usage": {
"input_tokens": 42,
"output_tokens": 256
}
}
}
chat.response
A complete (non-streamed) response.
{
"type": "chat.response",
"data": {
"message_id": "msg_def456",
"session_id": "sess_abc123",
"role": "assistant",
"content": "Full response content here...",
"model": "claude-sonnet-4-20250514",
"usage": {
"input_tokens": 42,
"output_tokens": 128
}
}
}
session.created
Confirmation that a session was created.
{
"type": "session.created",
"data": {
"session_id": "sess_new789",
"channel_id": "desktop",
"created_at": "2026-02-17T10:30:00Z"
}
}
error
An error occurred processing a request.
{
"type": "error",
"data": {
"code": "provider_error",
"message": "All providers failed for this request",
"request_type": "chat.message",
"details": {
"attempts": [
{ "provider": "anthropic", "error": "rate_limited" },
{ "provider": "openai", "error": "timeout" }
]
}
}
}
pong
Response to a client ping.
{
"type": "pong",
"data": {
"timestamp": 1708167000,
"server_time": 1708167001
}
}
Error Codes
| Code | Description |
|---|---|
invalid_message | Malformed WebSocket message |
session_not_found | Referenced session doesn't exist |
provider_error | AI provider returned an error |
rate_limited | Request rate limit exceeded |
cancelled | Response was cancelled by client |
internal_error | Internal server error |
Connection Lifecycle
Client Server
│ │
│──── WebSocket Upgrade ───────▶│
│◀──── 101 Switching ──────────│
│ │
│──── session.create ──────────▶│
│◀──── session.created ────────│
│ │
│──── chat.message ────────────▶│
│◀──── chat.stream.start ──────│
│◀──── chat.stream.delta ──────│
│◀──── chat.stream.delta ──────│
│◀──── chat.stream.end ────────│
│ │
│──── ping ────────────────────▶│
│◀──── pong ───────────────────│
│ │
│──── close ───────────────────▶│
│◀──── close ──────────────────│
Reconnection
Clients should implement exponential backoff for reconnection:
let reconnectDelay = 1000;
function connect() {
const ws = new WebSocket("ws://localhost:1420/ws");
ws.onopen = () => {
reconnectDelay = 1000; // Reset on success
};
ws.onclose = () => {
setTimeout(connect, reconnectDelay);
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
};
}
The WebSocket connection supports automatic session resumption. Pass session_id as a query parameter to resume where you left off.