@goodandready/dsh-model-sync 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -39,7 +39,7 @@
39
39
 
40
40
  **`dsh-model-sync`** keeps your **DeepSeek Harness** model catalog up-to-date with upstream AI providers.
41
41
 
42
- Instead of manually editing YAML configs whenever a provider launches a new model, adjusts context window limits, or changes pricing, `dsh-model-sync` automatically discovers new releases, updates capability metadata (`vision`, `tools`, `reasoning`, `embeddings`), tracks account balances, and reconciles models on the fly without server restarts.
42
+ Instead of manually editing YAML configs whenever a provider launches a new model, adjusts context window limits, or changes pricing, `dsh-model-sync` automatically discovers new releases, updates capability metadata (`vision`, `tools`, `reasoning`, `embeddings`), tracks account balances, manages model selection visibility, and reconciles models on the fly without server restarts.
43
43
 
44
44
  ```mermaid
45
45
  graph LR
@@ -54,6 +54,8 @@ graph LR
54
54
  Registry -->|x-api-key Auth| P2[Anthropic Claude / Custom Gateways]
55
55
  Registry -->|query-key Auth| P3[Google Gemini]
56
56
  Registry -->|Local Probe| P4[Local Ollama / vLLM / SGLang]
57
+ Registry -->|CommandCode API| P5[Command Code / visibleModels]
58
+ Registry -->|Plan Discovery| P6[ClineBot Subscription Plan]
57
59
  end
58
60
 
59
61
  subgraph Reconcile [Catalog Reconciler & Auditing]
@@ -61,8 +63,10 @@ graph LR
61
63
  P2 --> Normalizer
62
64
  P3 --> Normalizer
63
65
  P4 --> Normalizer
66
+ P5 --> Normalizer
67
+ P6 --> Normalizer
64
68
  Normalizer --> Diff[Sync Diff Tracker: Added / Deprecated]
65
- Diff --> Catalog[Active DSH Models Catalog]
69
+ Diff --> Catalog[Active DSH Models Catalog & visibleModels]
66
70
  end
67
71
 
68
72
  style Trigger fill:#1e1e2e,stroke:#89b4fa,stroke-width:2px,color:#cdd6f4
@@ -75,21 +79,24 @@ graph LR
75
79
  ## ✨ Key Features
76
80
 
77
81
  * 🔄 **Automated Catalog Discovery**: Syncs model lists, aliases, context windows, and capability flags (`vision`, `tools`, `reasoning`, `embeddings`) across 25+ providers.
78
- * 🛡️ **Minijinja Chat Template & Role Compatibility**: Automatically sets `compat.supportsDeveloperRole: false` for Groq open-source models (Qwen, Llama, Mistral, Gemma, DeepSeek) and DeepSeek API, preventing 400 `Unexpected message role` errors with reasoning models while preserving user overrides. Syncs model lists, aliases, context windows, and capability flags (`vision`, `tools`, `reasoning`, `embeddings`) across 25+ providers.
79
- * 🌐 **25+ Built-in Provider Adapters**: Pre-configured discovery for OpenAI, Anthropic, Google, DeepSeek, xAI, OpenRouter, Groq, Mistral, Cerebras, Fireworks, HuggingFace, Moonshot, NVIDIA, Qwen, Together, Xiaomi MiMo, SiliconFlow, and local Ollama.
82
+ * 🛡️ **Minijinja Chat Template & Role Compatibility**: Automatically sets `compat.supportsDeveloperRole: false` for Groq open-source models (Qwen, Llama, Mistral, Gemma, DeepSeek) and DeepSeek API, preventing 400 `Unexpected message role` errors with reasoning models while preserving user overrides.
83
+ * 🌐 **25+ Built-in Provider Adapters**: Pre-configured discovery for OpenAI, Anthropic, Google, DeepSeek, xAI, OpenRouter, Groq, Mistral, Cerebras, Fireworks, HuggingFace, Moonshot, NVIDIA, Qwen, Together, Xiaomi MiMo, SiliconFlow, Command Code, ClineBot, and local Ollama.
84
+ * 🎛️ **Command Code Model Selection**: Full discovery of Command Code models with granular checkbox selection in the UI, automatically syncing `visibleModels` to `llm-commandcode` settings.
85
+ * 🎯 **ClineBot Subscription Plan Sync**: Dedicated adapter querying `GET /users/me/plan` to discover only active subscription-included models, eliminating catalog clutter from out-of-plan endpoints.
80
86
  * 🔌 **Generic / Custom Adapter Support**: Connect arbitrary OpenAI-compatible `/v1/models` endpoints with configurable auth (`bearer`, `x-api-key`, `query-key`, `none`).
81
87
  * 📊 **Balance & Quota Monitoring**: Queries upstream billing endpoints (where supported) to report account credit balances and prevent unexpected exhaustion.
82
88
  * 📜 **Sync History & Diff Auditing**: Tracks all newly discovered models, deprecated IDs, and modified capabilities with timestamped diff logs in the Web UI.
83
89
  * 🖥️ **Full Web GUI Panel (**Settings → Model Synchronization**)**:
84
90
  * Live status cards per provider with active model counters;
85
91
  * Instant "Sync Now" button for on-demand catalog refreshes;
92
+ * Granular model checkboxes for selection and visibility management;
86
93
  * Provider enable/disable toggles and custom endpoint inputs.
87
94
 
88
95
  ---
89
96
 
90
97
  ## 🛠️ Supported Discovery Providers
91
98
 
92
- | Provider Identifier | Authentication Format | Capabilities Auto-Detection |
99
+ | Provider Identifier | Authentication Format | Capabilities Auto-Detection & Notes |
93
100
  |---|---|---|
94
101
  | `openai` | Bearer Token | `vision`, `tools`, `reasoning`, `embeddings` |
95
102
  | `anthropic` | `x-api-key` header | `vision`, `tools`, `reasoning` |
@@ -99,6 +106,8 @@ graph LR
99
106
  | `openrouter` | Bearer Token | Multi-vendor model catalog with pricing & context limits |
100
107
  | `groq` | Bearer Token | `tools`, `reasoning`, `vision`, `supportsDeveloperRole: false` |
101
108
  | `mistral` | Bearer Token | `tools`, `reasoning`, `vision` |
109
+ | `commandcode` | Bearer Token | `https://api.commandcode.ai/provider/v1/models` catalog with `visibleModels` selection syncing to `llm-commandcode` |
110
+ | `clinebot` | Bearer Token | Active subscription plan discovery (`/users/me/plan`), restricting sync strictly to plan-included models |
102
111
  | `fireworks` | Bearer Token | Open-weights inference endpoints |
103
112
  | `huggingface` | Bearer Token | Serverless Inference API catalog |
104
113
  | `together` | Bearer Token | Open-source model catalog |
@@ -117,3 +126,32 @@ dsh plugin --profile web add @goodandready/dsh-model-sync
117
126
  > Restart DSH Web UI after installation (`systemctl --user restart dsh-web`) to activate background catalog synchronization.
118
127
 
119
128
  ---
129
+
130
+ ## ⚙️ Configuration (`settings.yaml`)
131
+
132
+ ```yaml
133
+ dsh-model-sync:
134
+ enabled: true
135
+ syncIntervalMinutes: 60
136
+ autoReconcile: true
137
+ enableBalanceChecks: true
138
+ providers:
139
+ commandcode:
140
+ enabled: true
141
+ clinebot:
142
+ enabled: true
143
+ ```
144
+
145
+ | Parameter | Type | Default | Description |
146
+ |---|---|---|---|
147
+ | `enabled` | `boolean` | `true` | Master switch for background synchronization |
148
+ | `syncIntervalMinutes` | `number` | `60` | Polling frequency for catalog refreshes |
149
+ | `autoReconcile` | `boolean` | `true` | Automatically apply discovered models to active catalog |
150
+ | `enableBalanceChecks` | `boolean` | `true` | Query billing endpoints where supported |
151
+ | `providers.<id>.enabled`| `boolean` | `true` | Enable or disable discovery for specific provider |
152
+
153
+ ---
154
+
155
+ ## 📄 License
156
+
157
+ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
package/README.ru.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <div align="center">
4
4
 
5
- <h3>Динамическая синхронизация каталогов моделей и автоматический мониторинг баланса для DeepSeek Harness</h3>
5
+ <h3>Динамическая синхронизация каталогов LLM-моделей и автоматический мониторинг баланса для DeepSeek Harness</h3>
6
6
 
7
7
  <p align="center">
8
8
  <a href="https://www.npmjs.com/package/@goodandready/dsh-model-sync"><img src="https://img.shields.io/npm/v/@goodandready/dsh-model-sync.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
@@ -24,9 +24,9 @@
24
24
  <table align="center">
25
25
  <tr>
26
26
  <td align="center">
27
- ⭐ <strong>Если вам нравится этот плагин, поставьте ему звезду на GitHub</strong> — это покажет мне, что плагин вам полезен, и будет мотивировать меня развивать его дальше.
27
+ ⭐ <strong>Если вам нравится этот плагин, поставьте ему Star на GitHub</strong> — это покажет мне, что плагин полезен, и добавит мотивации продолжать его развитие.
28
28
  <br><br>
29
- 🐛 <strong>Если вы нашли баг или хотите предложить новый функционал</strong>, создайте issue на GitHub на любом языке — я рассмотрю ваше предложение и реализую полезные идеи в одной из следующих версий плагина.
29
+ 🐛 <strong>Если вы нашли баг или хотите предложить новую функцию</strong>, создайте Issue на GitHub на любом языке — я рассмотрю предложение и реализую полезные улучшения в одной из следующих версий плагина.
30
30
  </td>
31
31
  </tr>
32
32
  </table>
@@ -37,32 +37,36 @@
37
37
 
38
38
  ## ⚡ Обзор
39
39
 
40
- **`dsh-model-sync`** обеспечивает автоматическую актуализацию каталога моделей **DeepSeek Harness** напрямую от подключенных провайдеров.
40
+ **`dsh-model-sync`** поддерживает каталог моделей **DeepSeek Harness** в актуальном состоянии с апстрим AI-провайдерами.
41
41
 
42
- Вместо ручного редактирования YAML-файлов при выходе новых моделей, смене лимитов контекста или цен, плагин автоматически обнаруживает релизы, обновляет флаги возможностей (`vision`, `tools`, `reasoning`, `embeddings`), отслеживает остатки баланса и обновляет каталог на лету без перезапуска сервера.
42
+ Вместо ручного редактирования YAML-файлов при выпуске провайдером новой модели, изменении размера контекста или цен, `dsh-model-sync` автоматически обнаруживает новые релизы, обновляет метаданные возможностей (`vision`, `tools`, `reasoning`, `embeddings`), отслеживает баланс аккаунтов, управляет видимостью моделей в интерфейсе и синхронизирует модели на лету без перезапуска сервера.
43
43
 
44
44
  ```mermaid
45
45
  graph LR
46
- subgraph Trigger [Планировщик и ручной запуск]
46
+ subgraph Trigger [Расписание и ручной запуск]
47
47
  Cron[⏰ Фоновый планировщик опроса] --> Engine[Ядро dsh-model-sync]
48
- WebUI[🖥️ Кнопка «Синхронизировать сейчас»] --> Engine
48
+ WebUI[🖥️ Настройки: Кнопка Sync Now] --> Engine
49
49
  end
50
50
 
51
- subgraph Providers [25+ Внешних провайдеров]
51
+ subgraph Providers [25+ Апстрим-провайдеров]
52
52
  Engine --> Registry{Реестр адаптеров}
53
- Registry -->|Авторизация Bearer| P1[OpenAI / DeepSeek / OpenRouter / Groq]
54
- Registry -->|Заголовок x-api-key| P2[Anthropic Claude / Кастомные шлюзы]
55
- Registry -->|Ключ query-key| P3[Google Gemini]
56
- Registry -->|Локальный опрос| P4[Локальная Ollama / vLLM / SGLang]
53
+ Registry -->|Bearer Auth| P1[OpenAI / DeepSeek / OpenRouter / Groq]
54
+ Registry -->|x-api-key Auth| P2[Anthropic Claude / Пользовательские шлюзы]
55
+ Registry -->|query-key Auth| P3[Google Gemini]
56
+ Registry -->|Локальный опрос| P4[Локальные Ollama / vLLM / SGLang]
57
+ Registry -->|CommandCode API| P5[Command Code / visibleModels]
58
+ Registry -->|Опрос подписки| P6[План подписки ClineBot]
57
59
  end
58
60
 
59
61
  subgraph Reconcile [Сверка каталога и аудит]
60
- P1 --> Normalizer[Нормализация и разметка возможностей]
62
+ P1 --> Normalizer[Нормализатор моделей и тегирование возможностей]
61
63
  P2 --> Normalizer
62
64
  P3 --> Normalizer
63
65
  P4 --> Normalizer
64
- Normalizer --> Diff[Журнал изменений: Добавлено / Устарело]
65
- Diff --> Catalog[Активный каталог моделей DSH]
66
+ P5 --> Normalizer
67
+ P6 --> Normalizer
68
+ Normalizer --> Diff[Трекер изменений: Добавлено / Устарело]
69
+ Diff --> Catalog[Активный каталог моделей DSH и visibleModels]
66
70
  end
67
71
 
68
72
  style Trigger fill:#1e1e2e,stroke:#89b4fa,stroke-width:2px,color:#cdd6f4
@@ -74,15 +78,41 @@ graph LR
74
78
 
75
79
  ## ✨ Ключевые возможности
76
80
 
77
- * 🔄 **Автоматическое обнаружение моделей**: синхронизация списков моделей, контекстных окон и возможностей (`vision`, `tools`, `reasoning`, `embeddings`) для 25+ провайдеров.
78
- * 🌐 **25+ встроенных адаптеров**: готовая поддержка OpenAI, Anthropic, Google, DeepSeek, xAI, OpenRouter, Groq, Mistral, Cerebras, Fireworks, HuggingFace, Moonshot, NVIDIA, Qwen, Together, Xiaomi MiMo, SiliconFlow и локальной Ollama.
79
- * 🔌 **Универсальные кастомные адаптеры**: подключение любых сторонних OpenAI-совместимых эндпоинтов `/v1/models` (`bearer`, `x-api-key`, `query-key`, `none`).
80
- * 📊 **Мониторинг баланса и квот**: опрос биллинга провайдеров (где доступно) для контроля остатка кредитов.
81
- * 📜 **Журнал изменений (Diff Log)**: фиксация добавленных, удаленных и обновленных моделей с отметками времени.
82
- * 🖥️ **Панель управления в Web GUI (**Настройки → Синхронизация моделей**)**:
83
- * Статус-карточки со счетчиками активных моделей по каждому провайдеру;
84
- * Кнопка «Синхронизировать сейчас» для мгновенного обновления;
85
- * Переключатели провайдеров и ввод пользовательских адресов.
81
+ * 🔄 **Автоматическое обнаружение каталога**: синхронизация списков моделей, алиасов, контекстных окон и флагов возможностей (`vision`, `tools`, `reasoning`, `embeddings`) для 25+ провайдеров.
82
+ * 🛡️ **Совместимость шаблонов чата и ролей Minijinja**: автоматическая установка `compat.supportsDeveloperRole: false` для открытых моделей Groq (Qwen, Llama, Mistral, Gemma, DeepSeek) и DeepSeek API, предотвращая ошибки 400 `Unexpected message role` для reasoning-моделей с сохранением пользовательских настроек.
83
+ * 🌐 **25+ встроенных адаптеров провайдеров**: готовая поддержка OpenAI, Anthropic, Google, DeepSeek, xAI, OpenRouter, Groq, Mistral, Cerebras, Fireworks, HuggingFace, Moonshot, NVIDIA, Qwen, Together, Xiaomi MiMo, SiliconFlow, Command Code, ClineBot и локальной Ollama.
84
+ * 🎛️ **Управление моделями Command Code**: полное обнаружение моделей Command Code с возможностью выбора чекбоксами в UI и автоматической синхронизацией `visibleModels` в настройках `llm-commandcode`.
85
+ * 🎯 **Синхронизация тарифного плана ClineBot**: выделенный адаптер, опрашивающий `GET /users/me/plan`, который получает исключительно модели, входящие в активную подписку, исключая появление сотен недоступных моделей.
86
+ * 🔌 **Поддержка универсальных и кастомных адаптеров**: подключение произвольных OpenAI-совместимых эндпоинтов `/v1/models` с настраиваемой авторизацией (`bearer`, `x-api-key`, `query-key`, `none`).
87
+ * 📊 **Мониторинг баланса и квот**: опрос биллинговых эндпоинтов провайдеров (где поддерживается) для предотвращения неожиданного исчерпания средств.
88
+ * 📜 **История синхронизации и аудит диффов**: отслеживание всех добавленных моделей, устаревших ID и изменённых возможностей с журналом изменений во времени.
89
+ * 🖥️ **Полноценная панель Web UI (**Настройки → Синхронизация моделей**)**:
90
+ * Карточки статуса по каждому провайдеру со счётчиками активных моделей;
91
+ * Кнопка мгновенной синхронизации «Sync Now»;
92
+ * Выбор и скрытие моделей чекбоксами;
93
+ * Переключатели включения/отключения провайдеров и ввод кастомных эндпоинтов.
94
+
95
+ ---
96
+
97
+ ## 🛠️ Поддерживаемые провайдеры
98
+
99
+ | Идентификатор провайдера | Формат авторизации | Автоопределение возможностей и примечания |
100
+ |---|---|---|
101
+ | `openai` | Bearer Token | `vision`, `tools`, `reasoning`, `embeddings` |
102
+ | `anthropic` | Заголовок `x-api-key` | `vision`, `tools`, `reasoning` |
103
+ | `google` | Query-параметр / Ключ | `vision`, `tools`, `reasoning`, `embeddings` |
104
+ | `deepseek` | Bearer Token | `tools`, `reasoning` |
105
+ | `xai` | Bearer Token | `vision`, `tools`, `reasoning` |
106
+ | `openrouter` | Bearer Token | Мультипровайдерный каталог с ценами и контекстными лимитами |
107
+ | `groq` | Bearer Token | `tools`, `reasoning`, `vision`, `supportsDeveloperRole: false` |
108
+ | `mistral` | Bearer Token | `tools`, `reasoning`, `vision` |
109
+ | `commandcode` | Bearer Token | Каталог `https://api.commandcode.ai/provider/v1/models` с сохранением выбора в `visibleModels` для `llm-commandcode` |
110
+ | `clinebot` | Bearer Token | Опрос активного тарифного плана (`/users/me/plan`), синхронизация строго включённых в подписку моделей |
111
+ | `fireworks` | Bearer Token | Эндпоинты открытых весов |
112
+ | `huggingface` | Bearer Token | Каталог Serverless Inference API |
113
+ | `together` | Bearer Token | Каталог моделей Open-Source |
114
+ | `ollama` | Локальный HTTP (`none`) | Локальное обнаружение офлайн-моделей (`/api/tags`) |
115
+ | `custom` | Настраиваемый | Любой кастомный шлюз, совместимый с OpenAI или Google |
86
116
 
87
117
  ---
88
118
 
@@ -92,27 +122,36 @@ graph LR
92
122
  dsh plugin --profile web add @goodandready/dsh-model-sync
93
123
  ```
94
124
 
125
+ > [!IMPORTANT]
126
+ > Перезапустите Web UI DSH после установки (`systemctl --user restart dsh-web`), чтобы активировать фоновую синхронизацию каталогов.
127
+
95
128
  ---
96
129
 
97
- ## 🚀 Улучшения в версии 0.4.0
130
+ ## ⚙️ Конфигурация (`settings.yaml`)
131
+
132
+ ```yaml
133
+ dsh-model-sync:
134
+ enabled: true
135
+ syncIntervalMinutes: 60
136
+ autoReconcile: true
137
+ enableBalanceChecks: true
138
+ providers:
139
+ commandcode:
140
+ enabled: true
141
+ clinebot:
142
+ enabled: true
143
+ ```
98
144
 
99
- - **One-Click обновление плагина из настроек**: В карточку настроек плагина добавлен блок обновления с проверкой актуальной версии в npm, цветовым бейджем статуса и кнопкой обновления в один клик.
100
- - **Защита write-эндпоинтов по Loopback**: Все мутирующие HTTP-маршруты (`/apply`, `/policy`, `/clear-cache`, `/updater/update`) строго проверяют адрес источника (`127.0.0.1`, `::1`, `::ffff:127.0.0.1`) и заголовок Origin/Host для предотвращения несанкционированного доступа.
101
- - **Полная поддержка темы DSH (0 rgba / 0 hex)**: Все стили интерфейса переведены на официальные токены дизайн-системы DSH (`--dsw-alias-*`), обеспечивая нативную интеграцию со светлой и тёмной темами оформления.
102
- - **Модульная архитектура синхронизатора**: Монолитная логика синхронизации разделена на специализированные модули (`synchronizer-helpers.js`, `synchronizer-transfer.js`, `synchronizer-prober.js`), что упрощает поддержку и снижает размер основного файла ниже 600 строк.
103
- - **Чистый дистрибутив пакета**: Исключены внутренние файлы планирования и отладки; размер сжатого npm-пакета составляет менее 70 KiB, все файлы укладываются в лимит 256 KiB.
145
+ | Параметр | Тип | По умолчанию | Описание |
146
+ |---|---|---|---|
147
+ | `enabled` | `boolean` | `true` | Главный переключатель фоновой синхронизации |
148
+ | `syncIntervalMinutes` | `number` | `60` | Периодичность опроса провайдеров (в минутах) |
149
+ | `autoReconcile` | `boolean` | `true` | Автоматически применять обнаруженные модели к активному каталогу |
150
+ | `enableBalanceChecks` | `boolean` | `true` | Опрашивать биллинговые эндпоинты при наличии поддержки |
151
+ | `providers.<id>.enabled`| `boolean` | `true` | Включение или отключение обнаружения для конкретного провайдера |
104
152
 
105
- ## 🚀 Улучшения в v0.3.13
153
+ ---
106
154
 
107
- * ⚡ **ETag / 304 Not Modified для фонового опроса UI**:
108
- * Реализована генерация слабого ETag для маршрута `GET /dsh-model-sync/status`. При 15-секундном опросе UI возвращается пустой ответ `304 Not Modified`, исключающий холостую сериализацию JSON и сетевой оверхед.
109
- * 🌐 **Условные запросы к API провайдеров (Upstream Conditional Requests)**:
110
- * Передача заголовков `If-None-Match` и `If-Modified-Since` в generic и declarative адаптерах. При ответе `304` от апстрим-провайдера мгновенно возвращается кэш без повторного парсинга моделей.
111
- * 🎯 **Debounce поиска и useMemo в веб-интерфейсе**:
112
- * Добавлен debounce (150 мс) на ввод в строку поиска и мемоизация `React.useMemo` для фильтрации и сортировки моделей в пикере, обеспечивая плавный отклик без микрофризов.
113
- * 🧠 **Расширенное распознавание возможностей моделей**:
114
- * Распознавание современных reasoning/thinking моделей (`DeepSeek-R1`, `o1`, `o3-mini`, CoT) и моделей для кода (`code`), поддержка тега `code` в политиках фильтрации.
115
- * 🛡️ **Экспоненциальный джиттер при повторах**:
116
- * Защита от thundering herd с помощью случайного коэффициента джиттера (`0.5 - 1.0`) при повторных запросах к API провайдеров.
155
+ ## 📄 Лицензия
117
156
 
118
- ---
157
+ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
package/README.zh.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <div align="center">
4
4
 
5
- <h3>DeepSeek Harness 服务商模型目录动态同步与账户余额监控插件</h3>
5
+ <h3>面向 DeepSeek Harness 的动态大模型目录同步与自动化余额监控插件</h3>
6
6
 
7
7
  <p align="center">
8
8
  <a href="https://www.npmjs.com/package/@goodandready/dsh-model-sync"><img src="https://img.shields.io/npm/v/@goodandready/dsh-model-sync.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
@@ -12,7 +12,7 @@
12
12
  </p>
13
13
 
14
14
  <p align="center">
15
- <a href="https://goodandready.app/"><img src="https://img.shields.io/badge/作者全部项目-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="作者全部项目"></a>
15
+ <a href="https://goodandready.app/"><img src="https://img.shields.io/badge/作者所有项目-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="作者所有项目"></a>
16
16
  </p>
17
17
 
18
18
  <p align="center">
@@ -35,49 +35,38 @@
35
35
 
36
36
  ---
37
37
 
38
- ## 🚀 v0.3.13 性能与质量优化
39
-
40
- * ⚡ **ETag / 304 Not Modified 状态缓存**:
41
- * 为 `GET /dsh-model-sync/status` 提供弱 ETag 支持。UI 定时 15 秒轮询时直接返回 `304 Not Modified`,避免多余 JSON 序列化与流量损耗。
42
- * 🌐 **上游条件请求 (Upstream Conditional Requests)**:
43
- * 向提供商 API 发送 `If-None-Match` / `If-Modified-Since`。当提供商返回 304 时直接命中缓存,无需重复解析。
44
- * 🎯 **模型搜索防抖与 useMemo 优化**:
45
- * 模型选择器搜索框加入 150ms debounce 与 `React.useMemo` 缓存,大幅提升大模型列表下的筛选与排序流畅度。
46
- * 🧠 **扩展模型能力检测**:
47
- * 支持现代推理/思考模型(DeepSeek-R1、o1/o3-mini)及代码专用模型(`code`)的能力标记与策略过滤。
48
- * 🛡️ **带抖动的指数退避重试 (Exponential Jitter)**:
49
- * 在 API 失败重试时加入随机抖动系数,有效防止高并发下的惊群效应。
50
-
51
- ---
52
-
53
38
  ## ⚡ 插件概览
54
39
 
55
- **`dsh-model-sync`** 保持 **DeepSeek Harness** 模型选单与上游大模型服务商实时同步。
40
+ **`dsh-model-sync`** 保持您的 **DeepSeek Harness** 模型目录与上游各大 AI 服务商最新发布完全同步。
56
41
 
57
- 每当服务商上线新模型、扩充上下文长度或调整定价时,插件自动拉取最新清单,同步模型能力标签(`vision` 视觉、`tools` 工具调用、`reasoning` 深度思考、`embeddings` 嵌入),并在无需重启服务的前提下热重载目录。
42
+ 每当服务商推出新模型、调整上下文窗口上限或变更费率时,不再需要手动逐项编辑 YAML 配置文件。`dsh-model-sync` 能够自动发现新版本、更新模型能力元数据(`vision`、`tools`、`reasoning`、`embeddings`)、监控账户余额并在无需重启服务的情况下即时对齐模型目录。
58
43
 
59
44
  ```mermaid
60
45
  graph LR
61
46
  subgraph Trigger [调度与手动触发]
62
- Cron[⏰ 后台定时轮询调度器] --> Engine[dsh-model-sync 核心引擎]
47
+ Cron[⏰ 后台轮询调度器] --> Engine[dsh-model-sync 核心引擎]
63
48
  WebUI[🖥️ 设置面板: 立即同步按钮] --> Engine
64
49
  end
65
50
 
66
51
  subgraph Providers [25+ 上游服务商]
67
- Engine --> Registry{适配器注册表}
68
- Registry -->|Bearer 鉴权| P1[OpenAI / DeepSeek / OpenRouter / Groq]
69
- Registry -->|x-api-key 鉴权| P2[Anthropic Claude / 自定义网关]
70
- Registry -->|query-key 鉴权| P3[Google Gemini]
71
- Registry -->|本地探针| P4[本地 Ollama / vLLM / SGLang]
52
+ Engine --> Registry{适配器注册中心}
53
+ Registry -->|Bearer 认证| P1[OpenAI / DeepSeek / OpenRouter / Groq]
54
+ Registry -->|x-api-key 认证| P2[Anthropic Claude / 自定义网关]
55
+ Registry -->|query-key 认证| P3[Google Gemini]
56
+ Registry -->|本地探测| P4[本地 Ollama / vLLM / SGLang]
57
+ Registry -->|CommandCode 接口| P5[Command Code / visibleModels]
58
+ Registry -->|套餐检测| P6[ClineBot 订阅套餐]
72
59
  end
73
60
 
74
- subgraph Reconcile [目录对齐与审计]
75
- P1 --> Normalizer[模型归一化与能力特征打标]
61
+ subgraph Reconcile [目录对齐与审计追踪]
62
+ P1 --> Normalizer[模型标准化器与能力打标]
76
63
  P2 --> Normalizer
77
64
  P3 --> Normalizer
78
65
  P4 --> Normalizer
79
- Normalizer --> Diff[差异审计流: 新增 / 废弃模型]
80
- Diff --> Catalog[DSH 当前生效模型选单]
66
+ P5 --> Normalizer
67
+ P6 --> Normalizer
68
+ Normalizer --> Diff[差异跟踪: 新增 / 已废弃]
69
+ Diff --> Catalog[当前可用 DSH 模型目录与 visibleModels]
81
70
  end
82
71
 
83
72
  style Trigger fill:#1e1e2e,stroke:#89b4fa,stroke-width:2px,color:#cdd6f4
@@ -87,30 +76,81 @@ graph LR
87
76
 
88
77
  ---
89
78
 
90
- ## 📦 安装指南
79
+ ## ✨ 核心特性
80
+
81
+ * 🔄 **自动化目录同步**:自动对齐 25+ 服务商的模型清单、别名、上下文窗口及能力标签(`vision`、`tools`、`reasoning`、`embeddings`)。
82
+ * 🛡️ **Minijinja 聊天模板与角色兼容性保障**:自动为 Groq 开源模型(Qwen、Llama、Mistral、Gemma、DeepSeek)及 DeepSeek API 注入 `compat.supportsDeveloperRole: false`,避免推理模型报 400 `Unexpected message role` 错误,同时保留用户自定义配置。
83
+ * 🌐 **内置 25+ 服务商适配器**:预置支持 OpenAI、Anthropic、Google、DeepSeek、xAI、OpenRouter、Groq、Mistral、Cerebras、Fireworks、HuggingFace、Moonshot、NVIDIA、Qwen、Together、Xiaomi MiMo、SiliconFlow、Command Code、ClineBot 以及本地 Ollama。
84
+ * 🎛️ **Command Code 模型精细管理**:完整发现 Command Code 模型目录,在 Web 界面提供勾选过滤,自动同步 `visibleModels` 至 `llm-commandcode` 设置中。
85
+ * 🎯 **ClineBot 订阅套餐精准匹配**:专属适配器直接调用 `GET /users/me/plan`,只拉取当前有效订阅套餐包含的模型,彻底解决全量拉取 400+ 外部模型导致的混淆。
86
+ * 🔌 **通用与自定义适配器扩展**:支持接入任意兼容 OpenAI `/v1/models` 规范的网关,支持多种认证方式(`bearer`、`x-api-key`、`query-key`、`none`)。
87
+ * 📊 **余额与用量监控**:实时查询上游计费端点,精准反馈账户额度并防止因欠费导致任务中断。
88
+ * 📜 **同步历史与变更审计**:完整记录新模型上线、旧模型废弃及能力变更的时间线与详细差异。
89
+ * 🖥️ **全功能 Web 管理面板 (**设置 → 模型同步**)**:
90
+ * 每个服务商的卡片化状态展示与模型计数;
91
+ * 一键即时同步(Sync Now);
92
+ * 模型勾选过滤与显示控制;
93
+ * 服务商启用开关及自定义 Endpoint 地址输入。
94
+
95
+ ---
96
+
97
+ ## 🛠️ 支持的服务商列表
98
+
99
+ | 服务商标识符 | 认证机制 | 能力自检与特性说明 |
100
+ |---|---|---|
101
+ | `openai` | Bearer Token | `vision`, `tools`, `reasoning`, `embeddings` |
102
+ | `anthropic` | `x-api-key` 请求头 | `vision`, `tools`, `reasoning` |
103
+ | `google` | Query 参数 / 密钥 | `vision`, `tools`, `reasoning`, `embeddings` |
104
+ | `deepseek` | Bearer Token | `tools`, `reasoning` |
105
+ | `xai` | Bearer Token | `vision`, `tools`, `reasoning` |
106
+ | `openrouter` | Bearer Token | 多厂商汇聚模型目录,包含计费与上下文规格 |
107
+ | `groq` | Bearer Token | `tools`, `reasoning`, `vision`, `supportsDeveloperRole: false` |
108
+ | `mistral` | Bearer Token | `tools`, `reasoning`, `vision` |
109
+ | `commandcode` | Bearer Token | 对接 `https://api.commandcode.ai/provider/v1/models`,同步勾选至 `llm-commandcode` 的 `visibleModels` |
110
+ | `clinebot` | Bearer Token | 仅同步有效订阅套餐 (`/users/me/plan`) 包含的模型 |
111
+ | `fireworks` | Bearer Token | 开源权重推理节点 |
112
+ | `huggingface` | Bearer Token | Serverless Inference API 目录 |
113
+ | `together` | Bearer Token | 开源大模型服务网络 |
114
+ | `ollama` | 本地 HTTP (`none`) | 本地离线模型实时发现 (`/api/tags`) |
115
+ | `custom` | 自定义配置 | 任意兼容 OpenAI 或 Google 规范的 API 网关 |
116
+
117
+ ---
118
+
119
+ ## 📦 快速安装
91
120
 
92
121
  ```bash
93
122
  dsh plugin --profile web add @goodandready/dsh-model-sync
94
123
  ```
95
124
 
96
- ---
125
+ > [!IMPORTANT]
126
+ > 安装后请重启 DSH Web 界面 (`systemctl --user restart dsh-web`),以激活后台目录自动同步。
97
127
 
98
- ## 🚀 v0.4.0 新特性与增强
128
+ ---
99
129
 
100
- - **设置卡片一键更新**: 在 DSH 设置卡片中集成自动版本检查,展示当前与最新版本对比徽标,支持一键安全升级并在更新后提示重启。
101
- - **严格本地环回 (Loopback) 安全保护**: 所有修改状态的 HTTP 路由 (`/apply`, `/policy`, `/clear-cache`, `/updater/update`) 均强制校验本地环回 IP (`127.0.0.1`, `::1`, `::ffff:127.0.0.1`) 及请求来源,杜绝外部恶意调用。
102
- - **100% 原生主题变量适配**: 客户端 UI 全面移除硬编码颜色与 `rgba()` 样式,完全切换至 DSH 官方 CSS 设计令牌 (`--dsw-alias-*`),无缝适配深色与浅色主题。
103
- - **同步引擎模块化拆分**: 核心同步逻辑已解耦拆分为专项子模块 (`synchronizer-helpers.js`, `synchronizer-transfer.js`, `synchronizer-prober.js`),大幅提升代码可读性与维护性。
104
- - **轻量分发包清理**: 彻底排除内部计划与调试文件,精简 npm 发布体积至 70 KiB 以下,所有文件均完全符合 256 KiB 上限规范。
130
+ ## ⚙️ 配置说明 (`settings.yaml`)
131
+
132
+ ```yaml
133
+ dsh-model-sync:
134
+ enabled: true
135
+ syncIntervalMinutes: 60
136
+ autoReconcile: true
137
+ enableBalanceChecks: true
138
+ providers:
139
+ commandcode:
140
+ enabled: true
141
+ clinebot:
142
+ enabled: true
143
+ ```
105
144
 
106
- ## 🚀 v0.3.14 新特性与增强
145
+ | 参数名称 | 类型 | 默认值 | 功能说明 |
146
+ |---|---|---|---|
147
+ | `enabled` | `boolean` | `true` | 后台自动同步主开关 |
148
+ | `syncIntervalMinutes` | `number` | `60` | 目录更新轮询间隔(分钟) |
149
+ | `autoReconcile` | `boolean` | `true` | 是否自动将新发现的模型生效至可用列表 |
150
+ | `enableBalanceChecks` | `boolean` | `true` | 在受支持的服务商处查询余额 |
151
+ | `providers.<id>.enabled`| `boolean` | `true` | 单独开启或关闭指定服务商的同步 |
107
152
 
108
- - **语言标准**: 插件原生支持 `en` 与 `zh` 双语,俄语本地化已独立解耦至 `goodandready/dsh-russian-lang` 翻译包 (#191)。
109
- - **发布隔离**: 通过 `.npmignore` 严格过滤测试、规划和文档文件,保证 npm 发布包体积小于 256 KiB。
110
- - **模型批量可用性测试**: 支持通过 `POST /dsh-model-sync/batch-try` 并发批量测试所选模型,展示延迟徽标并一键取消选中不可用模型。
111
- - **价格与上下文窗口过滤**: 支持按每百万 Token 最高价格及最小上下文长度过滤候选模型。
112
- - **配置导入与导出**: 支持通过 `GET /export` 与 `POST /import` 导出及恢复全部策略、模型选择、别名和定时计划。
113
- - **自定义模型别名映射**: 支持通过 `GET/POST /aliases` 为特定服务商与模型组合设置便于调用的简短别名。
153
+ ---
114
154
 
115
155
  ## 📄 开源协议
116
156
 
@@ -1,14 +1,51 @@
1
1
  import { catalogRequestError } from './reliability.js'
2
2
  import { canUseGenericAdapter, createGenericAdapter } from './generic-adapter.js'
3
- import { normalizeModels } from './models.js'
3
+ import { normalizeModels, parseClinePassModels } from './models.js'
4
4
 
5
5
  const MAX_RESPONSE_BYTES = 4 * 1024 * 1024
6
6
  const SAFE_AUTH = new Set(['bearer', 'x-api-key', 'query-key', 'none'])
7
- const SAFE_PARSERS = new Set(['openai', 'google'])
7
+ const SAFE_PARSERS = new Set(['openai', 'google', 'clinebot-plan'])
8
8
  const CAPABILITIES = new Set(['vision', 'tools', 'reasoning', 'embeddings', 'code'])
9
9
  const BLOCKED_HEADERS = /authorization|api[_-]?key|token|secret|cookie/i
10
10
 
11
11
  const PROVIDER_ADAPTERS = Object.freeze({
12
+ clinebot: {
13
+ endpoint: 'https://api.cline.bot/api/v1/users/me/plan',
14
+ auth: 'bearer',
15
+ parser: 'clinebot-plan',
16
+ defaultModels: [
17
+ 'cline-pass/deepseek-v4-flash',
18
+ 'cline-pass/deepseek-v4-pro',
19
+ 'cline-pass/glm-5.2',
20
+ 'cline-pass/kimi-k3',
21
+ 'cline-pass/kimi-k2.7-code',
22
+ 'cline-pass/kimi-k2.6',
23
+ 'cline-pass/qwen3.7-max',
24
+ 'cline-pass/qwen3.7-plus',
25
+ 'cline-pass/minimax-m3',
26
+ 'cline-pass/mimo-v2.5',
27
+ 'cline-pass/mimo-v2.5-pro',
28
+ ],
29
+ },
30
+ commandcode: {
31
+ endpoint: 'https://api.commandcode.ai/provider/v1/models',
32
+ auth: 'bearer',
33
+ fields: {
34
+ id: 'id',
35
+ name: 'name',
36
+ contextWindow: 'context_length',
37
+ },
38
+ defaultModels: [
39
+ 'deepseek/deepseek-v4.1-flash',
40
+ 'deepseek/deepseek-v4-flash',
41
+ 'qwen/qwen3.8-flash',
42
+ 'google/gemini-3.8-flash',
43
+ 'meta/muse-spark-1.3-contributor',
44
+ 'zai-org/glm-5.1',
45
+ 'xiaomi/mimo-v2.5-pro',
46
+ 'minimaxai/minimax-m3',
47
+ ],
48
+ },
12
49
  anthropic: { endpoint: 'https://api.anthropic.com/v1/models', auth: 'x-api-key', headers: { 'anthropic-version': '2023-06-01' } },
13
50
  google: { endpoint: 'https://generativelanguage.googleapis.com/v1beta/models', auth: 'query-key', parse: 'google' },
14
51
  openai: { endpoint: 'https://api.openai.com/v1/models', auth: 'bearer' },
@@ -62,6 +99,11 @@ function mappedRows(provider, rows, descriptor) {
62
99
  }
63
100
 
64
101
  function parseRows(provider, payload, descriptor) {
102
+ if (descriptor.parse === 'clinebot-plan') {
103
+ const plan = payload?.data?.plan || payload?.data || payload?.plan || payload
104
+ const included = plan?.features?.included || plan?.includedModels || []
105
+ return parseClinePassModels(included)
106
+ }
65
107
  if (descriptor.parse === 'google') {
66
108
  const rows = Array.isArray(payload?.models) ? payload.models : []
67
109
  return normalizeModels(provider, rows.map((row) => ({
package/lib/inventory.js CHANGED
@@ -5,6 +5,7 @@ export const API_KEY_PROVIDER_IDS = Object.freeze([
5
5
  'ant-ling',
6
6
  'azure-openai-responses',
7
7
  'cerebras',
8
+ 'commandcode',
8
9
  'cloudflare-ai-gateway',
9
10
  'cloudflare-workers-ai',
10
11
  'deepseek',
@@ -63,7 +64,18 @@ export function listApiKeyProviders(llm, settings) {
63
64
  const out = []
64
65
  const seen = new Set()
65
66
  for (const entry of directory) {
66
- const profile = profiles[entry.provider] ?? {}
67
+ let profile = profiles[entry.provider]
68
+ if (!profile && (entry.provider === 'commandcode' || entry.settingsNs === 'llm-commandcode')) {
69
+ const cc = settings?.get?.('llm-commandcode')
70
+ profile = {
71
+ apiKeyEnv: cc?.apiKeyEnv ?? 'COMMANDCODE_API_KEY',
72
+ credentialRef: cc?.apiKeyEnv ?? 'COMMANDCODE_API_KEY',
73
+ baseURL: cc?.apiBase ?? 'https://api.commandcode.ai',
74
+ models: cc?.visibleModels ?? [],
75
+ }
76
+ } else {
77
+ profile = profile ?? {}
78
+ }
67
79
  if (!isApiKeyProvider(entry, profile) || seen.has(entry.provider)) continue
68
80
  seen.add(entry.provider)
69
81
  out.push({
package/lib/models.js CHANGED
@@ -380,3 +380,153 @@ export function diffModels(previous, next) {
380
380
  hasChanges: added.length > 0 || removed.length > 0 || renamed.length > 0 || changed.length > 0,
381
381
  }
382
382
  }
383
+
384
+ export const CLINEPASS_MODELS = Object.freeze([
385
+ {
386
+ id: 'cline-pass/deepseek-v4-flash',
387
+ name: 'DeepSeek V4 Flash',
388
+ contextWindow: 200000,
389
+ maxTokens: 8192,
390
+ capabilities: { vision: true, code: true, reasoning: true },
391
+ input: ['text', 'image'],
392
+ },
393
+ {
394
+ id: 'cline-pass/deepseek-v4-pro',
395
+ name: 'DeepSeek V4 Pro',
396
+ contextWindow: 200000,
397
+ maxTokens: 8192,
398
+ capabilities: { vision: true, code: true, reasoning: true },
399
+ input: ['text', 'image'],
400
+ },
401
+ {
402
+ id: 'cline-pass/glm-5.2',
403
+ name: 'GLM 5.2',
404
+ contextWindow: 200000,
405
+ maxTokens: 8192,
406
+ capabilities: { code: true },
407
+ input: ['text'],
408
+ },
409
+ {
410
+ id: 'cline-pass/kimi-k3',
411
+ name: 'Kimi K3',
412
+ contextWindow: 200000,
413
+ maxTokens: 8192,
414
+ capabilities: { reasoning: true },
415
+ input: ['text'],
416
+ },
417
+ {
418
+ id: 'cline-pass/kimi-k2.7-code',
419
+ name: 'Kimi K2.7 Code',
420
+ contextWindow: 200000,
421
+ maxTokens: 8192,
422
+ capabilities: { code: true, tools: true },
423
+ input: ['text'],
424
+ },
425
+ {
426
+ id: 'cline-pass/kimi-k2.6',
427
+ name: 'Kimi K2.6',
428
+ contextWindow: 200000,
429
+ maxTokens: 8192,
430
+ capabilities: { code: true },
431
+ input: ['text'],
432
+ },
433
+ {
434
+ id: 'cline-pass/qwen3.7-max',
435
+ name: 'Qwen 3.7 Max',
436
+ contextWindow: 200000,
437
+ maxTokens: 8192,
438
+ capabilities: { vision: true },
439
+ input: ['text', 'image'],
440
+ },
441
+ {
442
+ id: 'cline-pass/qwen3.7-plus',
443
+ name: 'Qwen 3.7 Plus',
444
+ contextWindow: 200000,
445
+ maxTokens: 8192,
446
+ capabilities: { vision: true },
447
+ input: ['text', 'image'],
448
+ },
449
+ {
450
+ id: 'cline-pass/minimax-m3',
451
+ name: 'MiniMax M3',
452
+ contextWindow: 200000,
453
+ maxTokens: 8192,
454
+ capabilities: { reasoning: true },
455
+ input: ['text'],
456
+ },
457
+ {
458
+ id: 'cline-pass/mimo-v2.5',
459
+ name: 'MiMo V2.5',
460
+ contextWindow: 200000,
461
+ maxTokens: 8192,
462
+ capabilities: { code: true },
463
+ input: ['text'],
464
+ },
465
+ {
466
+ id: 'cline-pass/mimo-v2.5-pro',
467
+ name: 'MiMo V2.5 Pro',
468
+ contextWindow: 200000,
469
+ maxTokens: 8192,
470
+ capabilities: { code: true, reasoning: true },
471
+ input: ['text'],
472
+ },
473
+ ])
474
+
475
+ export function parseClinePassModels(includedInput) {
476
+ if (!includedInput) return [...CLINEPASS_MODELS]
477
+ let includedText = ''
478
+ if (Array.isArray(includedInput)) {
479
+ const foundStr = includedInput.find((item) => typeof item === 'string' && /includes\s+/i.test(item))
480
+ if (foundStr) {
481
+ includedText = foundStr
482
+ } else {
483
+ includedText = includedInput.filter((x) => typeof x === 'string').join(', ')
484
+ }
485
+ } else if (typeof includedInput === 'string') {
486
+ includedText = includedInput
487
+ } else {
488
+ return [...CLINEPASS_MODELS]
489
+ }
490
+
491
+ const clean = includedText
492
+ .replace(/^includes\s+/i, '')
493
+ .replace(/\band\b/gi, ',')
494
+ .replace(/\./g, '')
495
+ .trim()
496
+
497
+ const parts = clean
498
+ .split(',')
499
+ .map((p) => p.trim())
500
+ .filter(Boolean)
501
+
502
+ if (!parts.length) return [...CLINEPASS_MODELS]
503
+
504
+ const normalize = (s) =>
505
+ String(s || '')
506
+ .toLowerCase()
507
+ .replace(/[\s._-]+/g, '')
508
+
509
+ const matched = []
510
+ for (const name of parts) {
511
+ const targetNorm = normalize(name)
512
+ const found = CLINEPASS_MODELS.find(
513
+ (m) =>
514
+ normalize(m.name) === targetNorm ||
515
+ normalize(m.id.replace(/^cline-pass\//, '')) === targetNorm
516
+ )
517
+ if (found) {
518
+ matched.push(found)
519
+ } else {
520
+ const idPart = name.toLowerCase().replace(/[\s_]+/g, '-')
521
+ matched.push({
522
+ id: `cline-pass/${idPart}`,
523
+ name,
524
+ contextWindow: 200000,
525
+ maxTokens: 8192,
526
+ capabilities: {},
527
+ input: ['text'],
528
+ })
529
+ }
530
+ }
531
+ return normalizeModels('clinebot', matched.length ? matched : [...CLINEPASS_MODELS])
532
+ }
@@ -53,7 +53,20 @@ export function descriptorOf(settings, ns) {
53
53
  }
54
54
 
55
55
  export function profileOf(settings, provider) {
56
- try { return settings?.get(PIAI_NS)?.providers?.[provider] ?? {} }
56
+ try {
57
+ const piai = settings?.get(PIAI_NS)?.providers?.[provider]
58
+ if (piai) return piai
59
+ if (provider === 'commandcode') {
60
+ const cc = settings?.get?.('llm-commandcode')
61
+ return {
62
+ apiKeyEnv: cc?.apiKeyEnv ?? 'COMMANDCODE_API_KEY',
63
+ credentialRef: cc?.apiKeyEnv ?? 'COMMANDCODE_API_KEY',
64
+ baseURL: cc?.apiBase ?? 'https://api.commandcode.ai',
65
+ models: cc?.visibleModels ?? [],
66
+ }
67
+ }
68
+ return {}
69
+ }
57
70
  catch { return {} }
58
71
  }
59
72
 
@@ -369,13 +369,24 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
369
369
  const resultsToApply = results.filter((row) => row.status === 'ok' && row.changed && (!dryRun || providerAutoApply(row.provider)))
370
370
  if (resultsToApply.length > 0) {
371
371
  const settings = settingsOf(ctx)
372
- await replaceWithRetry(settings, PIAI_NS, (section) => {
373
- let next = section
374
- for (const row of resultsToApply) {
375
- next = catalogPatch(next, row.provider, row.next)
372
+ const piaiRows = resultsToApply.filter((r) => r.provider !== 'commandcode')
373
+ const ccRows = resultsToApply.filter((r) => r.provider === 'commandcode')
374
+ if (piaiRows.length > 0) {
375
+ await replaceWithRetry(settings, PIAI_NS, (section) => {
376
+ let next = section
377
+ for (const row of piaiRows) {
378
+ next = catalogPatch(next, row.provider, row.next)
379
+ }
380
+ return next
381
+ })
382
+ }
383
+ for (const row of ccRows) {
384
+ if (typeof settings?.mutate === 'function') {
385
+ await settings.mutate('llm-commandcode', [
386
+ { op: 'set', path: ['visibleModels'], value: row.selectedModels?.length ? row.selectedModels : row.next.map(m => m.id) }
387
+ ])
376
388
  }
377
- return next
378
- })
389
+ }
379
390
  result.applied = true
380
391
  }
381
392
  if (!dryRun || resultsToApply.length > 0) {
@@ -500,9 +511,6 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
500
511
  const settings = settingsOf(ctx)
501
512
  const section = settings?.get?.(PIAI_NS)
502
513
  const descriptor = descriptorOf(settings, PIAI_NS)
503
- if (!section || !descriptor || typeof settings.replace !== 'function') {
504
- throw Object.assign(new Error('llm-pi-ai settings are not writable'), { code: 'SETTINGS_UNAVAILABLE' })
505
- }
506
514
  const current = normalizeModels(provider, profileOf(settings, provider).models ?? row.models)
507
515
  const nextModels = selectedModels.length
508
516
  ? selectedModels.map((id) => availableById.get(id))
@@ -510,11 +518,24 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
510
518
  const previousModelSelections = structuredClone(config.modelSelections ?? {})
511
519
  const modelSelections = { ...previousModelSelections, [provider]: selectedModels }
512
520
 
513
- // Persist the allowlist first. If the LLM catalog write fails, roll the
514
- // allowlist back so the two namespaces cannot be left disagreeing.
521
+ // Persist the allowlist first.
515
522
  await saveConfig({ modelSelections })
516
523
  try {
517
- await replaceWithRetry(settings, PIAI_NS, (section) => catalogPatch(section, provider, nextModels))
524
+ if (provider === 'commandcode') {
525
+ if (typeof settings?.mutate === 'function') {
526
+ await settings.mutate('llm-commandcode', [
527
+ { op: 'set', path: ['visibleModels'], value: selectedModels }
528
+ ])
529
+ }
530
+ if (section?.providers?.commandcode && typeof settings?.replace === 'function') {
531
+ await replaceWithRetry(settings, PIAI_NS, (sec) => catalogPatch(sec, provider, nextModels))
532
+ }
533
+ } else {
534
+ if (!section || !descriptor || typeof settings.replace !== 'function') {
535
+ throw Object.assign(new Error('llm-pi-ai settings are not writable'), { code: 'SETTINGS_UNAVAILABLE' })
536
+ }
537
+ await replaceWithRetry(settings, PIAI_NS, (sec) => catalogPatch(sec, provider, nextModels))
538
+ }
518
539
  } catch (error) {
519
540
  try {
520
541
  await saveConfig({ modelSelections: previousModelSelections })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-model-sync",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Automatic model catalog synchronization for API-key DeepSeek Harness providers.",
5
5
  "keywords": [
6
6
  "deepseek-harness",