WebSocket
WebSocket
Một socket, mọi lệnh REST dưới dạng frame, kể cả sự kiện stream. Hợp với trình duyệt và client kết nối lâu dài.
Kết nối và xác thực
Endpoint: ws://127.0.0.1:8765/ws. Trình duyệt không đặt được header cho WebSocket, nên frame đầu tiên dùng để xác thực:
- Gửi
{ "type": "auth", "token": "<token>" }; node trả lời{ "type": "ready", "protocol": "clarkcant.ws.v1" }. - Không xác thực trong 10 giây, hoặc sai token: một frame
error, rồi đóng với mã4401.
Các frame
→ { "type": "auth", "token": "<token>" }
← { "type": "ready", "protocol": "clarkcant.ws.v1" }
→ { "type": "request", "id": "1", "method": "POST", "path": "/conversations/<conversationId>/messages/stream", "body": { "text": "hi" } }
← { "type": "event", "id": "1", "event": "delta", "data": { "text": "…" } }
← …
← { "type": "response", "id": "1", "status": 200, "body": null }
→ { "type": "ping" }
← { "type": "pong" }
| Frame | Chiều | Cấu trúc |
|---|---|---|
auth | client → node | { type, token }, chỉ ở frame đầu |
ready | node → client | { type, protocol: "clarkcant.ws.v1" } |
request | client → node | { type, id, method, path, query?, body? }: bất kỳ route REST nào, trừ các quyết định của con người (403 PERSON_ONLY); id được trả lại đúng như gửi, chuỗi hoặc số; tham số query đặt trong query, không đặt trong path |
event | node → client | { type, id, event, data }: mỗi sự kiện SSE một frame, trên các route stream |
response | node → client | { type, id, status, body }: luôn là frame cuối của id đó; body là null sau một stream |
ping / pong | cả hai | { "type": "ping" } → { "type": "pong" } |
- Mỗi socket chạy song song tối đa 16 request; mỗi request một
idriêng. - Request bị từ chối (
INVALID_FRAME,TOO_MANY_REQUESTS) nhận frame{ type: "error", id, code, message }thay choresponse; đó là frame cuối củaidđó. - Tên sự kiện giống hệt luồng SSE:
delta,reasoning,tool-start,tool-end,host-control,error,done. - Route nhị phân (file đính kèm, ảnh) trả
415 USE_HTTP: dùng HTTP cho dữ liệu nhị phân.
Ví dụ
const ws = new WebSocket("ws://127.0.0.1:8765/ws");
// Browsers cannot set headers on a WebSocket, so the first frame authenticates.
ws.onopen = () => ws.send(JSON.stringify({ type: "auth", token: "<token>" }));
ws.onmessage = (message) => {
const frame = JSON.parse(message.data);
if (frame.type === "ready") {
ws.send(JSON.stringify({
type: "request",
id: "1",
method: "POST",
path: "/conversations/<conversationId>/messages/stream",
body: { text: "hi" },
}));
}
if (frame.type === "event" && frame.event === "delta") console.log(frame.data.text);
if (frame.type === "response") console.log("finished", frame.id, frame.status);
};
ws.onclose = (event) => { if (event.code === 4401) console.log("token missing, wrong or too late"); };
Đừng nhúng token vào trang công khai. Ai giữ token là điều khiển được Clark của bạn. Chỉ dùng từ máy của bạn hoặc sau lớp đăng nhập của riêng bạn.
Các socket khác
Node còn phục vụ /voice (phiên giọng nói) và /terminal (thẻ Terminal). Đây là socket của ứng dụng, không phải giao diện tích hợp công khai hay một phần của giao thức frame này.