33 KiB
Gateway API Protocol
Flynn's gateway exposes a WebSocket-based JSON-RPC protocol for real-time communication with the AI agent. This document describes the protocol in detail.
Table of Contents
Overview
The gateway provides:
- WebSocket Server: Real-time bidirectional communication
- JSON-RPC 2.0: Structured request/response protocol
- Streaming Events: Real-time updates during agent processing
- HTTP Server: Serves static dashboard and handles webhook endpoints
- Node Capability Negotiation: Optional companion-node role/capability registration
Execution Model (Sessions + Per-Session Queue)
Two concepts matter for correct clients:
- connectionId: a single WebSocket connection identity (assigned on connect)
- sessionId: the conversation/session the connection is attached to (defaults to a per-connection session, but can be switched to resume an old session)
The gateway serialises agent work per session, not per WebSocket connection:
- Requests that target the same
sessionIdrun one-at-a-time (FIFO) in a per-session lane. - Requests for different sessions can run in parallel.
- Lane policy is configurable (
collect,followup,steer,steer_backlog,interrupt) with per-channel and per-session overrides. - Session-local overrides can be managed at runtime via
agent.sendcommands:/queue,/queue set ...,/queue reset.
This is implemented via a per-lane queue (LaneQueue) in the gateway server, and used by agent.send and agent.cancel.
sequenceDiagram
autonumber
participant C as Client
participant G as Gateway (WS JSON-RPC)
participant LQ as LaneQueue (per-session)
participant SB as SessionBridge
participant A as AgentOrchestrator
C->>G: agent.send {connectionId, message}
G->>SB: resolve sessionId for connectionId
SB-->>G: sessionId (laneId)
G->>LQ: enqueue(laneId, work)
alt lane idle
LQ-->>G: starts work immediately
else lane busy
Note over LQ: work queued (FIFO) for this lane
end
G->>A: process(message) in that session
A-->>G: streaming events (content/tool_start/tool_end/context_warning)
G-->>C: events + final done
C->>G: agent.cancel {connectionId}
G->>LQ: cancel(laneId) (queued items rejected)
G->>SB: cancel active op (best-effort)
G-->>C: result.cancelled=true/false
interrupt queue mode also requests active-run cancellation when a newer request is enqueued for the same session lane. Cancellation still completes at agent/tool-loop safe points. When this preemption happens, the requester receives a transient content notice and the audit log records queue.preempt.
Base URL
- WebSocket:
ws://localhost:18800(orwss://if using TLS) - HTTP:
http://localhost:18800(orhttps://if using TLS) - Health check:
GET /health
Default Ports
- Gateway:
18800 - Tailscale Serve (if enabled):
443(HTTPS)
Connection
WebSocket Handshake
// Browser
const ws = new WebSocket('ws://localhost:18800');
// Node.js (ws library)
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:18800');
Connection Lifecycle
- Client connects to WebSocket endpoint
- Server validates authentication (if configured)
- Connection assigned unique ID
- Client can start sending requests
- Server sends responses and events asynchronously
Disconnection
ws.onclose = (event) => {
console.log('Disconnected:', event.code, event.reason);
};
Common close codes:
1000: Normal closure1001: Endpoint going away1006: Abnormal closure (network issue)
Authentication
Bearer Token Auth
If server.token is configured, all WebSocket connections must provide authentication:
const ws = new WebSocket('ws://localhost:18800', {
headers: {
'Authorization': 'Bearer your-secret-token'
}
});
Tailscale Identity
If server.tailscale_identity is enabled, connections from Tailscale are trusted based on the Tailscale-User-Login header.
// Automatic when connecting via Tailscale
// No additional auth required if server.tailscale_identity is enabled
HTTP Auth
If server.auth_http is true (default: true), HTTP requests also require bearer token when server.token is set:
fetch('http://localhost:18800/api/health', {
headers: {
'Authorization': 'Bearer your-secret-token'
}
});
Exceptions (handled by their own trust/auth model and therefore bypass gateway token auth):
POST /webhooks/:name(HMAC-validated when webhook secret is configured)POST /gmail/push(Google Pub/Sub push)POST /teams/events(Microsoft Bot Framework activity callback)POST /google-chat/events(Google Chat event callback, optional webhook token check)POST /bluebubbles/events(BlueBubbles iMessage webhook callback, optional webhook token check)
WebChat PWA push-subscription endpoints (auth-protected):
GET /webchat/push/public-key(returns enabled/configured push metadata)GET /webchat/push/subscriptions(returns current subscription count/cap)POST /webchat/push/subscriptions(registers/updates one browser subscription)DELETE /webchat/push/subscriptions(removes one browser subscription by endpoint)
Message Format
Request (Client → Server)
interface GatewayRequest {
id: number; // Unique request ID (integer)
method: string; // Method name (e.g., 'agent.send', 'system.info')
params?: Record<string, unknown>; // Method parameters (optional)
}
Example:
{
"id": 1,
"method": "agent.send",
"params": {
"message": "Hello, Flynn!",
"session": "telegram:123456"
}
}
Response (Server → Client)
interface GatewayResponse {
id: number; // Request ID this responds to
result: unknown; // Method result
}
Example:
{
"id": 1,
"result": {
"sessionId": "telegram:123456",
"response": "Hello! How can I help you today?"
}
}
Error (Server → Client)
interface GatewayError {
id: number;
error: {
code: ErrorCode; // Error code (integer)
message: string; // Human-readable error message
};
}
Example:
{
"id": 1,
"error": {
"code": 4,
"message": "Request cancelled by client"
}
}
Event (Server → Client)
interface GatewayEvent {
id: number; // Request ID this relates to
event: EventType; // Event type string
data: unknown; // Event-specific data
}
Example:
{
"id": 1,
"event": "content",
"data": {
"text": "Hello! How can I help you today?"
}
}
Methods
System Methods
system.info
Get gateway information.
Request:
{
"id": 1,
"method": "system.info"
}
Response:
{
"id": 1,
"result": {
"version": "0.1.0",
"uptime": 12345,
"connections": 2
}
}
system.disconnect
Close the connection gracefully.
Request:
{
"id": 2,
"method": "system.disconnect"
}
system.presence
Return tracked sender presence snapshots (most recent first).
Online/offline is inferred from inactivity threshold in the daemon.
Request:
{
"id": 3,
"method": "system.presence",
"params": {
"channel": "telegram",
"status": "online",
"limit": 50
}
}
system.sessionAnalytics
Return aggregate session analytics from the SQLite message history.
Useful for operator dashboards and trend checks (sessions/day, message volume, top active sessions).
Request:
{
"id": 10,
"method": "system.sessionAnalytics",
"params": {
"days": 30,
"topLimit": 10
}
}
system.contextUsage
Return per-session estimated context-window budget snapshots.
Useful for proactive compaction monitoring and operator dashboards.
Request:
{
"id": 11,
"method": "system.contextUsage"
}
system.localBackends
Return status for user-level local LLM backend daemons (for example ollama.service and llama-server.service).
Request:
{
"id": 12,
"method": "system.localBackends"
}
Response:
{
"id": 12,
"result": {
"backends": [
{
"id": "ollama",
"provider": "ollama",
"name": "Ollama",
"unit": "ollama.service",
"configured": true,
"loadState": "loaded",
"activeState": "active",
"subState": "running",
"unitFileState": "enabled",
"description": "Ollama Service",
"pid": 12345,
"result": "success",
"statusText": "active (running)",
"availableActions": ["restart", "stop"]
}
]
}
}
system.dockerDependencies
Return status for docker-compose managed dependencies discovered from docker-compose.yml (excluding Flynn's own flynn service). Includes profile-scoped services (for example whisper-server, brave-search, searxng) when profiles are defined.
Request:
{
"id": 12,
"method": "system.dockerDependencies"
}
Response:
{
"id": 12,
"result": {
"dependencies": [
{
"id": "whisper",
"name": "Whisper (whisper.cpp)",
"service": "whisper-server",
"configured": true,
"state": "running",
"health": "healthy",
"statusText": "Up 4 minutes (healthy)",
"containerName": "flynn-whisper-server-1",
"availableActions": ["restart", "stop", "update"]
},
{
"id": "brave-search",
"name": "Brave Search",
"service": "brave-search",
"configured": true,
"state": "running",
"health": "healthy",
"statusText": "Up 2 minutes",
"containerName": "brave-search",
"availableActions": ["restart", "stop", "update"]
},
{
"id": "searxng",
"name": "SearXNG",
"service": "searxng",
"configured": true,
"state": "running",
"health": "none",
"statusText": "Up 2 minutes",
"containerName": "searxng",
"availableActions": ["restart", "stop", "update"]
}
]
}
}
system.dockerDependencyControl
Control a docker-compose dependency (start, restart, stop, update).
dependencymust match an ID returned bysystem.dockerDependencies.updatepulls the latest image for that compose service and runsdocker compose up -dto reconcile.
Request:
{
"id": 14,
"method": "system.dockerDependencyControl",
"params": {
"dependency": "whisper",
"action": "restart"
}
}
Response:
{
"id": 14,
"result": {
"dependency": "whisper",
"action": "restart",
"status": {
"id": "whisper",
"state": "running",
"health": "healthy",
"statusText": "running (healthy)"
},
"message": "Restarted whisper-server container."
}
}
system.localBackendControl
Control a local backend daemon (start, restart, stop, update).
updatesemantics:ollama: pulls configured Ollama models (tiers/local providers + embedding/audio models) viaollama pull.llamacpp: performs a safe service restart (model file refresh remains external to Flynn).
Request:
{
"id": 13,
"method": "system.localBackendControl",
"params": {
"backend": "ollama",
"action": "restart"
}
}
system.observabilitySources
Return graph/log-capable observability sources for the dashboard.
Request:
{
"id": 15,
"method": "system.observabilitySources"
}
Response:
{
"id": 15,
"result": {
"sources": [
{
"id": "systemd:flynn",
"name": "Flynn daemon",
"kind": "systemd_system",
"runtime": "systemd_system",
"status": "running",
"graphCapable": true,
"logCapable": true
},
{
"id": "docker:whisper",
"name": "Whisper (whisper.cpp)",
"kind": "docker_dependency",
"runtime": "docker_compose",
"status": "running",
"graphCapable": true,
"logCapable": true
}
]
}
}
system.observabilitySeries
Return sampled service trend points (bounded, in-memory) for dashboard charts.
Request:
{
"id": 16,
"method": "system.observabilitySeries",
"params": {
"windowMinutes": 60,
"bucketSeconds": 30,
"sourceIds": ["systemd:flynn", "docker:whisper"]
}
}
Response:
{
"id": 16,
"result": {
"generatedAt": 1739999999000,
"windowMinutes": 60,
"bucketSeconds": 30,
"series": [
{
"sourceId": "systemd:flynn",
"points": [
{ "ts": 1739999970000, "stateCode": 3, "healthCode": 2, "errorCount": 0, "restartCount": 0 },
{ "ts": 1739999985000, "stateCode": 3, "healthCode": 2, "errorCount": 0, "restartCount": 1 }
]
}
]
}
}
system.serviceLogs
Return recent logs for a discovered observability source. Flynn applies secret masking heuristics to returned lines.
Request:
{
"id": 17,
"method": "system.serviceLogs",
"params": {
"sourceId": "docker:whisper",
"lines": 200,
"sinceSeconds": 900
}
}
Response:
{
"id": 17,
"result": {
"sourceId": "docker:whisper",
"fetchedAt": 1739999999000,
"redacted": false,
"truncated": false,
"lines": [
{
"ts": 1739999990000,
"level": "warn",
"text": "queue depth rising"
}
]
}
}
Response:
{
"id": 13,
"result": {
"backend": "ollama",
"action": "restart",
"status": {
"id": "ollama",
"activeState": "active",
"subState": "running",
"statusText": "active (running)"
}
}
}
Response:
{
"id": 11,
"result": {
"sessions": [
{
"sessionId": "ws:abc-123",
"budget": {
"estimatedTokens": 172000,
"contextWindow": 200000,
"remainingTokens": 28000,
"usagePct": 86,
"thresholdPct": 80,
"thresholdTokens": 160000,
"shouldCompact": true
}
}
]
}
}
Response:
{
"id": 10,
"result": {
"daily": [
{ "day": "2026-02-16", "sessions": 14, "messages": 228 }
],
"topSessions": [
{ "sessionId": "telegram:123456", "messages": 42, "lastActivity": 1739700300 }
],
"topTools": [
{ "toolName": "web.search", "executions": 37 }
],
"topTopics": [
{ "topic": "kubernetes", "occurrences": 22 }
],
"averageMessagesPerSession": 16.29,
"totalSessions": 14,
"totalMessages": 228
}
}
Response:
{
"id": 3,
"result": {
"presence": [
{
"channel": "telegram",
"senderId": "123456",
"senderName": "alice",
"firstSeenAt": 1739700000000,
"lastSeenAt": 1739700300000,
"messageCount": 12,
"status": "online"
}
],
"summary": { "total": 1, "online": 1, "offline": 0 }
}
}
Response:
{
"id": 2,
"result": {
"success": true
}
}
Session Methods
sessions.list
List sessions with optional persisted inclusion, frontend filtering, and paging.
Request:
{
"id": 3,
"method": "sessions.list",
"params": {
"includePersisted": true,
"frontend": "ws",
"limit": 50,
"offset": 0
}
}
Response:
{
"id": 3,
"result": {
"sessions": [
{
"id": "telegram:123456",
"frontend": "telegram",
"userId": "123456",
"messageCount": 42,
"lastMessageAt": 1739448000000,
"config": {
"modelTier": "fast",
"queue": {
"mode": "followup",
"overflow": "drop_old",
"cap": 8,
"debounceMs": 250,
"summarizeOverflow": true
},
"elevation": {
"active": false,
"untilMs": 1739451600000
}
}
}
],
"total": 1
}
}
sessions.get
Get session details.
Request:
{
"id": 4,
"method": "sessions.get",
"params": {
"sessionId": "telegram:123456"
}
}
Response:
{
"id": 4,
"result": {
"id": "telegram:123456",
"createdAt": "2025-02-13T10:00:00Z",
"lastActiveAt": "2025-02-13T12:00:00Z",
"history": [
{
"role": "user",
"content": "Hello"
},
{
"role": "assistant",
"content": "Hi there!"
}
]
}
}
sessions.create
Create or resume a session.
Request:
{
"id": 5,
"method": "sessions.create",
"params": {
"sessionId": "telegram:123456"
}
}
Response:
{
"id": 5,
"result": {
"sessionId": "telegram:123456",
"created": true
}
}
sessions.delete
Delete a session.
Request:
{
"id": 6,
"method": "sessions.delete",
"params": {
"sessionId": "telegram:123456"
}
}
Response:
{
"id": 6,
"result": {
"success": true
}
}
Agent Methods
agent.send
Send a message to the agent and stream response.
Request:
{
"id": 7,
"method": "agent.send",
"params": {
"message": "What's the weather?",
"sessionId": "telegram:123456",
"attachments": [
{
"mimeType": "image/jpeg",
"data": "base64encodedimage..."
}
]
}
}
Response (final):
{
"id": 7,
"result": {
"content": "I can't check the weather without access to weather APIs. Would you like me to help you with something else?",
"usage": {
"inputTokens": 25,
"outputTokens": 30
}
}
}
Events (streamed during processing):
content event:
{
"id": 7,
"event": "content",
"data": {
"text": "I can't check the weather"
}
}
tool_start event:
{
"id": 7,
"event": "tool_start",
"data": {
"tool": "shell.exec",
"args": {
"command": "echo 'Hello'"
}
}
}
tool_end event:
{
"id": 7,
"event": "tool_end",
"data": {
"tool": "shell.exec",
"result": {
"success": true,
"output": "Hello\n",
"error": null
}
}
}
attachment event:
{
"id": 7,
"event": "attachment",
"data": {
"mimeType": "image/png",
"data": "base64encoded..."
}
}
context_warning event:
{
"id": 7,
"event": "context_warning",
"data": {
"level": "checkpoint",
"message": "Context usage is 86.0% (172,000/200,000 estimated tokens). Checkpoint saved to memory namespace `session/checkpoints/ws/abc-123`.",
"budget": {
"estimatedTokens": 172000,
"contextWindow": 200000,
"remainingTokens": 28000,
"usagePct": 86,
"thresholdPct": 80,
"thresholdTokens": 160000,
"shouldCompact": true
},
"actions": {
"checkpointSaved": true,
"autoCompacted": false,
"checkpointNamespace": "session/checkpoints/ws/abc-123"
}
}
}
done event:
{
"id": 7,
"event": "done",
"data": {
"content": "Complete response here..."
}
}
When queue policy rejects/supersedes a request before execution, the server emits an error event with code: 3 (AgentBusy) and includes data.queue metadata (code, laneId, mode, overflow, droppedCount).
agent.cancel
Cancel the current agent operation.
Used by the web dashboard/chat stop button and channel-level /stop / /cancel command fast-paths.
Cancellation is best-effort and stops at the next agent/tool-loop safe point.
Flynn now propagates a run-level abort signal into model/tool execution, so providers/tools that honor AbortSignal typically stop promptly instead of waiting for request/tool timeouts.
Node Methods
node.register
Register node role/capabilities for the current WebSocket connection.
Request:
{
"id": 9,
"method": "node.register",
"params": {
"nodeId": "companion-desktop",
"role": "companion",
"protocolVersion": 1,
"capabilities": ["ui.canvas", "notifications"]
}
}
Response:
{
"id": 9,
"result": {
"registered": true,
"node": { "id": "companion-desktop", "role": "companion" },
"protocol": { "serverVersion": 1, "clientVersion": 1, "negotiatedVersion": 1 },
"capabilities": {
"declared": ["ui.canvas", "notifications"],
"enabled": ["ui.canvas", "notifications"]
}
}
}
node.capabilities.get
Return negotiated capabilities for the currently registered node connection.
node.location.set
Update the last-known location for the currently registered node connection.
Requires server.nodes.enabled: true and server.nodes.location.enabled: true.
Request:
{
"id": 10,
"method": "node.location.set",
"params": {
"latitude": 37.7749,
"longitude": -122.4194,
"accuracyMeters": 12.4,
"source": "gps",
"capturedAt": 1763241200000
}
}
Response:
{
"id": 10,
"result": {
"updated": true,
"node": { "id": "companion-desktop", "role": "companion" },
"location": {
"latitude": 37.7749,
"longitude": -122.4194,
"accuracyMeters": 12.4,
"source": "gps",
"capturedAt": 1763241200000,
"receivedAt": 1763241200451
}
}
}
node.location.get
Return the stored last-known location for the currently registered node connection.
node.status.set
Publish companion/node runtime status metadata (for example macOS menu-bar heartbeat state).
Request:
{
"id": 12,
"method": "node.status.set",
"params": {
"platform": "macos",
"appVersion": "0.3.1",
"deviceName": "MacBook Pro",
"statusText": "Idle",
"batteryPct": 64,
"powerSource": "battery"
}
}
node.push_token.set
Register a node push token (APNs or FCM) for companion delivery routing.
Requires server.nodes.push.enabled: true.
Request:
{
"id": 13,
"method": "node.push_token.set",
"params": {
"provider": "fcm",
"token": "fcm_abcdefghijklmnopqrstuvwxyz123456"
}
}
system.capabilities
Return gateway protocol version, node policy status, and feature-gate snapshot.
Request:
{
"id": 11,
"method": "system.capabilities"
}
Response:
{
"id": 11,
"result": {
"protocol": { "version": 1 },
"nodes": {
"enabled": true,
"locationEnabled": true,
"pushEnabled": true,
"allowedRoles": ["companion"],
"registered": true,
"role": "companion",
"nodeId": "companion-desktop"
},
"featureGates": {
"ui.canvas": true
}
}
}
system.location
Return the operator-facing snapshot of registered node locations.
system.nodes
Return the operator-facing snapshot of registered node connections (identity, role, capabilities, location/status).
Push tokens are returned as masked previews (tokenPreview) and never exposed in full.
Canvas Methods
canvas.put
Upsert a session-scoped canvas artifact.
Request:
{
"id": 12,
"method": "canvas.put",
"params": {
"sessionId": "ws:abc123",
"artifactId": "summary-card",
"type": "note",
"title": "Draft Summary",
"content": { "markdown": "## Notes" },
"metadata": { "lane": "analysis" }
}
}
canvas.get
Fetch a single artifact by id.
canvas.list
List artifacts for a session (newest first).
canvas.delete
Delete a single artifact.
canvas.clear
Delete all artifacts for a session.
agent.setToolUseCallback
Set callback for tool use events (for confirmation UI).
Request:
{
"id": 9,
"method": "agent.setToolUseCallback",
"params": {
"sessionId": "telegram:123456",
"enabled": true
}
}
Response:
{
"id": 9,
"result": {
"success": true
}
}
Tool Methods
tools.list
List available tools.
Request:
{
"id": 10,
"method": "tools.list",
"params": {
"sessionId": "telegram:123456"
}
}
Response:
{
"id": 10,
"result": {
"tools": [
{
"name": "shell.exec",
"description": "Execute a shell command...",
"inputSchema": {
"type": "object",
"properties": {
"command": { "type": "string" }
}
}
}
]
}
}
tools.execute
Execute a tool directly (bypass agent).
Request:
{
"id": 11,
"method": "tools.execute",
"params": {
"sessionId": "telegram:123456",
"tool": "shell.exec",
"args": {
"command": "echo 'Hello'"
}
}
}
Response:
{
"id": 11,
"result": {
"success": true,
"output": "Hello\n"
}
}
Config Methods
config.get
Get current configuration (secrets redacted).
Request:
{
"id": 12,
"method": "config.get"
}
Response:
{
"id": 12,
"result": {
"models": {
"default": {
"anthropic": {
"apiKey": "***"
}
}
}
}
}
config.reload
Reload configuration from file.
Request:
{
"id": 13,
"method": "config.reload"
}
Response:
{
"id": 13,
"result": {
"success": true
}
}
Pairing Methods
pairing.generate
Generate a pairing code for unknown senders.
Request:
{
"id": 14,
"method": "pairing.generate",
"params": {
"channel": "telegram"
}
}
Response:
{
"id": 14,
"result": {
"code": "ABC123",
"expiresAt": "2025-02-13T12:05:00Z"
}
}
pairing.list
List active pairing codes.
Request:
{
"id": 15,
"method": "pairing.list"
}
Response:
{
"id": 15,
"result": {
"codes": [
{
"code": "ABC123",
"channel": "telegram",
"createdAt": "2025-02-13T12:00:00Z",
"expiresAt": "2025-02-13T12:05:00Z"
}
]
}
}
Events
Event Types
content
Streamed text content from the agent.
{
"id": 1,
"event": "content",
"data": {
"text": "Partial response..."
}
}
tool_start
Tool execution started.
{
"id": 1,
"event": "tool_start",
"data": {
"tool": "shell.exec",
"args": {
"command": "echo 'test'"
}
}
}
tool_end
Tool execution completed.
{
"id": 1,
"event": "tool_end",
"data": {
"tool": "shell.exec",
"result": {
"success": true,
"output": "test\n"
}
}
}
attachment
Outbound attachment (image, audio, file).
{
"id": 1,
"event": "attachment",
"data": {
"mimeType": "image/jpeg",
"data": "base64encoded...",
"filename": "output.jpg"
}
}
context_warning
Proactive context pressure signal emitted by agent.send before done.
{
"id": 1,
"event": "context_warning",
"data": {
"level": "warning",
"message": "Context usage is 76.0% (152000/200000 estimated tokens).",
"budget": {
"estimatedTokens": 152000,
"contextWindow": 200000,
"remainingTokens": 48000,
"usagePct": 76,
"thresholdPct": 80,
"thresholdTokens": 160000,
"shouldCompact": false
},
"actions": {
"checkpointSaved": false,
"autoCompacted": false
}
}
}
done
Agent processing complete (final response).
{
"id": 1,
"event": "done",
"data": {
"content": "Complete final response..."
}
}
error
Error occurred during processing.
{
"id": 1,
"event": "error",
"data": {
"code": 5,
"message": "Internal error: ...",
"queue": {
"code": "overflow",
"laneId": "ws:abc123",
"mode": "followup",
"overflow": "drop_new",
"droppedCount": 1
}
}
}
data.queue is optional and only present for queue policy rejections/superseded requests.
Error Codes
| Code | Name | Description |
|---|---|---|
| -1 | ParseError |
Invalid JSON in request |
| -2 | InvalidRequest |
Missing required fields |
| -3 | MethodNotFound |
Unknown method name |
| -4 | AuthRequired |
Authentication required but not provided |
| -5 | AuthFailed |
Authentication failed |
| 1 | SessionNotFound |
Session ID doesn't exist |
| 2 | ToolNotFound |
Tool name doesn't exist |
| 3 | AgentBusy |
Agent is processing another request |
| 4 | RequestCancelled |
Request was cancelled by client |
| 5 | InternalError |
Unexpected server error |
Example Client
Browser Client
class FlynnClient {
constructor(url, token) {
this.url = url;
this.token = token;
this.requestId = 0;
this.pending = new Map();
}
connect() {
const headers = {};
if (this.token) {
headers['Authorization'] = `Bearer ${this.token}`;
}
this.ws = new WebSocket(this.url, { headers });
this.ws.onopen = () => {
console.log('Connected to Flynn gateway');
};
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
this.handleMessage(message);
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
this.ws.onclose = () => {
console.log('Disconnected from Flynn gateway');
};
}
sendRequest(method, params = {}) {
return new Promise((resolve, reject) => {
const id = ++this.requestId;
const request = {
id,
method,
params
};
this.ws.send(JSON.stringify(request));
// Store promise for response
this.pending.set(id, { resolve, reject });
// Set timeout
setTimeout(() => {
if (this.pending.has(id)) {
this.pending.delete(id);
reject(new Error('Request timeout'));
}
}, 30000);
});
}
handleMessage(message) {
// Response
if ('result' in message) {
const { id, result } = message;
const pending = this.pending.get(id);
if (pending) {
this.pending.delete(id);
pending.resolve(result);
}
}
// Error
else if ('error' in message) {
const { id, error } = message;
const pending = this.pending.get(id);
if (pending) {
this.pending.delete(id);
const err = new Error(error.message);
err.code = error.code;
pending.reject(err);
}
}
// Event
else if ('event' in message) {
this.handleEvent(message);
}
}
handleEvent(event) {
const { id, event: eventType, data } = event;
switch (eventType) {
case 'content':
console.log('Content:', data.text);
break;
case 'tool_start':
console.log('Tool started:', data.tool, data.args);
break;
case 'tool_end':
console.log('Tool completed:', data.tool, data.result);
break;
case 'attachment':
console.log('Attachment received:', data.mimeType);
break;
case 'context_warning':
console.warn('Context warning:', data.level, data.message);
break;
case 'done':
console.log('Done:', data.content);
break;
case 'error':
console.error('Error:', data.code, data.message);
break;
}
}
// Convenience methods
async systemInfo() {
return this.sendRequest('system.info');
}
async listSessions() {
return this.sendRequest('sessions.list');
}
async contextUsage() {
return this.sendRequest('system.contextUsage');
}
async sendMessage(message, sessionId, attachments = []) {
return this.sendRequest('agent.send', {
message,
sessionId,
attachments
});
}
async listTools(sessionId) {
return this.sendRequest('tools.list', { sessionId });
}
}
// Usage
const client = new FlynnClient('ws://localhost:18800', 'your-token');
client.connect();
client.onopen = async () => {
const info = await client.systemInfo();
console.log('Gateway info:', info);
const response = await client.sendMessage('Hello, Flynn!', 'telegram:123456');
console.log('Response:', response);
};
Node.js Client
const WebSocket = require('ws');
class FlynnNodeClient extends FlynnClient {
connect() {
const options = {};
if (this.token) {
options.headers = { 'Authorization': `Bearer ${this.token}` };
}
this.ws = new WebSocket(this.url, options);
// ... same as browser client
}
}
// Usage
const client = new FlynnNodeClient('ws://localhost:18800', 'your-token');
client.connect();
HTTP Fetch Example
// Health check
async function checkHealth() {
const response = await fetch('http://localhost:18800/health', {
headers: {
'Authorization': 'Bearer your-token'
}
});
const status = await response.json();
console.log('Health:', status);
}
checkHealth();
For more implementation details, see:
- Protocol types:
src/gateway/protocol.ts - Handlers:
src/gateway/handlers/ - Gateway server:
src/gateway/server.ts - Companion runtime client helper:
src/companion/runtimeClient.ts(node + system +canvas.*typed RPC wrappers, optionalautoConnect) - Platform companion wrappers:
src/companion/platformClients.ts