@goodandready/dsh-model-sync 0.3.12 → 0.3.14

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
@@ -21,6 +21,16 @@
21
21
  <a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
22
22
  </p>
23
23
 
24
+ <table align="center">
25
+ <tr>
26
+ <td align="center">
27
+ ⭐ <strong>If you like this plugin, please star it on GitHub</strong> — it shows me that the plugin is useful to you and motivates me to keep developing it.
28
+ <br><br>
29
+ 🐛 <strong>If you find a bug or would like to request a feature</strong>, open a GitHub issue in any language — I will review your proposal and implement useful suggestions in a future plugin version.
30
+ </td>
31
+ </tr>
32
+ </table>
33
+
24
34
  </div>
25
35
 
26
36
  ---
@@ -107,6 +117,30 @@ dsh plugin --profile web add @goodandready/dsh-model-sync
107
117
 
108
118
  ---
109
119
 
120
+ ## 🚀 Enhancements in v0.3.14
121
+
122
+ - **Language Standard**: Canonical plugin in `en` and `zh`. Runtime Russian translations decoupled into `goodandready/dsh-russian-lang` (#191).
123
+ - **Packaging Isolation**: Dedicated `.npmignore` excludes tests, plans, and documentation to maintain a minimal < 256 KiB npm release bundle.
124
+ - **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.
125
+ - **Cost & Context Policies**: Set maximum price per million tokens and minimum context window thresholds in provider policies.
126
+ - **Catalog Export & Import**: Backup and restore full plugin configuration (policies, selections, aliases, scheduler) via `GET /export` and `POST /import`.
127
+ - **Model Alias Mapping**: Assign friendly aliases to provider/model pairs with `GET /aliases` and `POST /aliases` with interactive UI card.
128
+
129
+ ## 🚀 Enhancements in v0.3.13
130
+
131
+ * ⚡ **Zero-Overhead Polling via HTTP ETag / 304 Not Modified**:
132
+ * 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.
133
+ * 🌐 **Upstream Conditional HTTP Requests**:
134
+ * 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.
135
+ * 🎯 **Debounced Search & Memoized Sorting in UI**:
136
+ * 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.
137
+ * 🧠 **Expanded Capability Detection**:
138
+ * 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.
139
+ * 🛡️ **Exponential Retry Jitter**:
140
+ * Enhanced `retryWithBackoff` with randomized jitter (`0.5 - 1.0` multiplier) to prevent thundering-herd effects on provider APIs during network hiccups or rate limits.
141
+
142
+ ---
143
+
110
144
  ## 🛠️ Enhancements & Fixes in v0.3.11
111
145
 
112
146
  * 🗂️ **Streamlined Settings UI Surface**:
package/README.ru.md CHANGED
@@ -21,6 +21,16 @@
21
21
  <a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
22
22
  </p>
23
23
 
24
+ <table align="center">
25
+ <tr>
26
+ <td align="center">
27
+ ⭐ <strong>Если вам нравится этот плагин, поставьте ему звезду на GitHub</strong> — это покажет мне, что плагин вам полезен, и будет мотивировать меня развивать его дальше.
28
+ <br><br>
29
+ 🐛 <strong>Если вы нашли баг или хотите предложить новый функционал</strong>, создайте issue на GitHub на любом языке — я рассмотрю ваше предложение и реализую полезные идеи в одной из следующих версий плагина.
30
+ </td>
31
+ </tr>
32
+ </table>
33
+
24
34
  </div>
25
35
 
26
36
  ---
@@ -84,6 +94,21 @@ dsh plugin --profile web add @goodandready/dsh-model-sync
84
94
 
85
95
  ---
86
96
 
97
+ ## 🚀 Улучшения в v0.3.13
98
+
99
+ * ⚡ **ETag / 304 Not Modified для фонового опроса UI**:
100
+ * Реализована генерация слабого ETag для маршрута `GET /dsh-model-sync/status`. При 15-секундном опросе UI возвращается пустой ответ `304 Not Modified`, исключающий холостую сериализацию JSON и сетевой оверхед.
101
+ * 🌐 **Условные запросы к API провайдеров (Upstream Conditional Requests)**:
102
+ * Передача заголовков `If-None-Match` и `If-Modified-Since` в generic и declarative адаптерах. При ответе `304` от апстрим-провайдера мгновенно возвращается кэш без повторного парсинга моделей.
103
+ * 🎯 **Debounce поиска и useMemo в веб-интерфейсе**:
104
+ * Добавлен debounce (150 мс) на ввод в строку поиска и мемоизация `React.useMemo` для фильтрации и сортировки моделей в пикере, обеспечивая плавный отклик без микрофризов.
105
+ * 🧠 **Расширенное распознавание возможностей моделей**:
106
+ * Распознавание современных reasoning/thinking моделей (`DeepSeek-R1`, `o1`, `o3-mini`, CoT) и моделей для кода (`code`), поддержка тега `code` в политиках фильтрации.
107
+ * 🛡️ **Экспоненциальный джиттер при повторах**:
108
+ * Защита от thundering herd с помощью случайного коэффициента джиттера (`0.5 - 1.0`) при повторных запросах к API провайдеров.
109
+
110
+ ---
111
+
87
112
  ## 🛠️ Улучшения и исправления в v0.3.11
88
113
 
89
114
  * 🗂️ **Чистый интерфейс настроек (DSH Design Contract)**:
@@ -196,6 +221,15 @@ dsh-model-sync:
196
221
 
197
222
  ---
198
223
 
224
+ ## 🚀 Улучшения в версии 0.3.14
225
+
226
+ - **Языковой стандарт**: Каноническая поддержка `en` и `zh` в кодовой базе плагина; русскоязычная локализация вынесена в пакет `goodandready/dsh-russian-lang` (#191).
227
+ - **Изоляция релиза**: Настроен `.npmignore` для исключения тестов, планов и документации из npm-пакета.
228
+ - **Пакетная проверка моделей (Batch Try)**: Эндпоинт `POST /dsh-model-sync/batch-try`, бейджи задержки и мгновенная деселекция недоступных моделей.
229
+ - **Политики по цене и контексту**: Опциональные фильтры `maxPricePerMillion` и `minContextTokens` в настройках провайдера.
230
+ - **Экспорт и импорт конфигурации**: JSON-бэкап и перенос настроек через `GET /export` и `POST /import`.
231
+ - **Пользовательские алиасы моделей**: Управление алиасами моделей через `GET/POST /aliases` и UI-карточку.
232
+
199
233
  ## 📄 Лицензия
200
234
 
201
235
  MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
package/README.zh.md CHANGED
@@ -21,10 +21,35 @@
21
21
  <a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
22
22
  </p>
23
23
 
24
+ <table align="center">
25
+ <tr>
26
+ <td align="center">
27
+ ⭐ <strong>如果您喜欢这个插件,请在 GitHub 上为它点亮 Star</strong> — 这能让我知道插件对您有用,并鼓励我继续开发和维护它。
28
+ <br><br>
29
+ 🐛 <strong>如果您发现 Bug 或希望增加功能</strong>,请使用任意语言在 GitHub 上提交 Issue — 我会评估您的建议,并在后续版本中实现有价值的改进。
30
+ </td>
31
+ </tr>
32
+ </table>
33
+
24
34
  </div>
25
35
 
26
36
  ---
27
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
+
28
53
  ## ⚡ 插件概览
29
54
 
30
55
  **`dsh-model-sync`** 保持 **DeepSeek Harness** 模型选单与上游大模型服务商实时同步。
@@ -70,6 +95,15 @@ dsh plugin --profile web add @goodandready/dsh-model-sync
70
95
 
71
96
  ---
72
97
 
98
+ ## 🚀 v0.3.14 新特性与增强
99
+
100
+ - **语言标准**: 插件原生支持 `en` 与 `zh` 双语,俄语本地化已独立解耦至 `goodandready/dsh-russian-lang` 翻译包 (#191)。
101
+ - **发布隔离**: 通过 `.npmignore` 严格过滤测试、规划和文档文件,保证 npm 发布包体积小于 256 KiB。
102
+ - **模型批量可用性测试**: 支持通过 `POST /dsh-model-sync/batch-try` 并发批量测试所选模型,展示延迟徽标并一键取消选中不可用模型。
103
+ - **价格与上下文窗口过滤**: 支持按每百万 Token 最高价格及最小上下文长度过滤候选模型。
104
+ - **配置导入与导出**: 支持通过 `GET /export` 与 `POST /import` 导出及恢复全部策略、模型选择、别名和定时计划。
105
+ - **自定义模型别名映射**: 支持通过 `GET/POST /aliases` 为特定服务商与模型组合设置便于调用的简短别名。
106
+
73
107
  ## 📄 开源协议
74
108
 
75
109
  MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
@@ -5,7 +5,7 @@ import { normalizeModels } from './models.js'
5
5
  const MAX_RESPONSE_BYTES = 4 * 1024 * 1024
6
6
  const SAFE_AUTH = new Set(['bearer', 'x-api-key', 'query-key', 'none'])
7
7
  const SAFE_PARSERS = new Set(['openai', 'google'])
8
- const CAPABILITIES = new Set(['vision', 'tools', 'reasoning', 'embeddings'])
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({
@@ -120,7 +120,7 @@ export function normalizeAdapterConfig(config, profile = {}) {
120
120
  return { endpoint: endpoint.toString(), auth, parse, headers, modelsPath, capabilityMap, fields }
121
121
  }
122
122
 
123
- async function requestSpecific(profile, descriptor, { resolveCredential, fetchImpl, signal }) {
123
+ async function requestSpecific(profile, descriptor, { resolveCredential, fetchImpl, signal, etag, lastModified }) {
124
124
  const keyRef = profile.apiKeyEnv || profile.credentialRef || profile.apiKeyRef
125
125
  const apiKey = keyRef ? await resolveCredential(keyRef) : ''
126
126
  const url = new URL(descriptor.endpoint)
@@ -128,11 +128,14 @@ async function requestSpecific(profile, descriptor, { resolveCredential, fetchIm
128
128
  if (descriptor.auth === 'bearer' && apiKey) headers.Authorization = 'Bearer ' + apiKey
129
129
  if (descriptor.auth === 'x-api-key' && apiKey) headers['x-api-key'] = apiKey
130
130
  if (descriptor.auth === 'query-key' && apiKey) url.searchParams.set('key', apiKey)
131
+ if (etag) headers['If-None-Match'] = etag
132
+ if (lastModified) headers['If-Modified-Since'] = lastModified
131
133
  let response
132
134
  try { response = await fetchImpl(url, { headers, signal }) } catch (error) {
133
135
  if (error && typeof error === 'object') error.adapterCode = 'endpoint'
134
136
  throw error
135
137
  }
138
+ if (response.status === 304) return response
136
139
  if (!response.ok) {
137
140
  const error = catalogRequestError(response.status, response)
138
141
  error.adapterCode = [401, 403].includes(response.status) ? 'auth' : response.status === 429 ? 'rate-limit' : 'http'
@@ -145,6 +148,11 @@ async function fetchSpecific(profile, descriptor, options) {
145
148
  let payload
146
149
  try {
147
150
  const response = await requestSpecific(profile, descriptor, options)
151
+ if (response.status === 304) {
152
+ return { notModified: true, etag: response.headers?.get?.('etag') || options.etag, lastModified: response.headers?.get?.('last-modified') || options.lastModified }
153
+ }
154
+ const respEtag = response.headers?.get?.('etag') || undefined
155
+ const respLastMod = response.headers?.get?.('last-modified') || undefined
148
156
  const declared = Number(response.headers?.get?.('content-length') ?? NaN)
149
157
  if (Number.isFinite(declared) && declared > MAX_RESPONSE_BYTES) {
150
158
  const error = new Error('model catalog response is too large')
@@ -163,7 +171,16 @@ async function fetchSpecific(profile, descriptor, options) {
163
171
  throw error
164
172
  }
165
173
  const rows = parseRows(profile.provider, payload, descriptor)
166
- if (Array.isArray(rows) && rows.length > 0) return rows
174
+ if (Array.isArray(rows) && rows.length > 0) {
175
+ if (respEtag || respLastMod) {
176
+ Object.defineProperty(rows, '__httpMeta', {
177
+ value: { etag: respEtag, lastModified: respLastMod },
178
+ enumerable: false,
179
+ configurable: true,
180
+ })
181
+ }
182
+ return rows
183
+ }
167
184
  } catch (error) {
168
185
  if (descriptor.defaultModels && Array.isArray(descriptor.defaultModels) && descriptor.defaultModels.length > 0) {
169
186
  return normalizeModels(profile.provider, descriptor.defaultModels.map((id) => ({ id })))