# SSE Streaming

> Stream agent execution progress in real time with Server-Sent Events.

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.

Streaming is enabled per endpoint. Set `enable_streaming: true` when creating or updating the endpoint. All calls to that endpoint will return SSE responses.

## 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

A typical streaming session looks like this:

```
event: thinking
data: {"type":"thinking","status":"starting","message":"Resolving workspace..."}

event: thinking
data: {"type":"thinking","status":"loading_tools","message":"Loading workspace tools..."}

event: thinking
data: {"type":"thinking","status":"tools_loaded","message":"Loaded 12 tools"}

event: thinking
data: {"type":"thinking","status":"llm_call","message":"Calling gpt-4o-mini...","iteration":1}

event: tool_call
data: {"type":"tool_call","iteration":1,"tool":"github_list_issues","arguments":{"repo":"acme/api","state":"open"}}

event: tool_result
data: {"type":"tool_result","iteration":1,"tool":"github_list_issues","success":true,"latency_ms":234}

event: thinking
data: {"type":"thinking","status":"llm_call","message":"Calling gpt-4o-mini...","iteration":2}

event: done
data: {"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

The recommended approach for web applications. This gives you full control over parsing and error handling:

```typescript
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

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.

```typescript
// Using eventsource-parser (works with POST)

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

```typescript

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

```bash
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

Streaming responses include these headers:

```
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
X-Accel-Buffering: no
```

The `X-Accel-Buffering: no` header prevents Nginx and similar proxies from buffering the stream.

## JSON Output Format with Streaming

When `output_format` is set to `json`, token-level streaming is not available. The stream will emit `thinking`, `tool_call`, and `tool_result` events as usual, but the `done` event is emitted only after the complete JSON response is generated. This is because partial JSON cannot be validated against the output schema.

The `done` event for JSON output includes both `output` (stringified JSON) and `outputParsed` (the parsed object):

```
event: done
data: {"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

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

If the execution engine encounters an error, an `error` event is emitted and the stream is closed:

```
event: error
data: {"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.
