@dsh-plugins/dsh-llm-hub 1.8.0 → 1.9.1
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/CHANGELOG.md +35 -0
- package/README.en.md +6 -2
- package/README.md +11 -6
- package/lib/client.js +197 -709
- package/lib/index.js +34 -4
- package/package.json +3 -3
- package/scripts/gate-lib.mjs +38 -0
- package/scripts/prepublish-gate.mjs +2 -2
package/lib/index.js
CHANGED
|
@@ -1049,13 +1049,13 @@ export function apply(ctx, config) {
|
|
|
1049
1049
|
try {
|
|
1050
1050
|
const raw = await getJson('/user/balance', { apiKey })
|
|
1051
1051
|
const shaped = shapeBalance(raw)
|
|
1052
|
-
const
|
|
1052
|
+
const activeBalance = shaped.balances?.find((b) => Number(b.total) > 0) ?? shaped.balances?.[0]
|
|
1053
1053
|
return {
|
|
1054
1054
|
ref,
|
|
1055
1055
|
keyMasked: maskKey(apiKey),
|
|
1056
1056
|
isAvailable: shaped.isAvailable,
|
|
1057
|
-
total:
|
|
1058
|
-
currency:
|
|
1057
|
+
total: activeBalance?.total ?? '0.00',
|
|
1058
|
+
currency: activeBalance?.currency ?? 'CNY',
|
|
1059
1059
|
ok: true
|
|
1060
1060
|
}
|
|
1061
1061
|
} catch (e) {
|
|
@@ -1873,7 +1873,25 @@ export function apply(ctx, config) {
|
|
|
1873
1873
|
}
|
|
1874
1874
|
// 已经被新一轮取代:这份结论过期了,丢掉。
|
|
1875
1875
|
if (generation !== probeGeneration) return
|
|
1876
|
-
|
|
1876
|
+
let modelIds = []
|
|
1877
|
+
if (target.ns === NS) {
|
|
1878
|
+
const deepseek = sectionOf()
|
|
1879
|
+
const models = Array.isArray(deepseek?.models) ? deepseek.models : []
|
|
1880
|
+
modelIds = models.map((m) => (m !== null && typeof m === 'object' ? m.id : m)).filter((id) => typeof id === 'string')
|
|
1881
|
+
} else {
|
|
1882
|
+
const profile = piaiProfileOf(target.id)
|
|
1883
|
+
const models = Array.isArray(profile?.models) ? profile.models : []
|
|
1884
|
+
modelIds = models.map((m) => (m !== null && typeof m === 'object' ? m.id : m)).filter((id) => typeof id === 'string')
|
|
1885
|
+
}
|
|
1886
|
+
baseVerdicts.set(target.id, {
|
|
1887
|
+
provider: target.id,
|
|
1888
|
+
displayName: target.displayName,
|
|
1889
|
+
ns: target.ns,
|
|
1890
|
+
checkedAt: Date.now(),
|
|
1891
|
+
modelCount: modelIds.length,
|
|
1892
|
+
modelIds,
|
|
1893
|
+
...outcome
|
|
1894
|
+
})
|
|
1877
1895
|
}))
|
|
1878
1896
|
if (generation !== probeGeneration) return
|
|
1879
1897
|
// 清掉真正从路由表里消失的 provider。判据必须是**原始路由表**,不是本轮的
|
|
@@ -3197,6 +3215,18 @@ export function apply(ctx, config) {
|
|
|
3197
3215
|
|
|
3198
3216
|
// ── harness 子代理 ────────────────────────────────────────────────────────
|
|
3199
3217
|
|
|
3218
|
+
// 把本机装好的外部 agent CLI(codex / claude / agy)注册成子代理提供方,详见
|
|
3219
|
+
// `./harness.js`。
|
|
3220
|
+
//
|
|
3221
|
+
// 为什么用**局部注入**而不是加进模块顶部的 `inject` 数组:`inject` 是硬依赖,
|
|
3222
|
+
// 少一个服务整个插件就不加载 —— 那会让「宿主没装子代理栈」的部署连余额和模型
|
|
3223
|
+
// 发现一起失去。局部注入只让 harness 这一段跟着服务走,缺了就不启用。
|
|
3224
|
+
ctx.inject(['subagents', 'subprocess'], (scope) => {
|
|
3225
|
+
applyHarness(scope).catch((error) => {
|
|
3226
|
+
scope.logger?.warn?.(`dsh-llm-hub: harness 探测失败:${error?.message ?? error}`)
|
|
3227
|
+
})
|
|
3228
|
+
})
|
|
3229
|
+
|
|
3200
3230
|
// 自检路由:`GET /api/dsh-llm-hub/harness` 报告探到了什么、注册了几个。
|
|
3201
3231
|
//
|
|
3202
3232
|
// 为什么需要它:宿主的 `info` 级日志**不落盘**(实测 launchd.log 里只有
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plugins/dsh-llm-hub",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "DSH 模型管理层:网关可达性探测、模型目录拉取、DeepSeek + MiniMax/智谱/Moonshot/StepFun 余额与配额、本月用量统计、余额预警、Provider 健康看板,再把本机装好的 codex / claude / agy 注册成子代理(装了才出现)。零运行时依赖,不改动 DSH 安装。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
|
-
"deploy": "rsync -av --delete lib/ ~/.dsh/profiles/
|
|
32
|
-
"preview": "npm run deploy && open
|
|
31
|
+
"deploy": "rsync -av --delete lib/ ~/.dsh/profiles/${DSH_PROFILE:-desktop}/node_modules/@dsh-plugins/dsh-llm-hub/lib/ && rsync -av types package.json cordis.patch.yml README.md README.en.md LICENSE ~/.dsh/profiles/${DSH_PROFILE:-desktop}/node_modules/@dsh-plugins/dsh-llm-hub/",
|
|
32
|
+
"preview": "npm run deploy && echo '部署完成 —— 到桌面端应用里重载页面(它不是浏览器,没有可 open 的 URL)'",
|
|
33
33
|
"check": "for f in lib/*.js; do node --check \"$f\" || exit 1; done",
|
|
34
34
|
"test": "node --test",
|
|
35
35
|
"prepublishOnly": "npm run check && npm test && node scripts/prepublish-gate.mjs && node scripts/readme-gate.mjs"
|
package/scripts/gate-lib.mjs
CHANGED
|
@@ -86,3 +86,41 @@ export function detectChangedRuntime(prevRoot, curRoot, curFiles) {
|
|
|
86
86
|
}
|
|
87
87
|
return changed
|
|
88
88
|
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 取 `npm pack --json` 的那一条结果 —— 两种 npm 大版本形状都认。
|
|
92
|
+
*
|
|
93
|
+
* **npm 11 → 12 的破坏性变更**:11 回数组 `[{ files: [...] }]`,
|
|
94
|
+
* 12 回以包名为键的对象 `{ "<name>": { files: [...] } }`。
|
|
95
|
+
* 原先写死 `JSON.parse(out)[0].files`,在 npm 12 上 `[0]` 是 undefined,
|
|
96
|
+
* 报出来的是 `Cannot read properties of undefined (reading 'files')`
|
|
97
|
+
* —— 从这句话完全看不出跟 npm 版本有任何关系。
|
|
98
|
+
*
|
|
99
|
+
* 2026-10-02 同一天咬了两次(museav-cli v3.9.2、museav-mcp 2.2.0):各仓的
|
|
100
|
+
* publish workflow 里写着 `npm install -g npm@latest`,于是 npm 12 一发版,
|
|
101
|
+
* **所有仓的 CI 发版同时挂掉**,而本地(还是 npm 11)怎么跑都是绿的。
|
|
102
|
+
*
|
|
103
|
+
* 抽进 lib 是为了能测(门禁脚本顶层会跑 pack/连 registry,import 不进来)。
|
|
104
|
+
* 形状认不出时**抛一句能看懂的话** —— 这正是今天最缺的东西:当时那句
|
|
105
|
+
* `Cannot read properties of undefined` 让人以为是代码问题,实际是上游换了形状。
|
|
106
|
+
*/
|
|
107
|
+
export function packEntry(parsed) {
|
|
108
|
+
// 形状先记下来再判空:报错里要能看出 npm 回了什么,而不是只说「认不出」。
|
|
109
|
+
const shape = Array.isArray(parsed) ? 'array' : parsed === null ? 'null' : typeof parsed
|
|
110
|
+
// 先判空再 Object.values —— 否则 parsed 为 null/undefined 时
|
|
111
|
+
// Object.values 先抛「Cannot convert undefined or null to object」,
|
|
112
|
+
// 又回到那个「看不出是 npm 换了形状」的老问题上。
|
|
113
|
+
let entry
|
|
114
|
+
if (Array.isArray(parsed)) {
|
|
115
|
+
entry = parsed[0]
|
|
116
|
+
} else if (parsed && typeof parsed === 'object') {
|
|
117
|
+
entry = Object.values(parsed)[0]
|
|
118
|
+
}
|
|
119
|
+
if (!entry || !Array.isArray(entry.files)) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`npm pack --json 的返回形状认不出(顶层是 ${shape})` +
|
|
122
|
+
`,npm 可能又换了形状 —— 见 gate-lib.mjs 的 packEntry 注释`,
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
return entry
|
|
126
|
+
}
|
|
@@ -31,7 +31,7 @@ import { dirname, join, relative } from 'node:path'
|
|
|
31
31
|
import { fileURLToPath } from 'node:url'
|
|
32
32
|
import { tmpdir } from 'node:os'
|
|
33
33
|
import { createHash } from 'node:crypto'
|
|
34
|
-
import { EXCLUDE, isRuntime, collectAllFiles, detectChangedRuntime } from './gate-lib.mjs'
|
|
34
|
+
import { EXCLUDE, isRuntime, collectAllFiles, detectChangedRuntime, packEntry } from './gate-lib.mjs'
|
|
35
35
|
|
|
36
36
|
const GATE_VERSION = 1
|
|
37
37
|
|
|
@@ -48,7 +48,7 @@ try {
|
|
|
48
48
|
const out = execFileSync('npm', ['pack', '--dry-run', '--json'], {
|
|
49
49
|
cwd: pkgRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit']
|
|
50
50
|
})
|
|
51
|
-
packFiles = JSON.parse(out)
|
|
51
|
+
packFiles = packEntry(JSON.parse(out)).files.map(f => f.path)
|
|
52
52
|
} catch (err) {
|
|
53
53
|
console.error('[prepublish-gate] npm pack --dry-run 失败:', err.message)
|
|
54
54
|
process.exit(2)
|