@goodandready/dsh-model-sync 0.2.6 → 0.2.7

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
@@ -43,3 +43,9 @@ Omit `provider` to discover all enabled API-key providers in one run. The Settin
43
43
  ```
44
44
 
45
45
  An empty `models` array means all models in the latest available catalog. The selected catalog is written to DSH's `llm-pi-ai` settings, so the standard DSH model picker refreshes from the resulting list.
46
+
47
+
48
+ The full catalog discovered by an applied run is cached in the plugin's own settings
49
+ separately from the model allowlist. After a restart, the model chooser can show the
50
+ full last-known catalog while the standard DSH picker still exposes only the selected
51
+ models. Dry-run remains read-only and does not persist this cache.
package/lib/index.js CHANGED
@@ -18,6 +18,14 @@ export const Config = Schema.object({
18
18
  enabled: Schema.boolean().default(true),
19
19
  })).default([]),
20
20
  modelSelections: Schema.dict(Schema.array(Schema.string())).default({}),
21
+ // Full catalogs are cached separately from the allowlist applied to DSH.
22
+ // This keeps the model picker useful after a restart without widening the
23
+ // active llm-pi-ai catalog.
24
+ modelCatalogs: Schema.dict(Schema.array(Schema.object({
25
+ provider: Schema.string().required(),
26
+ id: Schema.string().required(),
27
+ name: Schema.string().required(),
28
+ }).loose())).default({}),
21
29
  })
22
30
 
23
31
  export function apply(ctx, config) {
@@ -38,10 +38,13 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
38
38
  ? [...new Set(config.modelSelections[row.provider].filter((id) => typeof id === 'string' && id))]
39
39
  : []
40
40
  const latest = lastResults.get(row.provider)
41
+ const cached = normalizeModels(row.provider, config.modelCatalogs?.[row.provider])
41
42
  return {
42
43
  ...row,
43
44
  selectedModels: selection,
44
- availableModels: latest?.status === 'ok' ? latest.availableModels ?? latest.models ?? row.models : row.models,
45
+ availableModels: latest?.status === 'ok'
46
+ ? latest.availableModels ?? latest.models ?? row.models
47
+ : cached.length > 0 ? cached : row.models,
45
48
  }
46
49
  })
47
50
  }
@@ -70,6 +73,7 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
70
73
  async function run(options = {}) {
71
74
  if (running) return running
72
75
  const config = getConfig() ?? {}
76
+ const cachedCatalogs = structuredClone(config.modelCatalogs ?? {})
73
77
  const dryRun = options.dryRun !== false
74
78
  const removeMissing = options.removeMissing === true
75
79
  const selected = typeof options.provider === 'string' && options.provider
@@ -101,6 +105,7 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
101
105
  removeMissing: effectiveRemoveMissing,
102
106
  changed: reconciliation.changed,
103
107
  })
108
+ cachedCatalogs[row.provider] = discovered.models
104
109
  }
105
110
  const result = { startedAt, finishedAt: Date.now(), dryRun, removeMissing, results, applied: false }
106
111
  if (!dryRun && results.some((row) => row.status === 'ok' && row.changed)) {
@@ -117,6 +122,20 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
117
122
  await settings.replace(PIAI_NS, next, descriptor.revision)
118
123
  result.applied = true
119
124
  }
125
+ if (!dryRun) {
126
+ const previousCatalogs = config.modelCatalogs ?? {}
127
+ if (JSON.stringify(previousCatalogs) !== JSON.stringify(cachedCatalogs)) {
128
+ try {
129
+ await saveConfig({ modelCatalogs: cachedCatalogs })
130
+ result.catalogCachePersisted = true
131
+ } catch (error) {
132
+ // The active DSH catalog has already been revision-checked. A
133
+ // cache failure must be visible but must not undo that successful
134
+ // provider update.
135
+ result.catalogCacheError = errorMessage(error)
136
+ }
137
+ }
138
+ }
120
139
  lastRun = result
121
140
  return result
122
141
  })()
@@ -132,7 +151,12 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
132
151
  const row = providers().find((item) => item.provider === provider)
133
152
  if (!row) throw Object.assign(new Error(`provider is not configured: ${provider}`), { code: 'PROVIDER_NOT_CONFIGURED' })
134
153
  const latest = lastRun?.results?.find((item) => item.provider === provider && item.status === 'ok')
135
- const available = normalizeModels(provider, latest?.availableModels ?? latest?.models ?? row.availableModels ?? row.models)
154
+ const cached = normalizeModels(provider, config.modelCatalogs?.[provider])
155
+ const latestCatalog = latest?.availableModels ?? latest?.models
156
+ const source = latestCatalog !== undefined
157
+ ? latestCatalog
158
+ : cached.length > 0 ? cached : row.availableModels ?? row.models
159
+ const available = normalizeModels(provider, source)
136
160
  const availableById = new Map(available.map((model) => [model.id, model]))
137
161
  const unknown = selectedModels.filter((id) => !availableById.has(id))
138
162
  if (unknown.length > 0) throw Object.assign(new Error(`unknown models for ${provider}: ${unknown.join(', ')}`), { code: 'UNKNOWN_MODEL' })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-model-sync",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Automatic model catalog synchronization for API-key DeepSeek Harness providers.",
5
5
  "keywords": [
6
6
  "deepseek-harness",