SSE Streaming
When enable_streaming is set to true on an agent endpoint, the execution endpoint returns Server-Sent Events (SSE) instead of a blocking JSON response. This lets you show real-time progress to users as the agent thinks, calls tools, and produces output.
Event Types
Section titled “Event Types”The SSE stream emits the following event types in order:
| Event | When | Payload |
|---|---|---|
thinking | Agent is processing | { status, message, iteration? } |
tool_call | Agent is invoking a tool | { iteration, tool, arguments } |
tool_result | Tool execution completed | { iteration, tool, success, latency_ms } |
done | Final output ready | { output, outputParsed?, tool_calls, usage } |
error | Execution failed | { message } |
Event Flow Example
Section titled “Event Flow Example”A typical streaming session looks like this:
event: thinkingdata: {"type":"thinking","status":"starting","message":"Resolving workspace..."}
event: thinkingdata: {"type":"thinking","status":"loading_tools","message":"Loading workspace tools..."}
event: thinkingdata: {"type":"thinking","status":"tools_loaded","message":"Loaded 12 tools"}
event: thinkingdata: {"type":"thinking","status":"llm_call","message":"Calling gpt-4o-mini...","iteration":1}
event: tool_calldata: {"type":"tool_call","iteration":1,"tool":"github_list_issues","arguments":{"repo":"acme/api","state":"open"}}
event: tool_resultdata: {"type":"tool_result","iteration":1,"tool":"github_list_issues","success":true,"latency_ms":234}
event: thinkingdata: {"type":"thinking","status":"llm_call","message":"Calling gpt-4o-mini...","iteration":2}
event: donedata: {"type":"done","output":"Found 3 open issues...","tool_calls":[{"name":"github_list_issues","arguments":{"repo":"acme/api","state":"open"},"result":"...","latencyMs":234,"success":true}],"usage":{"tokens_input":1245,"tokens_output":189,"cost_cents":0.34,"latency_ms":2891,"iterations":2}}JavaScript: fetch + ReadableStream
Section titled “JavaScript: fetch + ReadableStream”The recommended approach for web applications. This gives you full control over parsing and error handling:
async function callAgentStreaming(slug: string, input: string, apiKey: string) { const response = await fetch(`https://api.aerostack.dev/api/run/${slug}`, { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ input }), });
if (!response.ok) { throw new Error(`HTTP ${response.status}: ${await response.text()}`); }
const reader = response.body!.getReader(); const decoder = new TextDecoder(); let buffer = '';
while (true) { const { done, value } = await reader.read(); if (done) break;
buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || '';
for (const line of lines) { if (line.startsWith('data: ')) { const data = JSON.parse(line.slice(6));
switch (data.type) { case 'thinking': console.log(`[${data.status}] ${data.message}`); break; case 'tool_call': console.log(`Calling tool: ${data.tool}`); break; case 'tool_result': console.log(`Tool ${data.tool}: ${data.success ? 'OK' : 'FAILED'} (${data.latency_ms}ms)`); break; case 'done': console.log('Final output:', data.output); console.log('Usage:', data.usage); return data; case 'error': throw new Error(data.message); } } } }}JavaScript: EventSource
Section titled “JavaScript: EventSource”For simpler use cases where you do not need to send custom headers. Note that EventSource only supports GET requests, so you will need a wrapper library like eventsource-parser or use the fetch approach above for POST requests.
// Using eventsource-parser (works with POST)import { createParser } from 'eventsource-parser';
async function streamAgent(slug: string, input: string, apiKey: string) { const response = await fetch(`https://api.aerostack.dev/api/run/${slug}`, { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ input }), });
const parser = createParser((event) => { if (event.type === 'event' && event.data) { const data = JSON.parse(event.data); handleEvent(data); } });
const reader = response.body!.getReader(); const decoder = new TextDecoder();
while (true) { const { done, value } = await reader.read(); if (done) break; parser.feed(decoder.decode(value, { stream: true })); }}
function handleEvent(event: any) { switch (event.type) { case 'thinking': updateStatusUI(event.message); break; case 'tool_call': addToolCallToUI(event.tool, event.arguments); break; case 'tool_result': updateToolResultUI(event.tool, event.success); break; case 'done': showFinalOutput(event.output, event.usage); break; case 'error': showError(event.message); break; }}React Example
Section titled “React Example”function useAgentStream() { const [status, setStatus] = useState<string>(''); const [toolCalls, setToolCalls] = useState<any[]>([]); const [output, setOutput] = useState<string>(''); const [loading, setLoading] = useState(false);
const run = useCallback(async (slug: string, input: string, apiKey: string) => { setLoading(true); setStatus('Starting...'); setToolCalls([]); setOutput('');
const response = await fetch(`https://api.aerostack.dev/api/run/${slug}`, { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ input }), });
const reader = response.body!.getReader(); const decoder = new TextDecoder(); let buffer = '';
while (true) { const { done, value } = await reader.read(); if (done) break;
buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || '';
for (const line of lines) { if (!line.startsWith('data: ')) continue; const data = JSON.parse(line.slice(6));
if (data.type === 'thinking') setStatus(data.message); if (data.type === 'tool_call') setToolCalls(prev => [...prev, data]); if (data.type === 'tool_result') { setToolCalls(prev => prev.map(tc => tc.tool === data.tool ? { ...tc, ...data } : tc )); } if (data.type === 'done') setOutput(data.output); } }
setLoading(false); }, []);
return { run, status, toolCalls, output, loading };}curl Example
Section titled “curl Example”curl -N -X POST https://api.aerostack.dev/api/run/summarizer \ -H "Authorization: Bearer aek_your_api_key" \ -H "Content-Type: application/json" \ -d '{"input": "Summarize this article..."}'The -N flag disables output buffering, so you see events as they arrive.
Response Headers
Section titled “Response Headers”Streaming responses include these headers:
Content-Type: text/event-streamCache-Control: no-cacheConnection: keep-aliveX-Accel-Buffering: noThe X-Accel-Buffering: no header prevents Nginx and similar proxies from buffering the stream.
JSON Output Format with Streaming
Section titled “JSON Output Format with Streaming”The done event for JSON output includes both output (stringified JSON) and outputParsed (the parsed object):
event: donedata: {"type":"done","output":"{\"summary\":\"...\",\"sentiment\":\"positive\"}","outputParsed":{"summary":"...","sentiment":"positive"},"tool_calls":[],"usage":{"tokens_input":245,"tokens_output":89,"cost_cents":0.12,"latency_ms":1823,"iterations":1}}Timeout Behavior
Section titled “Timeout Behavior”The agent execution engine enforces a hard timeout of 60 seconds (or the endpoint’s timeout_ms, whichever is lower). If the timeout is reached during a streaming session, the stream ends with whatever output has been produced so far. A thinking event with status: "timeout" is emitted before the stream closes.
Error Handling
Section titled “Error Handling”If the execution engine encounters an error, an error event is emitted and the stream is closed:
event: errordata: {"type":"error","message":"Workspace not found or no active tokens."}Always handle the error event type in your client code to gracefully show errors to users.