@goodandready/dsh-model-sync 0.5.8 → 0.5.9
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/lib/adapter-registry.js +26 -3
- package/lib/generic-adapter.js +15 -2
- package/lib/history.js +5 -0
- package/lib/index.js +19 -16
- package/lib/models.js +1 -0
- package/lib/synchronizer.js +12 -3
- package/package.json +1 -1
package/lib/adapter-registry.js
CHANGED
|
@@ -97,14 +97,32 @@ function mappedRows(provider, rows, descriptor) {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
function parseRows(provider, payload, descriptor) {
|
|
100
|
+
if (payload && (payload.error || payload.errors)) {
|
|
101
|
+
const message = typeof payload.error === 'string'
|
|
102
|
+
? payload.error
|
|
103
|
+
: payload.error?.message || (Array.isArray(payload.errors) ? payload.errors.map(e => e?.message || e).join(', ') : 'endpoint returned error payload')
|
|
104
|
+
const err = new Error(`endpoint returned error payload: ${message}`)
|
|
105
|
+
err.adapterCode = 'schema'
|
|
106
|
+
throw err
|
|
107
|
+
}
|
|
100
108
|
const parserKind = descriptor.parse || descriptor.parser
|
|
101
109
|
if (parserKind === 'clinebot-plan') {
|
|
102
110
|
const plan = payload?.data?.plan || payload?.data || payload?.plan || payload
|
|
103
111
|
const included = plan?.features?.included || plan?.includedModels || []
|
|
112
|
+
if (!Array.isArray(included)) {
|
|
113
|
+
const err = new Error('model catalog response does not contain plan features')
|
|
114
|
+
err.adapterCode = 'schema'
|
|
115
|
+
throw err
|
|
116
|
+
}
|
|
104
117
|
return parseClinePassModels(included)
|
|
105
118
|
}
|
|
106
119
|
if (parserKind === 'google') {
|
|
107
|
-
const rows =
|
|
120
|
+
const rows = payload?.models
|
|
121
|
+
if (!Array.isArray(rows)) {
|
|
122
|
+
const err = new Error('model catalog response does not contain models array')
|
|
123
|
+
err.adapterCode = 'schema'
|
|
124
|
+
throw err
|
|
125
|
+
}
|
|
108
126
|
return normalizeModels(provider, rows.map((row) => ({
|
|
109
127
|
...row,
|
|
110
128
|
id: String(row.name ?? '').replace(/^models\//, ''),
|
|
@@ -113,8 +131,13 @@ function parseRows(provider, payload, descriptor) {
|
|
|
113
131
|
maxTokens: row.outputTokenLimit,
|
|
114
132
|
})))
|
|
115
133
|
}
|
|
116
|
-
const rows = pathValue(payload, descriptor.modelsPath) ?? payload?.data ?? payload?.models ?? payload?.availableModels
|
|
117
|
-
|
|
134
|
+
const rows = pathValue(payload, descriptor.modelsPath) ?? (Array.isArray(payload) ? payload : (payload?.data ?? payload?.models ?? payload?.availableModels))
|
|
135
|
+
if (!Array.isArray(rows)) {
|
|
136
|
+
const err = new Error('model catalog response does not contain models array')
|
|
137
|
+
err.adapterCode = 'schema'
|
|
138
|
+
throw err
|
|
139
|
+
}
|
|
140
|
+
return normalizeModels(provider, mappedRows(provider, rows, descriptor))
|
|
118
141
|
}
|
|
119
142
|
|
|
120
143
|
function privateHttpHost(hostname) {
|
package/lib/generic-adapter.js
CHANGED
|
@@ -75,8 +75,21 @@ async function readBody(response) {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
function rowsFrom(payload) {
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
if (payload && (payload.error || payload.errors)) {
|
|
79
|
+
const message = typeof payload.error === 'string'
|
|
80
|
+
? payload.error
|
|
81
|
+
: payload.error?.message || (Array.isArray(payload.errors) ? payload.errors.map(e => e?.message || e).join(', ') : 'endpoint returned error payload')
|
|
82
|
+
const err = new Error(`endpoint returned error payload: ${message}`)
|
|
83
|
+
err.adapterCode = 'schema'
|
|
84
|
+
throw err
|
|
85
|
+
}
|
|
86
|
+
const rows = Array.isArray(payload) ? payload : (payload?.data ?? payload?.models ?? payload?.availableModels)
|
|
87
|
+
if (!Array.isArray(rows)) {
|
|
88
|
+
const err = new Error('model catalog response does not contain models array')
|
|
89
|
+
err.adapterCode = 'schema'
|
|
90
|
+
throw err
|
|
91
|
+
}
|
|
92
|
+
return rows
|
|
80
93
|
}
|
|
81
94
|
|
|
82
95
|
export function canUseGenericAdapter(profile) {
|
package/lib/history.js
CHANGED
|
@@ -27,6 +27,11 @@ function cloneModel(provider, model) {
|
|
|
27
27
|
if (normalized.pricing) compact.pricing = normalized.pricing
|
|
28
28
|
if (normalized.contextWindow !== undefined) compact.contextWindow = normalized.contextWindow
|
|
29
29
|
if (normalized.maxTokens !== undefined) compact.maxTokens = normalized.maxTokens
|
|
30
|
+
if (normalized.input !== undefined) compact.input = normalized.input
|
|
31
|
+
if (normalized.compat !== undefined) compact.compat = normalized.compat
|
|
32
|
+
if (normalized.tags !== undefined) compact.tags = normalized.tags
|
|
33
|
+
if (normalized.aliases !== undefined && normalized.aliases.length > 0) compact.aliases = normalized.aliases
|
|
34
|
+
if (normalized.lifecycle !== undefined) compact.lifecycle = normalized.lifecycle
|
|
30
35
|
return compact
|
|
31
36
|
}
|
|
32
37
|
|
package/lib/index.js
CHANGED
|
@@ -44,7 +44,7 @@ export const Config = Schema.object({
|
|
|
44
44
|
title: Schema.string().required(),
|
|
45
45
|
message: Schema.string().required(),
|
|
46
46
|
providers: Schema.array(Schema.object({}).loose()).default([]),
|
|
47
|
-
}).loose()).default([]),
|
|
47
|
+
}).loose()).default([]).volatile(),
|
|
48
48
|
staleGraceRuns: Schema.number().default(2).volatile(),
|
|
49
49
|
lifecycleRetentionRuns: Schema.number().default(20).volatile(),
|
|
50
50
|
lifecycleRevision: Schema.number().default(0).volatile(),
|
|
@@ -54,14 +54,14 @@ export const Config = Schema.object({
|
|
|
54
54
|
firstMissingAt: Schema.number().default(0),
|
|
55
55
|
lastSeenAt: Schema.number().default(0),
|
|
56
56
|
lastObservedRun: Schema.number().default(0),
|
|
57
|
-
}).loose())).default({}),
|
|
57
|
+
}).loose())).default({}).volatile(),
|
|
58
58
|
history: Schema.array(Schema.object({
|
|
59
59
|
id: Schema.string().required(),
|
|
60
60
|
version: Schema.number().required(),
|
|
61
61
|
startedAt: Schema.number().required(),
|
|
62
62
|
finishedAt: Schema.number().required(),
|
|
63
63
|
providers: Schema.array(Schema.object({}).loose()).default([]),
|
|
64
|
-
}).loose()).default([]),
|
|
64
|
+
}).loose()).default([]).volatile(),
|
|
65
65
|
providers: Schema.array(Schema.object({
|
|
66
66
|
provider: Schema.string().required(),
|
|
67
67
|
enabled: Schema.boolean().default(true),
|
|
@@ -69,7 +69,7 @@ export const Config = Schema.object({
|
|
|
69
69
|
jitterMinutes: Schema.number().default(0),
|
|
70
70
|
ttlMinutes: Schema.number().default(0),
|
|
71
71
|
autoApply: Schema.boolean().default(false),
|
|
72
|
-
})).default([]),
|
|
72
|
+
})).default([]).volatile(),
|
|
73
73
|
aliases: Schema.dict(Schema.object({
|
|
74
74
|
provider: Schema.string(),
|
|
75
75
|
model: Schema.string(),
|
|
@@ -79,7 +79,7 @@ export const Config = Schema.object({
|
|
|
79
79
|
})),
|
|
80
80
|
strategy: Schema.string(),
|
|
81
81
|
updatedAt: Schema.number().default(0),
|
|
82
|
-
}).loose()).default({}),
|
|
82
|
+
}).loose()).default({}).volatile(),
|
|
83
83
|
modelSelections: Schema.dict(Schema.array(Schema.string())).default({}).volatile(),
|
|
84
84
|
adapterRegistry: Schema.dict(Schema.object({
|
|
85
85
|
enabled: Schema.boolean().default(true),
|
|
@@ -90,7 +90,7 @@ export const Config = Schema.object({
|
|
|
90
90
|
fields: Schema.any().default({}),
|
|
91
91
|
capabilityMap: Schema.any().default({}),
|
|
92
92
|
headers: Schema.dict(Schema.string()).default({}),
|
|
93
|
-
}).loose()).default({}),
|
|
93
|
+
}).loose()).default({}).volatile(),
|
|
94
94
|
modelPolicies: Schema.dict(Schema.object({
|
|
95
95
|
include: Schema.array(Schema.string()).default([]),
|
|
96
96
|
exclude: Schema.array(Schema.string()).default([]),
|
|
@@ -100,7 +100,7 @@ export const Config = Schema.object({
|
|
|
100
100
|
maxPricePerMillion: Schema.number(),
|
|
101
101
|
enableContextFilter: Schema.boolean().default(false),
|
|
102
102
|
minContextTokens: Schema.number(),
|
|
103
|
-
}).loose()).default({}),
|
|
103
|
+
}).loose()).default({}).volatile(),
|
|
104
104
|
// Full catalogs are cached separately from the allowlist applied to DSH.
|
|
105
105
|
// This keeps the model picker useful after a restart without widening the
|
|
106
106
|
// active llm-pi-ai catalog.
|
|
@@ -108,7 +108,7 @@ export const Config = Schema.object({
|
|
|
108
108
|
provider: Schema.string().required(),
|
|
109
109
|
id: Schema.string().required(),
|
|
110
110
|
name: Schema.string().required(),
|
|
111
|
-
}).loose())).default({}),
|
|
111
|
+
}).loose())).default({}).volatile(),
|
|
112
112
|
})
|
|
113
113
|
|
|
114
114
|
// DSH serves a namespace's settings form from the volatile fields of its profile
|
|
@@ -123,6 +123,7 @@ export function plainConfig(value) {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
export function apply(ctx, config) {
|
|
126
|
+
let liveConfig = undefined
|
|
126
127
|
const baseConfig = structuredClone(plainConfig(config || {}))
|
|
127
128
|
const resolveConfig = (value) => {
|
|
128
129
|
try {
|
|
@@ -134,7 +135,7 @@ export function apply(ctx, config) {
|
|
|
134
135
|
return liveConfig ?? baseConfig
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
|
-
|
|
138
|
+
liveConfig = resolveConfig(baseConfig)
|
|
138
139
|
let reconfigure = () => {}
|
|
139
140
|
let saveConfigImpl = async (patch) => {
|
|
140
141
|
const plainPatch = plainConfig(patch)
|
|
@@ -146,14 +147,16 @@ export function apply(ctx, config) {
|
|
|
146
147
|
ctx.logger.debug('[dsh-model-sync] settings service read failed:', err)
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
|
-
if (settings
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
150
|
+
if (!settings || typeof settings.update !== 'function') {
|
|
151
|
+
throw Object.assign(new Error('dsh-model-sync settings are not writable'), { code: 'CONFIG_UNAVAILABLE' })
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
await settings.update(NS, plainPatch)
|
|
155
|
+
} catch (err) {
|
|
156
|
+
if (typeof ctx.logger?.warn === 'function') {
|
|
157
|
+
ctx.logger.warn('[dsh-model-sync] settings.update failed:', err)
|
|
156
158
|
}
|
|
159
|
+
throw err
|
|
157
160
|
}
|
|
158
161
|
liveConfig = resolveConfig({ ...liveConfig, ...plainPatch })
|
|
159
162
|
reconfigure()
|
package/lib/models.js
CHANGED
|
@@ -273,6 +273,7 @@ function normalizePricing(row) {
|
|
|
273
273
|
}
|
|
274
274
|
if (typeof source.currency === 'string' && source.currency.trim()) pricing.currency = source.currency.trim()
|
|
275
275
|
if (typeof source.unit === 'string' && source.unit.trim()) pricing.unit = source.unit.trim()
|
|
276
|
+
if (typeof source.source === 'string' && source.source.trim()) pricing.source = source.source.trim()
|
|
276
277
|
return pricing
|
|
277
278
|
}
|
|
278
279
|
|
package/lib/synchronizer.js
CHANGED
|
@@ -159,12 +159,18 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
|
|
|
159
159
|
recordCredentialOutcome(row.provider, { status: 'ok', retries })
|
|
160
160
|
if (result && result.notModified) {
|
|
161
161
|
const config = getConfig() ?? {}
|
|
162
|
-
const
|
|
162
|
+
const existing = providerHttpMeta.get(row.provider)
|
|
163
|
+
if (existing && (result.etag || result.lastModified)) {
|
|
164
|
+
if (result.etag) existing.etag = result.etag
|
|
165
|
+
if (result.lastModified) existing.lastModified = result.lastModified
|
|
166
|
+
}
|
|
167
|
+
const memoryModels = existing?.models
|
|
168
|
+
const cached = normalizeModels(row.provider, memoryModels ?? config.modelCatalogs?.[row.provider] ?? row.models ?? [])
|
|
163
169
|
return { provider: row.provider, status: 'ok', models: cached, retries, notModified: true }
|
|
164
170
|
}
|
|
165
171
|
const models = Array.isArray(result) ? result : []
|
|
166
172
|
if (result?.__httpMeta) {
|
|
167
|
-
providerHttpMeta.set(row.provider, result.__httpMeta)
|
|
173
|
+
providerHttpMeta.set(row.provider, { ...result.__httpMeta, models })
|
|
168
174
|
}
|
|
169
175
|
return { provider: row.provider, status: 'ok', models, retries }
|
|
170
176
|
} catch (error) {
|
|
@@ -332,7 +338,10 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
|
|
|
332
338
|
const advertised = selectedModels.length
|
|
333
339
|
? policyModels.filter((model) => selectedModels.includes(model.id))
|
|
334
340
|
: policyModels
|
|
335
|
-
const
|
|
341
|
+
const policyExcludedIds = current
|
|
342
|
+
.filter((model) => !filterModels([model], policy).length || (selectedModels.length && !selectedModels.includes(model.id)))
|
|
343
|
+
.map((model) => model.id)
|
|
344
|
+
const explicitRemoveIds = new Set([...lifecycle.removedIds, ...policyExcludedIds])
|
|
336
345
|
const reconciliation = reconcileModels(row.provider, current, advertised, { removeMissing: false, removeIds: explicitRemoveIds })
|
|
337
346
|
results.push({
|
|
338
347
|
...discovered,
|