diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..39fb6c7 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,465 @@ +# Navi API Reference + +Base URL: `http://localhost:8000` + +--- + +## REST API + +### Health + +#### `GET /health` + +Проверка доступности сервера. + +**Response `200`** +```json +{ "status": "ok" } +``` + +--- + +### Profiles & Tools + +#### `GET /agents/profiles` + +Список доступных профилей агента. + +**Response `200`** +```json +[ + { + "id": "secretary", + "name": "Secretary", + "description": "General-purpose assistant", + "enabled_tools": ["todo", "web_search", "filesystem", "..."], + "llm_backend": "ollama", + "model": "gemma4:e2b-it-q8_0" + } +] +``` + +#### `GET /agents/tools` + +Список всех зарегистрированных инструментов (built-in + user tools). + +**Response `200`** +```json +[ + { "name": "web_search", "description": "Search the web using DuckDuckGo." }, + { "name": "filesystem", "description": "Read, write and list files." } +] +``` + +--- + +### Sessions + +Сессия — это контейнер для диалога с агентом. Каждая сессия привязана к профилю и хранит историю сообщений. + +#### `POST /sessions` + +Создать новую сессию. + +**Request body** +```json +{ "profile_id": "secretary" } +``` + +**Response `201`** +```json +{ + "session_id": "550e8400-e29b-41d4-a716-446655440000", + "profile_id": "secretary", + "created_at": "2026-04-10T18:00:00+00:00" +} +``` + +**Errors** +- `404` — профиль не найден + +--- + +#### `GET /sessions` + +Список всех сессий, отсортированных по активности (закреплённые первыми). + +**Response `200`** +```json +[ + { + "session_id": "550e8400-...", + "profile_id": "secretary", + "message_count": 12, + "preview": "Последние 60 символов последнего сообщения", + "pinned": false, + "created_at": "2026-04-10T15:00:00+00:00", + "last_active": "2026-04-10T18:00:00+00:00" + } +] +``` + +--- + +#### `GET /sessions/{session_id}` + +Полная информация о сессии с историей сообщений (display history — никогда не сжимается). + +**Response `200`** +```json +{ + "session_id": "550e8400-...", + "profile_id": "secretary", + "created_at": "...", + "last_active": "...", + "messages": [ + { + "role": "user", + "content": "Привет", + "created_at": "2026-04-10T18:00:00+00:00" + }, + { + "role": "assistant", + "content": "Привет. Чем помочь?", + "created_at": "2026-04-10T18:00:05+00:00" + }, + { + "role": "assistant", + "tool_calls": [ + { + "id": "abc123", + "name": "web_search", + "arguments": { "query": "..." } + } + ] + }, + { + "role": "tool", + "content": "результат инструмента", + "tool_call_id": "abc123", + "name": "web_search" + } + ] +} +``` + +Поля сообщения (`role` всегда присутствует, остальные — по наличию): + +| Поле | Тип | Описание | +|----------------|----------------------|----------| +| `role` | `user\|assistant\|tool\|system` | Автор сообщения | +| `content` | `string\|null` | Текст сообщения | +| `images` | `string[]` | Base64-строки изображений (user/assistant) | +| `tool_calls` | `ToolCall[]` | Вызовы инструментов (assistant) | +| `tool_call_id` | `string` | ID вызова, к которому относится ответ (tool) | +| `name` | `string` | Имя инструмента (tool) | +| `created_at` | `string` (ISO 8601) | Время создания | +| `is_summary` | `bool` | Сжатый блок истории (injected by compressor) | + +**Errors** +- `404` — сессия не найдена + +--- + +#### `DELETE /sessions/{session_id}` + +Удалить сессию и её файлы. + +**Response `204`** — нет тела + +**Errors** +- `404` — сессия не найдена + +--- + +#### `PATCH /sessions/{session_id}/pin` + +Закрепить или открепить сессию. + +**Request body** +```json +{ "pinned": true } +``` + +**Response `200`** +```json +{ "session_id": "...", "pinned": true } +``` + +--- + +#### `GET /sessions/{session_id}/context` + +LLM-контекст сессии (то, что модель реально видит). Может отличаться от `messages` — сжатые истории заменяют часть сообщений. Endpoint для отладки. + +**Response `200`** +```json +{ + "session_id": "...", + "profile_id": "secretary", + "message_count": 8, + "total_chars": 4200, + "context": [ ...сообщения в том же формате, что и messages... ] +} +``` + +--- + +#### `POST /sessions/{session_id}/files` + +Загрузить файл для сессии. Используется перед отправкой сообщения, если нужно приложить файл. + +**Request**: `multipart/form-data`, поле `file`. + +**Ограничения** +- Максимальный размер: 200 MB +- Запрещённые расширения: `.exe`, `.dll`, `.so`, `.sh`, `.bat`, `.cmd`, `.ps1`, `.vbs`, `.bin`, `.elf` и другие исполняемые форматы +- При совпадении имён файл переименовывается (`file_1.txt`, `file_2.txt`, ...) +- Файлы удаляются автоматически через 24 часа неактивности сессии + +**Response `201`** +```json +{ + "name": "report.pdf", + "size": 102400, + "path": "session_files/550e8400-.../report.pdf", + "content_type": "application/pdf" +} +``` + +**Errors** +- `400` — запрещённое расширение +- `404` — сессия не найдена +- `413` — файл превышает лимит + +--- + +### Messages (non-streaming) + +#### `POST /sessions/{session_id}/messages` + +Отправить сообщение и получить ответ синхронно (без стриминга). Блокирует до завершения всего цикла агента. + +**Request body** +```json +{ "content": "Сколько звёзд в галактике?" } +``` + +**Response `200`** +```json +{ "role": "assistant", "content": "По оценкам, от 100 до 400 миллиардов." } +``` + +**Errors** +- `404` — сессия не найдена +- `500` — ошибка агента или превышен лимит итераций + +> Для production-клиентов предпочтительнее WebSocket — он даёт стриминг, прогресс выполнения инструментов и мышление модели. + +--- + +## WebSocket + +### `WS /ws/sessions/{session_id}` + +Основной канал для общения с агентом в реальном времени. Поддерживает стриминг текста, стриминг мышления, события инструментов, прикрепление файлов и изображений. + +**Подключение**: если сессия не найдена, сервер закрывает соединение с кодом `4004`. + +**Reconnect**: если клиент переподключается во время активного стрима, сервер автоматически дошлёт пропущенные события — никаких дополнительных действий не требуется. + +--- + +### Client → Server + +Все сообщения от клиента — JSON-объекты. + +#### Отправка сообщения + +```json +{ + "type": "message", + "content": "Текст сообщения", + "images": ["base64string...", "..."], + "files": [ + { "name": "report.pdf", "size": 102400, "path": "session_files/.../report.pdf" } + ] +} +``` + +| Поле | Обязательно | Описание | +|-----------|-------------|----------| +| `type` | да | Всегда `"message"` | +| `content` | да | Текст сообщения (непустой) | +| `images` | нет | Список base64-строк изображений. Допускается как чистый base64, так и `data:image/...;base64,...` — сервер обрежет префикс автоматически | +| `files` | нет | Файлы, загруженные через `POST /sessions/{id}/files`. Сервер дописывает их пути в текст сообщения, чтобы агент знал об их существовании | + +--- + +### Server → Client + +Сервер присылает события последовательно в порядке их возникновения. + +#### `stream_start` +```json +{ "type": "stream_start" } +``` +Начало обработки сообщения. Клиент должен заблокировать ввод. + +--- + +#### `thinking_delta` +```json +{ "type": "thinking_delta", "delta": "фрагмент мышления..." } +``` +Фрагмент внутреннего рассуждения модели (streaming). Приходит только если у модели включён режим thinking. Накапливайте `delta` до `thinking_end`. + +--- + +#### `thinking_end` +```json +{ "type": "thinking_end" } +``` +Мышление завершено. После этого начнётся либо стриминг текста (`stream_delta`), либо вызов инструментов. + +--- + +#### `turn_thinking` +```json +{ + "type": "turn_thinking", + "thinking": "полный текст рассуждения...", + "is_subagent": false +} +``` +Блок мышления модели во время выбора инструмента. Приходит целиком (не по кускам). `is_subagent: true` означает, что мышление от субагента внутри `spawn_agent`. + +--- + +#### `tool_started` +```json +{ + "type": "tool_started", + "tool": "web_search", + "args": { "query": "погода в москве" }, + "is_subagent": false +} +``` +Агент начал выполнение инструмента. Приходит немедленно, до завершения вызова — для показа спиннера. `is_subagent: true` — вызов из субагента. + +--- + +#### `tool_call` +```json +{ + "type": "tool_call", + "tool": "web_search", + "args": { "query": "погода в москве" }, + "result": "Сегодня в Москве +12°C, облачно.", + "success": true, + "is_subagent": false +} +``` +Инструмент завершил работу. Приходит после `tool_started` с тем же `tool` и `args`. `success: false` — инструмент вернул ошибку. + +--- + +#### `stream_delta` +```json +{ "type": "stream_delta", "delta": "фрагмент ответа..." } +``` +Фрагмент финального текстового ответа агента (streaming). Накапливайте в строку. + +--- + +#### `stream_end` +```json +{ + "type": "stream_end", + "content": "полный текст ответа", + "context_tokens": 4913, + "max_context_tokens": 65536 +} +``` +Агент завершил ответ. `content` — полный накопленный текст (дублирует сумму `stream_delta`). `context_tokens` — сколько токенов использовано в контексте. Клиент должен разблокировать ввод. + +--- + +#### `context_compressed` +```json +{ + "type": "context_compressed", + "messages_before": 42, + "messages_after": 12 +} +``` +Контекст был автоматически сжат после ответа (компрессия срабатывает при заполнении ≥80% контекстного окна). Информационное событие. + +--- + +#### `error` +```json +{ "type": "error", "message": "Session not found" } +``` +Ошибка обработки. После некоторых ошибок стрим продолжается, после других — завершается. + +--- + +### Типичная последовательность событий + +**Простой вопрос без инструментов:** +``` +stream_start +thinking_delta × N (если модель думает) +thinking_end +stream_delta × N +stream_end +``` + +**Запрос с вызовом инструментов:** +``` +stream_start +turn_thinking (мышление при выборе инструмента, если есть) +tool_started +tool_call +turn_thinking (мышление перед следующим инструментом, если есть) +tool_started +tool_call +thinking_delta × N (финальный ответ) +thinking_end +stream_delta × N +stream_end +context_compressed (опционально, если контекст переполнен) +``` + +**Запрос с субагентом (`spawn_agent`):** +``` +stream_start +tool_started (spawn_agent, is_subagent=false) + turn_thinking (is_subagent=true) + tool_started (инструмент субагента, is_subagent=true) + tool_call (is_subagent=true) +tool_call (spawn_agent завершён, is_subagent=false) +stream_delta × N +stream_end +``` + +--- + +## Файлы + +**Статика клиента**: `GET /static/**` — раздаётся из директории `client/`. Заголовок `Cache-Control: no-store`. + +**Загруженные файлы сессии**: хранятся в `session_files/{session_id}/`. Агент обращается к ним напрямую через инструмент `filesystem`. Удаляются через 24 часа неактивности сессии или при удалении сессии. + +--- + +## Коды ошибок + +| HTTP | Причина | +|------|---------| +| `400` | Запрещённый тип файла | +| `404` | Сессия или профиль не найдены | +| `413` | Файл превышает 200 MB | +| `500` | Внутренняя ошибка агента | +| WS `4004` | Сессия не найдена при подключении |