WebSocket Protocol

1. Introduction to WebSocket

WebSocket is a protocol for bidirectional communication over a single TCP connection. It is well suited for scenarios that require persistent connections, low latency, and continuous server-to-client message delivery. The client first sends a standard HTTP request, and the server upgrades the protocol by using Upgrade: websocket. After the upgrade is successful, the connection no longer follows the HTTP model of one request and one response. Instead, the client and server exchange message frames with each other.

The DolphinX HTTP API and WebSocket API share the same set of business capabilities. The difference is that HTTP expresses a call by using method + path + query + body. After a WebSocket connection is established, business calls are represented as JSON request frames, where action identifies the API to call, and payload carries the request parameters.

Use WebSocket in the following scenarios:

  • You need streaming conversations, real-time display of model output, or context processing events.
  • The client needs to reuse the same connection to send multiple consecutive requests.
  • You need to use requestId / correlationId to match multiple requests and responses on the same connection.

For occasional queries, administrative operations, or use cases that do not require real-time streaming responses, HTTP calls are usually simpler.

2. Connection

The WebSocket endpoint uses the same path as HTTP and is connected through a protocol upgrade:

ws://<host>:<port>/agent-bus/v1
wss://<host>:<port>/agent-bus/v1

During the handshake, the client should declare the agent-bus subprotocol and include the token obtained after login in the Authorization header:

Sec-WebSocket-Protocol: agent-bus
Authorization: Bearer <token>

Example of sending a WebSocket request in Python:

import asyncio
import json
import websockets

async def main():
    token = "<token>"
    url = "ws://localhost:8848/agent-bus/v1"

    async with websockets.connect(
        url,
        additional_headers={"Authorization": f"Bearer {token}"},
        subprotocols=["agent-bus"],
    ) as ws:
        request = {
        "type": "REQUEST",
        "requestId": "req_001",
        "action": "agent.accessible",
   }

        await ws.send(json.dumps(request, ensure_ascii=False))
        response = await ws.recv()
        print(response)

asyncio.run(main())

3. Message Format

All WebSocket messages are in JSON format.

Request Frame

The client sends a REQUEST frame to call an API. A request frame recognizes only the following top-level parameters. All other parameters are ignored:

Parameters Required Description
type Yes Must be "REQUEST".
action Yes The WebSocket action name. See the Action Mapping Table below.
requestId No, but recommended The request ID generated by the client. The server returns the same value in the correlationId field of the response frame.
agentId Depends on the action The agent ID. Required when the corresponding HTTP API contains {agentId} or the business logic requires an agent to be specified.
sessionId Depends on the action The session ID. Required when the corresponding HTTP API contains {sessionId} or the business logic requires a session to be specified.
payload Depends on the action JSON object that carries the HTTP request body, query string, and path parameters other than agentId / sessionId.
{
  "type": "REQUEST",
  "requestId": "req_001",
  "action": "chat.completions",
  "agentId": "agent_xxx",
  "sessionId": "sess_001",
  "payload": {
    "message": "hello",
    "stream": true,
    "contextEvent": true
  }
}

Response Frame

A standard non-streaming response returns a single RESPONSE frame. The client can use correlationId to match the requestId in the request.

{
  "type": "RESPONSE",
  "msgId": "...",
  "correlationId": "req_001",
  "action": "chat.completions",
  "agentId": "agent_xxx",
  "sessionId": "sess_001",
  "data": {}
}

Streaming Frame

A streaming request returns multiple messages. Each message is a standalone JSON object and is not wrapped in the HTTP SSE text format event: / data:. The client should continue reading until it receives STREAM_END or STREAM_ERROR.

{"type":"STREAM_START","correlationId":"req_001","action":"chat.completions","data":{"requestId":"req_001","model":"<resolved-model>"}}
{"type":"STREAM_REASONING","correlationId":"req_001","action":"chat.completions","data":{"reasoning":"..."}}
{"type":"STREAM_CHUNK","correlationId":"req_001","action":"chat.completions","data":{"content":"..."}}
{"type":"STREAM_END","correlationId":"req_001","action":"chat.completions","data":{"metadata":{}}}

WebSocket chat completion supports contextEvent=true. When enabled, processes such as context compression are returned in EVENT messages and use the same correlationId.

Error Frame

{
  "type": "ERROR",
  "msgId": "...",
  "correlationId": "req_001",
  "action": "chat.completions",
  "agentId": "agent_xxx",
  "sessionId": "sess_001",
  "error": {
    "code": "BUS_SESSION_NOT_FOUND",
    "message": "session not found"
  },
  "headers": {
    "busErrorCode": "BUS_SESSION_NOT_FOUND",
    "httpStatus": "404"
  }
}

4. Convert HTTP Requests to WebSocket Requests

To convert an HTTP request to a WebSocket request, follow these steps:

  1. Find the action that corresponds to the HTTP API in the Action Mapping Table.
  2. Put {agentId} / {sessionId} from the HTTP API into the top-level agentId / sessionId fields of the request frame.
  3. Put other path parameters, query parameters, and JSON request body fields into payload.
  4. Generate a requestId and send the complete JSON request frame.
  5. Read the messages returned by the server, and use correlationId in the response to match the request's requestId.

Do not include the same field at the top level and in payload with different values; otherwise, the server rejects the request. For example, if agentId is already provided at the top level, do not pass a different value in payload.agentId.

Example 1: GET Request with Path Parameters and Query Parameters

HTTP Request:

GET /agent-bus/v1/session/sess_001/messages?startSeq=1&endSeq=20
Authorization: Bearer <token>

Conversion Rules:

Location in HTTP HTTP Value Location in WebSocket
Path sess_001 Top-level sessionId
Query Parameters startSeq=1 payload.startSeq
Query Parameters endSeq=20 payload.endSeq
URL GET/session/{id}/messages action: "session.getMessages"

WebSocket request frame sent:

{
  "type": "REQUEST",
  "requestId": "req_42",
  "action": "session.getMessages",
  "sessionId": "sess_001",
  "payload": {
    "startSeq": 1,
    "endSeq": 20
  }
}}

Example 2: GET Request with agentId Moved from the Query Parameters to the Top Level

HTTP Request:

GET /agent-bus/v1/session/list?agentId=agent_xxx&limit=50&offset=0
Authorization: Bearer <token>

Conversion Rules:

Location in HTTP HTTP Value Location in WebSocket
Query Parameters agentId=agent_xxx Top-Level agentId
Query Parameters limit=50 payload.limit
Query Parameters offset=0 payload.offset
URL GET/session/list action: "session.list"

WebSocket request frame sent:

{
  "type": "REQUEST",
  "requestId": "req_43",
  "action": "session.list",
  "agentId": "agent_xxx",
  "payload": {
    "limit": 50,
    "offset": 0
  }
}

Example 3: POST Request with Path Parameters and JSON Request Body

HTTP Request:

POST /agent-bus/v1/workspace/sess_001/replace
Authorization: Bearer <token>
Content-Type: application/json

{
  "name": "main.dos",
  "hash": "xxh64:...",
  "replacements": [
    {
      "old": "return 1",
      "new": "return 2"
    }
  ]
}

Conversion Rules:

Location in HTTP HTTP Value Location in WebSocket
Path sess_001 Top-level sessionId
Request Body name payload.name
Request Body hash payload.hash
Request Body replacements payload.replacements
URL POST/workspace/{sessionId}/replace action: "workspace.replace"

WebSocket request frame sent:

{
  "type": "REQUEST",
  "requestId": "req_44",
  "action": "workspace.replace",
  "sessionId": "sess_001",
  "payload": {
    "name": "main.dos",
    "hash": "xxh64:...",
    "replacements": [
      {
        "old": "return 1",
        "new": "return 2"
      }
    ]
  }
}

5. Supported Actions

WebSocket supports the actions listed in the following table.

Action Corresponding HTTP API
agent.accessible GET/agent/accessible
agent.get GET/agent/{id}
agent.llm.selectable GET/agent/{id}/llm/selectable
session.create POST/session/create
session.get GET/session/{id}
session.update PUT/session/{id}
session.delete DELETE/session/{id}
session.list GET/session/list
session.getMessages GET/session/{id}/messages
session.generateSummary POST/session/{id}/summary
chat.completions POST/chat/completions
skill.catalog GET/skill/{agentId}/catalog
skill.getInstruction GET/skill/{id}/instruction
skill.files.list GET/skill/{id}/files/list
skill.files.get GET/skill/{id}/files/get
skill.files.search POST/skill/{id}/files/search
skill.files.batchGet POST/skill/{id}/files/batchGet
workspace.list GET/workspace/{sessionId}/files
workspace.read GET/workspace/{sessionId}/file
workspace.write POST/workspace/{sessionId}/file
workspace.delete DELETE/workspace/{sessionId}/file
workspace.search POST/workspace/{sessionId}/search
workspace.replace POST/workspace/{sessionId}/replace
network.fetch POST/network/fetch
mcp.tools.call POST/mcp/tools/call
context.preview POST/context/preview
llm.complete POST/llm/complete