@goodandready/dsh-model-sync 0.5.3 → 0.5.5
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 +4 -6
- package/lib/client.js +14 -0
- package/lib/http.js +6 -0
- package/lib/index.js +52 -4
- package/lib/inventory.js +3 -2
- package/lib/synchronizer-helpers.js +16 -5
- package/lib/synchronizer.js +2 -1
- package/package.json +1 -1
package/lib/adapter-registry.js
CHANGED
|
@@ -12,7 +12,7 @@ const PROVIDER_ADAPTERS = Object.freeze({
|
|
|
12
12
|
clinebot: {
|
|
13
13
|
endpoint: 'https://api.cline.bot/api/v1/users/me/plan',
|
|
14
14
|
auth: 'bearer',
|
|
15
|
-
|
|
15
|
+
parse: 'clinebot-plan',
|
|
16
16
|
defaultModels: [
|
|
17
17
|
'cline-pass/deepseek-v4-flash',
|
|
18
18
|
'cline-pass/deepseek-v4-pro',
|
|
@@ -99,12 +99,13 @@ function mappedRows(provider, rows, descriptor) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
function parseRows(provider, payload, descriptor) {
|
|
102
|
-
|
|
102
|
+
const parserKind = descriptor.parse || descriptor.parser
|
|
103
|
+
if (parserKind === 'clinebot-plan') {
|
|
103
104
|
const plan = payload?.data?.plan || payload?.data || payload?.plan || payload
|
|
104
105
|
const included = plan?.features?.included || plan?.includedModels || []
|
|
105
106
|
return parseClinePassModels(included)
|
|
106
107
|
}
|
|
107
|
-
if (
|
|
108
|
+
if (parserKind === 'google') {
|
|
108
109
|
const rows = Array.isArray(payload?.models) ? payload.models : []
|
|
109
110
|
return normalizeModels(provider, rows.map((row) => ({
|
|
110
111
|
...row,
|
|
@@ -224,9 +225,6 @@ async function fetchSpecific(profile, descriptor, options) {
|
|
|
224
225
|
return rows
|
|
225
226
|
}
|
|
226
227
|
} catch (error) {
|
|
227
|
-
if (descriptor.defaultModels && Array.isArray(descriptor.defaultModels) && descriptor.defaultModels.length > 0) {
|
|
228
|
-
return normalizeModels(profile.provider, descriptor.defaultModels.map((id) => ({ id })))
|
|
229
|
-
}
|
|
230
228
|
throw error
|
|
231
229
|
}
|
|
232
230
|
if (descriptor.defaultModels && Array.isArray(descriptor.defaultModels) && descriptor.defaultModels.length > 0) {
|
package/lib/client.js
CHANGED
|
@@ -785,6 +785,20 @@ window.__ModuleLoader__.load({
|
|
|
785
785
|
.finally(() => setBusy(false))
|
|
786
786
|
}
|
|
787
787
|
|
|
788
|
+
const resolveSmartAlias = (aliasName) => {
|
|
789
|
+
setResolvingKey(aliasName)
|
|
790
|
+
fetch(`${API}/aliases/resolve?alias=${encodeURIComponent(aliasName)}`)
|
|
791
|
+
.then(async (r) => {
|
|
792
|
+
const res = await r.json()
|
|
793
|
+
if (!r.ok) throw new Error(res.error?.message ?? `HTTP ${r.status}`)
|
|
794
|
+
setResolvedAliases((prev) => ({ ...prev, [aliasName]: res }))
|
|
795
|
+
})
|
|
796
|
+
.catch((e) => {
|
|
797
|
+
setResolvedAliases((prev) => ({ ...prev, [aliasName]: { error: String(e?.message ?? e) } }))
|
|
798
|
+
})
|
|
799
|
+
.finally(() => setResolvingKey(''))
|
|
800
|
+
}
|
|
801
|
+
|
|
788
802
|
const load = () => {
|
|
789
803
|
if (typeof document !== 'undefined' && document.hidden) return
|
|
790
804
|
setError('')
|
package/lib/http.js
CHANGED
|
@@ -240,6 +240,12 @@ export function normalizeSelectionRequest(body) {
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
export function registerHttpApi(ctx, synchronizer, { getSchedulerStatus = () => null } = {}) {
|
|
243
|
+
if (typeof ctx?.webServer?.register !== 'function') {
|
|
244
|
+
if (typeof ctx?.logger?.warn === 'function') {
|
|
245
|
+
ctx.logger.warn('[dsh-model-sync] webServer service unavailable, HTTP API skipped')
|
|
246
|
+
}
|
|
247
|
+
return []
|
|
248
|
+
}
|
|
243
249
|
const makeHandler = (route) => async (req, res) => {
|
|
244
250
|
if (!trusted(req, { requireLoopback: req.method === 'POST' })) return json(res, 403, { error: { code: 'forbidden', message: 'dsh-model-sync: cross-origin or non-loopback request rejected' } })
|
|
245
251
|
if (route === 'status') {
|
package/lib/index.js
CHANGED
|
@@ -122,11 +122,43 @@ export function plainConfig(value) {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
export function apply(ctx, config) {
|
|
125
|
-
const baseConfig = structuredClone(config || {})
|
|
126
|
-
const resolveConfig = (value) =>
|
|
125
|
+
const baseConfig = structuredClone(plainConfig(config || {}))
|
|
126
|
+
const resolveConfig = (value) => {
|
|
127
|
+
try {
|
|
128
|
+
return plainConfig(Config(plainConfig(value) || {}))
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if (typeof ctx?.logger?.warn === "function") {
|
|
131
|
+
ctx.logger.warn("[dsh-model-sync] invalid config payload, fallback to current config:", err?.message || err)
|
|
132
|
+
}
|
|
133
|
+
return liveConfig ?? baseConfig
|
|
134
|
+
}
|
|
135
|
+
}
|
|
127
136
|
let liveConfig = resolveConfig(baseConfig)
|
|
128
137
|
let reconfigure = () => {}
|
|
129
|
-
let saveConfigImpl
|
|
138
|
+
let saveConfigImpl = async (patch) => {
|
|
139
|
+
const plainPatch = plainConfig(patch)
|
|
140
|
+
let settings
|
|
141
|
+
try {
|
|
142
|
+
settings = (typeof ctx.get === 'function' ? ctx.get('settings') : null) ?? ctx.settings
|
|
143
|
+
} catch (err) {
|
|
144
|
+
if (typeof ctx.logger?.debug === 'function') {
|
|
145
|
+
ctx.logger.debug('[dsh-model-sync] settings service read failed:', err)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (settings && typeof settings.update === 'function') {
|
|
149
|
+
try {
|
|
150
|
+
await settings.update(NS, plainPatch)
|
|
151
|
+
} catch (err) {
|
|
152
|
+
if (typeof ctx.logger?.warn === 'function') {
|
|
153
|
+
ctx.logger.warn('[dsh-model-sync] settings.update failed:', err)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
liveConfig = resolveConfig({ ...liveConfig, ...plainPatch })
|
|
158
|
+
reconfigure()
|
|
159
|
+
return liveConfig
|
|
160
|
+
}
|
|
161
|
+
|
|
130
162
|
// ctx.inject starts a child fiber; keep a stable function reference for the
|
|
131
163
|
// synchronizer while that fiber connects to the Settings service.
|
|
132
164
|
const saveConfig = async (patch) => {
|
|
@@ -136,6 +168,23 @@ export function apply(ctx, config) {
|
|
|
136
168
|
return saveConfigImpl(patch)
|
|
137
169
|
}
|
|
138
170
|
|
|
171
|
+
const service = createProviderService(ctx)
|
|
172
|
+
const synchronizer = createModelSynchronizer(ctx, { getConfig: () => liveConfig, saveConfig })
|
|
173
|
+
const scheduler = createSyncScheduler(synchronizer, { getConfig: () => liveConfig })
|
|
174
|
+
reconfigure = () => scheduler.reconfigure()
|
|
175
|
+
scheduler.start()
|
|
176
|
+
|
|
177
|
+
ctx.effect(() => {
|
|
178
|
+
if (typeof ctx.on !== 'function') return () => {}
|
|
179
|
+
const dispose = ctx.on('loader/volatile-update', () => {
|
|
180
|
+
liveConfig = resolveConfig(config)
|
|
181
|
+
reconfigure()
|
|
182
|
+
})
|
|
183
|
+
return () => {
|
|
184
|
+
if (typeof dispose === 'function') dispose()
|
|
185
|
+
}
|
|
186
|
+
}, 'dsh-model-sync: volatile-update listener')
|
|
187
|
+
|
|
139
188
|
// settings.register was removed before 0.1.7-rc.2. The host hands the profile entry
|
|
140
189
|
// config to apply() directly, and liveConfig above is derived from it; the
|
|
141
190
|
// Loader mutates the boxes in place and re-announces them with
|
|
@@ -153,7 +202,6 @@ export function apply(ctx, config) {
|
|
|
153
202
|
manifestUrl: new URL('../package.json', import.meta.url),
|
|
154
203
|
}), 'dsh-model-sync: plugin updater')
|
|
155
204
|
ctx.effect(() => {
|
|
156
|
-
if (!liveConfig.enabled) return () => {}
|
|
157
205
|
ctx.provide('modelSync', service)
|
|
158
206
|
ctx.provide('modelSyncRunner', synchronizer)
|
|
159
207
|
return () => {}
|
package/lib/inventory.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeModels } from './models.js'
|
|
2
|
+
import { sectionOf } from './synchronizer-helpers.js'
|
|
2
3
|
|
|
3
4
|
export const API_KEY_PROVIDER_IDS = Object.freeze([
|
|
4
5
|
'anthropic',
|
|
@@ -42,7 +43,7 @@ const API_KEY_PROVIDER_SET = new Set(API_KEY_PROVIDER_IDS)
|
|
|
42
43
|
|
|
43
44
|
function settingsProfiles(settings) {
|
|
44
45
|
try {
|
|
45
|
-
return settings
|
|
46
|
+
return sectionOf(settings, 'llm-pi-ai')?.providers ?? {}
|
|
46
47
|
} catch {
|
|
47
48
|
return {}
|
|
48
49
|
}
|
|
@@ -66,7 +67,7 @@ export function listApiKeyProviders(llm, settings) {
|
|
|
66
67
|
for (const entry of directory) {
|
|
67
68
|
let profile = profiles[entry.provider]
|
|
68
69
|
if (!profile && (entry.provider === 'commandcode' || entry.settingsNs === 'llm-commandcode')) {
|
|
69
|
-
const cc = settings
|
|
70
|
+
const cc = sectionOf(settings, 'llm-commandcode')
|
|
70
71
|
profile = {
|
|
71
72
|
apiKeyEnv: cc?.apiKeyEnv ?? 'COMMANDCODE_API_KEY',
|
|
72
73
|
credentialRef: cc?.apiKeyEnv ?? 'COMMANDCODE_API_KEY',
|
|
@@ -55,12 +55,23 @@ export function descriptorOf(settings, ns) {
|
|
|
55
55
|
catch { return undefined }
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export function sectionOf(settings, ns) {
|
|
59
|
+
try {
|
|
60
|
+
const desc = descriptorOf(settings, ns)
|
|
61
|
+
if (desc && desc.value !== undefined) return desc.value
|
|
62
|
+
if (typeof settings?.get === 'function') return settings.get(ns)
|
|
63
|
+
return undefined
|
|
64
|
+
} catch {
|
|
65
|
+
return undefined
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
58
69
|
export function profileOf(settings, provider) {
|
|
59
70
|
try {
|
|
60
|
-
const piai = settings
|
|
71
|
+
const piai = sectionOf(settings, PIAI_NS)?.providers?.[provider]
|
|
61
72
|
if (piai) return piai
|
|
62
73
|
if (provider === 'commandcode') {
|
|
63
|
-
const cc = settings
|
|
74
|
+
const cc = sectionOf(settings, 'llm-commandcode')
|
|
64
75
|
return {
|
|
65
76
|
apiKeyEnv: cc?.apiKeyEnv ?? 'COMMANDCODE_API_KEY',
|
|
66
77
|
credentialRef: cc?.apiKeyEnv ?? 'COMMANDCODE_API_KEY',
|
|
@@ -97,7 +108,7 @@ export function adapterErrorType(error) {
|
|
|
97
108
|
export async function replaceWithRetry(settings, ns, patchFn, attempts = 3) {
|
|
98
109
|
let lastError
|
|
99
110
|
for (let i = 0; i < attempts; i++) {
|
|
100
|
-
const section = settings
|
|
111
|
+
const section = sectionOf(settings, ns)
|
|
101
112
|
const descriptor = descriptorOf(settings, ns)
|
|
102
113
|
if (!section || !descriptor || typeof settings.replace !== 'function') {
|
|
103
114
|
throw Object.assign(new Error(`${ns} settings are not writable`), { code: 'SETTINGS_UNAVAILABLE' })
|
|
@@ -120,7 +131,7 @@ export async function replaceWithRetry(settings, ns, patchFn, attempts = 3) {
|
|
|
120
131
|
|
|
121
132
|
export function rotationRefs(settings, provider) {
|
|
122
133
|
try {
|
|
123
|
-
const rows = settings
|
|
134
|
+
const rows = sectionOf(settings, 'dsh-key-rotation')?.providers
|
|
124
135
|
if (!Array.isArray(rows)) return []
|
|
125
136
|
return rows.filter((row) => row?.provider === provider).flatMap((row) => Array.isArray(row.keys) ? row.keys : [])
|
|
126
137
|
} catch {
|
|
@@ -149,7 +160,7 @@ export async function performHistoryRollback({ ctx, config, providers = [], hist
|
|
|
149
160
|
if (!snapshot || !Array.isArray(snapshot.before)) throw Object.assign(new Error(`history snapshot not found: ${provider}`), { code: 'HISTORY_NOT_FOUND' })
|
|
150
161
|
if (!providers.some((row) => row.provider === provider && row.configured)) throw Object.assign(new Error(`provider is not configured: ${provider}`), { code: 'PROVIDER_NOT_CONFIGURED' })
|
|
151
162
|
const settings = settingsOf(ctx)
|
|
152
|
-
const section = settings
|
|
163
|
+
const section = sectionOf(settings, PIAI_NS)
|
|
153
164
|
const descriptor = descriptorOf(settings, PIAI_NS)
|
|
154
165
|
if (!section || !descriptor || typeof settings.replace !== 'function') {
|
|
155
166
|
throw Object.assign(new Error('llm-pi-ai settings are not writable'), { code: 'SETTINGS_UNAVAILABLE' })
|
package/lib/synchronizer.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
settingsOf,
|
|
19
19
|
llmOf,
|
|
20
20
|
descriptorOf,
|
|
21
|
+
sectionOf,
|
|
21
22
|
profileOf,
|
|
22
23
|
errorMessage,
|
|
23
24
|
diagnosticMessage,
|
|
@@ -488,7 +489,7 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
|
|
|
488
489
|
if (typeof saveConfig !== 'function') throw Object.assign(new Error('dsh-model-sync settings are not writable'), { code: 'CONFIG_UNAVAILABLE' })
|
|
489
490
|
|
|
490
491
|
const settings = settingsOf(ctx)
|
|
491
|
-
const section = settings
|
|
492
|
+
const section = sectionOf(settings, PIAI_NS)
|
|
492
493
|
const descriptor = descriptorOf(settings, PIAI_NS)
|
|
493
494
|
const current = normalizeModels(provider, profileOf(settings, provider).models ?? row.models)
|
|
494
495
|
const nextModels = selectedModels.length
|