Der JBot WebSocket-Server ist ein asynchroner Server (basierend auf der
Python-Bibliothek websockets), der sowohl TCP-basierte als
auch Unix-Domain-Socket-Verbindungen akzeptiert. Clients erhalten nach
erfolgreichem Handshake eine ready-Nachricht mit einer
zugewiesenen chat_id und client_id.
TCP (ws:// / wss://)
ws://<host>:<port><path>?client_id=<id>&token=<token>
wss://<host>:<port><path>?client_id=<id>&token=<token>
127.0.0.1 (konfigurierbar)8765/ (konfigurierbar)ssl_certfile und ssl_keyfile konfiguriert sindUnix Domain Socket
unix:<socket_path><path>?client_id=<id>&token=<token>
unix_socket_path gesetzt istchmod 0o600 gesichert| Parameter | Erforderlich | Beschreibung |
|---|---|---|
client_id | Nein | Eindeutige Client-Identifikation (max. 128 Zeichen). Wird für allow_from-Autorisierung und als sender_id verwendet. Falls nicht angegeben, wird eine zufällige ID wie anon-<12-hex-chars> generiert. |
token | Abhängig | Authentifizierungs-Token |
1. Statischer Token
Der Client sendet den Token als Query-Parameter: ?token=mein-geheimer-token. Der Vergleich erfolgt mittels hmac.compare_digest (timing-safe).
2. Ausgestellte Kurzzeit-Tokens
Wenn token_issue_path konfiguriert ist, kann der Client vor dem WebSocket-Handshake ein kurzlebiges Token per HTTP abrufen:
GET <path> (z. B. /issue-token)
Authorization: Bearer <token_issue_secret>
{
"token": "<short-lived-token>",
"expires_in": 300
}
token_ttl_s: Gültigkeitsdauer in Sekunden (30–86.400, Standard: 300)3. Kein Token
Wenn websocket_requires_token: false gesetzt ist, sind Verbindungen auch ohne Token erlaubt.
4. Wildcard-Host-Schutz
Wenn host auf 0.0.0.0 oder :: gesetzt ist, muss entweder token oder token_issue_secret gesetzt sein – andernfalls lehnt die Konfigurationsvalidierung ab.
{
"websocket": {
"allow_from": ["*"]
}
}
["*"]: Alle Clients erlaubt (Standard)["client-a", "client-b"]: Nur Clients mit diesen IDsNach erfolgreichem WebSocket-Upgrade sendet der Server sofort:
{
"event": "ready",
"chat_id": "550e8400-e29b-41d4-a716-446655440000",
"client_id": "mein-client-1"
}
| Feld | Typ | Beschreibung |
|---|---|---|
event | string | Immer "ready" |
chat_id | string | UUIDv4 – Default-Chat-Session dieser Verbindung |
client_id | string | Die effektive Client-ID (entweder der übergebene Wert oder generiert) |
| Feld | Typ | Standard | Beschreibung |
|---|---|---|---|
enabled | bool | false | WebSocket-Server aktivieren |
host | string | "127.0.0.1" | Bind-Adresse |
port | int | 8765 | TCP-Port |
unix_socket_path | string | "" | Alternativ: Unix-Socket-Pfad (absolut) |
path | string | "/" | WebSocket-Upgrade-Pfad (muss mit / beginnen) |
token | string | "" | Statischer Auth-Token |
token_issue_path | string | "" | HTTP-Pfad zum Ausstellen von Kurzzeit-Tokens |
token_issue_secret | string | "" | Secret für Token-Ausstellungs-Endpoint |
token_ttl_s | int | 300 | Gültigkeitsdauer ausgestellter Tokens (30–86.400 s) |
websocket_requires_token | bool | true | Token für Verbindungsaufbau erforderlich |
allow_from | list[string] | ["*"] | Erlaubte Client-IDs |
streaming | bool | true | Streaming aktiviert |
max_message_bytes | int | 37_748_736 | Maximale Frame-Größe (≈36 MB) |
ping_interval_s | float | 20.0 | Ping-Intervall (5–300 s) |
ping_timeout_s | float | 20.0 | Ping-Timeout (5–300 s) |
ssl_certfile | string | "" | TLS-Zertifikat (für WSS) |
ssl_keyfile | string | "" | TLS-Private-Key (für WSS) |
Der Server sendet automatisch WebSocket-Ping-Frames im konfigurierten Intervall. Clients müssen mit Pong antworten. Bei Timeout wird die Verbindung getrennt.
Bei Verbindungsabbau wird der Client aus allen Chat-Subscriptions entfernt. Eine neue Verbindung kann sich mit attach wieder in bestehende Sessions einklinken.
Der JBot WebSocket-Server akzeptiert zwei Nachrichtenformate:
type-Feld{"content": "..."}-ObjekteEinfache Textnachrichten ohne type-Feld werden als Chat-Nachricht an die Default-Session gesendet.
Hallo JBot, was kannst du?
Oder als JSON-Objekt (erkannt an content/text/message-Key ohne type):
{"content": "Hallo JBot"}
Alle modernen Nachrichten verwenden dieses Format:
{
"type": "<typ>",
"chat_id": "<uuid>",
"content": "<nachricht>",
...
}
{
"type": "message",
"chat_id": "550e8400-e29b-41d4-a716-446655440000",
"content": "Erkläre mir JBot",
"media": [
{ "data_url": "data:image/png;base64,...", "name": "screenshot.png" }
],
"webui": true,
"turn_id": "optional-turn-id",
"cli_apps": "optional-cli-apps",
"mcp_presets": "optional-mcp-presets",
"image_generation": { "enabled": true, "aspect_ratio": "16:9" }
}
| Feld | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
type | string | Ja | Immer "message" |
chat_id | string | Ja | UUIDv4 (Pattern: ^[A-Za-z0-9_:-]{1,64}$) |
content | string | Nein* | Nachrichtentext (*optional bei Medien-Anhang) |
media | array | Nein | Liste von Medien-Objekten |
webui | bool | Nein | true wenn vom JBot WebUI gesendet |
cli_apps | string | Nein | CLI-App-Mentions |
mcp_presets | string | Nein | MCP-Preset-Mentions |
image_generation | object | Nein | {"enabled": true, "aspect_ratio": "16:9"} |
Event-Ablauf: goal_status: running → delta / reasoning_delta → message → turn_end → goal_status: idle
{"type": "new_chat"}
{"type": "attach", "chat_id": "550e8400-e29b-41d4-a716-446655440000"}
{"type": "fork_chat", "chat_id": "<quell-uuid>"}
{
"type": "set_workspace_scope",
"chat_id": "550e8400-e29b-41d4-a716-446655440000",
"path": "/home/user/projects/meinprojekt"
}
{"type": "transcribe_audio", ...}
{"event": "error", "detail": "invalid chat_id", "reason": "malformed"}
| detail | Bedeutung |
|---|---|
"invalid chat_id" | Chat-ID ungültig |
"missing content" | Weder Text noch Medien |
"image_rejected" | Medien-Validierung fehlgeschlagen |
"unknown type: ..." | Unbekannter Envelope-Typ |
"workspace_scope_rejected" | Workspace-Berechtigung verweigert |
Alle vom Server gesendeten Nachrichten sind JSON-Objekte mit einem event-Feld.
| Event | Beschreibung |
|---|---|
ready | Verbindung etabliert |
attached | Chat / Attach bestätigt |
message | Agent-Antwort (final) |
delta | Streaming-Text-Delta |
stream_end | Streaming-Segment beendet |
reasoning_delta | Reasoning-Stream-Delta |
reasoning_end | Reasoning-Stream beendet |
tool_hint / progress | Tool-Ausführung |
file_edit | Dateiänderung |
turn_end | Turn abgeschlossen |
goal_state | Persistenter Goal-Status |
goal_status | running / idle |
session_updated | Session-Metadaten geändert |
runtime_model_updated | Modell gewechselt |
error | Fehler |
{"event": "ready", "chat_id": "...", "client_id": "..."}
{"event": "attached", "chat_id": "..."}
{"event": "delta", "chat_id": "...", "text": "JBot", "stream_id": "..."}
{"event": "stream_end", "chat_id": "...", "text": "JBot ist...", "stream_id": "..."}
{"event": "reasoning_delta", "chat_id": "...", "text": "Ich analysiere..."}
{"event": "reasoning_end", "chat_id": "..."}
{"event": "goal_status", "status": "running", "started_at": 1712000000.123}
{"event": "turn_end", "chat_id": "...", "latency_ms": 1234, "goal_state": {...}}
{"event": "goal_status", "status": "idle"}
chat_id nur an abonnierte Clientsruntime_model_updated und globale session_updated an alle verbundenen Clientsgoal_state / goal_status ohne Subscriber: nur Logreasoning_delta / reasoning_end erscheinen vor dem normalen delta-Stream. Clients sollten Reasoning-Text visuell abgrenzen (z. B. zusammenklappbarer "Thinking"-Block).
runtime_model_updated – bei Modell-Wechselsession_updated – bei globalen Session-ÄnderungenBild- und Video-Uploads direkt im WebSocket-Frame als Base64-kodierte Data-URLs im media-Array des message-Envelopes.
{
"type": "message",
"chat_id": "550e8400-e29b-41d4-a716-446655440000",
"content": "Analysiere dieses Bild",
"media": [
{ "data_url": "data:image/png;base64,...", "name": "screenshot.png" }
]
}
| Feld | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
data_url | string | Ja | Base64-Data-URL mit MIME-Type-Präfix |
name | string | Nein | Optionaler Dateiname |
Bilder
| MIME-Type | Erweiterung | Max. Größe | Max. Anzahl |
|---|---|---|---|
image/png | .png | 8 MB | 4 |
image/jpeg | .jpg/.jpeg | 8 MB | 4 |
image/webp | .webp | 8 MB | 4 |
image/gif | .gif | 8 MB | 4 |
Ausgeschlossen: image/svg+xml (Sicherheitsgründe: XSS-Risiko).
Videos
| MIME-Type | Erweiterung | Max. Größe | Max. Anzahl |
|---|---|---|---|
video/mp4 | .mp4 | 20 MB | 1 |
video/webm | .webm | 20 MB | 1 |
video/quicktime | .mov | 20 MB | 1 |
| Limit | Wert |
|---|---|
| Max. WebSocket-Frame | 37.748.736 Bytes (≈36 MB) |
| Max. Bilder pro Nachricht | 4 |
| Max. Video pro Nachricht | 1 |
| Pro Bild | 8.388.608 Bytes (8 MB) |
| Pro Video | 20.971.520 Bytes (20 MB) |
reason | Bedeutung |
|---|---|
malformed | Ungültiges Medien-Objekt |
decode | Base64-Dekodierung fehlgeschlagen |
mime | MIME-Type nicht erlaubt |
size | Dateigröße überschritten |
too_many_images | Mehr als 4 Bilder |
too_many_videos | Mehr als 1 Video |
<media_dir>/websocket/<uuid>_<name>media an Agenten weitergebenmedia und/oder media_urls mit signierten HTTP-URLsoriginal_bytes ≈ base64_chars × 3 / 4
Eine 8 MB große Datei erzeugt ≈10.9 MB Base64 (×1.37 Overhead).
Vollständiger JBot WebSocket-Client in Node.js unter Verwendung der ws-Bibliothek. Abhängigkeit:
npm install ws
Alternativ kann die in Node.js 22+ eingebaute WebSocket-API verwendet werden.
// client-basic.js
import WebSocket from 'ws';
const HOST = '127.0.0.1', PORT = 8765, TOKEN = 'mein-token';
const url = `ws://${HOST}:${PORT}/?client_id=nodejs-app&token=${TOKEN}`;
const ws = new WebSocket(url);
ws.on('open', () => console.log('✅ Verbunden mit JBot'));
ws.on('message', (data) => {
console.log(`📩 [${JSON.parse(data).event}]`, data.toString());
});
ws.on('close', (c, r) => console.log(`🔌 Geschlossen: ${c} ${r}`));
ws.on('error', (e) => console.error('❌', e.message));
setTimeout(() => ws.send('Hallo JBot, was kannst du?'), 5000);
Der vollständige Source-Code inklusive TypeScript-Typdefinitionen ist als Markdown verfügbar: websocket/06-nodejs-client.md
| Methode | Beschreibung |
|---|---|
connect() | Verbindet, resolved mit {chatId, clientId} |
sendMessage(text, chatId?) | JSON-Envelope senden |
sendMessageWithMedia(text, media, chatId?) | Mit Medien-Anhang |
sendText(text) | Legacy Plain-Text |
createChat() | Neue Session |
attachChat(chatId) | Session beitreten |
setWorkspace(chatId, path) | Workspace ändern |
close() | Trennen |
onReady(chatId, clientId)
onMessage(msg)
onDelta(msg)
onStreamEnd(msg)
onReasoning(type, msg) // type: "delta" | "end"
onToolHint(msg)
onProgress(msg)
onFileEdit(msg)
onTurnEnd(msg)
onGoalState(msg)
onGoalStatus(msg)
onSessionUpdated(msg)
onRuntimeModelUpdated(msg)
onError(msg)
onClose(code, reason)
ready warten – Keine Nachrichten vor ready sendenstream_end enthält finalen Textgoal_status: running, endet mit idleerror-Events immer behandelnchat_id speichern, nach Reconnect mit attach subscriben