SSE Event Stream
Qoder Cloud Agents streams real-time execution status to clients over Server-Sent Events (SSE). A single connection delivers every event as it happens — no polling required.
Connection URL
GET https://api.qoder.com.cn/api/v1/cloud/sessions/{session_id}/events/stream
Request headers:
Authorization: Bearer $QODER_PAT
Accept: text/event-stream
SSE Format
Each event consists of three lines separated by a blank line:
id: evt_01abc123
event: agent.message
data: {"content":[{"type":"text","text":"Hello, I'm ready to help."}]}
|
Field |
Description |
|
|
Unique event ID, used for resume-on-reconnect |
|
|
Event type, which determines the structure of |
|
|
JSON-encoded event payload |
Event Catalog
|
Event type |
Meaning |
When it fires |
|
|
|
A user message was sent |
After the client POSTs the event |
|
|
|
The user interrupted execution |
When the client sends an interrupt |
|
|
|
The user defined an expected outcome |
When the client sets a goal |
|
|
|
The Session started running |
After a message is received and processing begins |
|
|
|
Model request started |
When the Agent calls the LLM |
|
|
|
The Agent is thinking |
During model inference |
|
|
|
The Agent replied |
When the model emits text |
|
|
|
The Agent initiated a tool call (Bash/Read/Write/Edit/Glob/Grep/WebFetch/WebSearch) |
When the model decides to call a tool |
See "Tool call pairs" below |
|
|
Tool execution result, paired with tool_use via |
After tool execution completes |
See "Tool call pairs" below |
|
|
The Session went idle |
When a turn completes |
See "status_idle full schema" below |
|
|
Model request ended |
When the LLM call finishes |
|
|
|
A Session error occurred |
On a runtime exception |
|
|
|
The Session terminated |
When the Session closes or times out |
|
Each event's data JSON also includes these common fields: id, type, turn_id (omitted for user.define_outcome), session_id, created_at, processed_at, schema_version.
Typical Event Lifecycle
A complete conversation turn fires events in this order:
user.message ← user input
session.status_running ← execution begins
span.model_request_start ← model request starts
agent.thinking ← reasoning (optional)
agent.message ← reply content (may emit multiple delta chunks)
session.status_idle ← Idle, awaiting next turn
span.model_request_end ← Model request tail
Tool call pairs
Every agent.tool_use has a matching agent.tool_result in the same turn_id, linked by tool_use_id.
agent.tool_use key fields:
{
"type": "agent.tool_use",
"id": "evt_1d4c9b3c873b...",
"name": "Bash",
"input": { "command": "...", "description": "..." },
"tool_use_id": "toolu_bdrk_01Kj...",
"turn_id": "turn_...",
"session_id": "sess_...",
"requires_confirmation": false
}
The id of this event uses the evt_ prefix like other events; the tool_use_id field uses the external model provider format toolu_bdrk_<24>. name is capitalized.
agent.tool_result key fields:
{
"type": "agent.tool_result",
"id": "evt_<32hex>",
"name": "Bash",
"content": [{ "type": "text", "text": "..." }],
"tool_use_id": "toolu_bdrk_01Kj...",
"turn_id": "turn_...",
"is_error": false
}
Sorting advice: Sort by created_at, then by type (tool_use before tool_result), then by id. Do not rely solely on id ordering — the platform-generated tool_result id may sort lexically before the tool_use id.
status_idle full schema
The session.status_idle event carries stop_reason, usage, turn_id, and session_id in addition to status:
{
"type": "session.status_idle",
"id": "evt_840b8dc4b1534e52a2b858bf26b7de00",
"status": "idle",
"stop_reason": { "type": "end_turn" },
"usage": {
"input_tokens": 23487,
"output_tokens": 1024,
"cache_read_input_tokens": 11223,
"cache_creation_input_tokens": 0
},
"turn_id": "turn_...",
"session_id": "sess_..."
}
stop_reason.type is currently observed as end_turn; other enum values (cancel / max_turns / error etc.) appear depending on the scenario.
Reconnect with after_id
A new SSE connection replays all historical events by default. To receive only new events, pass after_id:
GET /sessions/{session_id}/events/stream?after_id=evt_01abc123
The browser's native EventSource automatically sends Last-Event-ID on reconnect, and the server uses it to deliver incremental events.
For long-running connections, track the last id you received and use after_id to resume after disconnects.
delta_flush_interval_ms
A query parameter that controls how often agent.message deltas are flushed:
GET /sessions/{session_id}/events/stream?delta_flush_interval_ms=100
The default is 50ms. Higher values reduce event frequency and rendering overhead on the client.
curl Examples
curl -N \
-H "Authorization: Bearer $QODER_PAT" \
-H "Accept: text/event-stream" \
"https://api.qoder.com.cn/api/v1/cloud/sessions/sess_abc123/events/stream"
Resuming with after_id:
curl -N \
-H "Authorization: Bearer $QODER_PAT" \
-H "Accept: text/event-stream" \
"https://api.qoder.com.cn/api/v1/cloud/sessions/sess_abc123/events/stream?after_id=evt_01abc123"
JavaScript EventSource Example
const url = new URL('https://api.qoder.com.cn/api/v1/cloud/sessions/sess_abc123/events/stream');
// To resume: url.searchParams.set('after_id', lastEventId);
const es = new EventSource(url, {
headers: { 'Authorization': `Bearer ${QODER_PAT}` }
});
es.addEventListener('agent.message', (e) => {
const data = JSON.parse(e.data);
console.log('Agent:', data.content);
});
es.addEventListener('session.status_idle', () => {
console.log('Turn complete; awaiting next user input');
});
es.addEventListener('session.error', (e) => {
const data = JSON.parse(e.data);
console.error('Error:', data.error);
es.close();
});
es.onerror = () => {
console.warn('Connection lost; the browser will reconnect automatically');
};
Client Implementation Tips
-
Track the last event ID — update it on every event so you can resume after disconnects.
-
Concatenate deltas —
agent.messagemay arrive in streaming deltas; assemble them into the full text on the client. -
Watch for
terminated— when received, close the connection and stop reconnecting. -
Timeout and reconnect — if 30 seconds pass with no event, close the connection and reconnect to avoid silent stalls.
-
Filter events — subscribe only to the event types you care about; ignoring span-level events reduces overhead.
FAQ
Q: I'm getting a flood of historical events on connect. How do I avoid this? A: Pass after_id set to the last event ID you processed. The server will only return events after that ID.
Q: Will the SSE connection close on its own? A: When the Session terminates, the server emits a terminated event and closes the connection. The idle timeout depends on Session configuration.
Q: Can I use WebSockets instead? A: Only SSE is supported today. SSE is lighter for one-way push and natively supports reconnection.
Q: If I set delta_flush_interval_ms too high, will events be dropped? A: No. Deltas are batched, not dropped. Final content remains complete.