# Задание A42: подключение провайдеров — OpenRouter, NVIDIA, Ollama, квота Codex ## Дата поступления 2026-08-31 ## База `origin/main` (`ff303b5`) — туда уже слиты правки ревьюера по вебу и A41 (чистая первая установка). ``` git fetch origin --prune git checkout -b antigravity/a42-provider-connect origin/main ``` В `main` напрямую не пушить. ## Порядок исполнения Два прохода: **Flash** реализует, **Pro** проводит аудит. Пункт **P0-5** написан для аудитора. Это задание **по коду**. Вёрстка и холст — отдельное задание A43, туда не залезать. --- ## Задача Владелец сообщает: «опенроутер не подключается, нвидиа не подключаются, кодекс выдаёт ошибку по квоте, хотя квоты полные, оллама не выдаёт облачные модели». Причины найдены ревьюером и проверены по коду. Заново их выяснять не нужно — нужно чинить. ## Что проверено ревьюером ### OpenRouter и NVIDIA реализованы наполовину ``` adapters/__init__.py OpenRouterAdapter и NvidiaAdapter в реестре есть web/static/index.html:163 пункты в списке провайдеров есть app.js:2482 шаг мастера с полем API-ключа и Base URL есть action_handler.py:574 add_account сохраняет ТОЛЬКО для (local, local-llm, llama.cpp, ollama, vllm) и только при непустом base_url action_handler.py:588 для всех остальных возвращается ok=True с текстом «Навигация» — и не сохраняется ничего ``` То есть мастер докладывает об успехе и **не сохраняет ничего**. Аккаунт не появляется, потому что его никто не создал. Дальше по цепочке пусто тоже: ``` auto_assigner.py:129 слоты объявлены для ollama; openrouter и nvidia отсутствуют auto_assigner.py:219 роли по умолчанию — то же самое router_config.py в конфигурации по умолчанию нет ни одного из трёх model_discovery_service.py:216 _probe_provider не имеет ветки ни для openrouter, ни для nvidia, и возвращает None ``` ### Ollama обнаруживает не то `model_discovery_service.py:325` заводит `ollama` в одну ветку с `local`, `llama.cpp`, `vllm`. Эта ветка: 1. перебирает **зашитые** идентификаторы `local-1` и `local-2` — профиль `ollama-1` не смотрит вообще; 2. читает учётные данные провайдера `local`, а не `ollama`; 3. по умолчанию идёт на `http://127.0.0.1:8081/v1` — это порт llama.cpp, а не Ollama (11434); 4. дёргает `/v1/models` и ничего не знает про облачные модели Ollama. Скриншот владельца: «Список моделей ещё не получен от провайдера ollama» при подключённом `ollama-1`. Та же болезнь рядом: ветка Codex перебирает зашитые `codex-orch`, `codex-worker-1`, `codex-worker-2`. После A26 идентификаторы выдаются автоматически (`codex-4`, `codex-5`), и такой профиль обнаружение пропустит. ### «Квота исчерпана» при полной квоте На скриншоте у Codex значок «Квота исчерпана», а Session и Weekly показывают `Н/Д`. То есть **вердикт об исчерпании выносится там, где квота не измерена вовсе**. ``` unified_health.py:467 ветка: max_cd > 0 либо overall_state == QUOTA_EXHAUSTED → health_state = STATUS_QUOTA_EXHAUSTED unified_health.py:470 ветка RATE_LIMITED идёт НИЖЕ ``` `max_cd` берётся из `frec.reset_at > now` — это **окно отката после ошибки**, а не остаток квоты. Отсюда два разных дефекта: 1. Любой откат показывается как исчерпание квоты, хотя квота может быть полной. 2. Ветка `RATE_LIMITED` практически мертва: при активном лимите запросов `reset_at` всегда в будущем, поэтому строка 467 срабатывает раньше и лимит запросов выдаёт себя за исчерпанную квоту. И третье, в `health_tracker.py:483`: при пустом имени модели или значении `default` исчерпанным помечается **весь аккаунт** (`record.overall_state`). Hermes имя модели передаёт не всегда. Классификатор в `codex_adapter.py:132` ловит подстроку `quota` в любом месте текста ошибки — проверить, не попадают ли туда сообщения, к квоте не относящиеся. --- ## P0-1. OpenRouter и NVIDIA подключаются по-настоящему 1. **`add_account` сохраняет профиль** для `openrouter`, `nvidia`, `nvidia-nim`: создаёт определение профиля, пишет учётные данные (ключ и адрес), назначает роль — по образцу существующей локальной ветки. 2. **Слоты и роли по умолчанию** для обоих провайдеров в `AutoAssigner`, как сделано для `ollama`. 3. **Адреса по умолчанию**: `https://openrouter.ai/api/v1` и `https://integrate.api.nvidia.com/v1`; владелец может переопределить в мастере. 4. **Ветка с мнимым успехом не должна остаться ловушкой.** Провайдер, для которого сохранение не реализовано, обязан получать честный отказ с причиной, а не `ok: True`. Это главное требование пункта: молчаливый успех стоил владельцу нескольких попыток подключения. ## P0-2. Обнаружение моделей для трёх провайдеров 1. **OpenRouter**: запрос списка моделей по адресу профиля с его ключом. 2. **NVIDIA**: то же самое. 3. **Ollama — отдельная ветка**, не общая с llama.cpp: - адрес берётся из **самого профиля**, а не из зашитых `local-1`/`local-2`; - учётные данные читаются для провайдера `ollama`; - по умолчанию `http://127.0.0.1:11434`; - локальные модели — через нативный `/api/tags`; - **облачные модели Ollama** — отдельный источник, требующий ключа. Выяснить по действующей документации Ollama способ и адрес; **не выдумывать эндпоинт**. Если способ не подтверждён — так и написать в отчёте, а в интерфейсе показать `Н/Д` с причиной. 4. **Зашитые идентификаторы профилей убрать везде**, включая ветку Codex: перебирать профили провайдера из конфигурации. После A26 идентификаторы выдаются автоматически, и любой зашитый список рано или поздно промахнётся. 5. **Ошибка обнаружения показывается с текстом ответа сервера.** Сейчас `_probe_provider` возвращает `None` и когда ветки нет, и когда сервер отказал — владелец не может отличить одно от другого. ## P0-3. Квота говорит только то, что измерено 1. **Откат после ошибки — это не исчерпание квоты.** Разделить состояния: исчерпание объявлять по измеренному остатку, откат показывать как откат с причиной и временем окончания. 2. **Порядок веток исправить**: лимит запросов не должен выдавать себя за исчерпанную квоту. 3. **Ошибка без имени модели не помечает весь аккаунт.** Помечать конкретное семейство; общий вердикт — только при подтверждении. 4. **Ярлык называет источник.** «Квота исчерпана» — когда есть измерение. Иначе «Откат до HH:MM после ошибки: текст». 5. **Классификатор Codex** проверить на ложные срабатывания подстроки `quota`. ## P0-4. Проверка исполнением Заглушек недостаточно, но и ключей владельца у исполнителя нет. Поэтому: 1. **Сохранение профиля** проверить с заведомо неверным ключом: профиль обязан создаться, а проверка подключения — вернуть внятную ошибку авторизации, а не тишину. 2. **Ollama** проверить на живом сервере: локальные модели через `/api/tags` обязаны появиться в списке. 3. **Квота**: смоделировать откат после ошибки и убедиться, что интерфейс не пишет «квота исчерпана» при неизмеренной квоте. 4. **Отказ вместо мнимого успеха** проверить отдельно. ## P0-5. Аудит вторым проходом 1. **Искать оставшиеся зашитые идентификаторы профилей** по всему коду — это повторяющийся класс дефекта. 2. **Проверить, что мнимых успехов не осталось**: действие, ничего не сохранившее, не возвращает `ok: True`. 3. **Эндпоинт облачных моделей Ollama** сверить с документацией. Выдуманный адрес — дефект того же рода, что выдуманные метрики в A38. 4. **Побочные изменения** объяснить. 5. **Пропущенный пункт назвать пропущенным.** --- ## Ограничения - Ключи владельца не запрашивать и в репозиторий не класть. - Каталог `~/.hermes/agy_profiles/` не трогать. - Вёрстку и холст не менять — это A43. - Версию `0.1.1` не поднимать. - Правило честности без исключений: неизмеренное показывать как `Н/Д` с причиной. ## Критерии приёмки 1. Ветка в `origin`, `git status` чист. 2. OpenRouter и NVIDIA подключаются: профиль создаётся, учётные данные сохраняются, аккаунт виден в списке; проверено. 3. Действие, ничего не сохранившее, возвращает отказ с причиной; проверено. 4. Обнаружение моделей работает для openrouter, nvidia и ollama; для Ollama проверено на живом сервере. 5. Зашитых идентификаторов профилей в обнаружении не осталось. 6. Ошибка обнаружения доходит до интерфейса с текстом. 7. Откат после ошибки не показывается как исчерпание квоты; лимит запросов показывается как лимит запросов. 8. Ошибка без имени модели не помечает весь аккаунт. 9. `ruff check .` чисто; релизный гейт не ухудшен. 10. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, `X passed / Y skipped / Z failed`. На `origin/main` сейчас **491 passed**. ## Главное Два провайдера нельзя подключить вовсе, и мастер при этом рапортует об успехе — владелец несколько раз повторял заведомо безрезультатное действие. Третий подключается, но опрашивается по чужому адресу и чужому имени профиля. А Codex объявляется исчерпанным по квоте в тот момент, когда квота не измерена ни разу. Общее у всех четырёх — интерфейс утверждает то, чего не проверял. ## Порядок сдачи Передать точный `FINAL_COMMIT_SHA`.