> Регистрация на Rustvy по реферальной ссылке автора:
> При регистрации по этой ссылке вы получаете скидку 10% и бонусные токены.
> Подробные условия акции:
Rustvy (dsh-rustvy)
Русский | English
Значки производителей в списке моделей DeepSeek Harness, список моделей, который
остаётся настолько же актуальным, насколько актуален сам провайдер, и несколько
ключей провайдера в одном профиле — каждый чат отправляет запросы тем, который
выбрал. Один плагин, одно зеркало, без перезапусков.
Что он делает
У плагина три задачи. Первые две работают от одного зеркала публичного каталога
провайдера; третьей зеркало не нужно — ей нужен только маршрут профиля.
Значки. Один монохромный значок производителя перед каждой строкой модели в
списке выбора — нарисованный так же, как его рисует собственная панель провайдера:
CSS-маска, заливаемая currentColor, поэтому один файл на производителя
обслуживает обе темы оформления и все состояния строки — наведение, выбор,
отключение — без цветовых вариантов.
| Где | Что появляется |
|---|---|
| Меню моделей в композере | монохромный значок 16 px перед каждой строкой модели |
| Кнопка модели в композере | значок выбранной модели перед её названием (14 px) |
Всплывающее окно команды /model |
тот же значок на каждой строке |
Скины, перерисовывающие список (dsh-claude-style) |
тот же значок; собственная плашка скина в этой строке прячется, чтобы две плашки не встретились |
Список моделей. Профиль подключается к стороннему провайдеру через вручную
объявленный маршрут @deepseek-ai/dsh-llm-pi-ai, и именно список models этого
маршрута определяет, что вообще можно выбрать в интерфейсе: harness предлагает
модель только тогда, когда её отдаёт маршрут. Список, написанный вручную, устаревает
в тот день, когда провайдер выпускает новую модель — новая невидима, а снятая с
публикации остаётся выбираемой и падает уже на запросе. Плагин читает, что отдаёт
собственный эндпоинт маршрута, превращает это в точный формат записей адаптера,
показывает расхождение с установленным списком и по команде записывает его обратно
через ctx.configEditor.
Ключи. Несколько ключей провайдера в одном профиле и свой ключ для отдельного
чата — выбираемый в панели инструментов композера, сразу за списком режимов
доступа. Ключ добавляется в самом плагине, кнопкой «+»: ни .env, ни
cordis.patch.yml, ни страницу Models трогать не нужно, а выбор переживает
перезапуск. Учётная запись DeepSeek не затрагивается: подмена касается только
маршрутов этого плагина.
| Где | Что появляется |
|---|---|
| Настройки → Rustvy | раздел «API-ключи»: список ключей, кнопка «+», замена значения, переименование, проверка, назначение ключом по умолчанию, удаление |
| Композер, сразу за списком режимов доступа | компактный переключатель ключа этого чата, с меню и строкой «+ Добавить ключ» |
Раздела ключей в меню моделей и в окне /model нет: выбор ключа живёт в одном
месте. Меню моделей вместо этого не показывает те группы провайдеров, которые
оказались клонами маршрутов этого кольца, — иначе один и тот же список моделей
повторялся бы по разу на каждый ключ.
Зачем это нужно
Потому что обе поломки маршрута, который поддерживают вручную, — тихие. Модель,
которую провайдер добавил, не появляется, и никто не говорит почему: ошибки нет,
есть только отсутствие. Модель, которую провайдер снял, по-прежнему видна, и её
выбор даёт сетевую ошибку, которая выглядит как проблема со связью, а не как
устаревшая строка в YAML-файле.
И то и другое лечится чтением данных у провайдера вместо их переписывания. Список,
который записывает этот плагин, несёт настоящие размер окна контекста, лимит
вывода, входные модальности и уровни рассуждения, поэтому модель, получившая
поддержку изображений или новый уровень мышления, описана верно в тот же день, а не
наследует запасное значение, которое кто-то когда-то угадал.
А поскольку значки берутся из того же зеркала, совершенно новая модель приходит уже
с кешированным значком своего производителя: зеркало перечитывает каталог в тот
момент, когда эндпоинт называет модель, которой оно раньше не видело, — то есть
ровно тогда, когда выходит релиз.
Третья задача — про другую тишину. Ключ провайдера — это отдельный лимит и отдельный
учёт, а профиль один, поэтому «свой ключ на каждый чат» без плагина означает либо
второй профиль, либо вторую строку в YAML, которую кто-то должен поддерживать вручную.
Вручную поддержанный клон маршрута устаревает по тем же правилам, что и написанный
вручную список моделей, — в день релиза. Кольцо ключей пишет клоны само и тем же
проходом, которым записывается список моделей, поэтому маршрут ключа не отстаёт от
маршрута профиля, а чат, отправляющий запросы другим ключом, видит ровно тот же
выбор моделей, что и его сосед.
Ничего из этого не привязано к одному провайдеру. Сайт-источник, маршрут и строка
профиля — настройки; https://rustvy.xyz и rustvy являются значениями по
умолчанию.
Установка
dsh plugin --profile web add dsh-rustvy
Либо прямо с GitHub, если нужна самая свежая ревизия, а не опубликованная версия:
dsh plugin --profile web add github:koliy82/dsh-rustvy
Голый koliy82/dsh-rustvy без схемы не принимается: разбор источника установки
знает имя в реестре, git-спецификацию, tarball и абсолютный локальный путь, а
git-спецификации нужна схема либо сокращение github:.
Подтверждение сборки не запрашивается ни в том ни в другом случае: у пакета нет ни
install-скрипта, ни зависимостей, поэтому pnpm нечего собирать.
Из локальной копии этого репозитория:
pwsh -File .\install.ps1
install.ps1 копирует пакет в <профиль>\node_modules\dsh-rustvy как настоящий
каталог и дописывает имя в dsh.profile.bundles манифеста профиля. Работающий
harness подхватывает строку по изменению манифеста.
После этого один раз перезапустите harness или обновите страницу: значки и страница
настроек появляются на лету, а строка загрузчика читается на композиции.
Маршрут, в который пишется список моделей
Значки работают без всего остального. Половине со списком моделей нужен маршрут
@deepseek-ai/dsh-llm-pi-ai, в который можно писать. Добавьте в
cordis.patch.yml профиля — или отредактируйте в Настройки → Плагины →
Конфигурация плагинов:
- id: llm-pi-ai
name: "@deepseek-ai/dsh-llm-pi-ai"
config:
providers:
rustvy:
displayName: rustvy
apiKeyEnv: RUSTVY_API_KEY
api: openai-completions
baseURL: https://rustvy.xyz/v1
models:
- id: placeholder
name: placeholder
Список models обязан только разбираться: этот плагин его заменяет. Все остальные
поля при перегенерации переносятся без изменений, потому что переключатель
совместимости протокола или ссылка на учётные данные — это знание о вашем
развёртывании, которого нет ни в одном каталоге.
apiKeyEnv задаёт, откуда маршрут берёт ключ; он разрешается на каждый запрос через
механизм учётных данных harness — сначала унаследованное окружение процесса, затем
$DSH_HOME/.credentials.yaml, затем .env в каталоге запуска или в $DSH_HOME.
Сам ключ в конфигурационные файлы не попадает. Плагин при чтении списка моделей
подставляет ключ только тогда, когда окружение процесса уже содержит переменную с
таким именем; эндпоинт, под который он написан, отвечает и без ключа.
Чтобы сделать модель основной:
- id: agent-default-model
name: "@deepseek-ai/dsh-agent-default-model"
config:
provider: rustvy
model: claude-opus-5-5
reasoningEffort: high
Чистый профиль: маршрут создаётся сам
Всё выше — про профиль, в котором маршрут уже есть. На чистом DSH его нет, и раньше
плагин в этом случае не делал ничего и ничего не говорил: в списке моделей оставался
встроенный DeepSeek, ключа от которого нет, то есть не работало ничего. Теперь
хост-половина при монтировании проверяет, есть ли в строке llm-pi-ai провайдер
rustvy, и если его нет — создаёт: адрес, имя, apiKeyEnv, форму API и список
моделей, прочитанный у провайдера. Существующий маршрут не перезаписывается никогда и
ни в одном поле: displayName, apiKeyEnv, baseURL и models могли быть
исправлены руками, и это ваше знание о вашем развёртывании, которого нет ни в одном
каталоге.
DeepSeek не затрагивается никогда. Он живёт в других строках композиции
(llm-deepseek, llm-deepseek-account), а запись идёт только в строку llm-pi-ai, и в
ней — только в ключ rustvy, с сохранением чужих провайдеров той же строки. Это
закреплено тестом, который сравнивает строку llm-deepseek побайтово до и после, а не
рассуждением о том, что запись туда попасть не должна: требование пользователя
«deepseek вообще не трогал» абсолютное, и теперь, когда пишутся две строки вместо
одной, оно важнее, а не менее важно.
Ключ при установке не спрашивается. Это намеренное решение: создание маршрута не
должно ничего требовать, а маршрут без ключа — как раз то состояние, после которого
список моделей появляется. Ключ добавляется потом, кольцом ключей на той же странице
настроек или переменной RUSTVY_API_KEY. Поэтому нормальный вид сразу после установки
— «маршрут создан, ключ ещё не задан», и страница говорит именно это.
Модель по умолчанию переключается, только если вы её сами не выбирали. Условия два
и выполняются оба: маршрут отсутствовал и создан этим же монтированием, и у строки
agent-default-model нет переопределения в слое профиля. Второе — единственная
надёжная граница между чистой установкой и осознанным выбором: если модель когда-нибудь
выбирали вы, строка тронута не будет и композер останется на вашем выборе. Если
проверить выбор не удалось, плагин не пишет ничего — перезаписанную модель из него не
восстановить, а догадка здесь дороже бездействия.
Что говорит страница настроек. Исход создания маршрута — условная строка вверху
раздела Rustvy, над блоком аптайма, и рисуется она только когда есть что сказать:
| Исход | Строка |
|---|---|
| маршрут создан этим монтированием | «Маршрут rustvy создан этим монтированием: N моделей» и отдельно про ключ |
| маршрут уже был | ничего — кроме строки про ключ, если он не задан |
| создать не удалось | «Маршрут rustvy не создан: …» с причиной и следующим действием |
| модель по умолчанию переключена | «Модель по умолчанию переключена с X на Y — Z» |
| не переключена, потому что её выбирали вы | одна спокойная строка с объяснением |
| не переключена, потому что маршрут уже был | ничего |
Порядок разделов при этом не меняется: строка условная, а решение о порядке касалось
разделов. В установившемся состоянии не рисуется ничего, значит аптайм остаётся первым
ровно как поставлен; когда строка рисуется, установка либо сломана, либо только что
загружена, и это важнее порядка.
Кнопки на этой строке нет, и это решение, а не недоработка: запасной конец запускается
только при монтировании, отдельной конечной точки повтора нет, поэтому кнопка могла бы
лишь перечитать состояние, которое ничего не перезапускает, — получился бы элемент с
подписью «повторить», который не повторяет. Настоящее действие — перезагрузка окна, и
строка называет его текстом, по-разному для разных причин.
Про ключ страница утверждает ровно то, что знает. Хост спрашивает не окружение процесса,
а тот же механизм учётных данных, которым пользуется адаптер, и учитывает кольцо: ключ,
добавленный кольцом, делает провайдер рабочим, хотя RUSTVY_API_KEY в окружении пуста.
«Ключ ещё не задан» печатается только когда не разрешается ни то ни другое; когда хост
определить не смог, страница указывает оба места, ничего не утверждая. Две фразы
различаются ровно одним утверждением, и проверка в наборе не даёт им слиться: слияние
вернуло бы «ещё не задан» на машину, которая работает. Значение пересчитывается на
каждый запрос состояния, поэтому ключ, добавленный кольцом после загрузки страницы,
убирает строку без перезагрузки — иначе кольцо внизу показывало бы новый ключ, а строка
наверху утверждала бы, что ключа нет.
Отдельно страница говорит, откуда взят список моделей, если источник не ответил и
маршрут создан из локальной копии каталога: у такого списка неизвестная свежесть, и
перечитать его стоит, когда сеть появится. Кнопка «Обновить каталог и иконки» здесь не
лекарство — зеркало собирается с того же источника, и хост уже попытался обновить его
сам, пока создавал маршрут.
Как пользоваться
Откройте Настройки → Rustvy. Страница показывает, что держит зеркало, что отдаёт
провайдер и каково точное расхождение между ними. Кнопка «Обновить каталог и
иконки» освежает обе половины, «Записать список моделей» записывает
сгенерированный список. Без одного из этих нажатий не пишется ничего, если вы сами
не попросили об этом в настройках.
Раздел «API-ключи» на той же странице держит кольцо: кнопка «+» открывает
инлайн-форму «название + значение», у каждой строки ключа есть замена значения,
переименование, назначение ключом по умолчанию, проверка и удаление. Ключом по
умолчанию можно пользоваться, вообще не заходя в настройки: переключатель в панели
инструментов композера делает то же самое для того чата, который открыт.
Настройки
Настройки → Rustvy
Сверху вниз страница устроена так: выключатели и поля, затем блок «Аптайм моделей»,
«Модели провайдера», «Ключи на несколько чатов», «Сбор ошибок Rustvy» и в
самом низу «Зеркало каталога» с его адресами и файлами. Аптайм первый, потому что это
единственный блок, отвечающий на вопрос, с которым страницу открывают, — жива ли модель,
которую сейчас выберут; зеркало последнее, рядом с предупреждением о неудачном чтении
каталога, которого только оно и касается.
Значки:
| Параметр | По умолчанию | Смысл |
|---|---|---|
| Показывать иконки моделей | вкл | общий выключатель значков; в выключенном виде убирает все значки и не трогает список моделей |
| Иконка в кнопке выбранной модели | вкл | значок и в кнопке модели композера, а не только в строках меню; он же убирает с кнопки значок аптайма |
| Прятать логотипы других скинов | вкл | прятать собственную плашку скина в строке, где значок нарисован этим плагином |
| Показывать аптайм моделей | вкл | блок «Аптайм моделей» на этой странице и значок состояния в списке моделей и в кнопке выбранной модели; выключенный не читает зеркало коллектора вовсе |
| Размер значка, px | 16 | от 10 до 32 |
Список моделей:
| Параметр | По умолчанию | Смысл |
|---|---|---|
| Держать список моделей маршрута актуальным | вкл | общий выключатель; в выключенном виде YAML маршрута не трогается |
| Записывать список | при нажатии «Обновить» | только отдельной кнопкой — самостоятельно не пишет никогда · при нажатии «Обновить» — пишет по кнопке обновления · сам, при каждом обновлении — пишет и при фоновом суточном обновлении |
| Название модели | как на сайте провайдера | отображаемое имя провайдера либо сырой идентификатор |
| Предлагать уровень «без рассуждений» | вкл | добавлять уровень off рассуждающей модели, которая его не объявила |
| Оставлять модели, которые провайдер больше не отдаёт | выкл | сохранять установленную модель, которую эндпоинт больше не отдаёт, — на случай временно ошибочного списка, а не действительно снятой модели |
| Маршрут провайдера | rustvy |
какой ключ внутри providers записывать |
| Строка профиля | llm-pi-ai |
какая строка загрузчика его содержит |
| Сайт провайдера | https://rustvy.xyz |
откуда читать каталог и логотипы |
Режим записи по умолчанию намеренно средний: нажатие кнопки — это решение человека,
а фоновое истечение срока — нет, и переписывать конфигурацию загрузчика работающего
профиля, когда никто не просил, — это то, на что стоит спросить разрешение.
Каждый адрес и файл, с которым работает плагин, виден на странице: сайт, страница
каталога и эндпоинт списка моделей — ссылками, открывающимися в новой вкладке, а
пути к зеркалу, настройкам, копии для отката и патчу профиля — текстом с кнопкой
копирования. Кнопка «Показать блок для ручной правки» выдаёт ровно тот YAML,
который получился бы при автоматической записи, так что желающий вставить его в
cordis.patch.yml сам не переписывает краткий пересказ.
У одного поля нет элемента управления, потому что оно принадлежит YAML профиля, а не
форме: modelZones (по умолчанию ["standard"]) — зоны каталога, которые
допускаются, когда эндпоинт недоступен и состав списка определяет зеркало.
Ключи:
| Параметр | По умолчанию | Смысл |
|---|---|---|
| Ключи на несколько чатов | вкл | общий выключатель кольца; в выключенном виде подмена маршрута не применяется и каждый запрос идёт маршрутом профиля |
Выключение ничего не удаляет: маршруты ключей остаются в конфигурации профиля,
значения — в хранилище учётных данных, выбор чатов — в своём файле, поэтому
включённая обратно возможность продолжает с того места, на котором её выключили.
Переключатель живёт в разделе «API-ключи», а не рядом с выключателями значков и
списка моделей: он принадлежит кольцу, и там же страница объясняет, почему
добавление ключа недоступно, если хост не отдаёт нужный сервис.
Сбор ошибок:
| Параметр | По умолчанию | Смысл |
|---|---|---|
| Собирать ошибки rustvy | вкл | общий выключатель сбора: одна строка на инфраструктурный сбой провайдера уходит коллектору аптайма. Выключенный не отправляет ничего, и накопленная очередь остаётся на диске |
Выключатель живёт в собственном блоке «Сбор ошибок Rustvy», потому что это единственная
настройка страницы, которая что-то отправляет за пределы машины, — одна строка на сбой
провайдера уходит коллектору монитора. Сам блок — заголовок и выключатель, больше ничего:
ни пояснения под выключателем, ни счётчиков, ни кнопок отправки на странице нет. Что
именно уходит, что не уходит никогда и когда сбор срабатывает, описано в разделе
«Сбор ошибок и аптайм» ниже и в docs/error-report.md —
раскрытие не исчезло вместе с текстом на странице, оно переехало туда, где его можно
прочитать целиком. Состояние сбора — адрес коллектора, анонимный
идентификатор, счётчики отправленного, очереди и отброшенного, последний ответ, последняя
ошибка отправки и разбивка сбоев по видам — хост-половина по-прежнему отдаёт на своих
петлевых маршрутах /dsh-rustvy/state и /dsh-rustvy/errors, и смотреть его нужно там,
а не в интерфейсе. Адрес — uptimeEndpoint, по умолчанию
http://185.75.189.247:3789/api/report — поля ввода не имеет: его перенаправляют без
выпуска новой версии переменной окружения DSH_RUSTVY_UPTIME_ENDPOINT или правкой
<профиль>\.dsh-rustvy\config.json руками.
Настройки хранятся в <профиль>\.dsh-rustvy\config.json. Пять переменных окружения
перекрывают их — для развёртывания, которое направляет зеркало и сборщик в другое место
либо хочет смонтировать плагин неработающим, молчащим или без панели аптайма:
DSH_RUSTVY_ORIGIN и DSH_RUSTVY_DISABLED=1, DSH_RUSTVY_UPTIME_ENDPOINT,
DSH_RUSTVY_NO_ERRORS=1 и DSH_RUSTVY_NO_UPTIME=1.
Первая перенаправляет сайт провайдера, вторая выключает плагин целиком. Третья
перенаправляет коллектор аптайма и переопределяет и значение по умолчанию, и
config.json: принимается только адрес, разбирающийся как http/https, концевые
слеши обрезаются, а непригодное значение игнорируется, и тогда остаётся действующая
настройка. Четвёртая и пятая, каждая в значении 1 или true, выключают независимо от
настройки сбор ошибок и вид аптайма — тот же приём, которым DSH_RUSTVY_DISABLED гасит
значки.
Откуда берутся картинки
В сам пакет ничего не вшито. Хост-половина зеркалит два публичных объекта с сайта
провайдера в профиль, а браузерная половина читает их с локального адреса:
- каталог моделей — сайт рисует свою таблицу моделей из JSON-острова в
собственной странице (`), поэтому неаутентифицированный читатель получает ровно те данные, которые видит панель. Зеркалится в<профиль>.dsh-rustvy\catalog.json`. - файлы логотипов (
/logos/<имя>.svg) — копируются в
<профиль>\.dsh-rustvy\logos\при первом обращении.
Оба читаются по собственному исходящему маршруту процесса (lib/net.js): сначала
живой глобальный диспетчер, чтобы прокси, установленный dsh-proxy-gui или любым
другим маршрутом процесса, соблюдался, затем соединитель, построенный здесь из
HTTPS_PROXY / ALL_PROXY / HTTP_PROXY, — на случай, когда диспетчер не
установлен. NO_PROXY учитывается в обоих случаях. Сама страница наружу не выходит:
браузер спрашивает значок у 127.0.0.1, и именно поэтому всё работает на машине,
чей браузер до источника не достаёт вовсе.
Каталог обновляется раз в 24 часа и по требованию; холодное зеркало ожидается до
6 секунд, а устаревшее обновляется в фоне, пока строки продолжают показывать
имеющиеся значки. Когда источник недоступен, кеш на диске по-прежнему отдаёт всё,
что в нём есть.
Откуда берётся список моделей
Два источника в таком порядке, потому что отвечают они на разные вопросы:
- собственный эндпоинт маршрута (
GET {baseURL}/models) — авторитетный список
состава: ровно те модели, которые можно назвать в запросе, каждая со своими
context_k,max_output_tokens,modalitiesиreasoning_levels. Это тот же
эндпоинт, который опрашивает собственное обнаружение адаптера. - зеркало острова каталога — названия и запасной вариант. Это единственный
источник, несущий отображаемое имя, производителя и имя файла логотипа, поэтому
он соединяется с отдаваемым списком по идентификатору. Когда эндпоинт не
отвечает, состав определяет сам остров по своим полямenabledиzone, так что
маршрут остаётся актуальным и во время сбоя, а не застывает на последнем удачном
чтении. На здоровом источнике оба списка совпадают точно.
Модель, которую эндпоинт отдаёт, а зеркало ни разу не видело, — признак того, что
остров отстал от боевого API, а это происходит ровно в момент релиза, поэтому тогда
остров перечитывается; ожидание ограничено тем же коротким сроком, что и для
холодного зеркала.
Что именно записывается
Каждая сгенерированная запись несёт только те поля, которые объявлены в схеме
конфигурации адаптера, потому что список models профиля маршрута заменяет
каталог маршрута, а неизвестное поле в лучшем случае будет отброшено:
| Поле | Откуда берётся |
|---|---|
id |
идентификатор отдаваемой модели — то, что нужно назвать в запросе |
name |
отображаемое имя из острова либо идентификатор, если имени нет |
contextWindow |
context_k × 1000 |
maxTokens |
max_output_tokens |
input |
modalities, отфильтрованные до text/image |
reasoningEfforts |
reasoning_levels, отображённые на уровни адаптера, либо false для нерассуждающей модели |
compat |
не генерируется никогда — существующее всегда переносится |
Поле, о котором никто не высказался, пропускается, а не заполняется значением по
умолчанию, чтобы запасное значение самого адаптера оставалось в силе и написанное
вручную не закреплялось молча. compat — единственное, что переносится без
изменений: переключатель совместимости протокола это знание о шлюзе, которого нет
ни в одном каталоге.
Уровни рассуждения отображаются на словарь адаптера (off, minimal, low,
medium, high, xhigh, max); неизвестный ему уровень отбрасывается и сообщается
примечанием, а не отказом всей записи. Необъявленные уровни адаптер не
поддерживает, а объявленный off без боевого значения означает «предлагать, ничего
не отправляя» — поэтому этот переключатель включён по умолчанию.
Что запись отклоняет
Пустой список не записывается никогда: единственный способ его получить — неудачное
чтение, а маршрут без моделей не обслуживает ничего. Актуальный список тоже не
записывается, потому что configEditor.edit() согласует весь загрузчик, и тратить
одно согласование на отсутствие изменений означало бы перезагрузить работающий
профиль впустую. Актуальным при этом считается не только маршрут профиля: запись
происходит и тогда, когда сам он в порядке, а список моделей одного из клонов
кольца ключей отстал, — иначе чат, отправляющий запросы другим ключом, видел бы
меньше моделей, чем его сосед. Каждая запись сохраняет предыдущий документ в
<профиль>\.dsh-rustvy\models-backup.json, а кнопка «Откатить к предыдущему»
возвращает его обратно; состояние, которое откат вытесняет, само становится
следующей копией, поэтому откат тоже можно откатить.
Как строка сопоставляется со значком
Строки сопоставляются по видимому названию в таком порядке:
- идентификатор модели из зеркального каталога (
claude-opus-5-5,
abyss/glm-5.3, …); - её отображаемое имя (
OPUS 5.5); - идентификатор, с которого название начинается, — скин может оставить в том же
элементе собственную строку описания, поэтому название часто является префиксом,
а не всей строкой; побеждает самый длинный подходящий идентификатор, чтобы
claude-opus-5-5никогда не досталсяclaude-opus-5; - форма идентификатора — для модели, которой зеркало ни разу не видело:
claude-*→ Anthropic,gpt-*/o1…o9→ OpenAI,glm-*→ Zhipu,
kimi-*/k3→ Moonshot,qwen-*→ Alibaba Qwen,deepseek-*→ DeepSeek,
ahmia-*→ Ahmia,mimo-*→ Xiaomi,fable-*→ Anthropic,gemini-*→
Google,grok-*→ xAI.
Модель, не подошедшая ни под что, не получает значка вовсе: выдуманная иконка была
бы неправильным ответом, а не отсутствующим. Модель, которая подошла, но чей логотип
зеркало получить не смогло, получает вместо пустого квадрата плашку с монограммой —
первыми буквами производителя: маска с неработающим URL закрасилась бы сплошным
квадратом.
Строки уровней усилия, заголовки производителей, корневые ячейки меню, поле поиска и
строки о сбое каталога не подходят ни под одно правило и остаются без значка.
Кольцо API-ключей
Несколько ключей провайдера в одном профиле, и каждый чат отправляет запросы тем,
который выбрал его пользователь. Выбор живёт в панели инструментов композера —
рядом со списком режимов доступа, а не в меню моделей, — действует на один чат и
переживает перезапуск. Учётная запись DeepSeek не затрагивается ни в одном из мест
этого раздела.
Почему один ключ — это один маршрут
@deepseek-ai/dsh-llm-pi-ai берёт ключ из profile.apiKeyEnv — это имя, а не
значение, — и разрешает его через механизм учётных данных harness на каждый запрос,
ничего не кешируя. Поля для ключа нет ни в конфигурации вызова (LlmCallConfig), ни
в параметрах генерации (GenerateOptions), а baseURL и заголовки запекаются в
снапшот профиля. Передать адаптеру ключ «в этом запросе» нечем, поэтому единственный
честный способ дать чату другой ключ — другой маршрут: клон с теми же api,
baseURL и models и своим apiKeyEnv.
Из этого следует всё устройство. Добавление ключа пишет значение в хранилище учётных
данных под новым именем, клонирует маршрут профиля через ctx.configEditor и
запоминает запись в кольце. Выбор чата применяется на водопаде agent/request
подменой имени маршрута в конфигурации вызова — тем же приёмом и на том же шве,
которым harness применяет выбор модели для отдельного чата, поэтому он работает
ровно там, где уже работает выбор модели, и не заводит собственного пути к запросу.
| Ключ | Имя учётных данных | Маршрут | Отображаемое имя маршрута |
|---|---|---|---|
| первый (основной) | RUSTVY_API_KEY — как объявлен в профиле |
rustvy — как объявлен в профиле |
rustvy |
| второй | RUSTVY_API_KEY_2 |
rustvy-k2 |
rustvy · <название ключа> |
| n-й | RUSTVY_API_KEY_ |
rustvy-k |
rustvy · <название ключа> |
Имена строятся от маршрута из настроек, а не зашиты: при modelRoute: rustvy второй
ключ получает rustvy-k2, при другом имени маршрута — свои суффиксы. Клон копирует
профиль маршрута целиком, меняя только apiKeyEnv и отображаемое имя, поэтому
написанный вручную переключатель совместимости протокола и всё остальное знание о
своём шлюзе переходит в каждый ключ, а не теряется при первом же добавлении.
Предел — 12 ключей: это кольцо, а не реестр. Новый ключ берёт первый порядковый
номер, у которого свободны и имя учётных данных, и маршрут, поэтому имя удалённого
ключа не переиспользуется, а чужая строка, случайно оказавшаяся на таком имени, не
перезаписывается.
Основной ключ
Первый ключ кольца — не запись, которую кто-то добавил, а маршрут, уже объявленный в
профиле: он наследует его apiKeyEnv и его имя и основным остаётся всегда. Профиль,
который работал до этой возможности, продолжает работать без изменений — кольцо с
одним ключом не переименовывает ничего и никуда не пишет. Удалить основной ключ
нельзя: он принадлежит маршруту профиля, и его удаление сломало бы провайдера для
каждого чата, включая те, которые кольцом ни разу не воспользовались. Отказ так и
объясняет и предлагает заменить значение — эта операция доступна любому ключу и
конфигурации не касается, потому что имя учётных данных при ней не меняется.
Где что лежит
| Что | Где |
|---|---|
| Значения ключей | $DSH_HOME\.credentials.yaml, раздел refs — то же хранилище, в которое пишет страница Models harness и из которого читает адаптер |
| Метаданные кольца | <профиль>\.dsh-rustvy\keys.json — id, название, имя учётных данных, маршрут, признак основного, порядковый номер, дата создания и defaultKeyId |
| Выбор чатов | <профиль>\.dsh-rustvy\session-keys.json — sessionId → keyId |
| Анонимный идентификатор установки | <профиль>\.dsh-rustvy\install.json — случайный UUID и дата создания; принадлежит сбору ошибок, а не кольцу, и лежит рядом с остальными файлами плагина |
| Неотправленные отчёты об ошибках | <профиль>\.dsh-rustvy\errors-queue.json — очередь отчётов, которые коллектор аптайма ещё не принял |
| Последний вид монитора | <профиль>\.dsh-rustvy\uptime.json — кешированный ответ коллектора, который панель показывает с пометкой stale, когда коллектор недоступен; принадлежит виду аптайма, а не кольцу |
В файлах плагина значения ключа нет никогда: ни в keys.json, ни в
session-keys.json, ни в настройках, ни в копии профиля для отката, ни в очереди
отчётов об ошибках. Loopback-маршруты
отвечают маской — первые три и последние два символа — и восьмисимвольным отпечатком
SHA-256; этого достаточно, чтобы узнать ключ в списке и заметить две записи с одним
значением, и недостаточно, чтобы им воспользоваться. Само значение приходит в теле
единственного запроса, который его добавляет или заменяет, и никогда в строке
запроса: URL пишется в журналы, попадает в кеш и в историю браузера, а секрет — не
дело ни того, ни другого, ни третьего.
Выбор хранится на диске, а не в журнале сессии: это предпочтение маршрутизации
самого плагина, и плагин, пишущий собственные события в долговременный журнал,
оставил бы их в каждой сессии, где когда-либо был смонтирован. В файле лежат только
отклонения от ключа по умолчанию, поэтому чат, созданный до смены ключа по
умолчанию, следует за новым, а не держится старого.
Что видно в интерфейсе
Две поверхности: одна управляет кольцом, другая выбирает ключ для своего чата.
Переключатель появляется только тогда, когда выбирать есть из чего: при одном ключе
его нет вовсе, потому что один ключ — это не выбор.
Настройки → Rustvy → «API-ключи». Список ключей: название, маска, имя учётных
данных, маршрут, сколько моделей отдаёт этот маршрут и сколько чатов выбрали этот
ключ. Кнопка «+» в заголовке раздела открывает инлайн-форму «название +
значение» со скрытым полем и показом по желанию. У каждой строки — замена значения,
переименование (сохраняется по потере фокуса), назначение ключом по умолчанию,
проверка и удаление в два шага; у основного ключа кнопки удаления нет, и строка
объясняет почему. Ниже — блок «Маршруты ключей»: строка профиля и пакет, адрес
списка, счётчик «запросов через подмену», число чатов со своим выбором, списки
отсутствующих и отставших маршрутов, пути к обоим файлам с кнопкой копирования и
кнопка «Починить маршруты», которая появляется только тогда, когда маршрут
какого-то ключа действительно пропал.
Композер. Компактная кнопка в панели инструментов, сразу за списком режимов
доступа, — регистрация в слоте conversation.input.left: название активного ключа
либо «ключ по умолчанию», если у чата нет собственного выбора. В меню — по строке на
ключ с галочкой на активном, строка «По умолчанию» и строка «+ Добавить ключ» с
той же инлайн-формой; ключ, добавленный из чата, сразу выбирается для этого чата,
потому что добавляют его из разговора именно затем, чтобы отправлять запросы им.
Кнопка не рисуется, пока слот не передал sessionId: выбор принадлежит чату, и
переключатель, который сохранит его не тому чату, хуже отсутствия переключателя.
Меню выбора модели и всплывающее окно /model. Раздела ключей здесь нет — ни
строк, ни заголовка: выбор живёт в композере, и второй такой же список в меню только
удваивал бы место, где можно ошибиться. Декоратор делает здесь обратное — прячет
группы провайдеров, которые хост назвал в hiddenGroups, то есть клоны маршрутов
этого кольца, чтобы один и тот же список моделей не показывался по разу на каждый
ключ. Не объявить их хост не может: клону нужен собственный список моделей, иначе
после подмены маршрута запрос не найдёт в нём свою модель. Скрытие структурное и
осторожное: заголовок группы опознаётся по форме, а не по имени класса, потому что
классы хозяйских примитивов этому плагину не видны и между версиями не стабильны, а
прячется либо наименьший содержащий группу элемент, либо заголовок вместе с
идущими за ним строками до следующего заголовка. Если уверенности нет, не прячется
ничего: по ошибке спрятанная строка модели — это отнятый выбор, а лишний раз
показанная группа — только повтор.
Что происходит при добавлении ключа
Порядок операций намеренный. Сначала значение пишется в хранилище учётных данных:
маршрут, чьё имя ни во что не разрешается, роняет каждый запрос, который его назвал.
Затем идёт запись конфигурации, и если она не удалась, значение забирается обратно,
поэтому неудачное добавление не оставляет после себя ничего. Документ кольца пишется
последним, когда существуют и значение, и маршрут.
Запись конфигурации — настоящая и поддерживаемая: она идёт через ctx.configEditor,
который согласует профиль, и ровно то же самое делает страница Models harness, когда
в профиль добавляют провайдер. configEditor.edit() согласует весь загрузчик,
поэтому записи кольца выстроены в очередь: два ключа, добавленные одновременно, —
это две последовательные правки, а не две правки против документа, который одна
другая только что заменила. И когда карта маршрутов уже соответствует кольцу,
запись не происходит вовсе — тратить согласование на отсутствие изменений этот
плагин отказывается и в списке моделей.