Stream AI responses with SSE
Consume the POST /api/chat/stream response, assemble deltas and handle terminal events.
How the stream behaves
POST /api/chat/stream returns a Server-Sent Events response with Cache-Control: no-cache and X-Accel-Buffering: no. The server yields one data line for each provider text piece, then writes the completed response to local history before sending the terminal event.
Event shapes
| Event | JSON payload | Client action |
|---|---|---|
| delta | {"delta":"next text piece"} | Append delta to the visible answer |
| done | {"done":true,"text":"full answer","sessionId":"...","autocopy":{...}} | Treat the answer as complete and persist the returned session ID |
| error | {"error":"provider message"} | Stop rendering, show a recoverable error and decide whether to retry |
Inspect the wire format
curl --no-buffer -sS \n -X POST "$BASE_URL/api/chat/stream" \n -H "X-OpenSS-Session: $SESSION_TOKEN" \n -H "Content-Type: application/json" \n -d "{\"message\":\"Explain the selected text\",\"source\":\"manual\"}"\n\n# data: {\"delta\":\"First\"}\n#\n# data: {\"delta\":\" answer\"}\n#\n# data: {\"done\":true,\"text\":\"First answer\",\"sessionId\":\"...\",\"autocopy\":{...}}Parse the stream in Python
import json
import requests
with requests.post(
base_url + "/api/chat/stream",
headers={"X-OpenSS-Session": session_token},
json={"message": "Explain the selected text", "source": "manual"},
stream=True,
timeout=120,
) as response:
response.raise_for_status()
for line in response.iter_lines(decode_unicode=True):
if not line or not line.startswith("data: "):
continue
event = json.loads(line.removeprefix("data: "))
if "delta" in event:
print(event["delta"], end="", flush=True)
elif event.get("done"):
session_id = event["sessionId"]
print()
elif "error" in event:
raise RuntimeError(event["error"])Streaming a screenshot analysis
Include screenshotId and set source to capture. The server reads the temporary capture, combines the image with local OCR and the instruction, then streams provider output. OCR is supporting context; the model is instructed to use the screenshot as the primary visual source.
{
"source": "capture",
"screenshotId": "capture-id-from-/api/screenshots/capture",
"message": "Identify the key figures and list the next actions.",
"sessionId": null,
"modelTier": "mini"
}Retries and cancellation
- Do not retry after a done event; the answer has already been written to local history.
- A stream-level error is emitted as a data event rather than an HTTP error after streaming begins. Surface its text and allow a deliberate retry.
- POST /api/chat/cancel acknowledges a request ID, but a client should still stop reading and close its response stream.
- Keep the screenshot cleanup in a finally block so provider errors do not leave temporary captures behind.
OpenSS/src/openss/gui/server.py and schemas.py.