# Contract: Chat Message Stream (SSE)

**Feature**: `003-chat-streaming`  
**Version**: API v1  
**Consumer**: `ChatFront/src/app/core/services/chat-api.service.ts`  
**Producer**: `ChatController::messageStream` → `ChatConversationStreamService`

## Endpoint

| Method | Path | Auth |
|--------|------|------|
| `POST` | `/api/v1/chat/message/stream` | `Authorization: Bearer {jwt}` |

**Not for attachments.** Use `POST /api/v1/chat/message` (multipart) for PDF/JPG.

## Request

**Headers**:
```
Authorization: Bearer {access_token}
Content-Type: application/json
Accept: text/event-stream
```

**Body**:
```json
{
  "message": "¿Cuántos clientes tenemos?"
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `message` | string | yes | Non-empty after trim; same rules as sync `ChatConversationRequest` |

## Response

**Success**: HTTP `200`  
**Headers**:
```
Content-Type: text/event-stream; charset=UTF-8
Cache-Control: no-cache, no-transform
Connection: keep-alive
X-Accel-Buffering: no
```

**Body format**: SSE frames. Each event:
```
data: {"type":"status","phase":"analyzing","message":"Analizando tu pregunta…"}

data: {"type":"chunk","content":"En total "}

data: {"type":"done","assistant_id":42,"content":"En total hay 150 clientes."}

```

- One JSON object per `data:` line.
- Events separated by blank line (`\n\n`).
- UTF-8 encoding.

## Event Types

### `status`

Emitted during processing before/during non-token phases.

```json
{
  "type": "status",
  "phase": "analyzing",
  "message": "Analizando tu pregunta…"
}
```

| `phase` | Typical `message` |
|---------|-------------------|
| `analyzing` | Analizando tu pregunta… |
| `querying` | Consultando datos… |
| `generating` | Preparando respuesta… |

### `chunk`

Incremental assistant text.

```json
{
  "type": "chunk",
  "content": "fragmento de texto"
}
```

- Client MUST append `content` to the in-progress assistant bubble in order received.

### `done`

Stream completed successfully; assistant message persisted.

```json
{
  "type": "done",
  "assistant_id": 42,
  "content": "texto completo de la respuesta"
}
```

- `content` MUST match persisted DB row.
- Client MAY replace streaming bubble with final content and set `id` from `assistant_id`.

### `error`

Processing failed; assistant message NOT persisted.

```json
{
  "type": "error",
  "message": "Ha ocurrido un problema al contactar con el servicio de IA. Inténtalo otra vez."
}
```

After `error`, server closes stream. Client shows toast/bubble error; user may retry.

## Error HTTP (non-SSE)

If the request fails before stream starts:

| Status | Body (JSON, ApiResponseTrait) |
|--------|--------------------------------|
| 401 | Unauthorized — redirect login |
| 422 | Validation error (`message` empty) |
| 422 | Domain error (same codes as sync chat via `DomainErrorMapper`) |

## Sync Endpoint (unchanged)

| Method | Path | Use |
|--------|------|-----|
| `POST` | `/api/v1/chat/message` | Attachments; fallback |

**Response** (unchanged):
```json
{
  "success": true,
  "data": {
    "user": { "content": "...", "meta": null },
    "assistant": { "id": 1, "content": "..." }
  }
}
```

## TypeScript Types (frontend)

Add to `ChatFront/src/app/core/models/api.types.ts`:

```typescript
export type ChatStreamPhase = 'analyzing' | 'querying' | 'generating';
export type ChatStreamEventType = 'status' | 'chunk' | 'done' | 'error';

export interface ChatStreamEvent {
  type: ChatStreamEventType;
  phase?: ChatStreamPhase;
  message?: string;
  content?: string;
  assistant_id?: number;
}

export interface ChatStreamHandlers {
  onStatus?: (event: ChatStreamEvent) => void;
  onChunk?: (event: ChatStreamEvent) => void;
  onDone?: (event: ChatStreamEvent) => void;
  onError?: (event: ChatStreamEvent) => void;
}
```

## FR Traceability

| Requirement | Contract element |
|-------------|------------------|
| FR-001 | Endpoint `POST .../message/stream` |
| FR-002 | Events `status` with `phase` + `message` |
| FR-003 | Events `chunk` |
| FR-004 | Event `done` after DB persist |
| FR-005 | JWT required |
| FR-006 | Attachments excluded; sync endpoint documented |
| FR-008 | Event `error` with user-facing `message` |
