@dsh-plus/lifeboat 0.1.6 → 0.1.7
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/index.js +31 -11
- package/package.json +11 -11
- package/src/fallback-llm.ts +35 -13
- package/src/health-api.ts +2 -1
package/lib/index.js
CHANGED
|
@@ -61,6 +61,16 @@ const NS_AGENT_DEFAULT = settingsNamespace("agent-default-model");
|
|
|
61
61
|
const RAW_NS_LLM_PI = "dsh-plus-llm-pi";
|
|
62
62
|
/** 官方配置路径物化的 compat 子集(其余字段降级期丢弃)。 */
|
|
63
63
|
const COMPAT_WHITELIST = ["thinkingFormat", "supportsReasoningEffort"];
|
|
64
|
+
/**
|
|
65
|
+
* master 官方 llm-pi-ai 的 COMPAT_GATES(llm-pi-ai/src/catalog.ts):每个 compat
|
|
66
|
+
* 字段按协议门控,写时经 assertOfferedCompatFields 拒绝不适配字段——thinkingFormat /
|
|
67
|
+
* supportsReasoningEffort 仅 openai-completions 提供。翻译按推断 api 过滤,避免
|
|
68
|
+
* 整段 compat 写入被官方校验拒绝。
|
|
69
|
+
*/
|
|
70
|
+
const COMPAT_OFFER_APIS = {
|
|
71
|
+
thinkingFormat: /* @__PURE__ */ new Set(["openai-completions"]),
|
|
72
|
+
supportsReasoningEffort: /* @__PURE__ */ new Set(["openai-completions"])
|
|
73
|
+
};
|
|
64
74
|
/** 官方 reasoning 词表(ModelThinkingLevel 的保守子集),不在其中的值丢弃。 */
|
|
65
75
|
const REASONING_LEVELS = /* @__PURE__ */ new Set([
|
|
66
76
|
"off",
|
|
@@ -112,8 +122,14 @@ function translateProvider(routeKey, raw) {
|
|
|
112
122
|
if (raw.thinkingBudgets !== void 0) out.thinkingBudgets = raw.thinkingBudgets;
|
|
113
123
|
if (Array.isArray(raw.defaultInput) && raw.defaultInput.length > 0) out.defaultInput = raw.defaultInput;
|
|
114
124
|
if (raw.compat !== void 0) {
|
|
125
|
+
const api = inferApi(routeKey, raw);
|
|
115
126
|
const compat = {};
|
|
116
|
-
for (const key of COMPAT_WHITELIST)
|
|
127
|
+
for (const key of COMPAT_WHITELIST) {
|
|
128
|
+
const value = raw.compat[key];
|
|
129
|
+
if (value === void 0 || value === null) continue;
|
|
130
|
+
if (!COMPAT_OFFER_APIS[key]?.has(api)) continue;
|
|
131
|
+
compat[key] = value;
|
|
132
|
+
}
|
|
117
133
|
if (Object.keys(compat).length > 0) out.compat = compat;
|
|
118
134
|
}
|
|
119
135
|
if (Array.isArray(raw.models) && raw.models.length > 0) out.models = raw.models.map((m) => {
|
|
@@ -139,14 +155,12 @@ async function readRawSection(settingsFile, ns) {
|
|
|
139
155
|
if (typeof section !== "object" || section === null) return void 0;
|
|
140
156
|
return section;
|
|
141
157
|
}
|
|
142
|
-
/** 写 settings 带一次冲突重试(用户同时改配置时不抢,放弃并告警)。
|
|
143
|
-
* 冲突判定双条件:instanceof 覆盖同版本副本;error.code 覆盖跨版本副本
|
|
144
|
-
* (插件自带 rc.7 依赖与 host rc.8 抛错时 instanceof 为 false)。 */
|
|
158
|
+
/** 写 settings 带一次冲突重试(用户同时改配置时不抢,放弃并告警)。 */
|
|
145
159
|
async function writeWithRetry(write) {
|
|
146
160
|
try {
|
|
147
161
|
await write();
|
|
148
162
|
} catch (error) {
|
|
149
|
-
if (!(error instanceof SettingsConflictError
|
|
163
|
+
if (!(error instanceof SettingsConflictError)) throw error;
|
|
150
164
|
await write();
|
|
151
165
|
}
|
|
152
166
|
}
|
|
@@ -165,11 +179,16 @@ function installLlmFallback(ctx, deps) {
|
|
|
165
179
|
}
|
|
166
180
|
const translated = translateProviders(providers);
|
|
167
181
|
const fbProvider = `${provider}-fb`;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
182
|
+
try {
|
|
183
|
+
await writes.update(NS_PI_AI, { providers: translated });
|
|
184
|
+
await writes.update(NS_AGENT_DEFAULT, {
|
|
185
|
+
provider: fbProvider,
|
|
186
|
+
model
|
|
187
|
+
});
|
|
188
|
+
} catch (error) {
|
|
189
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
190
|
+
throw new Error(`翻译失败(官方 llm-pi-ai 拒绝写入翻译后的配置): ${message}`);
|
|
191
|
+
}
|
|
173
192
|
await deps.writeState({
|
|
174
193
|
active: true,
|
|
175
194
|
originalProvider: provider,
|
|
@@ -375,6 +394,7 @@ function installHostWatch(ctx, q) {
|
|
|
375
394
|
* - GET /dsh-plus/lifeboat/status → journal + 翻译状态 + 当前用户 patch 层已禁用的 dsh-plus 插件
|
|
376
395
|
* - POST /dsh-plus/lifeboat/restore {name} → removeDisable + journal + 告警
|
|
377
396
|
* rc8 用户 patch 层热应用:恢复无需重启,watchUserPatches 即时重载。
|
|
397
|
+
* (0.1.2-alpha.1 基线:用户 patch 层热应用机制保留,行为不变。)
|
|
378
398
|
* @module lifeboat/health-api
|
|
379
399
|
*/
|
|
380
400
|
const ROUTE_STATUS = "/dsh-plus/lifeboat/status";
|
|
@@ -439,7 +459,7 @@ function registerHealthApi(ctx, deps) {
|
|
|
439
459
|
}
|
|
440
460
|
const written = await removeDisable(deps.patchFile, body.name);
|
|
441
461
|
deps.journal("quarantine-restore", written ? `${body.name}:已移除用户 patch 层的禁用覆盖(热应用即时恢复)` : `${body.name}:无需恢复(patch 层无禁用覆盖)`);
|
|
442
|
-
deps.alert("[DSH] 插件恢复", `插件 ${body.name} 的隔离已解除(移除 patch 层禁用覆盖)。${written ? "
|
|
462
|
+
deps.alert("[DSH] 插件恢复", `插件 ${body.name} 的隔离已解除(移除 patch 层禁用覆盖)。${written ? "patch 层热应用即时生效,无需重启。" : "patch 层本无覆盖,可能已被手动恢复。"}`);
|
|
443
463
|
sendJson$1(res, 200, {
|
|
444
464
|
ok: true,
|
|
445
465
|
written
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plus/lifeboat",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "dsh-plus service plugin: 故障救生艇——兄弟插件崩溃时自动隔离(写用户 patch 层禁用),llm-pi 缺席时把其配置临时翻译为官方 llm-pi-ai 路由,恢复后自动还原",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -23,29 +23,29 @@
|
|
|
23
23
|
"dsh": {
|
|
24
24
|
"client": {
|
|
25
25
|
"inject": [
|
|
26
|
-
"@deepseek-ai/dsh-client-
|
|
27
|
-
"@deepseek-ai/dsh-client-
|
|
26
|
+
"@deepseek-ai/dsh-client-locale",
|
|
27
|
+
"@deepseek-ai/dsh-client-ui-renderer"
|
|
28
28
|
],
|
|
29
29
|
"platform": "web"
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"yaml": "^2.6.0",
|
|
34
|
-
"@dsh-plus/shared": "0.1.
|
|
34
|
+
"@dsh-plus/shared": "0.1.6"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
38
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.
|
|
39
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
40
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
38
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.2-alpha.1",
|
|
39
|
+
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.1",
|
|
40
|
+
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.1",
|
|
41
41
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@deepseek-ai/cordis": "4.0.1",
|
|
45
|
-
"@deepseek-ai/dsh-home-paths": "0.1.
|
|
46
|
-
"@deepseek-ai/dsh-host-webserver": "0.1.
|
|
47
|
-
"@deepseek-ai/dsh-llm": "0.1.
|
|
48
|
-
"@deepseek-ai/dsh-settings": "0.1.
|
|
45
|
+
"@deepseek-ai/dsh-home-paths": "0.1.2-alpha.1",
|
|
46
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.2-alpha.1",
|
|
47
|
+
"@deepseek-ai/dsh-llm": "0.1.2-alpha.1",
|
|
48
|
+
"@deepseek-ai/dsh-settings": "0.1.2-alpha.1",
|
|
49
49
|
"@deepseek-ai/schemastery": "3.18.1",
|
|
50
50
|
"@types/react": "~18.3.1",
|
|
51
51
|
"react": "^18.2.0",
|
package/src/fallback-llm.ts
CHANGED
|
@@ -35,6 +35,17 @@ export const FALLBACK_SUFFIX = '-fb'
|
|
|
35
35
|
/** 官方配置路径物化的 compat 子集(其余字段降级期丢弃)。 */
|
|
36
36
|
const COMPAT_WHITELIST = ['thinkingFormat', 'supportsReasoningEffort'] as const
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* master 官方 llm-pi-ai 的 COMPAT_GATES(llm-pi-ai/src/catalog.ts):每个 compat
|
|
40
|
+
* 字段按协议门控,写时经 assertOfferedCompatFields 拒绝不适配字段——thinkingFormat /
|
|
41
|
+
* supportsReasoningEffort 仅 openai-completions 提供。翻译按推断 api 过滤,避免
|
|
42
|
+
* 整段 compat 写入被官方校验拒绝。
|
|
43
|
+
*/
|
|
44
|
+
const COMPAT_OFFER_APIS: Readonly<Record<string, ReadonlySet<string>>> = {
|
|
45
|
+
thinkingFormat: new Set(['openai-completions']),
|
|
46
|
+
supportsReasoningEffort: new Set(['openai-completions']),
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
/** 官方 reasoning 词表(ModelThinkingLevel 的保守子集),不在其中的值丢弃。 */
|
|
39
50
|
const REASONING_LEVELS = new Set(['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'])
|
|
40
51
|
|
|
@@ -124,9 +135,15 @@ export function translateProvider(routeKey: string, raw: RawProvider): Translate
|
|
|
124
135
|
if (Array.isArray(raw.defaultInput) && raw.defaultInput.length > 0)
|
|
125
136
|
out.defaultInput = raw.defaultInput
|
|
126
137
|
if (raw.compat !== undefined) {
|
|
138
|
+
const api = inferApi(routeKey, raw)
|
|
127
139
|
const compat: Record<string, unknown> = {}
|
|
128
140
|
for (const key of COMPAT_WHITELIST) {
|
|
129
|
-
|
|
141
|
+
const value = raw.compat[key]
|
|
142
|
+
// 空值同样丢弃:官方写时拒绝 valueless 键(catalog.ts assertOfferedCompatFields)
|
|
143
|
+
if (value === undefined || value === null) continue
|
|
144
|
+
// 按推断 api 门控:仅写入该协议 offer 的字段,其余丢弃(官方写时拒绝不适配字段)
|
|
145
|
+
if (!COMPAT_OFFER_APIS[key]?.has(api)) continue
|
|
146
|
+
compat[key] = value
|
|
130
147
|
}
|
|
131
148
|
if (Object.keys(compat).length > 0) out.compat = compat
|
|
132
149
|
}
|
|
@@ -178,19 +195,12 @@ interface SettingsWrite {
|
|
|
178
195
|
): Promise<void>
|
|
179
196
|
}
|
|
180
197
|
|
|
181
|
-
/** 写 settings 带一次冲突重试(用户同时改配置时不抢,放弃并告警)。
|
|
182
|
-
* 冲突判定双条件:instanceof 覆盖同版本副本;error.code 覆盖跨版本副本
|
|
183
|
-
* (插件自带 rc.7 依赖与 host rc.8 抛错时 instanceof 为 false)。 */
|
|
198
|
+
/** 写 settings 带一次冲突重试(用户同时改配置时不抢,放弃并告警)。 */
|
|
184
199
|
async function writeWithRetry(write: () => Promise<void>): Promise<void> {
|
|
185
200
|
try {
|
|
186
201
|
await write()
|
|
187
202
|
} catch (error) {
|
|
188
|
-
|
|
189
|
-
error instanceof SettingsConflictError ||
|
|
190
|
-
(typeof error === 'object' &&
|
|
191
|
-
error !== null &&
|
|
192
|
-
(error as { code?: unknown }).code === 'SETTINGS_CONFLICT')
|
|
193
|
-
if (!conflict) throw error
|
|
203
|
+
if (!(error instanceof SettingsConflictError)) throw error
|
|
194
204
|
await write()
|
|
195
205
|
}
|
|
196
206
|
}
|
|
@@ -225,8 +235,16 @@ export function installLlmFallback(ctx: Context, deps: FallbackDeps): void {
|
|
|
225
235
|
}
|
|
226
236
|
const translated = translateProviders(providers)
|
|
227
237
|
const fbProvider = `${provider}${FALLBACK_SUFFIX}`
|
|
228
|
-
|
|
229
|
-
|
|
238
|
+
try {
|
|
239
|
+
// 官方 llm-pi-ai 的 installSettingsSection 注册了 validate(assertServiceable),
|
|
240
|
+
// 写时拒绝不适配的翻译结果;update 无 expectedRevision 本就无条件写,不做冲突
|
|
241
|
+
// 重试——失败一律分类为「翻译失败」。
|
|
242
|
+
await writes.update(NS_PI_AI, { providers: translated })
|
|
243
|
+
await writes.update(NS_AGENT_DEFAULT, { provider: fbProvider, model })
|
|
244
|
+
} catch (error) {
|
|
245
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
246
|
+
throw new Error(`翻译失败(官方 llm-pi-ai 拒绝写入翻译后的配置): ${message}`)
|
|
247
|
+
}
|
|
230
248
|
await deps.writeState({
|
|
231
249
|
active: true,
|
|
232
250
|
originalProvider: provider,
|
|
@@ -306,5 +324,9 @@ export function installLlmFallback(ctx: Context, deps: FallbackDeps): void {
|
|
|
306
324
|
ctx.root.on('settings/updated' as never, (ns: unknown) => {
|
|
307
325
|
if (typeof ns === 'string' && WATCHED_NS.has(ns)) run()
|
|
308
326
|
})
|
|
309
|
-
|
|
327
|
+
// 事件名已核对:master 0.1.2-alpha.1 的 dsh-llm 保留 'llm/adapters-updated'
|
|
328
|
+
// (packages/llm/llm/src/types.ts:23 module augmentation,index.ts:344 于每次
|
|
329
|
+
// adapter 注册/注销提交点 dispatch);@deepseek-ai/dsh-llm 的类型导入已携带该
|
|
330
|
+
// 声明,无需 as never。
|
|
331
|
+
ctx.root.on('llm/adapters-updated', run)
|
|
310
332
|
}
|
package/src/health-api.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* - GET /dsh-plus/lifeboat/status → journal + 翻译状态 + 当前用户 patch 层已禁用的 dsh-plus 插件
|
|
4
4
|
* - POST /dsh-plus/lifeboat/restore {name} → removeDisable + journal + 告警
|
|
5
5
|
* rc8 用户 patch 层热应用:恢复无需重启,watchUserPatches 即时重载。
|
|
6
|
+
* (0.1.2-alpha.1 基线:用户 patch 层热应用机制保留,行为不变。)
|
|
6
7
|
* @module lifeboat/health-api
|
|
7
8
|
*/
|
|
8
9
|
import { readFile } from 'node:fs/promises'
|
|
@@ -106,7 +107,7 @@ export function registerHealthApi(ctx: Context, deps: HealthDeps): void {
|
|
|
106
107
|
deps.alert(
|
|
107
108
|
'[DSH] 插件恢复',
|
|
108
109
|
`插件 ${body.name} 的隔离已解除(移除 patch 层禁用覆盖)。${
|
|
109
|
-
written ? '
|
|
110
|
+
written ? 'patch 层热应用即时生效,无需重启。' : 'patch 层本无覆盖,可能已被手动恢复。'
|
|
110
111
|
}`,
|
|
111
112
|
)
|
|
112
113
|
sendJson(res, 200, { ok: true, written })
|