@goodandready/dsh-model-sync 0.4.0 → 0.4.2

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
@@ -75,6 +75,7 @@ graph LR
75
75
  ## ✨ Key Features
76
76
 
77
77
  * 🔄 **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.
78
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.
79
80
  * 🔌 **Generic / Custom Adapter Support**: Connect arbitrary OpenAI-compatible `/v1/models` endpoints with configurable auth (`bearer`, `x-api-key`, `query-key`, `none`).
80
81
  * 📊 **Balance & Quota Monitoring**: Queries upstream billing endpoints (where supported) to report account credit balances and prevent unexpected exhaustion.
@@ -96,7 +97,7 @@ graph LR
96
97
  | `deepseek` | Bearer Token | `tools`, `reasoning` |
97
98
  | `xai` | Bearer Token | `vision`, `tools`, `reasoning` |
98
99
  | `openrouter` | Bearer Token | Multi-vendor model catalog with pricing & context limits |
99
- | `groq` | Bearer Token | Ultra-fast inference catalog |
100
+ | `groq` | Bearer Token | `tools`, `reasoning`, `vision`, `supportsDeveloperRole: false` |
100
101
  | `mistral` | Bearer Token | `tools`, `reasoning`, `vision` |
101
102
  | `fireworks` | Bearer Token | Open-weights inference endpoints |
102
103
  | `huggingface` | Bearer Token | Serverless Inference API catalog |
@@ -116,151 +117,3 @@ dsh plugin --profile web add @goodandready/dsh-model-sync
116
117
  > Restart DSH Web UI after installation (`systemctl --user restart dsh-web`) to activate background catalog synchronization.
117
118
 
118
119
  ---
119
-
120
- ## 🚀 Enhancements in v0.4.0
121
-
122
- - **One-Click In-App Updater**: Check npm registry for updates and trigger one-click upgrades directly within the DSH Settings Card, complete with version diff badges and server restart alerts.
123
- - **Strict Loopback Security**: All mutating HTTP endpoints (`/apply`, `/policy`, `/clear-cache`, `/updater/update`) now require verified loopback origin and remote socket IP to prevent CSRF / unauthorized network access.
124
- - **100% Theme Token Compliance**: Eliminated all hardcoded colors and `rgba()` values in client UI in favor of official DSH theme tokens (`--dsw-alias-*`), ensuring perfect dark/light mode rendering.
125
- - **Modular Architecture**: Decomposed core synchronization logic into specialized modules (`synchronizer-helpers.js`, `synchronizer-transfer.js`, `synchronizer-prober.js`), significantly enhancing code readability and maintainability.
126
- - **Clean Packaging**: Excluded development and internal planning artifacts from git and npm distribution, maintaining an ultra-lightweight release bundle (< 70 KiB).
127
-
128
- ## 🚀 Enhancements in v0.3.14
129
-
130
- - **Language Standard**: Canonical plugin in `en` and `zh`. Runtime Russian translations decoupled into `goodandready/dsh-russian-lang` (#191).
131
- - **Packaging Isolation**: Dedicated `.npmignore` excludes tests, plans, and documentation to maintain a minimal < 256 KiB npm release bundle.
132
- - **Batch Health Check**: Probe multiple selected models in parallel via `POST /dsh-model-sync/batch-try` with latency badges and one-click removal of unreachable models.
133
- - **Cost & Context Policies**: Set maximum price per million tokens and minimum context window thresholds in provider policies.
134
- - **Catalog Export & Import**: Backup and restore full plugin configuration (policies, selections, aliases, scheduler) via `GET /export` and `POST /import`.
135
- - **Model Alias Mapping**: Assign friendly aliases to provider/model pairs with `GET /aliases` and `POST /aliases` with interactive UI card.
136
-
137
- ## 🚀 Enhancements in v0.3.13
138
-
139
- * ⚡ **Zero-Overhead Polling via HTTP ETag / 304 Not Modified**:
140
- * Implemented weak ETag generation for `GET /dsh-model-sync/status`. When the Web UI polls every 15 seconds, unchanged catalogs receive an empty `304 Not Modified` response, eliminating redundant JSON serialization and network traffic.
141
- * 🌐 **Upstream Conditional HTTP Requests**:
142
- * Added `If-None-Match` and `If-Modified-Since` headers to upstream provider discovery. Catalogs that return `304 Not Modified` resolve instantly from memory cache without re-parsing or diff re-computation.
143
- * 🎯 **Debounced Search & Memoized Sorting in UI**:
144
- * Introduced 150ms input debouncing and `React.useMemo` for model filtering/sorting in the model picker, ensuring fluid search responsiveness on catalogs with hundreds of models.
145
- * 🧠 **Expanded Capability Detection**:
146
- * Added detection for modern reasoning models (`DeepSeek-R1`, `o1`, `o3-mini`, `thinking`) and specialized code generation models (`code`), plus support for the `code` capability filter in policy matching and UI picker.
147
- * 🛡️ **Exponential Retry Jitter**:
148
- * Enhanced `retryWithBackoff` with randomized jitter (`0.5 - 1.0` multiplier) to prevent thundering-herd effects on provider APIs during network hiccups or rate limits.
149
-
150
- ---
151
-
152
- ## 🛠️ Enhancements & Fixes in v0.3.11
153
-
154
- * 🗂️ **Streamlined Settings UI Surface**:
155
- * Removed the redundant top-level settings sidebar section fallback (`settings.section`), keeping configuration strictly inside the standard DSH plugin settings card (`settings.plugin.item`).
156
- * 🛡️ **Hardened Cordis Service Access**:
157
- * Service access for `settings` and `llm` is now performed via `ctx.get(...)` with safe fallback to direct property inspection, preventing silent resolution failures on Cordis proxy implementations.
158
-
159
- ---
160
-
161
- ## 🛠️ Enhancements & Fixes in v0.3.8
162
-
163
- * ⚙️ **Reactive Settings Card Binding (`settingsScope`)**:
164
- * Integrated modern DSH kernel `settingsScope` service (`ctx.settingsScope.bind({ namespace: 'dsh-model-sync' })`) with `useSyncExternalStore`.
165
- * Added direct configuration controls for the background scheduler in the Web UI: enable/disable background sync (`scheduleEnabled`), customize interval (`intervalMinutes`), and toggle automatic application of new models (`autoApply`).
166
- * Gracefully handles all snapshot lifecycle states (`ready`, `loading`, `unavailable`) and collects field validation errors without dropping form drafts.
167
- * 🌐 **Full Model Picker Localization**:
168
- * Translated all previously hardcoded strings in the model selector, including search input placeholder and sorting options (`Name A→Z`, `Price ↑`, `Context ↓`, `Newest ↓`), dynamically reacting to DSH locale switching (`en` / `ru`).
169
- * 📜 **Synchronization History Localization**:
170
- * Added the missing `noHistory` key to English and Russian localization dictionaries, ensuring informative feedback when sync history is empty.
171
- * 📋 **Project Design Contract**:
172
- * Added authoritative `docs/design/DESIGN.md` defining UI surfaces, styling foundations, token conventions, and state contracts.
173
-
174
- ---
175
-
176
- ## 🛠️ Enhancements & Fixes in v0.3.6
177
-
178
- * 🔑 **Universal Credential Resolution (`credentialRef` & `apiKeyRef`)**:
179
- * Added full support for modern DSH `credentialRef` and `apiKeyRef` references across inventory detection, provider adapters, and OpenAI-compatible generic routes.
180
- * Providers configured via DSH Credentials Service are now accurately discovered and authenticated without requiring legacy `apiKeyEnv`.
181
- * ♻️ **Lifecycle Cleanup for Deprecated Models**:
182
- * Fixed an issue where models previously marked as `deprecated` would remain indefinitely in settings when removed by the upstream provider. When `removeMissing: true` is enabled, missing deprecated models now cleanly transition to `removed` status once the grace period expires.
183
- * ⏰ **Accurate Scheduler Inactive Status**:
184
- * Corrected `scheduler.status()` to return `nextRunAt: null` when background synchronization is disabled, avoiding misleading run times in the Web UI.
185
- * 🛡️ **Web UI Resilience & Big Catalog Scalability**:
186
- * Eliminated potential UI crashes during model sorting by safely handling missing model display names.
187
- * Replaced spread operations on model arrays with iterative aggregations, preventing call stack overflow (`RangeError`) on massive catalogs (10,000+ models).
188
- * Surfaced detailed probe error messages in the UI when testing individual models via the `▶` button.
189
- * Localized cache expiration and offline status strings in Russian and English.
190
- * ⚡ **Deterministic Model Diffing & HTTP Stream Safety**:
191
- * Implemented stable key-sorted serialization in `diffModels` and `sameModel` to eliminate false-positive diffs caused by non-deterministic JSON key order.
192
- * Added early stream destruction on oversized HTTP request bodies to prevent memory bloat.
193
-
194
- ---
195
-
196
- ## 🚀 Enhancements in v0.3.10
197
-
198
- * 📦 **Compacted History Storage**:
199
- * Stripped non-essential metadata (`description`, `tags`, `aliases`) from catalog snapshots (`before`/`after`) in `settings.yaml`, retaining only essential properties (`id`, `name`, `capabilities`, `pricing`, `contextWindow`, `maxTokens`).
200
- * Optimized default snapshot retention (`DEFAULT_SNAPSHOT_RETENTION = 3`) to keep configuration files lean while preserving full rollback functionality.
201
- * ⚡ **Immediate Scheduler First Tick**:
202
- * Scheduler now triggers an initial discovery run immediately upon startup when enabled without requiring an initial 60-minute wait.
203
- * 🛡️ **Client-side Policy Regex Validation**:
204
- * Added instant regex pattern verification in the Web UI before submitting `/policy` requests with localized error feedback.
205
-
206
- ---
207
-
208
- ## 🚀 Enhancements in v0.3.9
209
-
210
- * 🔒 **Serialized Settings Mutations (`saveConfig`)**:
211
- * Chained internal settings persistence via an async promise mutex (`saveQueue`).
212
- * Eliminates race conditions between simultaneous sync runs and user UI interactions, ensuring clean atomic writes to `settings.yaml`.
213
- * 🛡️ **Hardened Cross-Origin & CSRF Protections**:
214
- * Enhanced `trusted(req)` checking to evaluate `sec-fetch-site`, `origin`, and `referer` against the HTTP `Host`.
215
- * Blocks unauthorized cross-origin mutations while preserving legitimate local and reverse-proxy requests.
216
- * 🎨 **Standardized Plugin Style Discovery**:
217
- * Added `data-dsh-plugin="dsh-model-sync"` attribute to injected UI style tags in compliance with the DeepSeek Harness plugin authoring standard.
218
-
219
- ---
220
-
221
- ## 🚀 Enhancements in v0.3.5
222
-
223
- * ⏱️ **Adaptive Rate Limit & Retry-After Handling**:
224
- * Automatically parses HTTP headers `Retry-After` (in seconds or RFC dates) and `x-ratelimit-reset`.
225
- * Exponential backoff prioritizes the exact delay requested by upstream providers, eliminating unnecessary throttling.
226
- * Re-classified HTTP 429 errors under a dedicated `rate-limit` diagnostic label.
227
- * 🔄 **Optimistic Concurrency & Conflict Auto-Recovery**:
228
- * Added automated retry loop for `SETTINGS_CONFLICT` (HTTP 409). When concurrent settings updates occur in DSH, the plugin seamlessly re-fetches the latest revision and reapplies the catalog mutations.
229
- * 🎯 **Advanced Web UI Picker & Filtering**:
230
- * **Tokenized Smart Search**: Multi-word queries (`claude 3.5 sonnet`) match across model IDs, display names, tags, and descriptions simultaneously.
231
- * **Quick Filter Chips**: One-click toggles for `Context ≥ 100k` and `Cheap ≤ $1/1M` in addition to capability toggles (`Vision`, `Tools`, `Reasoning`, `Embeddings`).
232
- * **Batch Selection Controls**: One-click buttons to *Select all shown*, *Deselect all shown*, and *Invert selection*.
233
- * **Chunked Rendering / Pagination**: Renders models in batches of 50 to maintain smooth 60fps UI performance even with catalogs containing 1,000+ models.
234
- * **Deprecation Badges**: Models with scheduled retirement dates display an explicit countdown badge (`Deprecates in Nd`).
235
- * 💾 **Storage & Metadata Optimizations**:
236
- * Robust context limit parser supporting suffix multipliers (`128k`, `1M`, `128,000`).
237
- * Automatic history compaction: historical snapshots older than the 5 latest sync runs have heavy before/after dumps pruned while retaining summary diffs, keeping `settings.yaml` ultra-compact.
238
-
239
- ---
240
-
241
- ## ⚙️ Configuration Reference (`settings.yaml`)
242
-
243
- ```yaml
244
- dsh-model-sync:
245
- enabled: true
246
- syncIntervalMinutes: 60
247
- autoReconcile: true
248
- providers:
249
- openrouter:
250
- enabled: true
251
- keyEnv: OPENROUTER_API_KEY
252
- deepseek:
253
- enabled: true
254
- keyEnv: DEEPSEEK_API_KEY
255
- custom:
256
- enabled: false
257
- baseURL: http://127.0.0.1:8000/v1
258
- authType: bearer
259
- keyEnv: CUSTOM_API_KEY
260
- ```
261
-
262
- ---
263
-
264
- ## 📄 License
265
-
266
- MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
package/README.ru.md CHANGED
@@ -116,128 +116,3 @@ dsh plugin --profile web add @goodandready/dsh-model-sync
116
116
  * Защита от thundering herd с помощью случайного коэффициента джиттера (`0.5 - 1.0`) при повторных запросах к API провайдеров.
117
117
 
118
118
  ---
119
-
120
- ## 🛠️ Улучшения и исправления в v0.3.11
121
-
122
- * 🗂️ **Чистый интерфейс настроек (DSH Design Contract)**:
123
- * Удалена избыточная регистрация fallback-раздела верхнего уровня бокового меню (`settings.section`), настройки доступны только через стандартную карточку в «Настройки → Плагины → Настройки плагинов» (`settings.plugin.item`).
124
- * 🛡️ **Надёжный доступ к сервисам Cordis**:
125
- * Доступ к сервисам ядра (`settings`, `llm`) переведён на `ctx.get(...)` с безопасным fallback к прямым свойствам контекста для исключения ошибок проксирования.
126
-
127
- ---
128
-
129
- ## 🛠️ Улучшения и исправления в v0.3.8
130
-
131
- * ⚙️ **Реактивное управление настройками через `settingsScope`**:
132
- * Интегрирована встроенная служба ядра DSH `settingsScope` (`ctx.settingsScope.bind({ namespace: 'dsh-model-sync' })`) с использованием `useSyncExternalStore`.
133
- * В карточку настроек плагина добавлены элементы прямого управления планировщиком: включение фонового расписания (`scheduleEnabled`), настройка интервала синхронизации (`intervalMinutes`) и флаг автоприменения изменений (`autoApply`).
134
- * Корректная обработка всех статусов снимка (`ready`, `loading`, `unavailable`) с валидацией и сохранением полей без прерывания на первой ошибке.
135
- * 🌐 **Полная локализация пикера моделей**:
136
- * Заменены захардкоженные русские строки в селекторе моделей: плейсхолдер поиска и варианты сортировки (`Имя A→Z`, `Цена ↑`, `Контекст ↓`, `Новые ↓`) теперь динамически переключаются при смене языка интерфейса (`en` / `ru`).
137
- * 📜 **Локализация пустой истории синхронизаций**:
138
- * Добавлен отсутствовавший ключ `noHistory` в словари английского и русского языков («No synchronization history.» / «Истории синхронизаций нет.»).
139
- * 📋 **Дизайн-контракт проекта**:
140
- * Создан обязательный документ `docs/design/DESIGN.md`, фиксирующий пользовательские поверхности, токены `--dsw-*` и контракты компонентов.
141
-
142
- ---
143
-
144
- ## 🛠️ Улучшения и исправления в v0.3.6
145
-
146
- * 🔑 **Универсальное разрешение учетных данных (`credentialRef` и `apiKeyRef`)**:
147
- * Добавлена полная поддержка ссылок `credentialRef` и `apiKeyRef` в модулях инвентаря, реестре адаптеров и generic-маршрутах OpenAI-совместимых провайдеров.
148
- * Провайдеры, настроенные через DSH Credentials Service, теперь корректно обнаруживаются и аутентифицируются без необходимости обязательного наличия `apiKeyEnv`.
149
- * ♻️ **Очистка устаревших моделей в жизненном цикле**:
150
- * Исправлена ошибка, из-за которой модели в статусе `deprecated` оставались в конфигурации бессрочно после их удаления провайдером. При флаге `removeMissing: true` такие модели теперь гарантированно переводятся в статус `removed` по истечении grace-периода.
151
- * ⏰ **Точный статус неактивного планировщика**:
152
- * Исправлен `scheduler.status()`: при отключённой фоновой синхронизации `nextRunAt` теперь возвращает `null`, исключая вводящие в заблуждение даты следующего запуска в Web UI.
153
- * 🛡️ **Надежность и масштабируемость Web UI**:
154
- * Защита от сбоев сортировки при отсутствии поля `name` у модели.
155
- * Замена spread-операций на итеративную агрегацию при вычислении экстремумов контекста и цены, что предотвращает переполнение стека (`RangeError`) на огромных каталогах (10 000+ моделей).
156
- * Детальный вывод текста ошибки при тестировании модели по кнопке `▶`.
157
- * Локализация срока давности кеша в карточке провайдера.
158
- * ⚡ **Детерминированное сравнение каталогов и безопасность потоков**:
159
- * Реализована стабильная сортировка ключей перед сравнением моделей, исключающая фиктивные diff-записи из-за произвольного порядка ключей в JSON.
160
- * Добавлено прерывание сетевого потока при превышении лимита размера тела HTTP-запроса для защиты от утечек памяти.
161
-
162
- ---
163
-
164
- ## 🚀 Новые возможности в v0.3.10
165
-
166
- * 📦 **Компактизация хранения истории**:
167
- * Очистка некритичных метаданных (`description`, `tags`, `aliases`) из снимков моделей (`before`/`after`) в `settings.yaml`, с сохранением ключевых свойств (`id`, `name`, `capabilities`, `pricing`, `contextWindow`, `maxTokens`).
168
- * Оптимизирована глубина хранения снимков (`DEFAULT_SNAPSHOT_RETENTION = 3`) для предотвращения разрастания конфигурационного файла при сохранении возможности безопасного отката.
169
- * ⚡ **Мгновенный первый запуск планировщика**:
170
- * Планировщик автоматически запускает первичный синк сразу при старте, если расписание активно и предыдущие запуски отсутствуют.
171
- * 🛡️ **Клиентская валидация regex-политик**:
172
- * Добавлена проверка синтаксиса регулярных выражений в интерфейсе настроек перед отправкой запроса на `/policy` с локализованными подсказками.
173
-
174
- ---
175
-
176
- ## 🚀 Новые возможности в v0.3.9
177
-
178
- * 🔒 **Атомарная очередь сохранения настроек (`saveConfig`)**:
179
- * Сериализация внутренних мутаций конфигурации через цепочку промисов (`saveQueue`).
180
- * Предотвращает состояние гонки между фоновой синхронизацией и действиями пользователя в интерфейсе, гарантируя целостность `settings.yaml`.
181
- * 🛡️ **Усиленная защита от CSRF и cross-origin атак**:
182
- * Расширенная валидация запросов `trusted(req)` с проверкой заголовков `sec-fetch-site`, `origin` и `referer` против `Host`.
183
- * Блокировка межсайтовых вызовов мутаций без нарушения локальных обращений и проксирования.
184
- * 🎨 **Стандартизация инжекции стилей интерфейса**:
185
- * Добавлен атрибут `data-dsh-plugin="dsh-model-sync"` для тега стилей в полном соответствии со стандартом разработки плагинов DeepSeek Harness.
186
-
187
- ---
188
-
189
- ## 🚀 Новые возможности в v0.3.5
190
-
191
- * ⏱️ **Адаптивная обработка Rate Limit и Retry-After**:
192
- * Автоматический парсинг HTTP-заголовков `Retry-After` (в секундах и формате RFC-дат) и `x-ratelimit-reset`.
193
- * При повторах задержка выставляется строго по требованию провайдера.
194
- * Ошибки 429 классифицируются в отдельную категорию `rate-limit`.
195
- * 🔄 **Автоматическое разрешение конфликтов настроек**:
196
- * Добавлен цикл повторов для `SETTINGS_CONFLICT` (HTTP 409). При параллельных правках конфигурации DSH плагин автоматически перечитывает ревизию и накладывает изменения без ошибок.
197
- * 🎯 **Оптимизация панели выбора моделей в Web UI**:
198
- * **Токенизированный умный поиск**: Слова поискового запроса (`claude 3.5 sonnet`) сопоставляются независимо по ID, названиям, тегам и описаниям моделей.
199
- * **Быстрые фильтр-чипы**: Фильтры `Контекст ≥ 100k` и `Дешевые ≤ $1/1M` в дополнение к фильтрам возможностей (`Зрение`, `Инструменты`, `Рассуждение`, `Эмбеддинги`).
200
- * **Массовый выбор**: Кнопки *Выбрать все показанные*, *Снять выбор с показанных*, *Инвертировать выбор*.
201
- * **Порционный рендеринг (пагинация)**: Отображение порциями по 50 моделей с кнопкой «Показать ещё» для плавной работы интерфейса в каталогах с 1000+ моделями.
202
- * **Бейджи устаревания моделей**: Для моделей с датой вывода из эксплуатации выводится обратный отсчет (`Устареет через N дн.`).
203
- * 💾 **Хранение и парсинг метаданных**:
204
- * Корректный разбор числовых значений с запятыми и суффиксами (`128k`, `1M`, `128,000`).
205
- * Автоматическая компактификация: снимки каталогов старше 5 последних запусков очищаются от тяжелых дампов, сохраняя diff-аудит и компактность `settings.yaml`.
206
-
207
- ---
208
-
209
- ## ⚙️ Пример конфигурации (`settings.yaml`)
210
-
211
- ```yaml
212
- dsh-model-sync:
213
- enabled: true
214
- syncIntervalMinutes: 60
215
- autoReconcile: true
216
- providers:
217
- openrouter:
218
- enabled: true
219
- keyEnv: OPENROUTER_API_KEY
220
- deepseek:
221
- enabled: true
222
- keyEnv: DEEPSEEK_API_KEY
223
- custom:
224
- enabled: false
225
- baseURL: http://127.0.0.1:8000/v1
226
- authType: bearer
227
- keyEnv: CUSTOM_API_KEY
228
- ```
229
-
230
- ---
231
-
232
- ## 🚀 Улучшения в версии 0.3.14
233
-
234
- - **Языковой стандарт**: Каноническая поддержка `en` и `zh` в кодовой базе плагина; русскоязычная локализация вынесена в пакет `goodandready/dsh-russian-lang` (#191).
235
- - **Изоляция релиза**: Настроен `.npmignore` для исключения тестов, планов и документации из npm-пакета.
236
- - **Пакетная проверка моделей (Batch Try)**: Эндпоинт `POST /dsh-model-sync/batch-try`, бейджи задержки и мгновенная деселекция недоступных моделей.
237
- - **Политики по цене и контексту**: Опциональные фильтры `maxPricePerMillion` и `minContextTokens` в настройках провайдера.
238
- - **Экспорт и импорт конфигурации**: JSON-бэкап и перенос настроек через `GET /export` и `POST /import`.
239
- - **Пользовательские алиасы моделей**: Управление алиасами моделей через `GET/POST /aliases` и UI-карточку.
240
-
241
- ## 📄 Лицензия
242
-
243
- MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
package/README.zh.md CHANGED
@@ -115,13 +115,3 @@ dsh plugin --profile web add @goodandready/dsh-model-sync
115
115
  ## 📄 开源协议
116
116
 
117
117
  MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
118
-
119
- ## 🛠️ v0.3.11 改进与修复
120
-
121
- * 🗂️ **规范化设置界面**:
122
- * 移除侧边栏顶层多余的 `settings.section` fallback,统一使用标准插件设置卡片 `settings.plugin.item`。
123
- * 🛡️ **增强 Cordis 服务访问稳定性**:
124
- * 使用 `ctx.get(...)` 安全获取 `settings` 与 `llm` 服务,避免代理对象属性解析异常。
125
-
126
- ---
127
-
package/lib/client.js CHANGED
@@ -386,6 +386,10 @@ window.__ModuleLoader__.load({
386
386
  }
387
387
 
388
388
  const NS = 'dsh-model-sync'
389
+ // Plugins page row seat (DSH 0.1.6-alpha.2): key = '<package name>#<row id>'.
390
+ const PKG = '@goodandready/dsh-model-sync'
391
+ const ROW_ID = 'dsh-model-sync'
392
+ const ROW_CONFIG_KEY = PKG + '#' + ROW_ID
389
393
 
390
394
  function useLocale(ctx) {
391
395
  return React.useSyncExternalStore(
@@ -1227,10 +1231,11 @@ window.__ModuleLoader__.load({
1227
1231
  )
1228
1232
  }
1229
1233
 
1230
- function ModelSyncCard({ ctx }) {
1234
+ function ModelSyncCard({ ctx, view }) {
1231
1235
  const locale = useLocale(ctx)
1232
1236
  const t = locale === 'zh' ? zh : en
1233
- const [open, setOpen] = React.useState(false)
1237
+ const page = view === 'page'
1238
+ const [open, setOpen] = React.useState(!!page)
1234
1239
  const [cardStatus, setCardStatus] = React.useState(null)
1235
1240
  React.useEffect(() => {
1236
1241
  ensureStyles()
@@ -1244,8 +1249,13 @@ window.__ModuleLoader__.load({
1244
1249
  return () => clearInterval(id)
1245
1250
  }, [open])
1246
1251
  const cardBadge = (cardStatus?.lastRun?.results?.reduce((s, r) => s + (r.diff?.added?.length || 0), 0) || 0)
1247
- return React.createElement('li', { className: 'dms-card' + (open ? ' dms-card-open' : '') },
1248
- React.createElement('button', { type: 'button', className: 'dms-card-header', 'aria-expanded': open, 'aria-label': `${t[open ? 'hideSettings' : 'showSettings']}: ${t.title}`, onClick: () => setOpen((value) => !value) },
1252
+ // Row seat (plugins.row.config): the host page draws title/icon/crumb and the
1253
+ // padding, so the summary is a one-liner and the page drops our card chrome.
1254
+ if (view === 'summary') {
1255
+ return React.createElement('span', { className: 'dms-card-description' }, t.desc)
1256
+ }
1257
+ return React.createElement(page ? 'div' : 'li', { className: page ? 'dms-page' : 'dms-card' + (open ? ' dms-card-open' : '') },
1258
+ React.createElement('button', { type: 'button', className: 'dms-card-header', style: page ? { display: 'none' } : undefined, 'aria-expanded': page ? true : open, 'aria-label': `${t[open ? 'hideSettings' : 'showSettings']}: ${t.title}`, onClick: () => setOpen((value) => !value) },
1249
1259
  React.createElement('span', { className: 'dms-card-head-text' },
1250
1260
  React.createElement('span', { className: 'dms-card-name' }, t.title),
1251
1261
  React.createElement('span', { className: 'dms-card-description' }, t.desc)
@@ -1255,7 +1265,7 @@ window.__ModuleLoader__.load({
1255
1265
  React.createElement('path', { d: 'M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5', fill: 'currentColor' })
1256
1266
  )
1257
1267
  ),
1258
- open ? React.createElement('div', { className: 'dms-card-body' }, React.createElement(ErrorBoundary, null, React.createElement(ModelSyncSection, { ctx, includeIntro: false }))) : null
1268
+ (page || open) ? React.createElement('div', { className: 'dms-card-body' }, React.createElement(ErrorBoundary, null, React.createElement(ModelSyncSection, { ctx, includeIntro: false }))) : null
1259
1269
  )
1260
1270
  }
1261
1271
 
@@ -1267,6 +1277,8 @@ window.__ModuleLoader__.load({
1267
1277
  return () => {}
1268
1278
  }, 'dsh-model-sync: dictionaries')
1269
1279
  try {
1280
+ // Row seat first (the seat the current core renders), legacy seat after it.
1281
+ ctx.slots.inject('plugins.row.config', () => ctx.slots.register({ name: 'plugins.row.config', key: ROW_CONFIG_KEY, locale: NS, inject: () => ({ ctx }) }, (props) => React.createElement(ErrorBoundary, null, React.createElement(ModelSyncCard, { ...props, ctx }))))
1270
1282
  ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({ name: 'settings.plugin.item', key: NS, locale: NS, inject: () => ({ ctx }) }, (props) => React.createElement(ErrorBoundary, null, React.createElement(ModelSyncCard, { ...props, ctx }))))
1271
1283
  } catch (err) {
1272
1284
  try { console.debug('[dsh-model-sync] slots inject best-effort:', err) } catch { /* console unavailable */ }
package/lib/models.js CHANGED
@@ -92,7 +92,14 @@ function normalizeCapabilities(row) {
92
92
  }
93
93
  }
94
94
 
95
- const sources = [row?.capabilities, row?.supportedCapabilities, row?.supported_capabilities]
95
+ const sources = [
96
+ row?.capabilities,
97
+ row?.supportedCapabilities,
98
+ row?.supported_capabilities,
99
+ row?.supportedFeatures,
100
+ row?.supported_features,
101
+ row?.features,
102
+ ]
96
103
  for (const source of sources) {
97
104
  if (Array.isArray(source)) {
98
105
  for (const token of source) {
@@ -148,6 +155,74 @@ function normalizeCapabilities(row) {
148
155
  return Object.keys(result).length > 0 ? result : undefined
149
156
  }
150
157
 
158
+
159
+ function isGroqMinijinjaModel(id) {
160
+ if (typeof id !== 'string') return false
161
+ const lower = id.toLowerCase().replace(/^groq\//, '')
162
+ return (
163
+ lower.startsWith('qwen') ||
164
+ lower.startsWith('meta-llama') ||
165
+ lower.startsWith('llama') ||
166
+ lower.startsWith('mistral') ||
167
+ lower.startsWith('mixtral') ||
168
+ lower.startsWith('gemma') ||
169
+ lower.startsWith('deepseek')
170
+ )
171
+ }
172
+
173
+ function normalizeCompat(provider, row, id) {
174
+ const source = row?.compat && typeof row.compat === 'object' ? { ...row.compat } : {}
175
+ const isGroq = provider === 'groq'
176
+ const isDeepSeek = provider === 'deepseek'
177
+
178
+ let defaultSupportsDeveloperRole
179
+ if (isGroq && id && isGroqMinijinjaModel(id)) {
180
+ defaultSupportsDeveloperRole = false
181
+ } else if (isDeepSeek) {
182
+ defaultSupportsDeveloperRole = false
183
+ }
184
+
185
+ if (source.supportsDeveloperRole === undefined && defaultSupportsDeveloperRole !== undefined) {
186
+ source.supportsDeveloperRole = defaultSupportsDeveloperRole
187
+ }
188
+
189
+ return Object.keys(source).length > 0 ? source : undefined
190
+ }
191
+
192
+ function normalizeInput(row) {
193
+ const modalitySources = [row?.inputModalities, row?.input_modalities, row?.modalities]
194
+ for (const source of modalitySources) {
195
+ if (Array.isArray(source) && source.length > 0) {
196
+ const set = new Set()
197
+ for (const item of source) {
198
+ if (typeof item !== 'string') continue
199
+ const val = item.trim().toLowerCase()
200
+ if (val === 'image' || val === 'vision') set.add('image')
201
+ else if (val === 'text') set.add('text')
202
+ else if (val) set.add(val)
203
+ }
204
+ const order = ['text', 'image']
205
+ const result = []
206
+ for (const k of order) {
207
+ if (set.has(k)) {
208
+ result.push(k)
209
+ set.delete(k)
210
+ }
211
+ }
212
+ for (const k of set) {
213
+ result.push(k)
214
+ }
215
+ if (result.length > 0) return result
216
+ }
217
+ }
218
+
219
+ if (Array.isArray(row?.input)) {
220
+ return row.input.map((item) => (typeof item === 'string' ? item.trim() : item)).filter(Boolean)
221
+ }
222
+
223
+ return undefined
224
+ }
225
+
151
226
  function normalizeTags(row) {
152
227
  const source = row?.tags ?? row?.labels
153
228
  if (!Array.isArray(source)) return undefined
@@ -208,6 +283,9 @@ export function normalizeModel(provider, row) {
208
283
  row = { id: row }
209
284
  }
210
285
  if (!row || typeof row !== 'object') return null
286
+ const effectiveProvider = (typeof provider === 'string' && provider.trim())
287
+ ? provider.trim()
288
+ : (typeof row.provider === 'string' ? row.provider.trim() : '')
211
289
  const id = String(row.id ?? row.slug ?? row.model ?? '').trim()
212
290
  if (!id) return null
213
291
  const name = String(row.name ?? row.displayName ?? row.display_name ?? id).trim() || id
@@ -218,13 +296,17 @@ export function normalizeModel(provider, row) {
218
296
  const tags = normalizeTags(row)
219
297
  const aliases = normalizeAliases(row, id)
220
298
  const lifecycle = normalizeLifecycle(row)
299
+ const input = normalizeInput(row)
300
+ const compat = normalizeCompat(effectiveProvider, row, id)
221
301
  return {
222
- provider,
302
+ provider: effectiveProvider || provider,
223
303
  id,
224
304
  name,
225
305
  ...(contextWindow === undefined ? {} : { contextWindow }),
226
306
  ...(maxTokens === undefined ? {} : { maxTokens }),
227
307
  ...(row.description ? { description: String(row.description) } : {}),
308
+ ...(input === undefined ? {} : { input }),
309
+ ...(compat === undefined ? {} : { compat }),
228
310
  ...(capabilities === undefined ? {} : { capabilities }),
229
311
  ...(pricing === undefined ? {} : { pricing }),
230
312
  ...(tags === undefined ? {} : { tags }),
package/lib/reconcile.js CHANGED
@@ -18,8 +18,20 @@ export function reconcileModels(provider, current, discovered, {
18
18
  const stale = []
19
19
  for (const row of before) {
20
20
  const fresh = advertisedById.get(row.id)
21
- if (fresh) next.push(fresh)
22
- else if (renamedBefore.has(row.id)) {
21
+ if (fresh) {
22
+ const merged = {
23
+ ...row,
24
+ ...fresh,
25
+ ...(row.capabilities || fresh.capabilities ? {
26
+ capabilities: { ...(fresh.capabilities ?? {}), ...(row.capabilities ?? {}) },
27
+ } : {}),
28
+ ...(row.compat || fresh.compat ? {
29
+ compat: { ...(fresh.compat ?? {}), ...(row.compat ?? {}) },
30
+ } : {}),
31
+ ...(fresh.input?.length ? { input: fresh.input } : row.input ? { input: row.input } : {}),
32
+ }
33
+ next.push(merged)
34
+ } else if (renamedBefore.has(row.id)) {
23
35
  // An explicit provider alias is safe evidence that the old id was renamed.
24
36
  } else {
25
37
  stale.push(row)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-model-sync",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Automatic model catalog synchronization for API-key DeepSeek Harness providers.",
5
5
  "keywords": [
6
6
  "deepseek-harness",