@gehennawu/dsh-service 2.0.7 → 2.0.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/README.en.md +96 -81
- package/README.md +96 -81
- package/client.js +1 -1
- package/index.js +180 -9
- package/model-config-routes.js +641 -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 +11 -3
- package/provider-availability.js +84 -0
- package/quota-adapters.js +55 -0
- package/screenshots/README.md +4 -3
- package/screenshots/configuration.png +0 -0
- package/screenshots/configuration_en.png +0 -0
- package/screenshots/health-diagnostics.png +0 -0
- package/screenshots/health-diagnostics_en.png +0 -0
- package/screenshots/model-configuration.png +0 -0
- package/screenshots/model-configuration_en.png +0 -0
- package/screenshots/overview.png +0 -0
- package/screenshots/overview_en.png +0 -0
- package/subagent-routes.js +66 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gehennawu/dsh-service",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.9",
|
|
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",
|
|
@@ -52,7 +59,7 @@
|
|
|
52
59
|
"homepage": "https://github.com/gehennawu/dsh-service#readme",
|
|
53
60
|
"engines": {
|
|
54
61
|
"node": ">=22",
|
|
55
|
-
"dsh": ">=0.1.1-rc.2 <0.2.1-alpha.
|
|
62
|
+
"dsh": ">=0.1.1-rc.2 <0.2.1-alpha.4"
|
|
56
63
|
},
|
|
57
64
|
"dependencies": {
|
|
58
65
|
"@deepseek-ai/schemastery": "^3.18.3"
|
|
@@ -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/quota-adapters.js
CHANGED
|
@@ -1270,6 +1270,37 @@ async function fetchCliproxyUsage({ profile, config, credential, signal, request
|
|
|
1270
1270
|
return windows
|
|
1271
1271
|
}
|
|
1272
1272
|
|
|
1273
|
+
// Non-monetary points retain decimal strings instead of implying a currency or quota denominator.
|
|
1274
|
+
function normalizePoePoints(payload) {
|
|
1275
|
+
const raw = payload?.current_point_balance
|
|
1276
|
+
const text = typeof raw === 'string' ? raw : typeof raw === 'number' && Number.isFinite(raw) ? String(raw) : ''
|
|
1277
|
+
if (text.length > 64 || !/^-?\d+(?:\.\d+)?$/.test(text)) return { windows: [] }
|
|
1278
|
+
return { windows: [{ id: 'points', kindKey: 'points', text: `${text} points`, unit: 'points' }] }
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function normalizeVercelCredits(payload) {
|
|
1282
|
+
// The API declares USD decimal strings. Lifetime spend is not a quota denominator.
|
|
1283
|
+
const balance = payload?.balance
|
|
1284
|
+
if (typeof balance !== 'string' || balance.length > 64 || !/^\d+(?:\.\d+)?$/.test(balance)
|
|
1285
|
+
|| !Number.isFinite(Number(balance)) || Number(balance) > Number.MAX_SAFE_INTEGER) return { windows: [] }
|
|
1286
|
+
return { windows: [{ id: 'balance', kindKey: 'balance', text: `$${balance}`, unit: 'usd' }] }
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
function normalizeClinePassUsage(payload) {
|
|
1290
|
+
if (payload?.success !== true || !Array.isArray(payload?.data?.limits)) return { windows: [] }
|
|
1291
|
+
const windows = []
|
|
1292
|
+
for (const [type, id] of [['five_hour', 'five-hour'], ['weekly', 'weekly'], ['monthly', 'monthly']]) {
|
|
1293
|
+
const matches = payload.data.limits.filter((entry) => entry?.type === type)
|
|
1294
|
+
if (matches.length > 1) return { windows: [] }
|
|
1295
|
+
const entry = matches[0]
|
|
1296
|
+
if (typeof entry?.percentUsed !== 'number' || !Number.isFinite(entry.percentUsed) || entry.percentUsed < 0) continue
|
|
1297
|
+
const resetsAt = typeof entry.resetsAt === 'string' && /^\d{4}-\d{2}-\d{2}T.*(?:Z|[+-]\d{2}:\d{2})$/.test(entry.resetsAt)
|
|
1298
|
+
? normalizeResetTimestamp(entry.resetsAt) : undefined
|
|
1299
|
+
windows.push({ id, percent: Math.min(100, Math.round(entry.percentUsed)), ...(resetsAt !== undefined ? { resetsAt } : {}) })
|
|
1300
|
+
}
|
|
1301
|
+
return { windows }
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1273
1304
|
function createQuotaAdapterCatalog() {
|
|
1274
1305
|
return Object.freeze([
|
|
1275
1306
|
createEndpointAdapter({
|
|
@@ -1330,6 +1361,30 @@ function createQuotaAdapterCatalog() {
|
|
|
1330
1361
|
keyHints: ['SILICONFLOW_API_KEY'],
|
|
1331
1362
|
hosts: ['siliconflow.cn'],
|
|
1332
1363
|
}),
|
|
1364
|
+
createEndpointAdapter({
|
|
1365
|
+
kind: 'vercel-ai-gateway',
|
|
1366
|
+
normalize: normalizeVercelCredits,
|
|
1367
|
+
endpoints: ['https://ai-gateway.vercel.sh/v1/credits'],
|
|
1368
|
+
keyHints: ['AI_GATEWAY_API_KEY'],
|
|
1369
|
+
hosts: ['ai-gateway.vercel.sh'],
|
|
1370
|
+
routeIds: ['vercel-ai-gateway'],
|
|
1371
|
+
}),
|
|
1372
|
+
createEndpointAdapter({
|
|
1373
|
+
kind: 'poe',
|
|
1374
|
+
normalize: normalizePoePoints,
|
|
1375
|
+
endpoints: ['https://api.poe.com/usage/current_balance'],
|
|
1376
|
+
keyHints: ['POE_API_KEY'],
|
|
1377
|
+
hosts: ['api.poe.com'],
|
|
1378
|
+
routeIds: ['poe'],
|
|
1379
|
+
}),
|
|
1380
|
+
createEndpointAdapter({
|
|
1381
|
+
kind: 'clinepass',
|
|
1382
|
+
normalize: normalizeClinePassUsage,
|
|
1383
|
+
endpoints: ['https://api.cline.bot/api/v1/users/me/plan/usage-limits'],
|
|
1384
|
+
keyHints: ['CLINEPASS_API_KEY'],
|
|
1385
|
+
hosts: ['api.cline.bot'],
|
|
1386
|
+
routeIds: ['clinepass'],
|
|
1387
|
+
}),
|
|
1333
1388
|
createEndpointAdapter({
|
|
1334
1389
|
kind: 'deepseek',
|
|
1335
1390
|
normalize: normalizeDeepseekBalance,
|
package/screenshots/README.md
CHANGED
|
@@ -16,9 +16,10 @@ README 使用的中英文界面截图。文件名与对应功能如下:
|
|
|
16
16
|
| `session-manager.png` | `session-manager_en.png` | 会话管理 |
|
|
17
17
|
| `mobile-adaptation.png` | `mobile-adaptation_en.png` | 移动端适配 |
|
|
18
18
|
| `plugin-config.png` | `plugin-config_en.png` | 插件配置 |
|
|
19
|
-
| `maintenance.png` | `maintenance_en.png` |
|
|
20
|
-
| `configuration.png` | `configuration_en.png` |
|
|
19
|
+
| `maintenance.png` | `maintenance_en.png` | 维护聚合页 |
|
|
20
|
+
| `configuration.png` | `configuration_en.png` | 配置聚合页 |
|
|
21
|
+
| `model-configuration.png` | `model-configuration_en.png` | 模型配置:渠道与模型 |
|
|
21
22
|
|
|
22
23
|
截图宽度建议 800–1200px,PNG 格式。README 新增截图引用时,必须同时加入本目录并通过 `test/docs-static.test.js`;本目录由 `package.json#files` 打入 npm 包。
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
后续替换保持现有文件名,并确保截图不暴露 API key、Cookie、Oasis-Token、管理密钥、会话正文或其他凭据/隐私数据。
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/screenshots/overview.png
CHANGED
|
Binary file
|
|
Binary file
|
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 缺席时保留原目录项);
|
|
@@ -34,6 +36,24 @@ export function createSubagentRoutes({
|
|
|
34
36
|
current = catalog.current
|
|
35
37
|
}
|
|
36
38
|
const config = subagentRouteRef.current
|
|
39
|
+
// 独立 Agent 预设(v2.0.9):清单来自宿主 agentPresets 服务(0.2.1-alpha.3+;旧宿主缺席
|
|
40
|
+
// ⇒ 空清单 + presetSupported:false,客户端禁用选择器)。broken 的声明照常下发但带标记,
|
|
41
|
+
// 保存端拒绝选中它。
|
|
42
|
+
let presets = []
|
|
43
|
+
let presetSupported = false
|
|
44
|
+
const presetRegistry = ctx.get('agentPresets')
|
|
45
|
+
if (presetRegistry !== undefined && presetRegistry !== null && typeof presetRegistry.list === 'function') {
|
|
46
|
+
presetSupported = true
|
|
47
|
+
try {
|
|
48
|
+
const declared = await presetRegistry.list()
|
|
49
|
+
presets = (Array.isArray(declared) ? declared : [])
|
|
50
|
+
.filter((entry) => entry !== null && typeof entry === 'object' && typeof entry.id === 'string' && entry.id !== '')
|
|
51
|
+
.map((entry) => ({ id: entry.id, ...(typeof entry.name === 'string' && entry.name !== '' ? { name: entry.name } : {}), ...(typeof entry.broken === 'string' && entry.broken !== '' ? { broken: entry.broken } : {}) }))
|
|
52
|
+
.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0)
|
|
53
|
+
} catch (_) {
|
|
54
|
+
presets = []
|
|
55
|
+
}
|
|
56
|
+
}
|
|
37
57
|
// 自定义路由草稿(v1.4.12):三模式统一下发——客户端据此回填表单,切换模式不丢配置。
|
|
38
58
|
const routePresent = typeof config.provider === 'string' && config.provider !== '' && typeof config.model === 'string' && config.model !== ''
|
|
39
59
|
return {
|
|
@@ -47,6 +67,9 @@ export function createSubagentRoutes({
|
|
|
47
67
|
...(typeof config.reasoningEffort === 'string' && config.reasoningEffort !== '' ? { reasoningEffort: config.reasoningEffort } : {}),
|
|
48
68
|
} : {}),
|
|
49
69
|
...(Array.isArray(config.fallbacks) && config.fallbacks.length > 0 ? { fallbacks: config.fallbacks } : {}),
|
|
70
|
+
...(typeof config.preset === 'string' && config.preset !== '' ? { preset: config.preset } : {}),
|
|
71
|
+
presetSupported,
|
|
72
|
+
presets,
|
|
50
73
|
models,
|
|
51
74
|
...(current !== undefined ? { current } : {}),
|
|
52
75
|
},
|
|
@@ -127,7 +150,42 @@ export function createSubagentRoutes({
|
|
|
127
150
|
fallbacks.push(normalized)
|
|
128
151
|
}
|
|
129
152
|
}
|
|
153
|
+
// 独立 Agent 预设(v2.0.9):与模式无关,payload 携带 `preset` 键才生效(缺省 = 保留原值,
|
|
154
|
+
// 与 fallbacks 的草稿语义一致);空串 = 清除(跟随父代)。非空值必须在 agentPresets 清单内
|
|
155
|
+
// 且未 broken——宿主在派生端还会按 provider 能力二次把关,这里只挡「明显无效」的引用。
|
|
156
|
+
let preset
|
|
157
|
+
if (Object.prototype.hasOwnProperty.call(payload ?? {}, 'preset')) {
|
|
158
|
+
const rawPreset = payload.preset
|
|
159
|
+
if (rawPreset === undefined || rawPreset === null || rawPreset === '') {
|
|
160
|
+
preset = ''
|
|
161
|
+
} else if (typeof rawPreset !== 'string') {
|
|
162
|
+
return { ok: false, error: 'invalid-preset' }
|
|
163
|
+
} else {
|
|
164
|
+
const candidate = rawPreset.trim().slice(0, MAX_SUBAGENT_ROUTE_FIELD)
|
|
165
|
+
if (candidate === '') {
|
|
166
|
+
preset = ''
|
|
167
|
+
} else {
|
|
168
|
+
const registry = ctx.get('agentPresets')
|
|
169
|
+
if (registry === undefined || registry === null || typeof registry.list !== 'function') return { ok: false, error: 'preset-unavailable' }
|
|
170
|
+
let declared = []
|
|
171
|
+
try {
|
|
172
|
+
declared = await registry.list()
|
|
173
|
+
} catch (_) {
|
|
174
|
+
declared = []
|
|
175
|
+
}
|
|
176
|
+
const hit = (Array.isArray(declared) ? declared : []).find((entry) => entry !== null && typeof entry === 'object' && entry.id === candidate)
|
|
177
|
+
if (hit === undefined) return { ok: false, error: 'invalid-preset' }
|
|
178
|
+
if (typeof hit.broken === 'string' && hit.broken !== '') return { ok: false, error: 'invalid-preset' }
|
|
179
|
+
preset = candidate
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
130
183
|
return await serializeSubagentRouteWrite(async (config) => {
|
|
184
|
+
if (providerAvailability) {
|
|
185
|
+
await providerAvailability.loading
|
|
186
|
+
if (primary.provider) providerAvailability.assertEnabled(primary.provider, primary.model)
|
|
187
|
+
for (const fallback of fallbacks) providerAvailability.assertEnabled(fallback.provider, fallback.model)
|
|
188
|
+
}
|
|
131
189
|
config.mode = mode
|
|
132
190
|
if (mode === 'custom') {
|
|
133
191
|
config.provider = primary.provider
|
|
@@ -145,12 +203,19 @@ export function createSubagentRoutes({
|
|
|
145
203
|
if (fallbacks.length > 0) config.fallbacks = fallbacks
|
|
146
204
|
else delete config.fallbacks
|
|
147
205
|
}
|
|
206
|
+
// 预设:与模式无关;只有本轮 payload 携带了 `preset` 键才写/清。空值一律不落盘
|
|
207
|
+
// (磁盘形状与不认识该字段的旧版本逐字节兼容;注入端 absent = 跟随父代)。
|
|
208
|
+
if (preset !== undefined) {
|
|
209
|
+
if (preset === '') delete config.preset
|
|
210
|
+
else config.preset = preset
|
|
211
|
+
}
|
|
212
|
+
if (config.preset === '') delete config.preset
|
|
148
213
|
const routePresent = typeof config.provider === 'string' && config.provider !== '' && typeof config.model === 'string' && config.model !== ''
|
|
149
214
|
return { value: { ok: true, mode: config.mode, ...(routePresent ? {
|
|
150
215
|
provider: config.provider,
|
|
151
216
|
model: config.model,
|
|
152
217
|
...(typeof config.reasoningEffort === 'string' && config.reasoningEffort !== '' ? { reasoningEffort: config.reasoningEffort } : {}),
|
|
153
|
-
} : {}), ...(Array.isArray(config.fallbacks) && config.fallbacks.length > 0 ? { fallbacks: config.fallbacks } : {}) } }
|
|
218
|
+
} : {}), ...(Array.isArray(config.fallbacks) && config.fallbacks.length > 0 ? { fallbacks: config.fallbacks } : {}), ...(typeof config.preset === 'string' && config.preset !== '' ? { preset: config.preset } : {}) } }
|
|
154
219
|
})
|
|
155
220
|
} catch (error) {
|
|
156
221
|
return rpcTechnicalFailure(error)
|