@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 +6 -0
- package/lib/index.js +8 -0
- package/lib/synchronizer.js +26 -2
- package/package.json +1 -1
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) {
|
package/lib/synchronizer.js
CHANGED
|
@@ -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'
|
|
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
|
|
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' })
|