# 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 с авто-синком), 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=/nexus.sqlite # опц. NEXUS_MASTER_KEY= # шифрование ключей в БД (иначе только 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 ``` Приоритет ключа: `_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:;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//`, регистрация в `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` (логика). Сборки нет — правки применяются сразу.