# 8. HTTP API

🔵 Сервер отдаёт HTTP-управляющую плоскость плюс чат-эндпоинт, стримящий
Server-Sent Events (SSE). Список эндпоинтов печатается при старте в логе.

---

## 8.1 Эндпоинты

### Страницы и статика (публичные)

| Endpoint | Описание |
|---|---|
| `GET /` | markdown-сайт (лендинг) |
| `GET /site/` · `GET /site/:page` | тот же сайт в подпути |
| `GET /sessions` · `/budgets` · `/cron` · `/workflows` · `/dashboard` | админ-UI |
| `GET /agents/:name` | страница одного агента |
| `GET /widget` · `/widget.js` · `/dist/*` | виджет и его ассеты |
| `GET /demo` · `/demo/overlay` | демо виджета / оверлей поверх живых сайтов |
| `GET /distrib/*` | архивы дистрибутива (linux/windows zip + sha256, android apk) |
| `GET /proxy?url=…` · `/proxy/content?url=…` | HTTP-прокси для фронтенда |
| `GET /login` · `POST /login` · `POST /logout` | web-логин админ-UI |

### Публичный API

| Endpoint | Описание |
|---|---|
| `GET /health` | healthcheck |
| `GET /v1/models` | доступные модели |
| `GET /v1/templates` | шаблоны агентов (welcome, tools) |
| `POST /v1/chat/completions` | чат с агентом (SSE, собственный формат) |
| `POST /v1/continuations` | следующая страница лениво-пагинированной таблицы |
| `POST /v1/sessions/:id` | закрыть сессию (публичный — виджет шлёт `sendBeacon`) |
| `POST /v1/inbound/:template` | разовый ход агента от внешней системы (JSON, опционально bearer) |
| `POST /v1/user-ticket` | выдать HMAC-подписанный токен идентичности |
| `POST /v1/feedback` | like/dislike по ответу |
| `POST /v1/workflows/:name/webhook` | webhook-триггер воркфлоу (защищён токеном воркфлоу) |
| `/ext/**` (кастомные) | `http_route` / `static_route` — пользовательские REST-эндпоинты и статика, добавленные глобально (`config/routes/`) или агентами (`route_files:`); auth `public`/`bearer`/`token_env`/`user` на маршрут (см. §21) |

### Админ-API 🔒

| Endpoint | Описание |
|---|---|
| `GET /v1/agents` · `POST /v1/agents` | список агентов · создать агента |
| `GET /v1/agents/:name` | снапшот одного агента |
| `DELETE /v1/agents/:pid` | убить агента и потомков |
| `POST /v1/agents/:name/bench/run` | запустить бенчмарк шаблона |
| `GET /v1/agents/:name/bench/runs` · `/bench/runs/:id` | история и деталь бенчмарка |
| `GET /v1/sessions` | список сессий |
| `GET /v1/sessions/:id` · `DELETE /v1/sessions/:id` | деталь сессии · закрыть |
| `GET /v1/budgets` | бюджетные группы и процессы |
| `GET /v1/cron` · `DELETE /v1/cron/:name` | задания cron и история · удалить |
| `GET /v1/workflows` · `POST /v1/workflows/:name/run` | воркфлоу · запустить |
| `GET /v1/workflows/runs/:id` · `/runs/:id/events` | запуск воркфлоу · его события |
| `GET /v1/dashboard` | системный дашборд (нагрузка, токены, серии) |
| `GET /v1/feedback/stats` | статистика оценок |
| `GET /v1/users` | соответствия канал → пользователь |
| `POST /v1/users/link` · `POST /v1/users/:id/unlink` | слить/разъединить идентичности |
| `GET /v1/memory/:scope` | инспекция долгой памяти скоупа |

🔒 = требует `Authorization: Bearer <токен>`, когда настроена админ-auth
(см. §8.3).

### Пагинация

Списочные эндпоинты (`/v1/sessions`, `/v1/agents`, `/v1/templates`,
`/v1/budgets`) принимают `?limit=N&offset=M`. `limit` — размер страницы (обрезается
к 1–1000, по умолчанию 100), `offset` — сколько строк пропустить (по умолчанию 0).
Ответы включают `total` (а `/v1/sessions` — ещё `history_total`). `/v1/sessions`
добавляет `history_truncated: true`, когда прошлых сессий больше текущей страницы.

---

## 8.2 Chat completions

`POST /v1/chat/completions` стримит Server-Sent Events (SSE). Путь и форма
запроса похожи на OpenAI, но формат ответа **свой** — он не совместим с
OpenAI-SDK (нет `choices[].delta`, нет `data: [DONE]`).

```bash
curl http://127.0.0.1:3000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "ngu-agent",
    "messages": [{"role": "user", "content": "Как поступить в НГУ?"}]
  }'
```

Поля запроса:

| Поле | Обязат. | Смысл |
|---|---|---|
| `model` | да | имя шаблона (`ngu-agent`, `drom-agent`, …) или встроенный `assistant` |
| `messages` | да | список `{role, content}`; в контекст попадают роли `user` и `system` |
| `session` | нет | токен сессии — одна сессия = один изолированный агент. Опусти — legacy-режим (общий агент шаблона) |
| `stream` | нет | принимается для совместимости, **игнорируется** — всегда стрим |
| `temperature` | нет | принимается, игнорируется |
| `max_tokens` | нет | принимается, игнорируется |

Заголовок `X-User-Id` задаёт `user_id` для per-user-бюджетов и скоупа памяти.
Когда настроен `auth.web_user_secret_env`, сервер требует HMAC-подписанный
`X-User-Token` (из `POST /v1/user-ticket`); см. §8.8.

### Ответ (события SSE)

Тело — поток именованных SSE-событий; `done` — единственное, которое есть всегда.

| `event` | Полезная нагрузка `data` |
|---|---|
| `token` | `{"content": "<токен>", "final": bool}` |
| `tool_call` | `{"name": "<инструмент>", "body": "<рендер вызова>"}` |
| `tool_progress` | `{"name": "<инструмент>", "body": "<прогресс>"}` |
| `tool_result` | `{"name": "<инструмент>", "body": "<рендер результата>"}` |
| `result_blocks` | `{"name": "<инструмент>", "blocks": [ {type: "text"|"chart"|"table"|"file", …} ]}` |
| `budget` | `{"tokens": n, "prompt": n, "completion": n, "cached": n, "total": n, "cost": "$x.xxxx"}` |
| `done` | `{}` — стрим завершён |

`result_blocks` несёт визуальный слой инструмента (графики, таблицы, текст,
файлы); блок `table` может включать `continuation: {id}` — `POST /v1/continuations`
с этим id вернёт блоки следующей страницы, а блок `file` несёт `name`, `mime` и
`data` (base64) для скачивания. См. [общие поля](05-tools-core.md#51-общие-поля).

Ошибки (неизвестная модель, ошибка сессии, исчерпание бюджета, rate-limit)
сообщаются как `token`-события внутри `200`, а не HTTP-кодом ошибки.

---

## 8.3 Auth

Эндпоинты 🔒 принимают либо `Authorization: Bearer <токен>` (для API-клиентов),
либо cookie `agentos_session` (для браузера после web-логина).

- **Bearer-токены** — из `auth.admin_tokens` (карта `имя → токен` в
  `config/server.yaml`) или `auth.admin_token_env` (список `имя=токен`).
  **Имя** токена пишется в access-лог для аудита.
- **Web-логин** — из `auth.users` (`логин → пароль`). `POST /login` проверяет
  креды и ставит `HttpOnly` cookie (12 ч); логин пишется в access-лог.
  `POST /logout` очищает cookie. Браузерные UI (`/sessions`, `/budgets`)
  редиректят на `/login` при 401.

Если auth настроена, но ни один credential не разрешается — админ-API отдаёт 401
на каждый запрос (fail-closed). Без токенов и пользователей админ-API открыт
(dev-режим).

```bash
# API-клиент (bearer)
curl -H 'Authorization: Bearer <token>' http://127.0.0.1:3000/v1/sessions

# Web-логин (браузерный поток)
curl -c cookies.txt -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"<password>"}' \
  http://127.0.0.1:3000/login
curl -b cookies.txt http://127.0.0.1:3000/v1/sessions
```

---

## 8.4 Встраивание виджета

```html
<script src="http://127.0.0.1:3000/widget.js"></script>
```

Виджет отдаётся с `/widget`, общается с `/v1/chat/completions` и закрывает
сессии через `POST /v1/sessions/:id` (sendBeacon). Полный разбор виджета
(конфигурация, выбор модели, демо) — в §19.

---

## 8.5 Прокси

`/proxy` и `/proxy/content` скачивают URL для фронтенда. Ограничения:

- только `http`/`https`, порты 80/443;
- хосты только из `proxy.allow_hosts` (когда список непуст);
- приватные, loopback, link-local и reserved-диапазоны (включая
  `169.254.169.254`) блокируются всегда, на каждом редиректе.

---

## 8.6 Healthcheck

```bash
curl http://127.0.0.1:3000/health
```

---

## 8.7 HTTPS / TLS

Задай `AGENT_OS_TLS_CERT` и `AGENT_OS_TLS_KEY` (пути к PEM) — сервер поднимет
HTTPS. Иначе — обычный HTTP на `AGENT_OS_BIND` (по умолчанию `127.0.0.1:3000`).

---

## 8.8 Входящий webhook и идентичность

### Входящий webhook

`POST /v1/inbound/:template` — внешняя система (CRM, ERP, мониторинг, бэкенд
формы, …) запускает один ход агента и синхронно читает ответ — без SSE, без
массива `messages`.

```bash
curl -X POST http://127.0.0.1:3000/v1/inbound/ngu-agent \
  -H 'Content-Type: application/json' \
  -d '{"message":"какие общежития у НГУ?","session_id":"crm-123"}'
```

Запрос:

| Поле | Обязат. | Смысл |
|---|---|---|
| `message` | да | сообщение пользователя |
| `session_id` | нет | стабильный id для переиспользования контекста; опущен = новая сессия |
| `user_id` | нет | идентичность конечного пользователя (`Origin::Webhook`); опущен = аноним |

Ответ: `{"answer": "...", "status": "success", "session_id": "...",
"cost_usd": 0.0, "prompt_tokens": n, "completion_tokens": n, "data": null}`.

Auth — bearer-токен при заданном `inbound.token_env` (пусто = открыто, dev).

### Токены идентичности конечных пользователей

Когда `auth.web_user_secret_env` указывает env с HMAC-секретом, виджет получает
подписанный токен и шлёт его как `X-User-Token` вместо самоназванного
`X-User-Id`:

```bash
curl -X POST http://127.0.0.1:3000/v1/user-ticket \
  -H 'Content-Type: application/json' -d '{"user_id":"cw-browser-id"}'
# → {"user_id":"cw-browser-id","token":"cw-browser-id.<hmac>"}
```

Без секрета сервер доверяет `X-User-Id` напрямую (dev-режим).

### Канонические идентичности пользователей

`GET /v1/users` перечисляет соответствия `(origin, external_id) → canonical_id`;
оператор может слить два канальных id (например web-id и Telegram-id), чтобы
они делили память, бюджет и состояние:

```bash
curl -X POST http://127.0.0.1:3000/v1/users/link \
  -H 'Authorization: Bearer <token>' \
  -d '{"survivor":"cw-browser-id","merged":"tg:123"}'
```

Слияние мигрирует scoped state store, долгую память и in-memory бюджетные ведра;
дальнейшие сессии обоих каналов резолвятся в `survivor`.

---

## 8.9 Обратная связь (feedback)

Виджет шлёт оценки ответов (`like` / `dislike` / `cleared`) в `POST /v1/feedback`
(публичный), а статистика — в `GET /v1/feedback/stats` (🔒).

Оценка замыкает цикл качества: хук `memory` по лайку/дизлайку поднимает/опускает
`importance` фактов, вспомненных в этом ходе (§6.6). Тело запроса включает
`value`, `user_text`, `session`.
