@gehennawu/dsh-service 2.0.7 → 2.0.8
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.en.md +58 -10
- package/README.md +58 -10
- package/client.js +1 -1
- package/index.js +127 -7
- package/model-config-routes.js +633 -0
- package/model-config-schema.js +132 -0
- package/model-smart-fill.js +2206 -0
- package/model-workbench-routes.js +1740 -0
- package/model-workbench-settings.js +389 -0
- package/model-workbench.js +1618 -0
- package/package.json +10 -2
- package/provider-availability.js +84 -0
- package/subagent-routes.js +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gehennawu/dsh-service",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
4
4
|
"description": "DSH Web 运维面板:安全重启、健康监控、备份和 Linux 权限维护。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -25,6 +25,13 @@
|
|
|
25
25
|
"quota-routes.js",
|
|
26
26
|
"subagent-routes.js",
|
|
27
27
|
"backup-routes.js",
|
|
28
|
+
"model-workbench.js",
|
|
29
|
+
"model-workbench-routes.js",
|
|
30
|
+
"model-workbench-settings.js",
|
|
31
|
+
"model-config-routes.js",
|
|
32
|
+
"provider-availability.js",
|
|
33
|
+
"model-config-schema.js",
|
|
34
|
+
"model-smart-fill.js",
|
|
28
35
|
"plugin-health.js",
|
|
29
36
|
"client.js",
|
|
30
37
|
"cordis.patch.yml",
|
|
@@ -64,7 +71,8 @@
|
|
|
64
71
|
"client": {
|
|
65
72
|
"inject": [
|
|
66
73
|
"@deepseek-ai/dsh-client-connection",
|
|
67
|
-
"@deepseek-ai/dsh-api-remotes"
|
|
74
|
+
"@deepseek-ai/dsh-api-remotes",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-primitives"
|
|
68
76
|
],
|
|
69
77
|
"platform": "web"
|
|
70
78
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Profile-owned policy: configuration and credentials never move or disappear.
|
|
2
|
+
export function createProviderAvailability(store) {
|
|
3
|
+
let disabled = new Set()
|
|
4
|
+
let disabledModels = new Set()
|
|
5
|
+
const modelKey = (provider, model) => JSON.stringify([provider, model])
|
|
6
|
+
let ready = false
|
|
7
|
+
const loading = store.load().then((state) => {
|
|
8
|
+
disabled = new Set(Object.entries(state.disabledProviders ?? {}).filter(([, value]) => value === true).map(([id]) => id))
|
|
9
|
+
disabledModels = new Set(Object.entries(state.disabledModels ?? {}).filter(([, value]) => value === true).map(([key]) => key))
|
|
10
|
+
ready = true
|
|
11
|
+
})
|
|
12
|
+
const enabled = (provider) => !disabled.has(provider)
|
|
13
|
+
const modelEnabled = (provider, model) => !disabledModels.has(modelKey(provider, model))
|
|
14
|
+
const assertEnabled = (provider, model) => {
|
|
15
|
+
if (!ready || !enabled(provider) || model !== undefined && !modelEnabled(provider, model)) {
|
|
16
|
+
const code = !ready ? 'provider-availability-loading' : !enabled(provider) ? 'provider-disabled' : 'model-disabled'
|
|
17
|
+
const error = new Error(code)
|
|
18
|
+
error.code = code.toUpperCase().replaceAll('-', '_')
|
|
19
|
+
throw error
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
let changes = Promise.resolve()
|
|
23
|
+
const set = (provider, value, expected) => {
|
|
24
|
+
const result = changes.then(async () => {
|
|
25
|
+
await loading
|
|
26
|
+
if (enabled(provider) !== expected) throw new Error('conflict')
|
|
27
|
+
await store.mutate((state) => {
|
|
28
|
+
state.disabledProviders ??= {}
|
|
29
|
+
if (value) delete state.disabledProviders[provider]
|
|
30
|
+
else Object.defineProperty(state.disabledProviders, provider, { value: true, enumerable: true, configurable: true, writable: true })
|
|
31
|
+
})
|
|
32
|
+
if (value) disabled.delete(provider); else disabled.add(provider)
|
|
33
|
+
return { route: provider, enabled: value }
|
|
34
|
+
})
|
|
35
|
+
changes = result.catch(() => {})
|
|
36
|
+
return result
|
|
37
|
+
}
|
|
38
|
+
const setModel = (provider, model, value, expected) => {
|
|
39
|
+
const result = changes.then(async () => {
|
|
40
|
+
await loading
|
|
41
|
+
if (modelEnabled(provider, model) !== expected) throw new Error('conflict')
|
|
42
|
+
const key = modelKey(provider, model)
|
|
43
|
+
await store.mutate((state) => {
|
|
44
|
+
state.disabledModels ??= {}
|
|
45
|
+
if (value) delete state.disabledModels[key]
|
|
46
|
+
else Object.defineProperty(state.disabledModels, key, { value: true, enumerable: true, configurable: true, writable: true })
|
|
47
|
+
})
|
|
48
|
+
if (value) disabledModels.delete(key); else disabledModels.add(key)
|
|
49
|
+
return { route: provider, modelId: model, enabled: value }
|
|
50
|
+
})
|
|
51
|
+
changes = result.catch(() => {})
|
|
52
|
+
return result
|
|
53
|
+
}
|
|
54
|
+
return { loading, enabled, modelEnabled, assertEnabled, set, setModel }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Filter the shared catalog and guard exact routing, including inherited routes.
|
|
58
|
+
// Keep every original method and restore it when the owning Fiber is disposed.
|
|
59
|
+
export function installProviderAvailability(llm, policy, notify = () => {}) {
|
|
60
|
+
const originals = new Map()
|
|
61
|
+
const wrappers = new Map()
|
|
62
|
+
const wrap = (key, factory) => {
|
|
63
|
+
if (typeof llm[key] !== 'function') return
|
|
64
|
+
const original = llm[key]
|
|
65
|
+
const wrapper = factory(original.bind(llm))
|
|
66
|
+
originals.set(key, original); wrappers.set(key, wrapper); llm[key] = wrapper
|
|
67
|
+
}
|
|
68
|
+
wrap('listProviders', (original) => () => original().filter((row) => policy.enabled(row.id)))
|
|
69
|
+
wrap('listModels', (original) => async (provider, ...args) => { await policy.loading; return policy.enabled(provider) ? (await original(provider, ...args)).filter((row) => policy.modelEnabled(provider, row.id)) : [] })
|
|
70
|
+
wrap('resolveModelInfo', (original) => async (provider, ...args) => { await policy.loading; policy.assertEnabled(provider, args[0]); return original(provider, ...args) })
|
|
71
|
+
wrap('resolveCallConfig', (original) => async (config, ...args) => { await policy.loading; policy.assertEnabled(config.provider, config.model); return original(config, ...args) })
|
|
72
|
+
wrap('prepareCall', (original) => async (config, ...args) => {
|
|
73
|
+
await policy.loading; policy.assertEnabled(config.provider, config.model)
|
|
74
|
+
const prepared = await original(config, ...args)
|
|
75
|
+
return { ...prepared, stream: async function* (options) {
|
|
76
|
+
policy.assertEnabled(config.provider, config.model)
|
|
77
|
+
yield* prepared.stream(options)
|
|
78
|
+
} }
|
|
79
|
+
})
|
|
80
|
+
wrap('stream', (original) => async function* (options) { await policy.loading; policy.assertEnabled(options.provider, options.model); yield* original(options) })
|
|
81
|
+
let disposed = false
|
|
82
|
+
policy.loading.then(() => { if (!disposed) notify() }, () => {})
|
|
83
|
+
return () => { disposed = true; for (const [key, original] of originals) if (llm[key] === wrappers.get(key)) llm[key] = original; notify() }
|
|
84
|
+
}
|
package/subagent-routes.js
CHANGED
|
@@ -17,11 +17,13 @@ export function createSubagentRoutes({
|
|
|
17
17
|
listSubagentDispatches,
|
|
18
18
|
listSubagentModels,
|
|
19
19
|
rpcTechnicalFailure,
|
|
20
|
+
providerAvailability,
|
|
20
21
|
}) {
|
|
21
22
|
return {
|
|
22
23
|
'subagent-route': { feature: 'subagentRoute', handle: async (payload, rpcEndpoint) => {
|
|
23
24
|
try {
|
|
24
25
|
await subagentRouteLoadPromise
|
|
26
|
+
if (providerAvailability) await providerAvailability.loading
|
|
25
27
|
const llm = ctx.get('llm')
|
|
26
28
|
// 模型清单沿用 skills-models 的白名单口径(llm.listProviders × listModels,单渠道失败跳过),
|
|
27
29
|
// 并对每个精确模型附加 adapter 的 reasoning metadata(resolveModelInfo 缺席时保留原目录项);
|
|
@@ -128,6 +130,11 @@ export function createSubagentRoutes({
|
|
|
128
130
|
}
|
|
129
131
|
}
|
|
130
132
|
return await serializeSubagentRouteWrite(async (config) => {
|
|
133
|
+
if (providerAvailability) {
|
|
134
|
+
await providerAvailability.loading
|
|
135
|
+
if (primary.provider) providerAvailability.assertEnabled(primary.provider, primary.model)
|
|
136
|
+
for (const fallback of fallbacks) providerAvailability.assertEnabled(fallback.provider, fallback.model)
|
|
137
|
+
}
|
|
131
138
|
config.mode = mode
|
|
132
139
|
if (mode === 'custom') {
|
|
133
140
|
config.provider = primary.provider
|