239 lines
17 KiB
Markdown
239 lines
17 KiB
Markdown
# AGENTS.md — NexusAI
|
||
|
||
> Карта проекта для ИИ-агентов и разработчиков. Читай этот файл первым, чтобы не перечитывать весь код.
|
||
> Обновляй раздел **«Последние изменения»** и карту при значимых правках.
|
||
|
||
---
|
||
|
||
## 1. Что это
|
||
|
||
**NexusAI** — универсальный **MCP-сервис** для агентов. Даёт один набор инструментов для:
|
||
- **TTS** (text-to-speech), **STT** (speech-to-text, локально),
|
||
- генерации **изображений / видео / музыки**,
|
||
- **веб-поиска** (search-augmented модели),
|
||
- **vision** (описание изображения chat-моделью),
|
||
- служебных операций: список моделей, оценка стоимости, загрузка файла, статистика.
|
||
|
||
Плюс **веб-админка** (Fastify) для галереи контента, учёта трат и управления провайдерами/ключами.
|
||
|
||
Первый и основной провайдер — **NordRouter** (`https://nordrouter.com`, OpenAI-совместимый).
|
||
Архитектура провайдер-агностична: адаптеры добавляются в `registry`, слой инструментов не меняется.
|
||
|
||
---
|
||
|
||
## 2. Технологии
|
||
|
||
- **TypeScript**, monorepo через **npm workspaces** (CommonJS, `tsc`).
|
||
- **MCP**: `@modelcontextprotocol/sdk` (stdio-транспорт).
|
||
- **HTTP**: `undici` (fetch + ProxyAgent + FormData).
|
||
- **БД**: `better-sqlite3` (журнал + провайдеры).
|
||
- **Админка**: `fastify` + `@fastify/static`, фронт — ванильный HTML/CSS/JS (без сборки).
|
||
- **Схемы**: `zod` (+ `zod-to-json-schema` для MCP inputSchema).
|
||
- **STT**: Python sidecar `faster-whisper`, запускается через `uv`.
|
||
|
||
---
|
||
|
||
## 3. Карта проекта
|
||
|
||
```
|
||
NexusAI/
|
||
├── AGENTS.md # ← этот файл
|
||
├── README.md # пользовательская документация
|
||
├── TEST_RESUME.md # лог ручных тестов (search/STT/TTS/image/video/vision)
|
||
├── .env.example # все переменные окружения с описанием
|
||
├── package.json # workspaces + скрипты build/start/admin
|
||
├── sidecar/
|
||
│ └── stt_server.py # faster-whisper sidecar (JSON in argv → JSON stdout)
|
||
└── packages/
|
||
├── core/ # @nexusai/core — общая логика, БД, провайдеры
|
||
├── mcp-server/ # @nexusai/mcp-server — MCP stdio-сервер (инструменты)
|
||
└── admin/ # @nexusai/admin — веб-дашборд
|
||
```
|
||
|
||
### 3.1 `packages/core` (`@nexusai/core`)
|
||
|
||
| Файл | Назначение |
|
||
|---|---|
|
||
| `src/config.ts` | `loadConfig()` — читает все `NEXUS_*` / `NORDROUTER_*` env → `NexusConfig`. |
|
||
| `src/index.ts` | Реэкспорт всего публичного API core. |
|
||
| `src/db/journal.ts` | `Journal` — SQLite журнал генераций: `record/getById/list/deleteById/stats`. |
|
||
| `src/db/providers-store.ts` | `ProvidersStore` — провайдеры + **зашифрованные** ключи (AES-256-GCM). Отдаёт `ProviderView` с маской. |
|
||
| `src/security/crypto.ts` | `KeyCipher` (шифрование ключей), `maskSecret`. `enabled` = задан ли мастер-ключ. |
|
||
| `src/security/resolver.ts` | `KeyResolver` — эффективные настройки провайдера. **Приоритет ключа: ENV > DB**. Грациозный фолбэк, если DB-ключ нечитаем. |
|
||
| `src/media/storage.ts` | `MediaStorage` — скачивание URL/base64 на диск, `mimeToExt/extToMime`, `cleanup(ttlDays)`. |
|
||
| `src/media/content.ts` | `buildEmbeddedContent()` — упаковка файла в MCP-контент (image/audio) с лимитом. |
|
||
| `src/providers/types.ts` | Базовые типы: `Capability`, `MediaProvider`, `SearchProvider`, `SttProvider`, `ModelInfo` и пр. |
|
||
| `src/providers/registry.ts` | `ProviderRegistry` — карта name+capability → адаптер. |
|
||
| `src/providers/model-catalog.ts` | **`ModelCatalog`** — фоновая синхронизация каталога моделей + **fuzzy-подбор** модели (LCS + токены, дешёвая при равенстве), `ModelResolutionError` со списком моделей. |
|
||
| `src/providers/nordrouter/client.ts` | `NordRouterClient` — низкоуровневый HTTP (Bearer, rate-limit). |
|
||
| `src/providers/nordrouter/media.ts` | `NordRouterMediaProvider` — `/media/generate` + polling, `estimate`, `upload` (data-URL, MIME по расширению), `listModels`, `checkKey`. Capabilities: `tts,image,video,music`. |
|
||
| `src/providers/nordrouter/search.ts` | `NordRouterSearchProvider` — веб-поиск через `/v1/chat/completions` (perplexity/sonar-pro), citations из annotations. |
|
||
| `src/providers/nordrouter/index.ts` | `createNordRouterProviders()` — фабрика media+search. |
|
||
| `src/providers/local-stt/whisper.ts` | `LocalWhisperProvider` — спавнит sidecar, materialize аудио (path/url/base64). |
|
||
| `src/shared/http.ts` | `HttpClient` (retry, rate-limit, proxy, **FormData**), `RateLimiter`. |
|
||
| `src/shared/errors.ts` | `NexusError` + `codeFromHttpStatus`. |
|
||
| `src/shared/logger.ts` | `createLogger` — **пишет только в stderr** (stdout занят MCP). |
|
||
| `src/shared/polling.ts` | `pollUntilDone`, `sleep`. |
|
||
|
||
### 3.2 `packages/mcp-server` (`@nexusai/mcp-server`)
|
||
|
||
| Файл | Назначение |
|
||
|---|---|
|
||
| `src/index.ts` | Точка входа: поднимает MCP `Server` на stdio, регистрирует `ListTools`/`CallTool`. |
|
||
| `src/context.ts` | `buildContext()` — собирает `AppContext`: registry, storage, journal, providersStore, **catalogs** (Map<provider, ModelCatalog> с авто-синком), defaultProvider. |
|
||
| `src/schemas.ts` | Zod-схемы входа для всех инструментов. |
|
||
| `src/tools.ts` | `TOOLS[]` — описания + JSON Schema (генерится из zod). |
|
||
| `src/handlers.ts` | `Handlers` — реализация всех инструментов, запись в журнал, **резолв модели через catalog**, запись STT-транскрипта в `.md`. |
|
||
|
||
### 3.3 `packages/admin` (`@nexusai/admin`)
|
||
|
||
| Файл | Назначение |
|
||
|---|---|
|
||
| `src/server.ts` | Fastify: раздаёт `/media/:file` (с корректным Content-Type, incl. `text/markdown`), статику `public/`, API. |
|
||
| `src/context.ts` | `buildAdminContext()` — journal + providersStore + cipher (без registry). |
|
||
| `src/routes.ts` | REST API (см. §6). `KNOWN_TYPES = ['nordrouter','openai']`. |
|
||
| `public/index.html` | UI: вкладки, темы, галерея, модалка, автообновление. Ванильный CSS через `--vars`. |
|
||
| `public/app.js` | Логика UI: fetch API, рендер плиток, модалка, аудиоплеер, авто-refresh. |
|
||
|
||
---
|
||
|
||
## 4. MCP-инструменты (11)
|
||
|
||
| Инструмент | Что делает | Ключевые аргументы |
|
||
|---|---|---|
|
||
| `nexus_tts` | Синтез речи | `model` (default `audio/elevenlabs-tts`), `input.text/voice/...` |
|
||
| `nexus_image` | Генерация/редактирование картинок | `model` (default `image/nano-banana-2`), `input.prompt/image/...` |
|
||
| `nexus_video` | t2v / i2v (может занять минуты) | `model` (default `video/kling-2.6-t2v`), `input.prompt/image/...` |
|
||
| `nexus_music` | Генерация музыки | `model` (default `music/suno-v5`), `input...` |
|
||
| `nexus_stt` | Распознавание речи (локально) | `input.audioPath/audioUrl/audioBase64/language/model` |
|
||
| `nexus_web_search` | Веб-поиск с источниками | `query`, `recency`, `maxResults`, `model` |
|
||
| `nexus_vision` | Описание изображения chat-моделью | `image` (url/path/base64), `prompt`, `model` (default `google/gemini-3.1-flash-lite`) |
|
||
| `nexus_media_models` | Список моделей провайдера | `type`, `search` |
|
||
| `nexus_media_estimate` | Оценка стоимости до генерации | `model`, `input` |
|
||
| `nexus_media_upload` | Загрузка файла к провайдеру → URL | `path`, `name`, `mimeType` |
|
||
| `nexus_usage_stats` | Статистика из журнала | `sinceDays`, `list`, `limit` |
|
||
|
||
**Медиа-пайплайн** (`Handlers.media`): резолв модели через `ModelCatalog` → `provider.generate` (polling) → скачивание файла в media-dir → запись в журнал → возврат JSON (+ `note` при подмене модели).
|
||
|
||
**Fuzzy-резолв модели**: кривое имя → ближайшая (при равенстве — **дешевле**); нет совпадений → `BAD_REQUEST` с `available` (список категории, дешёвые первыми) и подсказкой попросить пользователя уточнить.
|
||
|
||
---
|
||
|
||
## 5. Возможности (capabilities)
|
||
|
||
`Capability = 'tts' | 'stt' | 'image' | 'video' | 'music' | 'search'`
|
||
(в журнале также встречается `vision`).
|
||
|
||
Маппинг NordRouter `type` → capability (в `model-catalog.ts`): `audio→tts`, `image→image`, `video→video`, `music→music`.
|
||
|
||
---
|
||
|
||
## 6. Admin REST API
|
||
|
||
| Метод | Путь | Назначение |
|
||
|---|---|---|
|
||
| GET | `/api/providers` | Список провайдеров (маска ключа), `knownTypes`, `cryptoEnabled`. |
|
||
| POST | `/api/providers` | Upsert провайдера (ключ шифруется). |
|
||
| POST | `/api/providers/:name/enabled` | Вкл/выкл. |
|
||
| POST | `/api/providers/:name/default` | Сделать default. |
|
||
| DELETE | `/api/providers/:name` | Удалить. |
|
||
| POST | `/api/providers/:name/check` | Проверка ключа реальным вызовом. |
|
||
| GET | `/api/stats?sinceDays=` | Статистика трат. |
|
||
| GET | `/api/generations?limit=&offset=&capability=` | Галерея (добавляет `mediaUrl`). |
|
||
| GET | `/api/generations/:id` | Одна запись. |
|
||
| DELETE | `/api/generations/:id` | Удалить запись + файл (только внутри media-dir). |
|
||
| GET | `/media/:file` | Раздача файла (Content-Type по расширению). |
|
||
|
||
---
|
||
|
||
## 7. Переменные окружения (главное)
|
||
|
||
```bash
|
||
# NordRouter
|
||
NORDROUTER_API_KEY=sk-...
|
||
NORDROUTER_BASE_URL=https://nordrouter.com
|
||
# Общие
|
||
NEXUS_DEFAULT_PROVIDER=nordrouter
|
||
NEXUS_MEDIA_DIR=./media-output # медиа + SQLite
|
||
NEXUS_DB_PATH=<media>/nexus.sqlite # опц.
|
||
NEXUS_MASTER_KEY=<hex32|passphrase> # шифрование ключей в БД (иначе только ENV-ключи)
|
||
NEXUS_EMBED_MAX_BYTES=10485760
|
||
NEXUS_LOG_LEVEL=info # логи → stderr
|
||
# STT
|
||
NEXUS_STT_PYTHON=uv
|
||
NEXUS_STT_MODEL=small # tiny|base|small|medium|large-v3 ИЛИ путь к CT2-модели
|
||
NEXUS_STT_DEVICE=auto ; NEXUS_STT_COMPUTE=auto
|
||
NEXUS_STT_LOCAL_ONLY=1 # оффлайн (без скачивания)
|
||
HF_ENDPOINT=https://hf-mirror.com # зеркало HF в закрытых сетях
|
||
# Admin
|
||
NEXUS_ADMIN_HOST=127.0.0.1 ; NEXUS_ADMIN_PORT=4123
|
||
# Proxy (опц.)
|
||
NEXUS_HTTP_PROXY=http://proxy:8080
|
||
```
|
||
|
||
Приоритет ключа: `<NAME>_API_KEY` (env) **>** зашифрованная запись в БД.
|
||
|
||
---
|
||
|
||
## 8. Команды
|
||
|
||
```bash
|
||
npm install
|
||
npm run build # core → mcp-server → admin (по отдельности: build:core|build:mcp|build:admin)
|
||
npm run start # запустить MCP-сервер (stdio)
|
||
npm run admin # запустить админку (http://127.0.0.1:4123)
|
||
```
|
||
|
||
MCP работает по stdio → **никогда не писать в stdout** кроме протокола; все логи идут в stderr.
|
||
|
||
### Локальный запуск админки в этой среде (фоновые процессы реапятся)
|
||
|
||
```bash
|
||
tmux new-session -d -s nexusadmin -c /home/user/moder/mcp/NexusAI \
|
||
"export NEXUS_MEDIA_DIR=/tmp/opencode/nexus-media NEXUS_MASTER_KEY=... NEXUS_ADMIN_PORT=4123; \
|
||
exec node packages/admin/dist/server.js >/tmp/opencode/admin_live.log 2>&1"
|
||
```
|
||
|
||
### Тестирование инструментов без полного MCP-хендшейка
|
||
|
||
Импортировать `buildContext` + `Handlers` из `dist/` и звать `handlers.handle('nexus_*', args)`.
|
||
⚠️ Скрипт должен лежать **внутри** `packages/mcp-server/` (иначе не резолвятся node_modules workspace).
|
||
Env для теста: `NEXUS_MEDIA_DIR`, `NEXUS_MASTER_KEY` (тот же, что при сохранении ключа), STT-переменные.
|
||
|
||
---
|
||
|
||
## 9. Важные особенности / подводные камни
|
||
|
||
- **stdout только для MCP.** Логи — в stderr (`createLogger`).
|
||
- **Ключ мастер-шифрования обязателен** для чтения ключей из БД; иначе только ENV-ключи. Без ключа сервис не падает (грациозный фолбэк).
|
||
- **NordRouter upload** ждёт `data:<mime>;base64,...` в поле `data`. MIME определяется по расширению — иначе `HTTP 415`.
|
||
- **Kling i2v** не принимает прямую ссылку на сгенерированный контент — нужен URL из `nexus_media_upload` (`?raw=1`).
|
||
- **Видео-модели** часто требуют полный набор полей (`duration`, `aspect_ratio`, `resolution`, ...), иначе `HTTP 400`.
|
||
- **STT** не качает модель в закрытой сети (HF недоступен). Варианты: `HF_ENDPOINT`, локальная модель + `NEXUS_STT_LOCAL_ONLY=1` (модель `faster-whisper-*` можно взять с ModelScope).
|
||
- **STT-транскрипт** сохраняется как `.md` в media-dir и виден в галерее/модалке (не путать с исходным аудио).
|
||
- **Стоимость видео** может превышать `estUsd` (например, Kling 2.6 i2v: est `$0.34` → факт `$0.69`).
|
||
- Некоторые модели бывают временно недоступны («Генерация временно недоступна — средства возвращены»).
|
||
- **ID моделей NordRouter для chat/vision**: `google/gemini-3.1-flash-lite`, `openai/gpt-5.x` (НЕ `gpt-4o`).
|
||
|
||
---
|
||
|
||
## 10. Последние изменения
|
||
|
||
Свежие сверху. Держи актуальным.
|
||
|
||
- **`8bd2e89`** — STT пишет транскрипт в `.md` (виден в галерее/модалке); `ModelCatalog` (фоновый синк + fuzzy-резолв с дешёвым фолбэком и подсказками); UI: аудиоплеер (кнопка в карточке + плеер в модалке), просмотр текстовых `.md`, автообновление активной вкладки без перезагрузки; `/media` отдаёт корректный Content-Type.
|
||
- **`b2f57cc`** — новый инструмент `nexus_vision`; фикс `nexus_media_upload` (MIME по расширению, чинит 415); `HttpClient` поддерживает FormData.
|
||
- **`c65f910`** — STT больше не пишет путь исходного аудио как `filePath` (убрана битая ссылка в галерее).
|
||
- **`1bc3746`** — Admin UX: светлая/тёмная тема, пагинация галереи, модалка + удаление, тип провайдера `openai`.
|
||
- **`6ff2b27`** — фикс краша MCP при наличии ключа в БД без `NEXUS_MASTER_KEY`.
|
||
- **`679e564`** — Admin (Phase 2): дашборд трат, галерея, управление провайдерами.
|
||
|
||
---
|
||
|
||
## 11. Куда добавлять новое
|
||
|
||
- **Новый провайдер** → адаптер в `core/src/providers/<name>/`, регистрация в `mcp-server/src/context.ts`, тип в `admin` `KNOWN_TYPES` при необходимости.
|
||
- **Новый инструмент** → схема в `schemas.ts`, ветка в `Handlers.handle`, описание в `tools.ts`.
|
||
- **Новое поле журнала** → миграция в `Journal.init`, обновить `GenerationRecord` и SELECT-колонки в `getById/list`.
|
||
- **UI** → `public/index.html` (разметка/CSS) + `public/app.js` (логика). Сборки нет — правки применяются сразу.
|