Overview
This issue documents the architecture and implementation details of how the OpenCode web interface handles client interaction, including messaging, event handling, and real-time updates.
Architecture
The opencode web command starts a local Hono-based HTTP server that serves as the backend API for the web interface. The architecture follows a Local Server + Remote UI Proxy model:
- Backend: Local Hono server running on the user's machine (default port 4096)
- Frontend: Static UI assets proxied from
https://app.opencode.ai
- Communication: RESTful API + Server-Sent Events (SSE) for real-time updates
Key Files
packages/opencode/src/cli/cmd/web.ts - Web command entry point
packages/opencode/src/server/server.ts - Main server configuration and routing
packages/opencode/src/server/routes/session.ts - Session and message handling
packages/opencode/src/server/event.ts - Server event definitions
packages/opencode/src/bus/bus-event.ts - Event bus system
packages/opencode/src/server/routes/question.ts - Question handling
packages/opencode/src/server/routes/permission.ts - Permission handling
Message Handling
Sending Messages
The web client sends messages to sessions via HTTP POST requests:
Endpoint: POST /session/:sessionID/message
Request Body:
{
text: string,
files?: FilePart[],
agent?: string,
model?: { providerID: string, modelID: string },
// ... other optional fields
}
Response: Streams the assistant's response as JSON
Implementation: packages/opencode/src/server/routes/session.ts:697-736
Related Endpoints:
POST /session/:sessionID/prompt_async - Asynchronous message sending (returns immediately)
POST /session/:sessionID/command - Send slash commands
POST /session/:sessionID/shell - Execute shell commands
Receiving Messages
Messages are received in two ways:
-
Initial Fetch: GET /session/:sessionID/message
- Returns all messages in a session with their parts
- Implementation:
packages/opencode/src/server/routes/session.ts:546-583
-
Real-time Updates: Via SSE stream (see Event Handling below)
Message Structure
Messages follow a structured format:
type Message = {
info: UserMessage | AssistantMessage,
parts: Part[]
}
type Part =
| TextPart
| ToolPart
| FilePart
| ReasoningPart
| SnapshotPart
| PatchPart
| AgentPart
| RetryPart
| CompactionPart
| SubtaskPart
| StepStartPart
| StepFinishPart
Reference: packages/opencode/src/session/message-v2.ts
Event Handling (Real-time Updates)
Event Stream Connection
Endpoint: GET /event
Protocol: Server-Sent Events (SSE)
Implementation: packages/opencode/src/server/server.ts:474-529
The client establishes a persistent SSE connection to receive real-time updates. The server:
- Immediately sends a
server.connected event upon connection
- Subscribes to all bus events and forwards them to the client
- Sends heartbeat events every 30 seconds to prevent connection timeouts
- Automatically closes the stream when the instance is disposed
Event Bus System
OpenCode uses a centralized event bus (BusEvent) to broadcast updates across the system:
// Event definition pattern
BusEvent.define("event.type", zodSchema)
Core Implementation: packages/opencode/src/bus/bus-event.ts
All events follow a discriminated union pattern:
type Event = {
type: string,
properties: { /* event-specific data */ }
}
Message Events
Event Types (from packages/opencode/src/session/message-v2.ts:401-430):
-
message.updated
{
type: "message.updated",
properties: {
info: Message
}
}
- Fired when a message is created or modified
- Contains the full message info
-
message.removed
{
type: "message.removed",
properties: {
sessionID: string,
messageID: string
}
}
- Fired when a message is deleted
-
message.part.updated
{
type: "message.part.updated",
properties: {
part: Part,
delta?: string
}
}
- Fired when a message part is created or updated
- Used for streaming responses (text parts updated incrementally)
- The
delta field contains incremental text updates
-
message.part.removed
{
type: "message.part.removed",
properties: {
sessionID: string,
messageID: string,
partID: string
}
}
- Fired when a message part is deleted
Non-Message Event Handling
Todo Events
Event Type: todo.updated (from packages/opencode/src/session/todo.ts:18)
{
type: "todo.updated",
properties: {
sessionID: string,
todos: Todo[]
}
}
REST Endpoints:
GET /session/:sessionID/todo - Fetch current todos
Implementation: packages/opencode/src/server/routes/session.ts:155-183
Question Events
Event Type: question.asked (from packages/opencode/src/question/index.ts:64)
{
type: "question.asked",
properties: {
requestID: string,
sessionID: string,
questions: Question[]
}
}
REST Endpoints:
GET /question/ - List all pending questions
POST /question/:requestID/reply - Answer a question
POST /question/:requestID/reject - Reject a question
Implementation: packages/opencode/src/server/routes/question.ts
When the AI needs user input, it:
- Creates a question request with a unique
requestID
- Broadcasts
question.asked event via SSE
- Waits for client response via POST
- Broadcasts
question.replied or question.rejected event
Permission Events
Event Type: permission.asked (from packages/opencode/src/permission/next.ts:97)
{
type: "permission.asked",
properties: {
requestID: string,
sessionID: string,
// ... permission details
}
}
REST Endpoints:
GET /permission/ - List all pending permissions
POST /permission/:requestID/reply - Approve/deny permission
Implementation: packages/opencode/src/server/routes/permission.ts
The permission flow:
- AI requests permission (e.g., to execute a command)
- Server broadcasts
permission.asked event
- Client displays permission UI
- User approves/denies via POST request
- Server broadcasts
permission.replied event
Session Events
Additional session-level events (from packages/opencode/src/session/index.ts:106-131):
session.created - New session created
session.updated - Session metadata updated
session.deleted - Session removed
session.diff - File changes summary
session.error - Session error occurred
Status Events
Event Type: session.status (from packages/opencode/src/session/status.ts:28)
{
type: "session.status",
properties: {
sessionID: string,
status: "idle" | "active" | "error"
}
}
Indicates the current execution state of a session.
Authentication
If OPENCODE_SERVER_PASSWORD is set, the server uses HTTP Basic Auth:
- Username:
OPENCODE_SERVER_USERNAME (default: "opencode")
- Password:
OPENCODE_SERVER_PASSWORD
Implementation: packages/opencode/src/server/server.ts:80-85
CORS Configuration
The server allows connections from:
localhost on any port
127.0.0.1 on any port
tauri://localhost (for desktop app)
*.opencode.ai (HTTPS only)
- Custom origins via the
--cors flag
Implementation: packages/opencode/src/server/server.ts:103-123
Client Implementation Pattern
A typical web client would:
-
Initialize:
- Connect to local server (detect via mDNS or user config)
- Establish SSE connection to
/event
-
Handle Events:
const eventSource = new EventSource('/event');
eventSource.onmessage = (event) => {
const { type, properties } = JSON.parse(event.data);
switch(type) {
case 'message.part.updated':
// Update UI with new message part
break;
case 'question.asked':
// Show question modal
break;
case 'permission.asked':
// Show permission request
break;
// ... handle other events
}
};
-
Send Messages:
await fetch(`/session/${sessionID}/message`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: userInput })
});
-
Respond to Questions/Permissions:
await fetch(`/question/${requestID}/reply`, {
method: 'POST',
body: JSON.stringify({ answers: [...] })
});
Key Observations
-
Separation of Concerns: The backend logic runs locally while UI assets are served remotely, allowing UI updates without CLI updates
-
Event-Driven Architecture: All real-time updates flow through a centralized event bus with strong typing via Zod schemas
-
Streaming Support: Message parts can be streamed incrementally using the delta field in message.part.updated events
-
Type Safety: All API contracts are defined using Zod schemas and exposed via OpenAPI documentation at /doc
-
Flexible Deployment: Supports network access (0.0.0.0), mDNS discovery, and custom CORS origins for various deployment scenarios
Related Documentation
- OpenAPI spec available at:
GET /doc
- Full event type definitions: Search codebase for
BusEvent.define
- Message schemas:
packages/opencode/src/session/message-v2.ts
Overview
This issue documents the architecture and implementation details of how the OpenCode web interface handles client interaction, including messaging, event handling, and real-time updates.
Architecture
The
opencode webcommand starts a local Hono-based HTTP server that serves as the backend API for the web interface. The architecture follows a Local Server + Remote UI Proxy model:https://app.opencode.aiKey Files
packages/opencode/src/cli/cmd/web.ts- Web command entry pointpackages/opencode/src/server/server.ts- Main server configuration and routingpackages/opencode/src/server/routes/session.ts- Session and message handlingpackages/opencode/src/server/event.ts- Server event definitionspackages/opencode/src/bus/bus-event.ts- Event bus systempackages/opencode/src/server/routes/question.ts- Question handlingpackages/opencode/src/server/routes/permission.ts- Permission handlingMessage Handling
Sending Messages
The web client sends messages to sessions via HTTP POST requests:
Endpoint:
POST /session/:sessionID/messageRequest Body:
Response: Streams the assistant's response as JSON
Implementation:
packages/opencode/src/server/routes/session.ts:697-736Related Endpoints:
POST /session/:sessionID/prompt_async- Asynchronous message sending (returns immediately)POST /session/:sessionID/command- Send slash commandsPOST /session/:sessionID/shell- Execute shell commandsReceiving Messages
Messages are received in two ways:
Initial Fetch:
GET /session/:sessionID/messagepackages/opencode/src/server/routes/session.ts:546-583Real-time Updates: Via SSE stream (see Event Handling below)
Message Structure
Messages follow a structured format:
Reference:
packages/opencode/src/session/message-v2.tsEvent Handling (Real-time Updates)
Event Stream Connection
Endpoint:
GET /eventProtocol: Server-Sent Events (SSE)
Implementation:
packages/opencode/src/server/server.ts:474-529The client establishes a persistent SSE connection to receive real-time updates. The server:
server.connectedevent upon connectionEvent Bus System
OpenCode uses a centralized event bus (
BusEvent) to broadcast updates across the system:Core Implementation:
packages/opencode/src/bus/bus-event.tsAll events follow a discriminated union pattern:
Message Events
Event Types (from
packages/opencode/src/session/message-v2.ts:401-430):message.updatedmessage.removedmessage.part.updateddeltafield contains incremental text updatesmessage.part.removedNon-Message Event Handling
Todo Events
Event Type:
todo.updated(frompackages/opencode/src/session/todo.ts:18)REST Endpoints:
GET /session/:sessionID/todo- Fetch current todosImplementation:
packages/opencode/src/server/routes/session.ts:155-183Question Events
Event Type:
question.asked(frompackages/opencode/src/question/index.ts:64)REST Endpoints:
GET /question/- List all pending questionsPOST /question/:requestID/reply- Answer a questionPOST /question/:requestID/reject- Reject a questionImplementation:
packages/opencode/src/server/routes/question.tsWhen the AI needs user input, it:
requestIDquestion.askedevent via SSEquestion.repliedorquestion.rejectedeventPermission Events
Event Type:
permission.asked(frompackages/opencode/src/permission/next.ts:97)REST Endpoints:
GET /permission/- List all pending permissionsPOST /permission/:requestID/reply- Approve/deny permissionImplementation:
packages/opencode/src/server/routes/permission.tsThe permission flow:
permission.askedeventpermission.repliedeventSession Events
Additional session-level events (from
packages/opencode/src/session/index.ts:106-131):session.created- New session createdsession.updated- Session metadata updatedsession.deleted- Session removedsession.diff- File changes summarysession.error- Session error occurredStatus Events
Event Type:
session.status(frompackages/opencode/src/session/status.ts:28)Indicates the current execution state of a session.
Authentication
If
OPENCODE_SERVER_PASSWORDis set, the server uses HTTP Basic Auth:OPENCODE_SERVER_USERNAME(default: "opencode")OPENCODE_SERVER_PASSWORDImplementation:
packages/opencode/src/server/server.ts:80-85CORS Configuration
The server allows connections from:
localhoston any port127.0.0.1on any porttauri://localhost(for desktop app)*.opencode.ai(HTTPS only)--corsflagImplementation:
packages/opencode/src/server/server.ts:103-123Client Implementation Pattern
A typical web client would:
Initialize:
/eventHandle Events:
Send Messages:
Respond to Questions/Permissions:
Key Observations
Separation of Concerns: The backend logic runs locally while UI assets are served remotely, allowing UI updates without CLI updates
Event-Driven Architecture: All real-time updates flow through a centralized event bus with strong typing via Zod schemas
Streaming Support: Message parts can be streamed incrementally using the
deltafield inmessage.part.updatedeventsType Safety: All API contracts are defined using Zod schemas and exposed via OpenAPI documentation at
/docFlexible Deployment: Supports network access (0.0.0.0), mDNS discovery, and custom CORS origins for various deployment scenarios
Related Documentation
GET /docBusEvent.definepackages/opencode/src/session/message-v2.ts