Event Streaming (SSE)
Relevant source files
The following files were used as context for generating this wiki page:
The Event Streaming system in Open Claude Cowork utilizes Server-Sent Events (SSE) to provide real-time, token-by-token updates from AI providers to the user interface. This implementation ensures a smooth conversational experience by streaming text, reasoning steps, tool invocations, and session status updates over a persistent HTTP connection.
The architecture follows a multi-tier flow where the Backend Server acts as an aggregator, consuming streams from various AI SDKs (Claude Agent SDK or Opencode SDK) and normalizing them into a consistent SSE format for the Electron Frontend. Sources: README.md:28-29, server/server.js:101-131
System Architecture and Data Flow
The streaming process begins when the Frontend initiates a POST request to /api/chat. The server configures the response headers to establish a persistent connection before delegating the query to the selected AI provider.
Streaming Sequence
The following diagram illustrates the lifecycle of an SSE connection from the initial user request to the final completion event.
The server maintains the connection using keep-alive and disables buffering to ensure immediate delivery of chunks. Sources: server/server.js:80-85, renderer/renderer.js:705-720
Connection Management
To prevent connection timeouts during long-running agent tasks, the server implements a heartbeat mechanism. A comment-based heartbeat (: heartbeat) is sent every 15 seconds to keep the socket active. Sources: server/server.js:89-93
Server-Side Implementation
The backend handles the normalization of provider-specific events into a unified event format. The core logic resides in the /api/chat route, which utilizes asynchronous generators from the provider classes.
SSE Configuration
The server sets specific headers to ensure the browser/client treats the response as a continuous stream:
| Header | Value | Purpose |
|---|---|---|
Content-Type | text/event-stream | Defines the response as SSE |
Cache-Control | no-cache, no-transform | Prevents intermediate proxies from caching or modifying data |
Connection | keep-alive | Maintains the persistent socket |
X-Accel-Buffering | no | Specifically for Nginx/proxies to disable buffering |
Sources: server/server.js:80-84
Normalized Event Types
The system streams several standardized event types to the frontend:
session_init: Contains thesession_idfor context persistence.text: Incremental string tokens for the assistant's response.tool_use: Metadata about a tool being invoked (name, input parameters).tool_result: The output returned from a tool execution.error: Information regarding failures during the stream.done: Signals the successful completion of the response.
Sources: server/providers/claude-provider.js:115-135, server/providers/opencode-provider.js:231-255
Provider Streaming Logic
Different providers implement streaming based on their underlying SDKs, but all wrap their logic in async *query() generators.
Claude Provider
The ClaudeProvider utilizes the @anthropic-ai/claude-agent-sdk. It iterates over the SDK's internal stream and yields blocks specifically filtering for text and tool_use. It also monitors system messages to capture and yield the session_id from init subtypes. Sources: server/providers/claude-provider.js:82-138
Opencode Provider
The OpencodeProvider uses an event-subscription model. It subscribes to events via client.event.subscribe() and listens for message.part.updated. It maintains internal tracking of lastYieldedLength to calculate and yield only the "delta" (new text) of a growing message part. Sources: server/providers/opencode-provider.js:145-185
Frontend Consumption
The renderer.js and preload.js work together to consume the stream and update the UI dynamically.
Stream Processing Logic
The frontend uses the Streams API to read the response body.
Sources: renderer/renderer.js:750-840
Multi-Chunk Rendering
To maintain performance and handle mixed content (markdown text interleaved with tool calls), the renderer uses "Markdown Containers". Each text segment between tool calls is assigned a chunkIndex. When a tool_use event is received, the current chunkIndex is incremented, forcing the next text event to create a new markdown-content div. Sources: renderer/renderer.js:985-1010, renderer/renderer.js:1062-1080
Request Abort Mechanism
The system supports interrupting an active stream through a coordinated abort process across the Electron bridge.
- Client Signal: The user clicks the stop button, triggering
window.electronAPI.abortCurrentRequest()inpreload.js. Sources: renderer/renderer.js:655-665 - Network Abort:
preload.jscallsabort()on theAbortControllerassociated with the fetch request, closing the client-side socket. Sources: preload.js:11-17 - Backend Signal: The frontend sends a secondary POST to
/api/abort. Sources: preload.js:21-30 - Provider Termination: The backend calls
provider.abort(chatId), which triggers anAbortControllerinside the provider'squerygenerator, stopping the SDK from making further requests to the AI model. Sources: server/server.js:154-165, server/providers/claude-provider.js:25-33
Summary
The Event Streaming system in Open Claude Cowork provides a robust implementation of real-time communication between AI agents and the user. By standardizing diverse provider SDK outputs into a unified SSE protocol, the system enables features like real-time tool visualization and reasoning transparency while maintaining connection stability through heartbeats and coordinated abort handling. This architecture ensures the frontend remains responsive and synchronized with the agent's complex, multi-turn task execution. Sources: README.md:120-125, server/server.js:120-145