dsh-model-health 0.2.18 → 0.2.19
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.md +1 -0
- package/dist/index.js +69 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -53,6 +53,7 @@ dsh --profile web --dump-config | grep dsh-model-health # 配置层含本行
|
|
|
53
53
|
**特性一览:**
|
|
54
54
|
|
|
55
55
|
- 支持 `llm-pi-ai`(多协议自定义提供商)与 `llm-deepseek`(官方)两类配置来源
|
|
56
|
+
- 内置模型显示:未写入配置文件的宿主内置模型(如 DeepSeek 默认目录 deepseek-flash / deepseek-v4-pro)自动从宿主 `llm` 注册表读取并去重合并;此类模型协议与凭据由宿主适配器管理,暂不支持在线探测(标记「跳过」)
|
|
56
57
|
- 可用性测试支持 `openai-completions` 与 `deepseek` 协议,其余协议自动标记「跳过」;对 200 响应校验 body 确实是成功应答(部分网关 200 但 body 是错误对象)
|
|
57
58
|
- API Key 通过 DSH credential service(`ctx.credentials.resolve`)解析,密钥不会输出到浏览器
|
|
58
59
|
- 测试结果(状态/延迟/错误)持久化到 localStorage,刷新页面后仍可见
|
package/dist/index.js
CHANGED
|
@@ -307,7 +307,7 @@ async function probeModel(row, apiKey) {
|
|
|
307
307
|
clearTimeout(timer);
|
|
308
308
|
}
|
|
309
309
|
}
|
|
310
|
-
function createTestRouteHandler(resolveApiKey, profile) {
|
|
310
|
+
function createTestRouteHandler(resolveApiKey, profile, findExtraRow) {
|
|
311
311
|
return async function handler(req, res) {
|
|
312
312
|
if (!isLocalOrigin(req)) {
|
|
313
313
|
sendJson(res, 403, {
|
|
@@ -331,6 +331,15 @@ function createTestRouteHandler(resolveApiKey, profile) {
|
|
|
331
331
|
try {
|
|
332
332
|
const found = collectModels(readSettingsCached(profile)).find((r) => r.key === key);
|
|
333
333
|
if (!found) {
|
|
334
|
+
if (findExtraRow ? await findExtraRow(key).catch(() => void 0) : void 0) {
|
|
335
|
+
sendJson(res, 200, {
|
|
336
|
+
ok: true,
|
|
337
|
+
key,
|
|
338
|
+
status: "skip",
|
|
339
|
+
error: "宿主内置模型:协议与凭据由宿主适配器管理,暂不支持在线探测"
|
|
340
|
+
});
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
334
343
|
sendJson(res, 404, {
|
|
335
344
|
ok: false,
|
|
336
345
|
error: `未找到 key 为 ${key} 的模型(配置可能已变化,请刷新列表)`
|
|
@@ -374,6 +383,47 @@ function createTestRouteHandler(resolveApiKey, profile) {
|
|
|
374
383
|
};
|
|
375
384
|
}
|
|
376
385
|
//#endregion
|
|
386
|
+
//#region src/host/registry.ts
|
|
387
|
+
/** 从宿主 llm 注册表收集全部已注册模型(内置 + 配置驱动)。 */
|
|
388
|
+
async function collectRegistryModels(llm) {
|
|
389
|
+
const providers = llm.listProviders();
|
|
390
|
+
return (await Promise.all(providers.map(async (p) => {
|
|
391
|
+
let models;
|
|
392
|
+
try {
|
|
393
|
+
models = await llm.listModels(p.id);
|
|
394
|
+
} catch {
|
|
395
|
+
return [];
|
|
396
|
+
}
|
|
397
|
+
return Promise.all(models.map(async (m) => {
|
|
398
|
+
let contextWindow = "-";
|
|
399
|
+
let maxTokens = "-";
|
|
400
|
+
if (typeof llm.resolveModelInfo === "function") try {
|
|
401
|
+
const info = await llm.resolveModelInfo(p.id, m.id);
|
|
402
|
+
if (info?.context?.contextWindow) contextWindow = info.context.contextWindow;
|
|
403
|
+
if (info?.defaultMaxTokens) maxTokens = info.defaultMaxTokens;
|
|
404
|
+
} catch {}
|
|
405
|
+
return {
|
|
406
|
+
key: `${p.id}/${m.id}`,
|
|
407
|
+
provider: p.id,
|
|
408
|
+
displayName: p.name || p.id,
|
|
409
|
+
modelId: m.id,
|
|
410
|
+
modelName: m.name || m.id,
|
|
411
|
+
contextWindow,
|
|
412
|
+
maxTokens,
|
|
413
|
+
input: m.inputModalities && m.inputModalities.length > 0 ? m.inputModalities.join("/") : "text",
|
|
414
|
+
api: "registry",
|
|
415
|
+
baseURL: "-",
|
|
416
|
+
apiKeyEnv: ""
|
|
417
|
+
};
|
|
418
|
+
}));
|
|
419
|
+
}))).flat();
|
|
420
|
+
}
|
|
421
|
+
/** 去掉注册表中与文件配置重复的模型(同一 provider 下的同一 modelId)。 */
|
|
422
|
+
function dedupeRegistryRows(registryRows, fileRows) {
|
|
423
|
+
const seen = new Set(fileRows.map((r) => `${r.provider}\u0000${r.modelId}`));
|
|
424
|
+
return registryRows.filter((r) => !seen.has(`${r.provider}\u0000${r.modelId}`));
|
|
425
|
+
}
|
|
426
|
+
//#endregion
|
|
377
427
|
//#region src/index.ts
|
|
378
428
|
const name = "dsh-model-health";
|
|
379
429
|
const inject = [
|
|
@@ -383,6 +433,18 @@ const inject = [
|
|
|
383
433
|
];
|
|
384
434
|
function apply(ctx) {
|
|
385
435
|
const profile = ctx.get("profileContext");
|
|
436
|
+
const llm = ctx.get("llm");
|
|
437
|
+
/** 配置文件模型 + 注册表补充模型(内置等文件里没有的条目)。 */
|
|
438
|
+
const collectAllRows = async () => {
|
|
439
|
+
const fileRows = collectModels(readSettingsCached(profile));
|
|
440
|
+
if (!llm) return fileRows;
|
|
441
|
+
try {
|
|
442
|
+
const extra = dedupeRegistryRows(await collectRegistryModels(llm), fileRows);
|
|
443
|
+
return [...fileRows, ...extra];
|
|
444
|
+
} catch {
|
|
445
|
+
return fileRows;
|
|
446
|
+
}
|
|
447
|
+
};
|
|
386
448
|
ctx.tools.register(defineTool({
|
|
387
449
|
name: "list_models",
|
|
388
450
|
description: "列出当前 DSH 配置(settings.yaml 或 profile patch)中已配置的所有模型,以 Markdown 表格展示。用于在对话中查看当前可用模型清单。",
|
|
@@ -396,15 +458,15 @@ function apply(ctx) {
|
|
|
396
458
|
},
|
|
397
459
|
async execute() {
|
|
398
460
|
const source = settingsPaths(profile).join(" + ");
|
|
399
|
-
return renderMarkdownTable(
|
|
461
|
+
return renderMarkdownTable(await collectAllRows(), source);
|
|
400
462
|
}
|
|
401
463
|
}));
|
|
402
464
|
ctx.webServer.register({
|
|
403
465
|
kind: "exact",
|
|
404
466
|
path: "/api/model-health/json",
|
|
405
|
-
handler: (_req, res) => {
|
|
467
|
+
handler: async (_req, res) => {
|
|
406
468
|
try {
|
|
407
|
-
const rows =
|
|
469
|
+
const rows = await collectAllRows();
|
|
408
470
|
sendJson(res, 200, {
|
|
409
471
|
ok: true,
|
|
410
472
|
count: rows.length,
|
|
@@ -429,7 +491,9 @@ function apply(ctx) {
|
|
|
429
491
|
} catch {
|
|
430
492
|
return;
|
|
431
493
|
}
|
|
432
|
-
}, profile)
|
|
494
|
+
}, profile, llm ? async (key) => {
|
|
495
|
+
return (await collectRegistryModels(llm)).find((r) => r.key === key);
|
|
496
|
+
} : void 0)
|
|
433
497
|
});
|
|
434
498
|
console.log(`[dsh-model-health] ready — tool "list_models" + routes GET /api/model-health/json, POST /api/model-health/test`);
|
|
435
499
|
}
|
package/package.json
CHANGED