---
name: sse_client
kind: function
lang: cpp
domain: core
version: 0.1.0
description: "Cliente Server-Sent Events C++ con reconnect exponencial + Last-Event-ID. Reusable para streams HTTP push (agent_runs, fsnotify, telemetria, dashboards)."
tags: [sse, http, client, network, agents, registry-gap]
purity: impure
signature: "fn_sse::Client::start(cfg, on_event, on_status) -- background thread"
params:
  - name: cfg
    desc: "Config con url, bearer_token opcional, connect_timeout_ms, reconnect_initial_ms, reconnect_max_ms y auto_reconnect"
  - name: on_event
    desc: "Callback invocado por cada Event{event,data,id,retry_ms} parseado del stream SSE. Corre en el background thread del cliente."
  - name: on_status
    desc: "Callback opcional con strings 'connecting'|'connected'|'disconnected'|'error: <msg>' al cambiar estado de conexion."
output: "void start(), void stop(), bool is_running() — control de un thread background que mantiene la conexion SSE viva con reconnect automatico"
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
example: |
  fn_sse::Config cfg;
  cfg.url = "http://127.0.0.1:8403/api/boards/issues/stream";
  fn_sse::Client cli;
  cli.start(cfg,
            [](const fn_sse::Event& e){ /* handle e.data JSON */ },
            [](const std::string& s){ std::printf("[sse] %s\n", s.c_str()); });
  // ... app vive aqui; cli corre en background
  cli.stop();
tested: true
tests:
  - connect_and_receive_3_events
  - parse_event_field
  - reconnect_on_disconnect
  - stop_kills_thread
test_file_path: "cpp/tests/test_sse_client.cpp"
file_path: "cpp/functions/core/sse_client.cpp"
framework: ""
notes: "curl popen sin libcurl link, mismo patron que llm_anthropic y http_request. Usa fork+exec en lugar de popen puro para obtener el PID del proceso curl y poder matarlo en stop() con SIGTERM, evitando un join colgado."
---

## Cuando usarla

- App necesita escuchar un stream HTTP push (`text/event-stream`) de forma continua.
- Ejemplos: `kanban_cpp` escucha `/api/boards/{issues,flows}/stream` para updates en vivo cuando un `.md` cambia; `skill_tree` escucha `/api/runs/<id>/sse` para progreso de `agent_runner_api`; dashboard cross-app lee un timeline SSE.
- NO usar para peticiones HTTP one-shot — eso es `http_request_cpp_core`.

## Ejemplo

```cpp
#include "core/sse_client.h"
#include <atomic>
#include <cstdio>
#include <thread>
#include <chrono>

std::atomic<int> events_seen{0};

int main() {
    fn_sse::Config cfg;
    cfg.url            = "http://127.0.0.1:8403/api/boards/issues/stream";
    cfg.auto_reconnect = true;

    fn_sse::Client cli;
    cli.start(cfg,
        [](const fn_sse::Event& e) {
            events_seen++;
            std::printf("event=%s data=%s id=%s\n",
                        e.event.c_str(), e.data.c_str(), e.id.c_str());
        },
        [](const std::string& status) {
            std::printf("[sse] %s\n", status.c_str());
        });

    // App vive aqui; cli corre en background con reconnect automatico.
    std::this_thread::sleep_for(std::chrono::seconds(60));
    cli.stop();
    return 0;
}
```

## Gotchas

- `curl -N` (no buffer) es imprescindible — sin esto stdout queda buffered y el cliente no ve eventos hasta que el buffer se llena.
- `--max-time 0` desactiva el timeout global de curl para streams indefinidos. Si quieres limitar la duracion total, setea `cfg.connect_timeout_ms` (solo afecta la conexion inicial).
- Reconnect no es instantaneo: backoff exponencial 1 s..30 s por defecto. El caller debe tolerar gaps.
- `stop()` mata el proceso curl via `SIGTERM` antes del join. Sin esto, el thread se cuelga hasta que el servidor cierre. Con fork+exec obtenemos el PID; si por algun motivo fork falla cae a `popen()` (sin PID conocido).
- Thread safety: `on_event` y `on_status` corren en el background thread del cliente. Si tocan state de ImGui u otra UI, proteger con mutex en el caller — no desde aqui.
- Last-Event-ID: si el servidor no manda `id:` por evento, el reconnect re-recibe el stream desde el inicio. Es responsabilidad del servidor enviar IDs si quiere resume sin repeticion.
- Si el servidor cierra graceful (FIN, exit 0 de curl), el cliente detecta EOF y reconecta segun `auto_reconnect`. Si el servidor devuelve error HTTP (4xx/5xx), curl tambien cierra y el cliente reconecta — no hay distincion entre "servidor no disponible" y "stream terminado limpiamente".
