@goodandready/dsh-model-sync 0.3.13 → 0.3.14
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 +19 -0
- package/README.ru.md +19 -0
- package/README.zh.md +19 -0
- package/lib/client.js +992 -88
- package/lib/http.js +52 -1
- package/lib/index.js +172 -163
- package/lib/policy.js +24 -1
- package/lib/synchronizer.js +144 -1
- package/package.json +1 -1
package/lib/synchronizer.js
CHANGED
|
@@ -547,6 +547,34 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
|
|
|
547
547
|
...(entry?.status !== 'ok' ? { error: entry?.message || entry?.status || 'probe failed' } : {}),
|
|
548
548
|
}
|
|
549
549
|
}
|
|
550
|
+
async function batchTryModels({ provider, models } = {}) {
|
|
551
|
+
if (typeof provider !== 'string' || !provider) throw new Error('provider is required')
|
|
552
|
+
if (!Array.isArray(models)) throw new Error('models must be an array')
|
|
553
|
+
const list = [...new Set(models.filter((m) => typeof m === 'string' && m.trim()).map((m) => m.trim()))].slice(0, 50)
|
|
554
|
+
const results = []
|
|
555
|
+
const concurrency = 3
|
|
556
|
+
for (let i = 0; i < list.length; i += concurrency) {
|
|
557
|
+
const chunk = list.slice(i, i + concurrency)
|
|
558
|
+
const chunkResults = await Promise.all(
|
|
559
|
+
chunk.map((model) => tryModel({ provider, model }).catch((err) => ({
|
|
560
|
+
provider,
|
|
561
|
+
model,
|
|
562
|
+
ok: false,
|
|
563
|
+
latencyMs: 0,
|
|
564
|
+
status: 'error',
|
|
565
|
+
error: err instanceof Error ? err.message : String(err)
|
|
566
|
+
})))
|
|
567
|
+
)
|
|
568
|
+
results.push(...chunkResults)
|
|
569
|
+
}
|
|
570
|
+
const reachable = results.filter((r) => r.ok).length
|
|
571
|
+
return {
|
|
572
|
+
provider,
|
|
573
|
+
results,
|
|
574
|
+
summary: { total: results.length, reachable, unreachable: results.length - reachable }
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
550
578
|
|
|
551
579
|
async function rollbackHistory({ historyId, provider } = {}) {
|
|
552
580
|
if (typeof historyId !== 'string' || !historyId) throw Object.assign(new Error('historyId is required'), { code: 'INVALID_HISTORY' })
|
|
@@ -632,6 +660,115 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
|
|
|
632
660
|
}
|
|
633
661
|
return { provider, selectedModels, models: nextModels, availableModels: available, selectableModels: safeAvailable, applied: true }
|
|
634
662
|
}
|
|
663
|
+
function exportConfig() {
|
|
664
|
+
const config = getConfig() ?? {}
|
|
665
|
+
const pmap = {}
|
|
666
|
+
for (const p of providers()) {
|
|
667
|
+
if (p.configured) {
|
|
668
|
+
pmap[p.provider] = {
|
|
669
|
+
policy: config.modelPolicies?.[p.provider] ?? {},
|
|
670
|
+
selectedModels: config.modelSelections?.[p.provider] ?? []
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return {
|
|
675
|
+
version: '1.0',
|
|
676
|
+
exportedAt: new Date().toISOString(),
|
|
677
|
+
plugin: '@goodandready/dsh-model-sync',
|
|
678
|
+
providers: pmap,
|
|
679
|
+
aliases: config.aliases ?? {},
|
|
680
|
+
scheduler: {
|
|
681
|
+
scheduleEnabled: config.scheduleEnabled ?? false,
|
|
682
|
+
intervalMinutes: config.intervalMinutes ?? 60,
|
|
683
|
+
autoApply: config.autoApply ?? false
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
async function importConfig(data) {
|
|
689
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
690
|
+
throw Object.assign(new Error('invalid config payload'), { code: 'INVALID_CONFIG' })
|
|
691
|
+
}
|
|
692
|
+
if (typeof saveConfig !== 'function') throw Object.assign(new Error('dsh-model-sync settings are not writable'), { code: 'CONFIG_UNAVAILABLE' })
|
|
693
|
+
const config = getConfig() ?? {}
|
|
694
|
+
const importedProviders = []
|
|
695
|
+
const importedAliases = []
|
|
696
|
+
const patch = {}
|
|
697
|
+
|
|
698
|
+
if (data.providers && typeof data.providers === 'object') {
|
|
699
|
+
const currentPolicies = structuredClone(config.modelPolicies ?? {})
|
|
700
|
+
const currentSelections = structuredClone(config.modelSelections ?? {})
|
|
701
|
+
for (const [pname, pdata] of Object.entries(data.providers)) {
|
|
702
|
+
if (pdata && typeof pdata === 'object') {
|
|
703
|
+
if (pdata.policy) {
|
|
704
|
+
currentPolicies[pname] = validatePolicy(pdata.policy)
|
|
705
|
+
importedProviders.push(pname)
|
|
706
|
+
}
|
|
707
|
+
if (Array.isArray(pdata.selectedModels)) {
|
|
708
|
+
currentSelections[pname] = pdata.selectedModels.filter((m) => typeof m === 'string')
|
|
709
|
+
importedProviders.push(pname)
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
patch.modelPolicies = currentPolicies
|
|
714
|
+
patch.modelSelections = currentSelections
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if (data.aliases && typeof data.aliases === 'object') {
|
|
718
|
+
const currentAliases = structuredClone(config.aliases ?? {})
|
|
719
|
+
for (const [k, v] of Object.entries(data.aliases)) {
|
|
720
|
+
if (typeof k === 'string' && /^[a-zA-Z0-9_\-.]{1,64}$/.test(k.trim()) && v && typeof v === 'object' && typeof v.provider === 'string' && typeof v.model === 'string') {
|
|
721
|
+
currentAliases[k.trim()] = { provider: v.provider, model: v.model, updatedAt: Date.now() }
|
|
722
|
+
importedAliases.push(k.trim())
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
patch.aliases = currentAliases
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (data.scheduler && typeof data.scheduler === 'object') {
|
|
729
|
+
if (typeof data.scheduler.scheduleEnabled === 'boolean') patch.scheduleEnabled = data.scheduler.scheduleEnabled
|
|
730
|
+
if (typeof data.scheduler.intervalMinutes === 'number') patch.intervalMinutes = data.scheduler.intervalMinutes
|
|
731
|
+
if (typeof data.scheduler.autoApply === 'boolean') patch.autoApply = data.scheduler.autoApply
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
await saveConfig(patch)
|
|
735
|
+
return {
|
|
736
|
+
success: true,
|
|
737
|
+
importedProviders: [...new Set(importedProviders)],
|
|
738
|
+
importedAliases: [...new Set(importedAliases)]
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function getAliases() {
|
|
743
|
+
const config = getConfig() ?? {}
|
|
744
|
+
return config.aliases ?? {}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
async function setAlias({ alias, provider, model } = {}) {
|
|
748
|
+
if (typeof alias !== 'string' || !/^[a-zA-Z0-9_\-.]{1,64}$/.test(alias.trim())) {
|
|
749
|
+
throw Object.assign(new Error('alias must be 1-64 alphanumeric characters or _-.'), { code: 'INVALID_ALIAS' })
|
|
750
|
+
}
|
|
751
|
+
const trimmedAlias = alias.trim()
|
|
752
|
+
if (typeof provider !== 'string' || !provider) throw new Error('provider is required')
|
|
753
|
+
if (typeof model !== 'string' || !model) throw new Error('model is required')
|
|
754
|
+
if (typeof saveConfig !== 'function') throw Object.assign(new Error('dsh-model-sync settings are not writable'), { code: 'CONFIG_UNAVAILABLE' })
|
|
755
|
+
const config = getConfig() ?? {}
|
|
756
|
+
const currentAliases = structuredClone(config.aliases ?? {})
|
|
757
|
+
currentAliases[trimmedAlias] = { provider, model, updatedAt: Date.now() }
|
|
758
|
+
await saveConfig({ aliases: currentAliases })
|
|
759
|
+
return { alias: trimmedAlias, provider, model, aliases: currentAliases }
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
async function deleteAlias({ alias } = {}) {
|
|
763
|
+
if (typeof alias !== 'string' || !alias) throw new Error('alias is required')
|
|
764
|
+
if (typeof saveConfig !== 'function') throw Object.assign(new Error('dsh-model-sync settings are not writable'), { code: 'CONFIG_UNAVAILABLE' })
|
|
765
|
+
const config = getConfig() ?? {}
|
|
766
|
+
const currentAliases = structuredClone(config.aliases ?? {})
|
|
767
|
+
delete currentAliases[alias.trim()]
|
|
768
|
+
await saveConfig({ aliases: currentAliases })
|
|
769
|
+
return { alias: alias.trim(), deleted: true, aliases: currentAliases }
|
|
770
|
+
}
|
|
771
|
+
|
|
635
772
|
|
|
636
773
|
return Object.freeze({
|
|
637
774
|
listProviders: providers,
|
|
@@ -645,12 +782,18 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
|
|
|
645
782
|
listPolicies,
|
|
646
783
|
health,
|
|
647
784
|
tryModel,
|
|
785
|
+
batchTryModels,
|
|
786
|
+
exportConfig,
|
|
787
|
+
importConfig,
|
|
788
|
+
getAliases,
|
|
789
|
+
setAlias,
|
|
790
|
+
deleteAlias,
|
|
648
791
|
credentialDiagnostics,
|
|
649
792
|
history,
|
|
650
793
|
report: () => structuredClone(lastReport),
|
|
651
794
|
notifications: notificationLedger,
|
|
652
795
|
updateNotification: updateNotificationState,
|
|
653
796
|
rollbackHistory,
|
|
654
|
-
status: () => ({ running: Boolean(running), lastRun, lastHealth, lastReport: structuredClone(lastReport), notifications: notificationLedger(), history: history(), policies: listPolicies(), lifecycle: structuredClone(getConfig()?.modelLifecycle ?? {}) }),
|
|
797
|
+
status: () => ({ running: Boolean(running), lastRun, lastHealth, lastReport: structuredClone(lastReport), notifications: notificationLedger(), history: history(), policies: listPolicies(), lifecycle: structuredClone(getConfig()?.modelLifecycle ?? {}), aliases: structuredClone(getConfig()?.aliases ?? {}) }),
|
|
655
798
|
})
|
|
656
799
|
}
|