dsh-model-health 0.2.9 → 0.2.10
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 +3 -2
- package/dist/index.js +99 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ dsh --profile web --dump-config | grep dsh-model-health # 配置层含本行
|
|
|
42
42
|
|
|
43
43
|
## 功能 / Features
|
|
44
44
|
|
|
45
|
-
读取 `$DSH_HOME/settings.yaml
|
|
45
|
+
读取 DSH 设置配置(兼容新旧版本:旧版 `$DSH_HOME/settings.yaml`;新版 `0.1.7-alpha.2+` 迁移至 profile 的 `cordis.patch.yml`,插件自动按优先级合并 `settings.yaml` / `settings.yaml.imported` / profile patch),提供三种使用方式:
|
|
46
46
|
|
|
47
47
|
| # | 形式 | 入口 | 说明 |
|
|
48
48
|
|---|------|------|------|
|
|
@@ -67,7 +67,8 @@ dsh --profile web --dump-config | grep dsh-model-health # 配置层含本行
|
|
|
67
67
|
┌─ host 侧 src/index.ts → dist/index.js ─────────────────────────┐
|
|
68
68
|
│ - ctx.tools.register:注册 list_models 工具(Markdown 表格输出) │
|
|
69
69
|
│ - ctx.webServer.register: │
|
|
70
|
-
│ GET /api/model-health/json 读取并解析 settings.yaml
|
|
70
|
+
│ GET /api/model-health/json 读取并解析 DSH 设置(settings.yaml │
|
|
71
|
+
│ 与 profile patch 多来源合并,兼容新旧 DSH 版本)→ JSON │
|
|
71
72
|
│ POST /api/model-health/test 对单模型发最小请求测可用性/延迟 │
|
|
72
73
|
│ (Origin 限本机;200 响应校验 body 为成功应答) │
|
|
73
74
|
│ - 解析 DSH credential service 获取 API Key │
|
package/dist/index.js
CHANGED
|
@@ -1,42 +1,101 @@
|
|
|
1
1
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
2
|
-
import {
|
|
3
|
-
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
4
3
|
import { homedir } from "node:os";
|
|
5
|
-
import {
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { DEFAULT_SCHEMA, Schema, Type, load } from "js-yaml";
|
|
6
6
|
//#region src/host/config.ts
|
|
7
|
+
/** 插件只关心的模型配置 section / entry id。 */
|
|
8
|
+
const MODEL_SECTIONS = /* @__PURE__ */ new Set(["llm-pi-ai", "llm-deepseek"]);
|
|
7
9
|
/** 解析 DSH 配置目录:优先 $DSH_HOME,回退到 ~/.dsh。 */
|
|
8
10
|
function dshHome() {
|
|
9
11
|
return process.env.DSH_HOME || join(homedir(), ".dsh");
|
|
10
12
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
const BaseSchemaTypes = DEFAULT_SCHEMA;
|
|
14
|
+
const LooseSchema = new Schema({
|
|
15
|
+
implicit: BaseSchemaTypes.implicit,
|
|
16
|
+
explicit: [...BaseSchemaTypes.explicit, new Type("tag:yaml.org,2002:js", {
|
|
17
|
+
kind: "scalar",
|
|
18
|
+
resolve: () => true,
|
|
19
|
+
construct: (value) => value
|
|
20
|
+
})]
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* 解析一份设置文件的内容为「模型 section → 配置」映射。
|
|
24
|
+
* 兼容两种文件形态:
|
|
25
|
+
* - 旧版 settings.yaml:顶层 map(llm-pi-ai: {...})
|
|
26
|
+
* - 新版 profile patch:YAML 序列(- id: llm-pi-ai / config: {...})
|
|
27
|
+
*/
|
|
28
|
+
function parseSettingsContent(raw) {
|
|
29
|
+
const doc = load(raw, { schema: LooseSchema });
|
|
30
|
+
if (Array.isArray(doc)) {
|
|
31
|
+
const cfg = {};
|
|
32
|
+
for (const entry of doc) {
|
|
33
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
34
|
+
if (typeof entry.id !== "string" || !MODEL_SECTIONS.has(entry.id)) continue;
|
|
35
|
+
if (entry.config === void 0 || entry.config === null) continue;
|
|
36
|
+
cfg[entry.id] = entry.config;
|
|
37
|
+
}
|
|
38
|
+
return cfg;
|
|
19
39
|
}
|
|
20
|
-
|
|
21
|
-
|
|
40
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return {};
|
|
41
|
+
const cfg = {};
|
|
42
|
+
const record = doc;
|
|
43
|
+
for (const key of MODEL_SECTIONS) if (Object.hasOwn(record, key)) cfg[key] = record[key];
|
|
44
|
+
return cfg;
|
|
22
45
|
}
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
settingsCache = null;
|
|
38
|
-
return readSettings();
|
|
46
|
+
/** 返回当前存在的候选设置文件路径(按优先级排列)。 */
|
|
47
|
+
function settingsPaths(profile) {
|
|
48
|
+
const home = dshHome();
|
|
49
|
+
const out = [];
|
|
50
|
+
for (const name of ["settings.yaml", "settings.yaml.imported"]) {
|
|
51
|
+
const p = join(home, name);
|
|
52
|
+
if (existsSync(p)) out.push(p);
|
|
53
|
+
}
|
|
54
|
+
if (profile) {
|
|
55
|
+
for (const p of [
|
|
56
|
+
join(home, "cordis.patch.yml"),
|
|
57
|
+
join(profile.dir, "cordis.yml"),
|
|
58
|
+
profile.patchPath
|
|
59
|
+
]) if (existsSync(p)) out.push(p);
|
|
39
60
|
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
/** 按文件 mtime 缓存解析结果:同一份文件只解析一次,内容变了自动失效。 */
|
|
64
|
+
const fileCache = /* @__PURE__ */ new Map();
|
|
65
|
+
/** 按给定候选路径列表读取并合并设置(后出现的来源覆盖前面的同名 section)。 */
|
|
66
|
+
function readSettingsFrom(paths) {
|
|
67
|
+
if (paths.length === 0) throw new Error("未找到 DSH 配置文件(已查找:$DSH_HOME/settings.yaml、settings.yaml.imported、profile patch)。请在 设置 → 模型 中配置后重试。");
|
|
68
|
+
const merged = {};
|
|
69
|
+
let lastError = null;
|
|
70
|
+
for (const path of paths) {
|
|
71
|
+
let mtimeMs;
|
|
72
|
+
try {
|
|
73
|
+
mtimeMs = statSync(path).mtimeMs;
|
|
74
|
+
} catch {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
let cached = fileCache.get(path);
|
|
78
|
+
if (!cached || cached.mtimeMs !== mtimeMs) try {
|
|
79
|
+
cached = {
|
|
80
|
+
mtimeMs,
|
|
81
|
+
cfg: parseSettingsContent(readFileSync(path, "utf8"))
|
|
82
|
+
};
|
|
83
|
+
fileCache.set(path, cached);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
lastError = /* @__PURE__ */ new Error(`解析配置失败 (${path}):${e.message}`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
for (const [key, value] of Object.entries(cached.cfg)) merged[key] = value;
|
|
89
|
+
}
|
|
90
|
+
if (lastError && Object.keys(merged).length === 0) throw lastError;
|
|
91
|
+
return merged;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 读取当前生效的 DSH 模型配置。
|
|
95
|
+
* @param profile 新版 DSH 的 profileContext(可空,空时按旧版逻辑只读 settings.yaml)
|
|
96
|
+
*/
|
|
97
|
+
function readSettingsCached(profile) {
|
|
98
|
+
return readSettingsFrom(settingsPaths(profile));
|
|
40
99
|
}
|
|
41
100
|
//#endregion
|
|
42
101
|
//#region src/host/models.ts
|
|
@@ -96,14 +155,14 @@ function collectModels(cfg) {
|
|
|
96
155
|
}
|
|
97
156
|
//#endregion
|
|
98
157
|
//#region src/host/markdown.ts
|
|
99
|
-
function renderMarkdownTable(rows) {
|
|
100
|
-
const
|
|
158
|
+
function renderMarkdownTable(rows, source) {
|
|
159
|
+
const configLabel = source || join(dshHome(), "settings.yaml");
|
|
101
160
|
if (rows.length === 0) return [
|
|
102
|
-
"当前
|
|
161
|
+
"当前 DSH 配置中未配置任何模型。/ No models are configured in the DSH config.",
|
|
103
162
|
"",
|
|
104
163
|
"请在 Web UI 的 设置 → 模型 中添加 DeepSeek API Key 或自定义提供商。",
|
|
105
164
|
"Please add a DeepSeek API key or a custom provider in Web UI → Settings → Models.",
|
|
106
|
-
`配置文件位置 / Config file: ${
|
|
165
|
+
`配置文件位置 / Config file(s): ${configLabel}`
|
|
107
166
|
].join("\n");
|
|
108
167
|
const header = "| # | Provider | Model ID | Name | Context Window | Max Output | Input | API | BaseURL |";
|
|
109
168
|
const sep = "|---|----------|----------|------|----------------|------------|-------|-----|---------|";
|
|
@@ -117,7 +176,7 @@ function renderMarkdownTable(rows) {
|
|
|
117
176
|
sep,
|
|
118
177
|
...lines,
|
|
119
178
|
"",
|
|
120
|
-
`_来源 / Source: ${
|
|
179
|
+
`_来源 / Source: ${configLabel}_`
|
|
121
180
|
].join("\n");
|
|
122
181
|
}
|
|
123
182
|
//#endregion
|
|
@@ -325,9 +384,10 @@ const inject = [
|
|
|
325
384
|
"credentials"
|
|
326
385
|
];
|
|
327
386
|
function apply(ctx) {
|
|
387
|
+
const profile = ctx.get("profileContext");
|
|
328
388
|
ctx.tools.register(defineTool({
|
|
329
389
|
name: "list_models",
|
|
330
|
-
description: "列出当前 DSH
|
|
390
|
+
description: "列出当前 DSH 配置(settings.yaml 或 profile patch)中已配置的所有模型,以 Markdown 表格展示。用于在对话中查看当前可用模型清单。",
|
|
331
391
|
parameters: {},
|
|
332
392
|
output: {
|
|
333
393
|
schema: { type: "string" },
|
|
@@ -337,7 +397,8 @@ function apply(ctx) {
|
|
|
337
397
|
}]
|
|
338
398
|
},
|
|
339
399
|
async execute() {
|
|
340
|
-
|
|
400
|
+
const source = settingsPaths(profile).join(" + ");
|
|
401
|
+
return renderMarkdownTable(collectModels(readSettingsCached(profile)), source);
|
|
341
402
|
}
|
|
342
403
|
}));
|
|
343
404
|
ctx.webServer.register({
|
|
@@ -345,11 +406,11 @@ function apply(ctx) {
|
|
|
345
406
|
path: "/api/model-health/json",
|
|
346
407
|
handler: (_req, res) => {
|
|
347
408
|
try {
|
|
348
|
-
const rows = collectModels(readSettingsCached());
|
|
409
|
+
const rows = collectModels(readSettingsCached(profile));
|
|
349
410
|
sendJson(res, 200, {
|
|
350
411
|
ok: true,
|
|
351
412
|
count: rows.length,
|
|
352
|
-
source: join(
|
|
413
|
+
source: settingsPaths(profile).join(" + "),
|
|
353
414
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
354
415
|
models: rows.map(toPublicRow)
|
|
355
416
|
});
|
package/package.json
CHANGED