# API Design — Módulo AI

## Endpoints

Todos os endpoints são prefixados com o contexto da aplicação e seguem as convenções REST do projeto (OpenAPI 3.1 via SpringDoc).

| Método | Path | Descrição | Tag |
|---|---|---|---|
| `POST` | `/binaries/{binaryId}/chat/sessions` | Criar nova sessão de chat | Chat |
| `GET` | `/binaries/{binaryId}/chat/sessions` | Listar sessões do binário | Chat |
| `GET` | `/chat/sessions/{sessionId}` | Detalhes da sessão | Chat |
| `DELETE` | `/chat/sessions/{sessionId}` | Deletar sessão | Chat |
| `GET` | `/chat/sessions/{sessionId}/messages` | Histórico de mensagens | Chat |
| `POST` | `/chat/sessions/{sessionId}/messages` | Enviar mensagem (SSE stream) | Chat |

---

## 1. Criar Sessão

### `POST /binaries/{binaryId}/chat/sessions`

Cria uma nova sessão de chat vinculada a um binário.

**Query Parameters**:

| Parâmetro | Tipo | Obrigatório | Default | Descrição |
|---|---|---|---|---|
| `model` | String | Não | `app.ai.default-chat-provider` | Modelo LLM: `gemma4:12b`, `gemma4:e4b`, `deepseek-chat` |

**Request Body**: Vazio

**Response** (`201 Created`):

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "binaryId": "660e8400-e29b-41d4-a716-446655440001",
  "projectId": "770e8400-e29b-41d4-a716-446655440002",
  "title": "malware.exe — 2026-06-08T15:30:00Z",
  "chatModel": "gemma4:12b",
  "messageCount": 0,
  "createdAt": "2026-06-08T15:30:00Z",
  "updatedAt": "2026-06-08T15:30:00Z"
}
```

**Erros**:

| Código | Condição |
|---|---|
| `404` | Binário não encontrado |
| `400` | Modelo não suportado (ex: `?model=invalid-model`) |

**Exemplo curl**:

```bash
curl -X POST "http://localhost:8080/binaries/{binaryId}/chat/sessions?model=deepseek-chat"
```

---

## 2. Listar Sessões

### `GET /binaries/{binaryId}/chat/sessions`

Lista todas as sessões de chat de um binário, ordenadas por data de atualização (mais recente primeiro).

**Response** (`200 OK`):

```json
[
  {
    "id": "550e8400-...",
    "binaryId": "660e8400-...",
    "projectId": "770e8400-...",
    "title": "Analisando funções de rede",
    "chatModel": "deepseek-chat",
    "messageCount": 12,
    "createdAt": "2026-06-08T15:30:00Z",
    "updatedAt": "2026-06-08T16:45:00Z"
  },
  {
    "id": "550e8401-...",
    "binaryId": "660e8400-...",
    "projectId": "770e8400-...",
    "title": "Investigando crypto",
    "chatModel": "gemma4:12b",
    "messageCount": 5,
    "createdAt": "2026-06-08T14:00:00Z",
    "updatedAt": "2026-06-08T14:20:00Z"
  }
]
```

**Exemplo curl**:

```bash
curl "http://localhost:8080/binaries/{binaryId}/chat/sessions"
```

---

## 3. Detalhes da Sessão

### `GET /chat/sessions/{sessionId}`

Retorna os detalhes de uma sessão específica.

**Response** (`200 OK`):

```json
{
  "id": "550e8400-...",
  "binaryId": "660e8400-...",
  "projectId": "770e8400-...",
  "title": "Analisando funções de rede",
  "chatModel": "deepseek-chat",
  "messageCount": 12,
  "createdAt": "2026-06-08T15:30:00Z",
  "updatedAt": "2026-06-08T16:45:00Z"
}
```

**Erros**: `404` se sessão não encontrada.

---

## 4. Deletar Sessão

### `DELETE /chat/sessions/{sessionId}`

Deleta a sessão e todas as suas mensagens (cascade).

**Response**: `204 No Content`

**Exemplo curl**:

```bash
curl -X DELETE "http://localhost:8080/chat/sessions/{sessionId}"
```

---

## 5. Histórico de Mensagens

### `GET /chat/sessions/{sessionId}/messages`

Retorna o histórico de mensagens da sessão, ordenado cronologicamente (mais antigo primeiro).

**Response** (`200 OK`):

```json
[
  {
    "id": "aa0e8400-...",
    "sessionId": "550e8400-...",
    "role": "USER",
    "content": "Quais funções lidam com rede?",
    "tokenCount": 12,
    "createdAt": "2026-06-08T15:30:30Z"
  },
  {
    "id": "bb0e8400-...",
    "sessionId": "550e8400-...",
    "role": "ASSISTANT",
    "content": "O binário importa funções de rede da WS2_32.dll. As funções sub_401200 e sub_401300 utilizam send() e recv()...",
    "tokenCount": 85,
    "createdAt": "2026-06-08T15:30:35Z"
  }
]
```

**Erros**: `404` se sessão não encontrada.

---

## 6. Enviar Mensagem (SSE Streaming)

### `POST /chat/sessions/{sessionId}/messages`

Envia uma mensagem do usuário e retorna a resposta da LLM em streaming via Server-Sent Events (SSE).

**Headers**:

```
Content-Type: application/json
Accept: text/event-stream
```

**Request Body**:

```json
{
  "content": "Quais funções nesse binário lidam com criptografia?"
}
```

**Response**: `200 OK` com `Content-Type: text/event-stream`

### Protocolo SSE

O stream consiste em uma sequência de eventos `data:`:

#### Evento: `chunk`

Fragmento de texto da resposta, enviado assim que gerado pela LLM:

```
data: {"type":"chunk","content":"O binário"}

data: {"type":"chunk","content":" utiliza "}

data: {"type":"chunk","content":"as seguintes"}

data: {"type":"chunk","content":" funções"}

data: {"type":"chunk","content":" relacionadas"}

data: {"type":"chunk","content":" a criptografia:"}

data: {"type":"chunk","content":"\n\n1. **sub_405000**"}

data: {"type":"chunk","content":" em 0x405000"}

...
```

#### Evento: `done`

Indica o fim do stream:

```
data: {"type":"done","sessionId":"550e8400-..."}
```

#### Evento: `error`

Indica um erro durante o processamento:

```
data: {"type":"error","message":"Failed to generate embedding: Ollama connection refused"}
```

Após um evento `error`, a conexão é fechada. Nenhum evento `done` é enviado.

### Tipos de Evento SSE

| `type` | Descrição | Campos |
|---|---|---|
| `chunk` | Fragmento de texto da resposta | `content: String` |
| `thinking` | IA está processando/raciocinando | `message: String` |
| `tool_call_start` | IA vai chamar uma ferramenta | `tool: String` |
| `tool_call_end` | Ferramenta executada | `tool: String`, `durationMs: long`, `resultSummary: String` |
| `done` | Stream concluído com sucesso | `sessionId: UUID` |
| `error` | Erro durante o processamento | `message: String` |

#### Exemplo: resposta com tool calling

```
data: {"type":"tool_call_start","tool":"searchFunctions"}

data: {"type":"tool_call_end","tool":"searchFunctions","durationMs":23,"resultSummary":"5 results: sub_401000, ..."}

data: {"type":"tool_call_start","tool":"getFunctionAssembly"}

data: {"type":"tool_call_end","tool":"getFunctionAssembly","durationMs":15,"resultSummary":"sub_401000 at 0x00401000"}

data: {"type":"chunk","content":"A função sub_401000"}

data: {"type":"chunk","content":" em 0x00401000"}

data: {"type":"chunk","content":" realiza a inicialização..."}

data: {"type":"done","sessionId":"550e8400-..."}
```

#### Exemplo: resposta direta (sem tools)

```
data: {"type":"chunk","content":"Olá!"}

data: {"type":"chunk","content":" Como posso ajudar?"}

data: {"type":"done","sessionId":"550e8400-..."}
```

### Fluxo Interno Durante o SSE

```
1. Salva UserMessage (role=USER) no banco
2. Embedda a query do usuário (qwen3-embedding)
3. Busca similaridade no pgvector (top-K chunks)
4. (Opcional) Expande call graph
5. Constrói system prompt com contexto RAG
6. Monta lista de mensagens: [SystemPrompt, history, UserMessage]
7. Chama ChatClient da LLM com tools registradas
8. Se o modelo decide usar tools:
   a. Envia SSE "tool_call_start" para cada tool
   b. Executa a tool, mede duração
   c. Envia SSE "tool_call_end" com nome + duração + resumo
   d. Reenvia resultados ao modelo, volta ao passo 7
9. Para cada token gerado na resposta final: envia SSE "chunk"
10. Ao finalizar: salva AssistantMessage (role=ASSISTANT) no banco
11. Envia SSE "done" com sessionId
12. Se erro em qualquer passo: envia SSE "error" e fecha conexão
```

### Exemplo curl (com streaming)

```bash
curl -X POST "http://localhost:8080/chat/sessions/{sessionId}/messages" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"content": "Explique a função sub_401000"}'
```

### Implementação no Controller

```java
@PostMapping(value = "/chat/sessions/{sessionId}/messages",
             produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> sendMessage(
        @PathVariable UUID sessionId,
        @Valid @RequestBody ChatRequest request) {

    return chatService.chat(sessionId, request.content())
        .map(this::toSseEvent)
        .onErrorResume(e -> Flux.just(errorEvent(e)));
}
```

O controller delega ao `ChatService.chat()` que retorna `Flux<StreamEvent>`. Cada `StreamEvent` é mapeado para o SSE correspondente:

- `StreamEvent.Chunk` → `{"type":"chunk","content":"..."}`
- `StreamEvent.ToolCallStart` → `{"type":"tool_call_start","tool":"..."}`
- `StreamEvent.ToolCallEnd` → `{"type":"tool_call_end","tool":"...","durationMs":N,"resultSummary":"..."}`
- `StreamEvent.Thinking` → `{"type":"thinking","message":"..."}`
- `StreamEvent.Done` → `{"type":"done","sessionId":"..."}`
- `StreamEvent.Error` → `{"type":"error","message":"..."}`

---

## DTOs

### `ChatRequest`

```java
public record ChatRequest(
    @NotBlank @Size(min = 1, max = 4000)
    String content
) {}
```

### `StreamEvent` (eventos SSE)

```java
public sealed interface StreamEvent
    permits StreamEvent.Chunk, StreamEvent.Thinking,
            StreamEvent.ToolCallStart, StreamEvent.ToolCallEnd,
            StreamEvent.Done, StreamEvent.Error {

  record Chunk(String content) implements StreamEvent {}

  record Thinking(String message) implements StreamEvent {}

  record ToolCallStart(String tool) implements StreamEvent {}

  record ToolCallEnd(String tool, long durationMs, String resultSummary)
      implements StreamEvent {}

  record Done(String sessionId) implements StreamEvent {}

  record Error(String message) implements StreamEvent {}
}
```

### `SessionResponse`

```java
public record SessionResponse(
    UUID id,
    UUID binaryId,
    UUID projectId,
    String title,
    String chatModel,
    int messageCount,
    Instant createdAt,
    Instant updatedAt
) {}
```

### `MessageResponse`

```java
public record MessageResponse(
    UUID id,
    UUID sessionId,
    String role,
    String content,
    Integer tokenCount,
    Instant createdAt
) {}
```

---

## Tratamento de Erros

| Código HTTP | `type` SSE | Condição |
|---|---|---|
| `400` | — | Request body inválido (content vazio, >4000 chars) |
| `404` | — | Sessão não encontrada |
| `404` | — | Binário não encontrado |
| `503` | `error` | Ollama indisponível (embeddings ou chat) |
| `503` | `error` | DeepSeek API indisponível |
| `500` | `error` | Erro interno (pgvector, RabbitMQ, etc.) |
| `402` | `error` | DeepSeek API: saldo insuficiente / quota excedida |

### Resposta de erro padrão (não-SSE)

```json
{
  "status": 404,
  "message": "Chat session with id '550e8400-...' not found",
  "timestamp": "2026-06-08T15:30:00Z"
}
```

---

## OpenAPI / Swagger

Todos os endpoints são documentados com `@Operation` e aparecem no Swagger UI (`/swagger-ui.html`).

**Tag**: `Chat`

**Schemas**:
- `ChatRequest`
- `SessionResponse`
- `MessageResponse`
- `StreamEvent` (não documentado como schema, pois é SSE)
