dsh-router-core 0.4.3 → 0.4.4
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/ext-prompt/index.js +2 -2
- package/lib/index.js +37 -10
- package/lib/types/ext-prompt/content.d.ts +10 -3
- package/package.json +1 -1
- package/src/ext-prompt/content.ts +14 -7
- package/src/suppliers/registry.test.ts +56 -0
- package/src/suppliers/registry.ts +40 -10
package/lib/ext-prompt/index.js
CHANGED
|
@@ -29,13 +29,13 @@ const PROMPT_CATEGORIES = [
|
|
|
29
29
|
id: "closure",
|
|
30
30
|
title: "全局收口",
|
|
31
31
|
defaultOn: true,
|
|
32
|
-
body: "收口(
|
|
32
|
+
body: "收口(只在任务完成时做,与跑测试同级):①反查过期:改动让哪些注释/文档/结论/代码引用点(凡以名字对齐的调用方、开关名、注册键)不符事实?当场改或删;②反查重复:新旧成两份就并成一处;③反查形状:该合的合、该归档的归位。做完那块区域该比动手前更整或更少。"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
id: "hardening",
|
|
36
36
|
title: "固化",
|
|
37
37
|
defaultOn: true,
|
|
38
|
-
body: "
|
|
38
|
+
body: "固化:只对数据丢失/安全这类真损失补检查——会变红(注入违规必须失败)、可重放(一条命令)、归属清晰(失败信息指向违规处);普通 bug 修好即止,不额外造闸门。"
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
id: "verdifiable",
|
package/lib/index.js
CHANGED
|
@@ -4009,6 +4009,33 @@ function writeJson$1(res, status, body) {
|
|
|
4009
4009
|
});
|
|
4010
4010
|
res.end(payload);
|
|
4011
4011
|
}
|
|
4012
|
+
/**
|
|
4013
|
+
* 包住会调插件的 handler:插件抛错时回 **JSON 500**,别让异常裸奔到 webServer。
|
|
4014
|
+
*
|
|
4015
|
+
* 为什么必须有这一层:webServer 的兜底是 `res.writeHead(400); res.end()`——**空
|
|
4016
|
+
* body**。面板拿到空 body,`response.json()` 抛 "Unexpected end of JSON input",
|
|
4017
|
+
* 真实原因(上游 500 / token 失效…)全丢,用户只看到一句解析错误。
|
|
4018
|
+
*
|
|
4019
|
+
* 这条路径是插件契约「出错就抛」(docs/suppliers.md「模型统一策略」) 的下游:插件
|
|
4020
|
+
* 老实抛了,核心就得老实接住并说清楚。以前 `listModels` 静默吞错,所以从没暴露过
|
|
4021
|
+
* 这个缺口(traework 2026-10-10 「获取模型只有几个」修完才浮现)。
|
|
4022
|
+
*/
|
|
4023
|
+
function guard(handler) {
|
|
4024
|
+
return async (req, res) => {
|
|
4025
|
+
try {
|
|
4026
|
+
await handler(req, res);
|
|
4027
|
+
} catch (err) {
|
|
4028
|
+
if (res.headersSent) {
|
|
4029
|
+
res.end();
|
|
4030
|
+
return;
|
|
4031
|
+
}
|
|
4032
|
+
writeJson$1(res, err instanceof SyntaxError ? 400 : 500, {
|
|
4033
|
+
ok: false,
|
|
4034
|
+
error: err.message
|
|
4035
|
+
});
|
|
4036
|
+
}
|
|
4037
|
+
};
|
|
4038
|
+
}
|
|
4012
4039
|
async function readBody$1(req, limit = 65536) {
|
|
4013
4040
|
const chunks = [];
|
|
4014
4041
|
let size = 0;
|
|
@@ -4067,14 +4094,14 @@ function supplierRoutes(base, loaded, store, router) {
|
|
|
4067
4094
|
routes.push({
|
|
4068
4095
|
kind: "exact",
|
|
4069
4096
|
path: `${p}/models`,
|
|
4070
|
-
handler: async (_req, res) => {
|
|
4097
|
+
handler: guard(async (_req, res) => {
|
|
4071
4098
|
const models = await router.modelsOf(s.id);
|
|
4072
4099
|
writeJson$1(res, 200, {
|
|
4073
4100
|
ok: true,
|
|
4074
4101
|
alias: s.getAlias(),
|
|
4075
4102
|
models
|
|
4076
4103
|
});
|
|
4077
|
-
}
|
|
4104
|
+
})
|
|
4078
4105
|
});
|
|
4079
4106
|
routes.push({
|
|
4080
4107
|
kind: "exact",
|
|
@@ -4202,7 +4229,7 @@ function supplierRoutes(base, loaded, store, router) {
|
|
|
4202
4229
|
routes.push({
|
|
4203
4230
|
kind: "exact",
|
|
4204
4231
|
path: `${p}/models/toggle`,
|
|
4205
|
-
handler: async (req, res) => {
|
|
4232
|
+
handler: guard(async (req, res) => {
|
|
4206
4233
|
const body = JSON.parse(await readBody$1(req));
|
|
4207
4234
|
store.get(s.id);
|
|
4208
4235
|
const models = await router.modelsOf(s.id);
|
|
@@ -4216,12 +4243,12 @@ function supplierRoutes(base, loaded, store, router) {
|
|
|
4216
4243
|
store.setModelEnabled(s.id, body.id, !!body.enabled);
|
|
4217
4244
|
router.invalidateModels(s.id);
|
|
4218
4245
|
writeJson$1(res, 200, { ok: true });
|
|
4219
|
-
}
|
|
4246
|
+
})
|
|
4220
4247
|
});
|
|
4221
4248
|
routes.push({
|
|
4222
4249
|
kind: "exact",
|
|
4223
4250
|
path: `${p}/models/add`,
|
|
4224
|
-
handler: async (req, res) => {
|
|
4251
|
+
handler: guard(async (req, res) => {
|
|
4225
4252
|
const id = (JSON.parse(await readBody$1(req)).id ?? "").trim();
|
|
4226
4253
|
if (id === "") {
|
|
4227
4254
|
writeJson$1(res, 400, {
|
|
@@ -4240,7 +4267,7 @@ function supplierRoutes(base, loaded, store, router) {
|
|
|
4240
4267
|
store.addCustomModel(s.id, id);
|
|
4241
4268
|
router.invalidateModels(s.id);
|
|
4242
4269
|
writeJson$1(res, 200, { ok: true });
|
|
4243
|
-
}
|
|
4270
|
+
})
|
|
4244
4271
|
});
|
|
4245
4272
|
routes.push({
|
|
4246
4273
|
kind: "exact",
|
|
@@ -4263,13 +4290,13 @@ function supplierRoutes(base, loaded, store, router) {
|
|
|
4263
4290
|
routes.push({
|
|
4264
4291
|
kind: "exact",
|
|
4265
4292
|
path: `${p}/models/bulk`,
|
|
4266
|
-
handler: async (req, res) => {
|
|
4293
|
+
handler: guard(async (req, res) => {
|
|
4267
4294
|
const body = JSON.parse(await readBody$1(req));
|
|
4268
4295
|
const models = await router.modelsOf(s.id);
|
|
4269
4296
|
store.setAllModelsEnabled(s.id, !!body.enabled, models.map((mm) => mm.id));
|
|
4270
4297
|
router.invalidateModels(s.id);
|
|
4271
4298
|
writeJson$1(res, 200, { ok: true });
|
|
4272
|
-
}
|
|
4299
|
+
})
|
|
4273
4300
|
});
|
|
4274
4301
|
routes.push({
|
|
4275
4302
|
kind: "exact",
|
|
@@ -4297,12 +4324,12 @@ function supplierRoutes(base, loaded, store, router) {
|
|
|
4297
4324
|
routes.push({
|
|
4298
4325
|
kind: "exact",
|
|
4299
4326
|
path: `${p}/models/fetch`,
|
|
4300
|
-
handler: async (_req, res) => {
|
|
4327
|
+
handler: guard(async (_req, res) => {
|
|
4301
4328
|
writeJson$1(res, 200, {
|
|
4302
4329
|
ok: true,
|
|
4303
4330
|
models: await router.modelsOf(s.id, true)
|
|
4304
4331
|
});
|
|
4305
|
-
}
|
|
4332
|
+
})
|
|
4306
4333
|
});
|
|
4307
4334
|
routes.push({
|
|
4308
4335
|
kind: "exact",
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 提示词内容层 —— 准则 v5
|
|
2
|
+
* 提示词内容层 —— 准则 v5 的副本,按原分隔拆成分类。
|
|
3
3
|
*
|
|
4
4
|
* ════════════════════════════════════════════════════════════════════
|
|
5
|
-
*
|
|
5
|
+
* 为什么原则上不改措辞
|
|
6
6
|
* ════════════════════════════════════════════════════════════════════
|
|
7
7
|
* 这些字是**用户自己写的准则**,不是本插件的文案。任何"顺手润色"都是擅自
|
|
8
8
|
* 改写用户的规则 —— 改出来的版本和用户心里的版本不一致,而用户看不见差异,
|
|
9
|
-
*
|
|
9
|
+
* 于是准则开始按**另一份**文本执行。所以改动只在**用户点名**时发生,不做
|
|
10
|
+
* 未被要求的措辞优化。
|
|
10
11
|
*
|
|
11
12
|
* 拆分只为「可开关」,不为了重排:顺序与原文出现顺序一致,`order` 也按此递增。
|
|
12
13
|
*
|
|
13
14
|
* ⚠️ **本文件是唯一事实源**。准则原本存放在 Mnemon 记忆系统的 `USER.md`;
|
|
14
15
|
* 那份已经在插件验证生效后删除(否则新旧两份注入,每轮白烧一次 token 且
|
|
15
16
|
* 永远有两份可能漂移的规则)。**改动准则 = 只改这里。**
|
|
17
|
+
*
|
|
18
|
+
* ⚠️ 2026-10-08 用户点名的两处改动(治「提示词导致思考过久」):
|
|
19
|
+
* ① 收口:加"只在任务完成时做",并**删掉第 ④ 条**「前三问都空时处置一件存量」
|
|
20
|
+
* —— 它没有终止条件,每次收口都必然能再找出一件存量,等于无限循环。
|
|
21
|
+
* ② 固化:由"每次用户可见损失都必须固化"降级为"只对数据丢失/安全这类真损失
|
|
22
|
+
* 补检查",并删掉「不固化的教训等于决定再犯一次」这句无界义务。
|
|
16
23
|
*/
|
|
17
24
|
/** 一个可独立开关的提示词分类。 */
|
|
18
25
|
export interface PromptCategory {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-router-core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "DSH plugin: a simplified 9router (AI routing gateway). Adds a 路由系统 (Routing System) sidebar entry beside 记忆系统; the panel shows suppliers, account pool, combos, and endpoint/API keys.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 提示词内容层 —— 准则 v5
|
|
2
|
+
* 提示词内容层 —— 准则 v5 的副本,按原分隔拆成分类。
|
|
3
3
|
*
|
|
4
4
|
* ════════════════════════════════════════════════════════════════════
|
|
5
|
-
*
|
|
5
|
+
* 为什么原则上不改措辞
|
|
6
6
|
* ════════════════════════════════════════════════════════════════════
|
|
7
7
|
* 这些字是**用户自己写的准则**,不是本插件的文案。任何"顺手润色"都是擅自
|
|
8
8
|
* 改写用户的规则 —— 改出来的版本和用户心里的版本不一致,而用户看不见差异,
|
|
9
|
-
*
|
|
9
|
+
* 于是准则开始按**另一份**文本执行。所以改动只在**用户点名**时发生,不做
|
|
10
|
+
* 未被要求的措辞优化。
|
|
10
11
|
*
|
|
11
12
|
* 拆分只为「可开关」,不为了重排:顺序与原文出现顺序一致,`order` 也按此递增。
|
|
12
13
|
*
|
|
13
14
|
* ⚠️ **本文件是唯一事实源**。准则原本存放在 Mnemon 记忆系统的 `USER.md`;
|
|
14
15
|
* 那份已经在插件验证生效后删除(否则新旧两份注入,每轮白烧一次 token 且
|
|
15
16
|
* 永远有两份可能漂移的规则)。**改动准则 = 只改这里。**
|
|
17
|
+
*
|
|
18
|
+
* ⚠️ 2026-10-08 用户点名的两处改动(治「提示词导致思考过久」):
|
|
19
|
+
* ① 收口:加"只在任务完成时做",并**删掉第 ④ 条**「前三问都空时处置一件存量」
|
|
20
|
+
* —— 它没有终止条件,每次收口都必然能再找出一件存量,等于无限循环。
|
|
21
|
+
* ② 固化:由"每次用户可见损失都必须固化"降级为"只对数据丢失/安全这类真损失
|
|
22
|
+
* 补检查",并删掉「不固化的教训等于决定再犯一次」这句无界义务。
|
|
16
23
|
*/
|
|
17
24
|
|
|
18
25
|
/** 一个可独立开关的提示词分类。 */
|
|
@@ -69,8 +76,8 @@ export const PROMPT_CATEGORIES: readonly PromptCategory[] = [
|
|
|
69
76
|
title: '全局收口',
|
|
70
77
|
defaultOn: true,
|
|
71
78
|
body:
|
|
72
|
-
'收口(
|
|
73
|
-
'
|
|
79
|
+
'收口(只在任务完成时做,与跑测试同级):①反查过期:改动让哪些注释/文档/结论/代码引用点(凡以名字对齐的调用方、开关名、注册键)不符事实?当场改或删;' +
|
|
80
|
+
'②反查重复:新旧成两份就并成一处;③反查形状:该合的合、该归档的归位。' +
|
|
74
81
|
'做完那块区域该比动手前更整或更少。',
|
|
75
82
|
},
|
|
76
83
|
{
|
|
@@ -78,8 +85,8 @@ export const PROMPT_CATEGORIES: readonly PromptCategory[] = [
|
|
|
78
85
|
title: '固化',
|
|
79
86
|
defaultOn: true,
|
|
80
87
|
body:
|
|
81
|
-
'
|
|
82
|
-
'归属清晰(失败信息指向违规处)
|
|
88
|
+
'固化:只对数据丢失/安全这类真损失补检查——会变红(注入违规必须失败)、可重放(一条命令)、' +
|
|
89
|
+
'归属清晰(失败信息指向违规处);普通 bug 修好即止,不额外造闸门。',
|
|
83
90
|
},
|
|
84
91
|
{
|
|
85
92
|
id: 'verdifiable',
|
|
@@ -211,3 +211,59 @@ test('刷新:status() 抛错 → 500 带错误信息', async () => {
|
|
|
211
211
|
assert.equal(body.ok, false)
|
|
212
212
|
assert.match(body.error ?? '', /status boom/)
|
|
213
213
|
})
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 插件在 `listModels` 里抛错时,核心必须回 **JSON 500**。
|
|
217
|
+
*
|
|
218
|
+
* 为什么值得单锁:webServer 的兜底是 `res.writeHead(400); res.end()` —— **空
|
|
219
|
+
* body**。面板 `response.json()` 于是抛 "Unexpected end of JSON input",真实原因
|
|
220
|
+
* (上游 500 / token 失效…)全丢。插件契约要求「出错就抛」(docs/suppliers.md),
|
|
221
|
+
* 所以这层必须接住。traework 修掉 `catch {}` 之后这条才第一次真被走到。
|
|
222
|
+
*/
|
|
223
|
+
test('models 路由:插件 listModels 抛错 → 500 + JSON body(不是空 400)', async () => {
|
|
224
|
+
for (const path of ['models', 'models/fetch']) {
|
|
225
|
+
const supplier = {
|
|
226
|
+
id: 'fake',
|
|
227
|
+
name: 'fake',
|
|
228
|
+
status: () => ({ id: 'fake', name: 'fake', accounts: [] }),
|
|
229
|
+
}
|
|
230
|
+
const loaded = { supplier, capabilities: new Set(), source: 'builtin' }
|
|
231
|
+
// router.modelsOf 复刻核心语义:无旧值 → 把插件的错抛出来
|
|
232
|
+
const router = {
|
|
233
|
+
modelsOf: async (): Promise<never> => { throw new Error('models api returned empty list') },
|
|
234
|
+
invalidateModels: () => {},
|
|
235
|
+
}
|
|
236
|
+
const routes = supplierRoutes('/router/api', loaded as never, { get: () => ({ custom: [], disabled: [], poolOrder: [], credits: {} }) } as never, router as never)
|
|
237
|
+
const route = routes.find((r) => r.path === `/router/api/suppliers/fake/${path}`)
|
|
238
|
+
assert.ok(route, `${path} 路由应注册`)
|
|
239
|
+
const sink = fakeRes()
|
|
240
|
+
await route!.handler({} as never, sink.res)
|
|
241
|
+
const body = sink.body as { ok: boolean; error?: string }
|
|
242
|
+
assert.equal(sink.status, 500, `${path}: 抛错该回 500`)
|
|
243
|
+
assert.equal(body.ok, false, `${path}: 必须是 JSON body,不能是空 body`)
|
|
244
|
+
assert.match(String(body.error), /empty list/, `${path}: 真实原因不能丢`)
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
test('models 路由:body 不是合法 JSON → 400(不是 500,也不是空 body)', async () => {
|
|
249
|
+
const supplier = { id: 'fake', name: 'fake', status: () => ({ id: 'fake', name: 'fake', accounts: [] }) }
|
|
250
|
+
const loaded = { supplier, capabilities: new Set(), source: 'builtin' }
|
|
251
|
+
const router = { modelsOf: async () => [], invalidateModels: () => {} }
|
|
252
|
+
const routes = supplierRoutes('/router/api', loaded as never, { get: () => ({ custom: [], disabled: [], poolOrder: [], credits: {} }) } as never, router as never)
|
|
253
|
+
const route = routes.find((r) => r.path === '/router/api/suppliers/fake/models/toggle')
|
|
254
|
+
assert.ok(route)
|
|
255
|
+
// 造一个会吐非法 JSON 的 req(readBody 读 data/end)
|
|
256
|
+
const req = {
|
|
257
|
+
on(event: string, cb: (chunk?: Buffer) => void) {
|
|
258
|
+
if (event === 'data') cb(Buffer.from('{ not json'))
|
|
259
|
+
if (event === 'end') cb()
|
|
260
|
+
return req
|
|
261
|
+
},
|
|
262
|
+
}
|
|
263
|
+
const sink = fakeRes()
|
|
264
|
+
await route!.handler(req as never, sink.res)
|
|
265
|
+
const body = sink.body as { ok: boolean; error?: string }
|
|
266
|
+
assert.equal(sink.status, 400, '客户端传坏 body 是 400,不是核心 500')
|
|
267
|
+
assert.equal(body.ok, false)
|
|
268
|
+
assert.ok(String(body.error).length > 0, '400 也要带原因,不能是空 body')
|
|
269
|
+
})
|
|
@@ -44,6 +44,36 @@ function writeJson(res: ServerResponse, status: number, body: unknown): void {
|
|
|
44
44
|
res.end(payload)
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* 包住会调插件的 handler:插件抛错时回 **JSON 500**,别让异常裸奔到 webServer。
|
|
49
|
+
*
|
|
50
|
+
* 为什么必须有这一层:webServer 的兜底是 `res.writeHead(400); res.end()`——**空
|
|
51
|
+
* body**。面板拿到空 body,`response.json()` 抛 "Unexpected end of JSON input",
|
|
52
|
+
* 真实原因(上游 500 / token 失效…)全丢,用户只看到一句解析错误。
|
|
53
|
+
*
|
|
54
|
+
* 这条路径是插件契约「出错就抛」(docs/suppliers.md「模型统一策略」) 的下游:插件
|
|
55
|
+
* 老实抛了,核心就得老实接住并说清楚。以前 `listModels` 静默吞错,所以从没暴露过
|
|
56
|
+
* 这个缺口(traework 2026-10-10 「获取模型只有几个」修完才浮现)。
|
|
57
|
+
*/
|
|
58
|
+
function guard(handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>) {
|
|
59
|
+
return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {
|
|
60
|
+
try {
|
|
61
|
+
await handler(req, res)
|
|
62
|
+
} catch (err) {
|
|
63
|
+
// headers 已发出就没法再改状态码了(writeJson 是原子的,这里基本不会发生)
|
|
64
|
+
if (res.headersSent) {
|
|
65
|
+
res.end()
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
// body 不是合法 JSON 是客户端的事(400),不该报成核心 500。这些 handler 里
|
|
69
|
+
// 唯一的 SyntaxError 来源就是 `JSON.parse(await readBody(req))`。
|
|
70
|
+
// (改 guard 之前这种情况由 webServer 兜底成**空** 400,改后保住状态码又带上原因。)
|
|
71
|
+
const status = err instanceof SyntaxError ? 400 : 500
|
|
72
|
+
writeJson(res, status, { ok: false, error: (err as Error).message })
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
47
77
|
async function readBody(req: IncomingMessage, limit = 64 << 10): Promise<string> {
|
|
48
78
|
const chunks: Buffer[] = []
|
|
49
79
|
let size = 0
|
|
@@ -109,10 +139,10 @@ export function supplierRoutes(base: string, loaded: LoadedSupplier, store: Supp
|
|
|
109
139
|
routes.push({
|
|
110
140
|
kind: 'exact',
|
|
111
141
|
path: `${p}/models`,
|
|
112
|
-
handler: async (_req, res) => {
|
|
142
|
+
handler: guard(async (_req, res) => {
|
|
113
143
|
const models = await router.modelsOf(s.id)
|
|
114
144
|
writeJson(res, 200, { ok: true, alias: s.getAlias(), models })
|
|
115
|
-
},
|
|
145
|
+
}),
|
|
116
146
|
})
|
|
117
147
|
|
|
118
148
|
// ---- 通用: 供应商开关 ----
|
|
@@ -235,7 +265,7 @@ export function supplierRoutes(base: string, loaded: LoadedSupplier, store: Supp
|
|
|
235
265
|
routes.push({
|
|
236
266
|
kind: 'exact',
|
|
237
267
|
path: `${p}/models/toggle`,
|
|
238
|
-
handler: async (req, res) => {
|
|
268
|
+
handler: guard(async (req, res) => {
|
|
239
269
|
const body = JSON.parse(await readBody(req)) as { id?: string; enabled?: boolean }
|
|
240
270
|
const cfg = store.get(s.id)
|
|
241
271
|
const models = await router.modelsOf(s.id)
|
|
@@ -246,14 +276,14 @@ export function supplierRoutes(base: string, loaded: LoadedSupplier, store: Supp
|
|
|
246
276
|
store.setModelEnabled(s.id, body.id, !!body.enabled)
|
|
247
277
|
router.invalidateModels(s.id)
|
|
248
278
|
writeJson(res, 200, { ok: true })
|
|
249
|
-
},
|
|
279
|
+
}),
|
|
250
280
|
})
|
|
251
281
|
|
|
252
282
|
// ---- 通用: models/add(自定义模型) ----
|
|
253
283
|
routes.push({
|
|
254
284
|
kind: 'exact',
|
|
255
285
|
path: `${p}/models/add`,
|
|
256
|
-
handler: async (req, res) => {
|
|
286
|
+
handler: guard(async (req, res) => {
|
|
257
287
|
const body = JSON.parse(await readBody(req)) as { id?: string }
|
|
258
288
|
const id = (body.id ?? '').trim()
|
|
259
289
|
if (id === '') {
|
|
@@ -268,7 +298,7 @@ export function supplierRoutes(base: string, loaded: LoadedSupplier, store: Supp
|
|
|
268
298
|
store.addCustomModel(s.id, id)
|
|
269
299
|
router.invalidateModels(s.id)
|
|
270
300
|
writeJson(res, 200, { ok: true })
|
|
271
|
-
},
|
|
301
|
+
}),
|
|
272
302
|
})
|
|
273
303
|
|
|
274
304
|
// ---- 通用: models/remove(自定义模型) ----
|
|
@@ -292,13 +322,13 @@ export function supplierRoutes(base: string, loaded: LoadedSupplier, store: Supp
|
|
|
292
322
|
routes.push({
|
|
293
323
|
kind: 'exact',
|
|
294
324
|
path: `${p}/models/bulk`,
|
|
295
|
-
handler: async (req, res) => {
|
|
325
|
+
handler: guard(async (req, res) => {
|
|
296
326
|
const body = JSON.parse(await readBody(req)) as { enabled?: boolean }
|
|
297
327
|
const models = await router.modelsOf(s.id)
|
|
298
328
|
store.setAllModelsEnabled(s.id, !!body.enabled, models.map((mm) => mm.id))
|
|
299
329
|
router.invalidateModels(s.id)
|
|
300
330
|
writeJson(res, 200, { ok: true })
|
|
301
|
-
},
|
|
331
|
+
}),
|
|
302
332
|
})
|
|
303
333
|
|
|
304
334
|
// ---- 通用: pool/order ----
|
|
@@ -324,10 +354,10 @@ export function supplierRoutes(base: string, loaded: LoadedSupplier, store: Supp
|
|
|
324
354
|
routes.push({
|
|
325
355
|
kind: 'exact',
|
|
326
356
|
path: `${p}/models/fetch`,
|
|
327
|
-
handler: async (_req, res) => {
|
|
357
|
+
handler: guard(async (_req, res) => {
|
|
328
358
|
const models = await router.modelsOf(s.id, true)
|
|
329
359
|
writeJson(res, 200, { ok: true, models })
|
|
330
|
-
},
|
|
360
|
+
}),
|
|
331
361
|
})
|
|
332
362
|
|
|
333
363
|
// ---- 通用: 测试模型(核心统一走 chatOnce 路径,账号池回退/冷却自动生效) ----
|