@goodandready/dsh-clinebot 0.6.12 → 0.6.13

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/CHANGELOG.md CHANGED
@@ -5,6 +5,11 @@ All notable changes to `@goodandready/dsh-clinebot` will be documented in this f
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.6.13] - 2026-10-21
9
+
10
+ ### Added
11
+ - **Automatic price refresh for new models**: model sync and auto-discovery compare the newly added IDs with the public catalog and fetch models.dev only when coverage is missing. Repeated misses are throttled, and the model-sync response includes a `priceSync` summary.
12
+
8
13
  ## [0.6.12] - 2026-10-21
9
14
 
10
15
  ### Added
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h3>Native ClineBot / ClinePass Provider Companion for DeepSeek Harness</h3>
6
6
 
7
- <p><b>0.6.12</b> adds model/account/error filters and a custom date range to analytics. Chat runs on the native <code>ctx.llm</code> adapter and requires DSH <code>0.2.1-rc.2</code>; the legacy <code>llm-pi-ai</code>, loopback proxy and <code>llm/stream</code> path are removed.</p>
7
+ <p><b>0.6.13</b> automatically refreshes public prices when new models appear. Chat runs on the native <code>ctx.llm</code> adapter and requires DSH <code>0.2.1-rc.2</code>; the legacy <code>llm-pi-ai</code>, loopback proxy and <code>llm/stream</code> path are removed.</p>
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://www.npmjs.com/package/@goodandready/dsh-clinebot"><img src="https://img.shields.io/npm/v/@goodandready/dsh-clinebot.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
package/README.ru.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h3>Нативное подключение провайдера ClineBot / ClinePass для DeepSeek Harness</h3>
6
6
 
7
- <p><b>0.6.12</b> добавляет фильтры аналитики по модели, аккаунту и ошибкам, а также произвольный диапазон дат. Чат работает через нативный адаптер <code>ctx.llm</code>, требуется DSH <code>0.2.1-rc.2</code>; старые <code>llm-pi-ai</code>, loopback-прокси и <code>llm/stream</code> удалены.</p>
7
+ <p><b>0.6.13</b> автоматически догружает публичные цены при появлении новых моделей. Чат работает через нативный адаптер <code>ctx.llm</code>, требуется DSH <code>0.2.1-rc.2</code>; старые <code>llm-pi-ai</code>, loopback-прокси и <code>llm/stream</code> удалены.</p>
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://www.npmjs.com/package/@goodandready/dsh-clinebot"><img src="https://img.shields.io/npm/v/@goodandready/dsh-clinebot.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
package/README.zh.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h3>适用于 DeepSeek Harness 的 ClineBot / ClinePass 原生模型提供商伴侣插件</h3>
6
6
 
7
- <p><b>0.6.12</b> 为分析添加模型/账户/错误筛选和自定义日期范围。聊天使用原生 <code>ctx.llm</code> 适配器,需要 DSH <code>0.2.1-rc.2</code>;旧的 <code>llm-pi-ai</code>、回环代理和 <code>llm/stream</code> 已删除。</p>
7
+ <p><b>0.6.13</b> 在出现新模型时自动刷新公开价格。聊天使用原生 <code>ctx.llm</code> 适配器,需要 DSH <code>0.2.1-rc.2</code>;旧的 <code>llm-pi-ai</code>、回环代理和 <code>llm/stream</code> 已删除。</p>
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://www.npmjs.com/package/@goodandready/dsh-clinebot"><img src="https://img.shields.io/npm/v/@goodandready/dsh-clinebot.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
package/lib/price-sync.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, writeFileSync, renameSync, mkdirSync, existsSync } from 'node:fs'
2
2
  import { homedir } from 'node:os'
3
3
  import path from 'node:path'
4
- import { normalizeModelKey } from './model-prices.js'
4
+ import { normalizeModelKey, getPublicPriceCatalog, setPublicPriceCatalog } from './model-prices.js'
5
5
 
6
6
  const DEFAULT_URL = 'https://models.dev/api.json'
7
7
  const PROVIDER_PRIORITY = {
@@ -79,6 +79,51 @@ export function loadPublicPriceCache(cachePath) {
79
79
  }
80
80
  }
81
81
 
82
+ let lastMissingSignature = ''
83
+ let lastAttemptAt = 0
84
+
85
+ export function resetPriceSyncState() {
86
+ lastMissingSignature = ''
87
+ lastAttemptAt = 0
88
+ }
89
+
90
+ /**
91
+ * Refresh the public catalog only when one of the active models is missing.
92
+ * A repeated missing set is retried at most once per interval, so an unknown
93
+ * custom model cannot hammer models.dev on every sync.
94
+ */
95
+ export async function ensurePublicPricesForModels(modelIds, {
96
+ url = DEFAULT_URL,
97
+ cachePath = '~/.dsh/clinebot-prices.json',
98
+ fetchImpl,
99
+ timeoutMs = 8000,
100
+ minIntervalMs = 6 * 60 * 60 * 1000,
101
+ now = Date.now(),
102
+ } = {}) {
103
+ const ids = [...new Set((Array.isArray(modelIds) ? modelIds : []).map((id) => String(id || '').trim()).filter(Boolean))]
104
+ const missing = ids.filter((id) => !getPublicPriceCatalog()[normalizeModelKey(id)])
105
+ if (missing.length === 0) return { synced: false, missing: [], count: 0 }
106
+ if (!fetchImpl && process.env.CLINEBOT_DISABLE_PRICE_AUTOSYNC === '1') {
107
+ return { synced: false, skipped: true, missing, count: 0 }
108
+ }
109
+ const signature = missing.slice().sort().join('|')
110
+ if (signature === lastMissingSignature && now - lastAttemptAt < minIntervalMs) {
111
+ return { synced: false, skipped: true, missing, count: 0 }
112
+ }
113
+ lastMissingSignature = signature
114
+ lastAttemptAt = now
115
+ const result = await syncPublicPrices({ url, cachePath, fetchImpl, timeoutMs })
116
+ setPublicPriceCatalog(result.catalog)
117
+ const stillMissing = ids.filter((id) => !result.catalog[normalizeModelKey(id)])
118
+ return {
119
+ synced: true,
120
+ count: result.count,
121
+ syncedAt: result.syncedAt,
122
+ source: result.source,
123
+ missing: stillMissing,
124
+ }
125
+ }
126
+
82
127
  /**
83
128
  * Fetch and cache the public catalog. The caller owns applying it to
84
129
  * model-prices; keeping that side effect out of this helper makes it testable.
@@ -11,6 +11,7 @@ import {
11
11
  migrateModelEntry,
12
12
  isCatalogDifferent,
13
13
  } from './models.js'
14
+ import { ensurePublicPricesForModels } from './price-sync.js'
14
15
  import {
15
16
  probeHealth,
16
17
  fetchUsageLimits,
@@ -243,6 +244,12 @@ export async function autoDiscoverPlanModels(ctx, { live, getSettingsApi, syncPr
243
244
  if (cacheFile) {
244
245
  await saveModelsDiskCache(cacheFile, usageData.dynamicModels, now, cacheIdentity)
245
246
  }
247
+ if (isDifferent) {
248
+ await ensurePublicPricesForModels(usageData.dynamicModels.map((model) => model.id), {
249
+ url: pub.priceCatalogUrl,
250
+ cachePath: pub.priceCatalogPath,
251
+ }).catch(() => {})
252
+ }
246
253
  return
247
254
  }
248
255
 
@@ -5,6 +5,7 @@ import { resolveActiveAccountKey, resolvePathWithHome } from '../provider-sync.j
5
5
  import { getAllModels, saveModelsDiskCache, getOriginalModelContext, getOriginalModelMaxTokens } from '../models.js'
6
6
  import { fetchUsageLimits, smokeChat } from '../cline-client.js'
7
7
  import { publicUsage } from '../http.js'
8
+ import { ensurePublicPricesForModels } from '../price-sync.js'
8
9
 
9
10
  export function registerModelsRoutes(ctx, { live, getSettingsApi, syncProviderState }) {
10
11
  // POST /dsh-clinebot/models/sync — dynamically load models from plan
@@ -39,6 +40,8 @@ export function registerModelsRoutes(ctx, { live, getSettingsApi, syncProviderSt
39
40
  return writeJson(res, 503, { ok: false, error: 'Settings service unavailable or read-only' })
40
41
  }
41
42
 
43
+ const previousIds = new Set((pub.dynamicModels || []).map((model) => model?.id).filter(Boolean))
44
+ const addedModels = dynamicModels.filter((model) => model?.id && !previousIds.has(model.id))
42
45
  const now = Date.now()
43
46
  const next = plainConfig(Config({
44
47
  ...plainConfig(live()),
@@ -57,12 +60,25 @@ export function registerModelsRoutes(ctx, { live, getSettingsApi, syncProviderSt
57
60
  })
58
61
  }
59
62
 
63
+ let priceSync = null
64
+ if (addedModels.length > 0) {
65
+ try {
66
+ priceSync = await ensurePublicPricesForModels(addedModels.map((model) => model.id), {
67
+ url: pub.priceCatalogUrl,
68
+ cachePath: pub.priceCatalogPath,
69
+ })
70
+ } catch {
71
+ /* price catalog refresh is best-effort */
72
+ }
73
+ }
74
+
60
75
  return writeJson(res, 200, {
61
76
  ok: true,
62
77
  plan: usageData.plan,
63
78
  discoveredCount: dynamicModels.length,
64
79
  totalModelsCount: allModels.length,
65
80
  models: allModels,
81
+ priceSync,
66
82
  })
67
83
  } catch (err) {
68
84
  return writeJson(res, 500, { ok: false, error: String(err?.message || err) })
@@ -9,7 +9,7 @@
9
9
  import { createRequire } from 'node:module'
10
10
 
11
11
  const require = createRequire(import.meta.url)
12
- let packageVersion = '0.6.12'
12
+ let packageVersion = '0.6.13'
13
13
  try {
14
14
  packageVersion = require('../../package.json').version || packageVersion
15
15
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-clinebot",
3
- "version": "0.6.12",
3
+ "version": "0.6.13",
4
4
  "description": "DeepSeek Harness companion for ClineBot / ClinePass: dynamic subscription models sync, quota exhaustion warnings, session metrics, dedicated settings page, live usage limits, and /cline slash-command.",
5
5
  "license": "MIT",
6
6
  "type": "module",