@goodandready/dsh-model-sync 0.5.0 → 0.5.2
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/alias-resolver.js +4 -1
- package/lib/client.js +13 -3
- package/lib/http.js +21 -2
- package/lib/synchronizer-helpers.js +38 -0
- package/lib/synchronizer-transfer.js +22 -6
- package/lib/synchronizer.js +7 -53
- package/package.json +2 -2
package/lib/alias-resolver.js
CHANGED
|
@@ -110,13 +110,16 @@ export function resolveAlias(aliasName, {
|
|
|
110
110
|
// 3. Strategy resolution (@fast, @cheap, @best-code or custom.strategy)
|
|
111
111
|
let strategy = custom?.strategy
|
|
112
112
|
const lower = clean.toLowerCase()
|
|
113
|
-
if (!strategy) {
|
|
113
|
+
if (!strategy && isVirtualAlias(clean)) {
|
|
114
114
|
if (lower === '@fast') strategy = 'fastest'
|
|
115
115
|
else if (lower === '@cheap') strategy = 'cheapest'
|
|
116
116
|
else if (lower === '@best-code') strategy = 'best-code'
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
if (strategy) {
|
|
120
|
+
if (!VIRTUAL_STRATEGIES.includes(strategy)) {
|
|
121
|
+
throw Object.assign(new Error(`Invalid strategy: ${strategy}`), { code: 'INVALID_STRATEGY' })
|
|
122
|
+
}
|
|
120
123
|
const available = []
|
|
121
124
|
for (const [provider, models] of Object.entries(modelCatalogs)) {
|
|
122
125
|
if (isCircuitOpen(provider)) continue
|
package/lib/client.js
CHANGED
|
@@ -5,6 +5,14 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports
|
|
6
6
|
const React = require('react')
|
|
7
7
|
|
|
8
|
+
let ChevronIcon = null
|
|
9
|
+
try {
|
|
10
|
+
const primitives = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
11
|
+
ChevronIcon = primitives && primitives.IconChevronDownOutline14
|
|
12
|
+
} catch {
|
|
13
|
+
/* Optional core chevron primitive not available; SVG fallback used */
|
|
14
|
+
}
|
|
15
|
+
|
|
8
16
|
const CSS = '.dms-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;margin:0;padding:0;overflow:hidden;box-sizing:border-box}' +
|
|
9
17
|
'.dms-card-open{background:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-border-l2)}' +
|
|
10
18
|
'.dms-card-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;display:flex;align-items:center;gap:12px;padding:14px 16px}' +
|
|
@@ -1425,9 +1433,11 @@ window.__ModuleLoader__.load({
|
|
|
1425
1433
|
React.createElement('span', { className: 'dms-card-description' }, t.desc)
|
|
1426
1434
|
),
|
|
1427
1435
|
cardBadge ? React.createElement('span', { style: { background: 'var(--dsw-alias-state-business-primary)', color: 'white', padding: '2px 6px', borderRadius: 6, fontSize: 11, marginLeft: 8 } }, `+${cardBadge}`) : null,
|
|
1428
|
-
|
|
1429
|
-
React.createElement(
|
|
1430
|
-
|
|
1436
|
+
ChevronIcon
|
|
1437
|
+
? React.createElement(ChevronIcon, { width: 14, height: 14, className: 'dms-card-chevron' + (open ? ' dms-card-chevron-open' : ''), 'aria-hidden': 'true' })
|
|
1438
|
+
: React.createElement('svg', { width: 14, height: 14, className: 'dms-card-chevron' + (open ? ' dms-card-chevron-open' : ''), viewBox: '0 0 14 14', fill: 'none', 'aria-hidden': 'true' },
|
|
1439
|
+
React.createElement('path', { d: 'M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5', fill: 'currentColor' })
|
|
1440
|
+
)
|
|
1431
1441
|
),
|
|
1432
1442
|
(page || open) ? React.createElement('div', { className: 'dms-card-body' }, React.createElement(ErrorBoundary, null, React.createElement(ModelSyncSection, { ctx, includeIntro: false }))) : null
|
|
1433
1443
|
)
|
package/lib/http.js
CHANGED
|
@@ -146,8 +146,26 @@ export function normalizeBatchTryRequest(body) {
|
|
|
146
146
|
export function normalizeAliasRequest(body) {
|
|
147
147
|
if (body === null || typeof body !== 'object' || Array.isArray(body)) throw new Error('request body must be an object')
|
|
148
148
|
if (typeof body.alias !== 'string' || !body.alias.trim()) throw new Error('alias is required')
|
|
149
|
-
if (
|
|
149
|
+
if (!validateAliasName(body.alias)) throw new Error('alias must be 1-64 alphanumeric characters, @ or _-.')
|
|
150
150
|
if (body.action === 'delete') return { action: 'delete', alias: body.alias.trim() }
|
|
151
|
+
if (Array.isArray(body.targets) && body.targets.length > 0) {
|
|
152
|
+
if (body.targets.length > 50) throw new Error('targets array is too long')
|
|
153
|
+
for (const t of body.targets) {
|
|
154
|
+
if (typeof t === 'string') {
|
|
155
|
+
if (!t || t.length > 256) throw new Error('target must be a short string')
|
|
156
|
+
} else if (t && typeof t === 'object') {
|
|
157
|
+
if (typeof t.provider !== 'string' || !t.provider || t.provider.length > 128) throw new Error('target provider must be a short string')
|
|
158
|
+
if (typeof t.model !== 'string' || !t.model || t.model.length > 256) throw new Error('target model must be a short string')
|
|
159
|
+
} else {
|
|
160
|
+
throw new Error('invalid target format')
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { action: 'set', alias: body.alias.trim(), targets: body.targets }
|
|
164
|
+
}
|
|
165
|
+
if (typeof body.strategy === 'string' && body.strategy.trim()) {
|
|
166
|
+
if (body.strategy.length > 64) throw new Error('strategy must be a short string')
|
|
167
|
+
return { action: 'set', alias: body.alias.trim(), strategy: body.strategy.trim() }
|
|
168
|
+
}
|
|
151
169
|
if (typeof body.provider !== 'string' || !body.provider.trim() || body.provider.length > 128) throw new Error('provider must be a short string')
|
|
152
170
|
if (typeof body.model !== 'string' || !body.model.trim() || body.model.length > 256) throw new Error('model must be a short string')
|
|
153
171
|
return { action: 'set', alias: body.alias.trim(), provider: body.provider.trim(), model: body.model.trim() }
|
|
@@ -254,7 +272,8 @@ export function registerHttpApi(ctx, synchronizer, { getSchedulerStatus = () =>
|
|
|
254
272
|
if (route === 'aliases' && req.method === 'GET') {
|
|
255
273
|
return json(res, 200, { aliases: synchronizer.getAliases() })
|
|
256
274
|
}
|
|
257
|
-
if (route === 'aliases-resolve'
|
|
275
|
+
if (route === 'aliases-resolve') {
|
|
276
|
+
if (req.method !== 'GET') return json(res, 405, { error: { code: 'method', message: 'GET only' } })
|
|
258
277
|
try {
|
|
259
278
|
const urlObj = new URL(req.url, 'http://127.0.0.1')
|
|
260
279
|
const alias = urlObj.searchParams.get('alias')
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { statusOfError } from './reliability.js'
|
|
2
|
+
import { historyEntry } from './history.js'
|
|
3
|
+
import { normalizeModels } from './models.js'
|
|
4
|
+
import { catalogPatch } from './reconcile.js'
|
|
2
5
|
|
|
3
6
|
export const PIAI_NS = 'llm-pi-ai'
|
|
4
7
|
export const DEFAULT_RELIABILITY = Object.freeze({
|
|
@@ -124,3 +127,38 @@ export function rotationRefs(settings, provider) {
|
|
|
124
127
|
return []
|
|
125
128
|
}
|
|
126
129
|
}
|
|
130
|
+
|
|
131
|
+
export function refsFor(row, settings) {
|
|
132
|
+
const profile = profileOf(settings, row.provider)
|
|
133
|
+
const refs = [
|
|
134
|
+
row.apiKeyEnv,
|
|
135
|
+
profile.credentialRef,
|
|
136
|
+
...(Array.isArray(profile.credentialRefs) ? profile.credentialRefs : []),
|
|
137
|
+
...(Array.isArray(profile.apiKeyRefs) ? profile.apiKeyRefs : []),
|
|
138
|
+
...rotationRefs(settings, row.provider),
|
|
139
|
+
]
|
|
140
|
+
return [...new Set(refs.filter((ref) => typeof ref === 'string' && ref.trim()).map((ref) => ref.trim()))].slice(0, 32)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function performHistoryRollback({ ctx, config, providers = [], historyId, provider } = {}) {
|
|
144
|
+
if (typeof historyId !== 'string' || !historyId) throw Object.assign(new Error('historyId is required'), { code: 'INVALID_HISTORY' })
|
|
145
|
+
if (typeof provider !== 'string' || !provider) throw Object.assign(new Error('provider is required'), { code: 'INVALID_HISTORY' })
|
|
146
|
+
const entry = historyEntry(config.history, historyId)
|
|
147
|
+
if (!entry) throw Object.assign(new Error(`history entry not found: ${historyId}`), { code: 'HISTORY_NOT_FOUND' })
|
|
148
|
+
const snapshot = (entry.providers ?? []).find((row) => row.provider === provider)
|
|
149
|
+
if (!snapshot || !Array.isArray(snapshot.before)) throw Object.assign(new Error(`history snapshot not found: ${provider}`), { code: 'HISTORY_NOT_FOUND' })
|
|
150
|
+
if (!providers.some((row) => row.provider === provider && row.configured)) throw Object.assign(new Error(`provider is not configured: ${provider}`), { code: 'PROVIDER_NOT_CONFIGURED' })
|
|
151
|
+
const settings = settingsOf(ctx)
|
|
152
|
+
const section = settings?.get?.(PIAI_NS)
|
|
153
|
+
const descriptor = descriptorOf(settings, PIAI_NS)
|
|
154
|
+
if (!section || !descriptor || typeof settings.replace !== 'function') {
|
|
155
|
+
throw Object.assign(new Error('llm-pi-ai settings are not writable'), { code: 'SETTINGS_UNAVAILABLE' })
|
|
156
|
+
}
|
|
157
|
+
const current = normalizeModels(provider, profileOf(settings, provider).models ?? [])
|
|
158
|
+
const target = normalizeModels(provider, snapshot.before)
|
|
159
|
+
if (JSON.stringify(current) === JSON.stringify(target)) {
|
|
160
|
+
return { historyId, provider, models: target, applied: false, allowlistUntouched: true }
|
|
161
|
+
}
|
|
162
|
+
await replaceWithRetry(settings, PIAI_NS, (sec) => catalogPatch(sec, provider, target))
|
|
163
|
+
return { historyId, provider, models: target, applied: true, allowlistUntouched: true }
|
|
164
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveAlias as resolveSmartAlias, validateAliasName } from './alias-resolver.js'
|
|
1
|
+
import { resolveAlias as resolveSmartAlias, validateAliasName, VIRTUAL_STRATEGIES, isVirtualAlias } from './alias-resolver.js'
|
|
2
2
|
|
|
3
3
|
export function createConfigTransfer({
|
|
4
4
|
getConfig = () => ({}),
|
|
@@ -68,9 +68,17 @@ export function createConfigTransfer({
|
|
|
68
68
|
for (const [k, v] of Object.entries(data.aliases)) {
|
|
69
69
|
if (validateAliasName(k) && v && typeof v === 'object') {
|
|
70
70
|
const entry = { updatedAt: Date.now() }
|
|
71
|
-
if (Array.isArray(v.targets))
|
|
72
|
-
|
|
73
|
-
else if (v.
|
|
71
|
+
if (Array.isArray(v.targets)) {
|
|
72
|
+
entry.targets = v.targets
|
|
73
|
+
} else if (v.strategy && typeof v.strategy === 'string') {
|
|
74
|
+
const strat = v.strategy.trim()
|
|
75
|
+
if (VIRTUAL_STRATEGIES.includes(strat)) {
|
|
76
|
+
entry.strategy = strat
|
|
77
|
+
entry.isVirtual = isVirtualAlias(k)
|
|
78
|
+
} else {
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
} else if (v.provider && v.model) {
|
|
74
82
|
entry.provider = v.provider
|
|
75
83
|
entry.model = v.model
|
|
76
84
|
}
|
|
@@ -110,8 +118,16 @@ export function createConfigTransfer({
|
|
|
110
118
|
|
|
111
119
|
if (Array.isArray(targets) && targets.length > 0) {
|
|
112
120
|
currentAliases[trimmedAlias] = { targets, updatedAt: Date.now() }
|
|
113
|
-
} else if (strategy &&
|
|
114
|
-
|
|
121
|
+
} else if (strategy !== undefined && strategy !== null && strategy !== '') {
|
|
122
|
+
const cleanStrategy = typeof strategy === 'string' ? strategy.trim() : ''
|
|
123
|
+
if (!VIRTUAL_STRATEGIES.includes(cleanStrategy)) {
|
|
124
|
+
throw Object.assign(new Error(`Invalid strategy: ${strategy}. Supported: ${VIRTUAL_STRATEGIES.join(', ')}`), { code: 'INVALID_STRATEGY' })
|
|
125
|
+
}
|
|
126
|
+
currentAliases[trimmedAlias] = {
|
|
127
|
+
strategy: cleanStrategy,
|
|
128
|
+
isVirtual: isVirtualAlias(trimmedAlias),
|
|
129
|
+
updatedAt: Date.now(),
|
|
130
|
+
}
|
|
115
131
|
} else {
|
|
116
132
|
if (typeof provider !== 'string' || !provider) throw new Error('provider is required')
|
|
117
133
|
if (typeof model !== 'string' || !model) throw new Error('model is required')
|
package/lib/synchronizer.js
CHANGED
|
@@ -6,7 +6,7 @@ import { reconcileModels, catalogPatch } from './reconcile.js'
|
|
|
6
6
|
import { normalizeModels } from './models.js'
|
|
7
7
|
import { filterModels, normalizePolicy, validatePolicy } from './policy.js'
|
|
8
8
|
import { createTimeoutSignal, isRetryableError, mapWithConcurrency, retryWithBackoff, statusOfError } from './reliability.js'
|
|
9
|
-
import { appendHistory, createHistoryEntry,
|
|
9
|
+
import { appendHistory, createHistoryEntry, listHistory } from './history.js'
|
|
10
10
|
import { lifecycleOptions, lifecycleStatus, selectableModels, updateLifecycle } from './lifecycle.js'
|
|
11
11
|
import { appendNotification, listNotifications, createSyncReport, updateNotification } from './reporting.js'
|
|
12
12
|
|
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
adapterErrorType,
|
|
25
25
|
replaceWithRetry,
|
|
26
26
|
rotationRefs,
|
|
27
|
+
refsFor,
|
|
28
|
+
performHistoryRollback,
|
|
27
29
|
} from './synchronizer-helpers.js'
|
|
28
30
|
import { createConfigTransfer } from './synchronizer-transfer.js'
|
|
29
31
|
import { createModelProber } from './synchronizer-prober.js'
|
|
@@ -50,18 +52,6 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
|
|
|
50
52
|
return registry.select(profile, config.adapterRegistry?.[profile.provider])
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
function refsFor(row, settings) {
|
|
54
|
-
const profile = profileOf(settings, row.provider)
|
|
55
|
-
const refs = [
|
|
56
|
-
row.apiKeyEnv,
|
|
57
|
-
profile.credentialRef,
|
|
58
|
-
...(Array.isArray(profile.credentialRefs) ? profile.credentialRefs : []),
|
|
59
|
-
...(Array.isArray(profile.apiKeyRefs) ? profile.apiKeyRefs : []),
|
|
60
|
-
...rotationRefs(settings, row.provider),
|
|
61
|
-
]
|
|
62
|
-
return [...new Set(refs.filter((ref) => typeof ref === 'string' && ref.trim()).map((ref) => ref.trim()))].slice(0, 32)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
55
|
function circuitOpen(provider, reliability) {
|
|
66
56
|
if (reliability.circuitBreakerFailures <= 0) return null
|
|
67
57
|
const state = circuits.get(provider)
|
|
@@ -462,32 +452,8 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
|
|
|
462
452
|
}
|
|
463
453
|
|
|
464
454
|
const prober = createModelProber({ health })
|
|
465
|
-
const tryModel = prober.tryModel
|
|
466
|
-
const batchTryModels = prober.batchTryModels
|
|
467
455
|
|
|
468
|
-
|
|
469
|
-
if (typeof historyId !== 'string' || !historyId) throw Object.assign(new Error('historyId is required'), { code: 'INVALID_HISTORY' })
|
|
470
|
-
if (typeof provider !== 'string' || !provider) throw Object.assign(new Error('provider is required'), { code: 'INVALID_HISTORY' })
|
|
471
|
-
const config = getConfig() ?? {}
|
|
472
|
-
const entry = historyEntry(config.history, historyId)
|
|
473
|
-
if (!entry) throw Object.assign(new Error(`history entry not found: ${historyId}`), { code: 'HISTORY_NOT_FOUND' })
|
|
474
|
-
const snapshot = (entry.providers ?? []).find((row) => row.provider === provider)
|
|
475
|
-
if (!snapshot || !Array.isArray(snapshot.before)) throw Object.assign(new Error(`history snapshot not found: ${provider}`), { code: 'HISTORY_NOT_FOUND' })
|
|
476
|
-
if (!providers().some((row) => row.provider === provider && row.configured)) throw Object.assign(new Error(`provider is not configured: ${provider}`), { code: 'PROVIDER_NOT_CONFIGURED' })
|
|
477
|
-
const settings = settingsOf(ctx)
|
|
478
|
-
const section = settings?.get?.(PIAI_NS)
|
|
479
|
-
const descriptor = descriptorOf(settings, PIAI_NS)
|
|
480
|
-
if (!section || !descriptor || typeof settings.replace !== 'function') {
|
|
481
|
-
throw Object.assign(new Error('llm-pi-ai settings are not writable'), { code: 'SETTINGS_UNAVAILABLE' })
|
|
482
|
-
}
|
|
483
|
-
const current = normalizeModels(provider, profileOf(settings, provider).models ?? [])
|
|
484
|
-
const target = normalizeModels(provider, snapshot.before)
|
|
485
|
-
if (JSON.stringify(current) === JSON.stringify(target)) {
|
|
486
|
-
return { historyId, provider, models: target, applied: false, allowlistUntouched: true }
|
|
487
|
-
}
|
|
488
|
-
await replaceWithRetry(settings, PIAI_NS, (section) => catalogPatch(section, provider, target))
|
|
489
|
-
return { historyId, provider, models: target, applied: true, allowlistUntouched: true }
|
|
490
|
-
}
|
|
456
|
+
const rollbackHistory = (opts) => performHistoryRollback({ ctx, config: getConfig() ?? {}, providers: providers(), ...opts })
|
|
491
457
|
|
|
492
458
|
async function setModelPolicy(provider, policy) {
|
|
493
459
|
if (typeof provider !== 'string' || !provider) throw new Error('provider is required')
|
|
@@ -567,12 +533,6 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
|
|
|
567
533
|
isCircuitOpen: (p) => Boolean(circuits.get(p)?.blockedUntil > Date.now()),
|
|
568
534
|
getHealthResults: () => lastHealth?.results ?? [],
|
|
569
535
|
})
|
|
570
|
-
const exportConfig = transfer.exportConfig
|
|
571
|
-
const importConfig = transfer.importConfig
|
|
572
|
-
const getAliases = transfer.getAliases
|
|
573
|
-
const setAlias = transfer.setAlias
|
|
574
|
-
const deleteAlias = transfer.deleteAlias
|
|
575
|
-
const resolveAlias = transfer.resolveAlias
|
|
576
536
|
|
|
577
537
|
return Object.freeze({
|
|
578
538
|
listProviders: providers,
|
|
@@ -585,21 +545,15 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
|
|
|
585
545
|
setModelPolicy,
|
|
586
546
|
listPolicies,
|
|
587
547
|
health,
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
exportConfig,
|
|
591
|
-
importConfig,
|
|
592
|
-
getAliases,
|
|
593
|
-
setAlias,
|
|
594
|
-
deleteAlias,
|
|
595
|
-
resolveAlias,
|
|
548
|
+
...prober,
|
|
549
|
+
...transfer,
|
|
596
550
|
credentialDiagnostics,
|
|
597
551
|
history,
|
|
598
552
|
report: () => structuredClone(lastReport),
|
|
599
553
|
notifications: notificationLedger,
|
|
600
554
|
updateNotification: updateNotificationState,
|
|
601
555
|
rollbackHistory,
|
|
602
|
-
testWebhook: async ({ url, secret }) => sendWebhookNotification(url, secret, { event: model-sync.test, timestamp: Date.now(), test: true }),
|
|
556
|
+
testWebhook: async ({ url, secret }) => sendWebhookNotification(url, secret, { event: 'model-sync.test', timestamp: Date.now(), test: true }),
|
|
603
557
|
status: () => ({ running: Boolean(running), lastRun, lastHealth, lastReport: structuredClone(lastReport), notifications: notificationLedger(), history: history(), policies: listPolicies(), lifecycle: structuredClone(getConfig()?.modelLifecycle ?? {}), aliases: structuredClone(getConfig()?.aliases ?? {}) }),
|
|
604
558
|
})
|
|
605
559
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-model-sync",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Automatic model catalog synchronization for API-key DeepSeek Harness providers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -54,6 +54,6 @@
|
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
56
56
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
57
|
-
"@deepseek-ai/dsh-llm": "^0.1.0-rc.
|
|
57
|
+
"@deepseek-ai/dsh-llm": "^0.1.7-rc.2 || ^0.2.0-rc.1"
|
|
58
58
|
}
|
|
59
59
|
}
|