@dsh-plugins/dsh-llm-hub 1.3.1 → 1.3.2
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 +12 -0
- package/lib/client.js +153 -4
- package/lib/index.js +30 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
本项目遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/)。
|
|
4
4
|
|
|
5
|
+
## [1.3.2] - 2026-09-24
|
|
6
|
+
|
|
7
|
+
### 🐛 修复与健壮性提升
|
|
8
|
+
|
|
9
|
+
- **⚡ 解决 MiniMax 等大模型临时限额导致下拉永久死锁问题**:
|
|
10
|
+
- 核心逻辑解耦:重构可用性过滤判定,严格区分「致命凭据故障(`CREDENTIAL_MISSING` / `AUTH_REJECTED`)」与「临时配额/余额状态(`QUOTA_EXHAUSTED` / `BALANCE_EMPTY` / `PAYMENT_REQUIRED`)」;
|
|
11
|
+
- 临时配额用尽(如 MiniMax 5 小时滚动配额 / 周配额达到 100% 用量)仅在设置页与状态卡片进行友好 warning 预警,**不再从 `llm.listProviders()` 下拉菜单中强行抹除**;
|
|
12
|
+
- 彻底破除「额度用尽 → 下拉消失 → 无法选择该模型发请求 → 运行期遥测恢复机制永久无法触发」的死锁链条;
|
|
13
|
+
- **🔄 后台主动保鲜与自愈机制(Auto-healing Probe)**:
|
|
14
|
+
- 在插件生命周期内新增守护探针轮询(`TTL = 5m`),结合 `.unref()` 保证无副作用后台运行;
|
|
15
|
+
- 当 MiniMax 5 小时重置窗口或余额恢复后,后台探针自动感知并重置为 `available`,无需手动重启或进入设置页刷新。
|
|
16
|
+
|
|
5
17
|
## [1.3.1] - 2026-09-23
|
|
6
18
|
|
|
7
19
|
### ✨ UI 视觉升级与极客装配 Hub
|
package/lib/client.js
CHANGED
|
@@ -187,7 +187,12 @@ window.__ModuleLoader__.load({
|
|
|
187
187
|
usageClear: '清空记录',
|
|
188
188
|
usageCleared: '已清空',
|
|
189
189
|
usageClearConfirm: '确认清空?',
|
|
190
|
-
usageNoData: '本月还没有记录'
|
|
190
|
+
usageNoData: '本月还没有记录',
|
|
191
|
+
suiteTitle: 'Webkubor DSH 扩展家族',
|
|
192
|
+
suiteDesc: '一键构建完整智能体开发环境',
|
|
193
|
+
suiteActive: '已激活',
|
|
194
|
+
suiteCopyInstall: '复制安装',
|
|
195
|
+
suiteCopied: '已复制命令'
|
|
191
196
|
},
|
|
192
197
|
en: {
|
|
193
198
|
balance: 'Balance',
|
|
@@ -297,7 +302,12 @@ window.__ModuleLoader__.load({
|
|
|
297
302
|
usageClear: 'Clear records',
|
|
298
303
|
usageCleared: 'Cleared',
|
|
299
304
|
usageClearConfirm: 'Confirm clear?',
|
|
300
|
-
usageNoData: 'no calls this month yet'
|
|
305
|
+
usageNoData: 'no calls this month yet',
|
|
306
|
+
suiteTitle: 'Webkubor DSH Plugin Suite',
|
|
307
|
+
suiteDesc: 'One-click full agent development environment',
|
|
308
|
+
suiteActive: 'Active',
|
|
309
|
+
suiteCopyInstall: 'Copy install',
|
|
310
|
+
suiteCopied: 'Command copied'
|
|
301
311
|
}
|
|
302
312
|
}
|
|
303
313
|
|
|
@@ -537,6 +547,25 @@ window.__ModuleLoader__.load({
|
|
|
537
547
|
'.dsh-llm-hub-latency-badge.is-fast{background:rgba(16,185,129,.08);color:var(--dsw-alias-state-success-primary,#10b981);border:.5px solid rgba(16,185,129,.2)}',
|
|
538
548
|
'.dsh-llm-hub-latency-badge.is-normal{background:rgba(234,179,8,.08);color:var(--dsw-alias-state-warn-primary,#eab308);border:.5px solid rgba(234,179,8,.2)}',
|
|
539
549
|
'.dsh-llm-hub-latency-badge.is-slow{background:rgba(239,68,68,.08);color:var(--dsw-alias-state-error-primary,#ef4444);border:.5px solid rgba(239,68,68,.2)}',
|
|
550
|
+
// Webkubor 插件全家桶互导矩阵
|
|
551
|
+
'.dsh-suite-dock{margin-top:14px;padding:14px 16px;background:var(--dsw-alias-bg-layer-2,rgba(255,255,255,0.03));border:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,0.08));border-radius:10px;backdrop-filter:blur(10px)}',
|
|
552
|
+
'.dsh-suite-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;flex-wrap:wrap;gap:8px}',
|
|
553
|
+
'.dsh-suite-title{font-size:12px;font-weight:650;color:var(--dsw-alias-label-primary);margin:0;display:flex;align-items:center;gap:6px}',
|
|
554
|
+
'.dsh-suite-desc{font-size:10.5px;color:var(--dsw-alias-label-tertiary);letter-spacing:.2px}',
|
|
555
|
+
'.dsh-suite-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px}',
|
|
556
|
+
'.dsh-suite-card{display:flex;flex-direction:column;justify-content:space-between;padding:10px 12px;border-radius:8px;background:var(--dsw-alias-bg-layer-3,rgba(255,255,255,0.02));border:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,0.05));transition:all .2s cubic-bezier(0.16,1,0.3,1)}',
|
|
557
|
+
'.dsh-suite-card:hover{transform:translateY(-1px);border-color:var(--dsw-alias-brand-primary,#3b82f6);box-shadow:0 4px 14px rgba(0,0,0,0.15)}',
|
|
558
|
+
'.dsh-suite-card-top{display:flex;align-items:center;gap:6px;margin-bottom:4px}',
|
|
559
|
+
'.dsh-suite-card-icon{font-size:15px;line-height:1}',
|
|
560
|
+
'.dsh-suite-card-name{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary);text-decoration:none}',
|
|
561
|
+
'.dsh-suite-card-desc{font-size:10.5px;color:var(--dsw-alias-label-secondary);line-height:1.4;margin-bottom:8px;flex:1}',
|
|
562
|
+
'.dsh-suite-card-bottom{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-top:auto;font-size:10.5px}',
|
|
563
|
+
'.dsh-suite-badge-active{display:inline-flex;align-items:center;gap:4px;color:var(--dsw-alias-state-success-primary,#10b981);font-weight:600;font-size:10.5px}',
|
|
564
|
+
'.dsh-suite-badge-active-dot{width:5px;height:5px;border-radius:50%;background:#10b981;box-shadow:0 0 6px rgba(16,185,129,0.6)}',
|
|
565
|
+
'.dsh-suite-btn-action{cursor:pointer;padding:2px 8px;border-radius:4px;font-size:10px;font-weight:550;background:var(--dsw-alias-interactive-bg-subtle,rgba(255,255,255,0.06));border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);transition:all .15s;text-decoration:none;display:inline-flex;align-items:center;gap:3px}',
|
|
566
|
+
'.dsh-suite-btn-action:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,0.12));border-color:var(--dsw-alias-border-l1)}',
|
|
567
|
+
'.dsh-suite-link{color:var(--dsw-alias-label-tertiary);text-decoration:none;font-size:10px;display:inline-flex;align-items:center}',
|
|
568
|
+
'.dsh-suite-link:hover{color:var(--dsw-alias-label-primary)}'
|
|
540
569
|
].join('')
|
|
541
570
|
document.head.appendChild(style)
|
|
542
571
|
}
|
|
@@ -659,7 +688,9 @@ window.__ModuleLoader__.load({
|
|
|
659
688
|
const verdict = Array.isArray(availabilityState.providers)
|
|
660
689
|
? availabilityState.providers.find((entry) => entry.provider === DEEPSEEK_PROVIDER)
|
|
661
690
|
: undefined
|
|
662
|
-
const hiddenFromDropdown =
|
|
691
|
+
const hiddenFromDropdown = Array.isArray(availabilityState.hidden)
|
|
692
|
+
? availabilityState.hidden.includes(DEEPSEEK_PROVIDER)
|
|
693
|
+
: (verdict !== undefined && verdict.state === 'unavailable')
|
|
663
694
|
|
|
664
695
|
const load = React.useCallback(() => {
|
|
665
696
|
if (!keyConfigured) {
|
|
@@ -881,7 +912,9 @@ window.__ModuleLoader__.load({
|
|
|
881
912
|
const verdict = Array.isArray(availabilityState.providers)
|
|
882
913
|
? availabilityState.providers.find((entry) => entry.provider === provider)
|
|
883
914
|
: undefined
|
|
884
|
-
const hiddenFromDropdown =
|
|
915
|
+
const hiddenFromDropdown = Array.isArray(availabilityState.hidden)
|
|
916
|
+
? availabilityState.hidden.includes(provider)
|
|
917
|
+
: (verdict !== undefined && verdict.state === 'unavailable')
|
|
885
918
|
|
|
886
919
|
React.useEffect(() => {
|
|
887
920
|
if (provider === '' || status === null || status.ok !== true || !status.balanceAdapter) return undefined
|
|
@@ -1632,6 +1665,115 @@ window.__ModuleLoader__.load({
|
|
|
1632
1665
|
links)
|
|
1633
1666
|
}
|
|
1634
1667
|
|
|
1668
|
+
/**
|
|
1669
|
+
* Webkubor DSH 扩展家族互导 Dock
|
|
1670
|
+
*/
|
|
1671
|
+
function WebkuborSuiteDock(props) {
|
|
1672
|
+
const t = typeof props.t === 'function' ? props.t : fallbackT
|
|
1673
|
+
const [copiedPkg, setCopiedPkg] = React.useState(null)
|
|
1674
|
+
|
|
1675
|
+
ensureStyle()
|
|
1676
|
+
|
|
1677
|
+
const suite = [
|
|
1678
|
+
{
|
|
1679
|
+
id: '@dsh-plugins/dsh-bloom-theme',
|
|
1680
|
+
aliases: ['dsh-bloom-theme', '@dsh-plugins/dsh-bloom-theme'],
|
|
1681
|
+
name: 'Bloom Theme',
|
|
1682
|
+
icon: '🌸',
|
|
1683
|
+
desc: '落霞 Bloom 极客质感毛玻璃主题与代码高亮',
|
|
1684
|
+
repo: 'https://github.com/webkubor/dsh-bloom-theme',
|
|
1685
|
+
pkg: '@dsh-plugins/dsh-bloom-theme'
|
|
1686
|
+
},
|
|
1687
|
+
{
|
|
1688
|
+
id: '@dsh-plugins/dsh-llm-hub',
|
|
1689
|
+
aliases: ['dsh-llm-hub', '@dsh-plugins/dsh-llm-hub'],
|
|
1690
|
+
name: 'LLM Hub',
|
|
1691
|
+
icon: '🎛️',
|
|
1692
|
+
desc: '多模型网关、余额配额、多Key轮换与极客大屏',
|
|
1693
|
+
repo: 'https://github.com/webkubor/dsh-llm-hub',
|
|
1694
|
+
pkg: '@dsh-plugins/dsh-llm-hub',
|
|
1695
|
+
isCurrent: true
|
|
1696
|
+
},
|
|
1697
|
+
{
|
|
1698
|
+
id: '@dsh-plugins/dsh-user-mirror',
|
|
1699
|
+
aliases: ['dsh-user-mirror', 'dsh-mirror', '@dsh-plugins/dsh-user-mirror'],
|
|
1700
|
+
name: 'User Mirror',
|
|
1701
|
+
icon: '🪞',
|
|
1702
|
+
desc: '用户角色数字画像、习惯偏好与记忆网络',
|
|
1703
|
+
repo: 'https://github.com/webkubor/dsh-mirror',
|
|
1704
|
+
pkg: '@dsh-plugins/dsh-user-mirror'
|
|
1705
|
+
},
|
|
1706
|
+
{
|
|
1707
|
+
id: '@dsh-plugins/dsh-env-inspector',
|
|
1708
|
+
aliases: ['dsh-env-inspector', '@dsh-plugins/dsh-env-inspector'],
|
|
1709
|
+
name: 'Env Inspector',
|
|
1710
|
+
icon: '🖥️',
|
|
1711
|
+
desc: '端口监听释放、CLI工具链与环境大屏',
|
|
1712
|
+
repo: 'https://github.com/webkubor/dsh-env-inspector',
|
|
1713
|
+
pkg: '@dsh-plugins/dsh-env-inspector'
|
|
1714
|
+
}
|
|
1715
|
+
]
|
|
1716
|
+
|
|
1717
|
+
const handleCopy = (pkg) => {
|
|
1718
|
+
const cmd = `dsh plugin install ${pkg}`
|
|
1719
|
+
if (navigator.clipboard) {
|
|
1720
|
+
navigator.clipboard.writeText(cmd)
|
|
1721
|
+
}
|
|
1722
|
+
setCopiedPkg(pkg)
|
|
1723
|
+
setTimeout(() => setCopiedPkg(null), 2000)
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
return h('div', { className: 'dsh-suite-dock' },
|
|
1727
|
+
h('div', { className: 'dsh-suite-head' },
|
|
1728
|
+
h('h3', { className: 'dsh-suite-title' },
|
|
1729
|
+
h('span', null, '🌟'),
|
|
1730
|
+
t('suiteTitle')
|
|
1731
|
+
),
|
|
1732
|
+
h('span', { className: 'dsh-suite-desc' }, t('suiteDesc'))
|
|
1733
|
+
),
|
|
1734
|
+
h('div', { className: 'dsh-suite-cards' },
|
|
1735
|
+
...suite.map((item) => {
|
|
1736
|
+
const isInstalled = item.isCurrent
|
|
1737
|
+
const isCopied = copiedPkg === item.pkg
|
|
1738
|
+
|
|
1739
|
+
return h('div', { key: item.id, className: 'dsh-suite-card' },
|
|
1740
|
+
h('div', { className: 'dsh-suite-card-top' },
|
|
1741
|
+
h('span', { className: 'dsh-suite-card-icon' }, item.icon),
|
|
1742
|
+
h('a', {
|
|
1743
|
+
href: item.repo,
|
|
1744
|
+
target: '_blank',
|
|
1745
|
+
rel: 'noopener noreferrer',
|
|
1746
|
+
className: 'dsh-suite-card-name'
|
|
1747
|
+
}, item.name)
|
|
1748
|
+
),
|
|
1749
|
+
h('div', { className: 'dsh-suite-card-desc' }, item.desc),
|
|
1750
|
+
h('div', { className: 'dsh-suite-card-bottom' },
|
|
1751
|
+
isInstalled ? (
|
|
1752
|
+
h('span', { className: 'dsh-suite-badge-active' },
|
|
1753
|
+
h('span', { className: 'dsh-suite-badge-active-dot' }),
|
|
1754
|
+
t('suiteActive')
|
|
1755
|
+
)
|
|
1756
|
+
) : (
|
|
1757
|
+
h('button', {
|
|
1758
|
+
type: 'button',
|
|
1759
|
+
className: 'dsh-suite-btn-action',
|
|
1760
|
+
onClick: () => handleCopy(item.pkg),
|
|
1761
|
+
title: `安装命令: dsh plugin install ${item.pkg}`
|
|
1762
|
+
}, isCopied ? `✓ ${t('suiteCopied')}` : `⚡ ${t('suiteCopyInstall')}`)
|
|
1763
|
+
),
|
|
1764
|
+
h('a', {
|
|
1765
|
+
href: item.repo,
|
|
1766
|
+
target: '_blank',
|
|
1767
|
+
rel: 'noopener noreferrer',
|
|
1768
|
+
className: 'dsh-suite-link'
|
|
1769
|
+
}, 'GitHub ↗')
|
|
1770
|
+
)
|
|
1771
|
+
)
|
|
1772
|
+
})
|
|
1773
|
+
)
|
|
1774
|
+
)
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1635
1777
|
/**
|
|
1636
1778
|
* 可用性判定缓存 —— 所有会话共用一份(同一个 host、同一份设置)。
|
|
1637
1779
|
*
|
|
@@ -1844,6 +1986,13 @@ window.__ModuleLoader__.load({
|
|
|
1844
1986
|
locale: NS,
|
|
1845
1987
|
inject: () => ({ t, availability })
|
|
1846
1988
|
}, HubFooter))
|
|
1989
|
+
ctx.slots.inject('settings.models.footer', () => ctx.slots.register({
|
|
1990
|
+
name: 'settings.models.footer',
|
|
1991
|
+
id: 'dsh-llm-hub-suite',
|
|
1992
|
+
order: 110,
|
|
1993
|
+
locale: NS,
|
|
1994
|
+
inject: () => ({ t })
|
|
1995
|
+
}, WebkuborSuiteDock))
|
|
1847
1996
|
}
|
|
1848
1997
|
|
|
1849
1998
|
exports.apply = apply
|
package/lib/index.js
CHANGED
|
@@ -1490,11 +1490,29 @@ export function apply(ctx) {
|
|
|
1490
1490
|
return [...merged.values()].sort((left, right) => left.provider.localeCompare(right.provider))
|
|
1491
1491
|
}
|
|
1492
1492
|
|
|
1493
|
+
/**
|
|
1494
|
+
* 判定是否属于应从模型下拉彻底摘掉的严重凭据故障。
|
|
1495
|
+
*
|
|
1496
|
+
* ⚠️ 设计原则(避免死锁):
|
|
1497
|
+
* 只有确凿的凭据故障(CREDENTIAL_MISSING / AUTH_REJECTED / ACCOUNT_UNAVAILABLE)才从下拉框剔除。
|
|
1498
|
+
* 配额耗尽(QUOTA_EXHAUSTED)与现金余额为 0(BALANCE_EMPTY / PAYMENT_REQUIRED)属于临时限额/计费状态,
|
|
1499
|
+
* 绝不能从下拉框中剔除,否则会导致:
|
|
1500
|
+
* 1) 用户在界面上永久丢失模型入口;
|
|
1501
|
+
* 2) 5小时滚动配额或周配额恢复后,用户因无法选模型而无法触发运行期遥测恢复;
|
|
1502
|
+
* 3) 形成死锁。
|
|
1503
|
+
*/
|
|
1504
|
+
const isDropDeadHidden = (verdict) => {
|
|
1505
|
+
if (verdict === null || typeof verdict !== 'object') return false
|
|
1506
|
+
if (verdict.state !== 'unavailable') return false
|
|
1507
|
+
const code = verdict.code
|
|
1508
|
+
return code === 'CREDENTIAL_MISSING' || code === 'AUTH_REJECTED' || code === 'ACCOUNT_UNAVAILABLE'
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1493
1511
|
/** 当前该从下拉里摘掉的 provider 集合。 */
|
|
1494
1512
|
const hiddenProviders = () => {
|
|
1495
1513
|
const hidden = new Set()
|
|
1496
1514
|
for (const verdict of currentAvailability()) {
|
|
1497
|
-
if (verdict
|
|
1515
|
+
if (isDropDeadHidden(verdict)) hidden.add(verdict.provider)
|
|
1498
1516
|
}
|
|
1499
1517
|
return hidden
|
|
1500
1518
|
}
|
|
@@ -1530,7 +1548,7 @@ export function apply(ctx) {
|
|
|
1530
1548
|
checkedAt: lastProbeAt,
|
|
1531
1549
|
probing: probeAll !== null,
|
|
1532
1550
|
providers,
|
|
1533
|
-
hidden: providers.filter(
|
|
1551
|
+
hidden: providers.filter(isDropDeadHidden).map((verdict) => verdict.provider)
|
|
1534
1552
|
}
|
|
1535
1553
|
}
|
|
1536
1554
|
|
|
@@ -1630,8 +1648,16 @@ export function apply(ctx) {
|
|
|
1630
1648
|
const timer = setTimeout(() => {
|
|
1631
1649
|
refreshAvailability().catch(() => {})
|
|
1632
1650
|
}, 1500)
|
|
1633
|
-
|
|
1634
|
-
|
|
1651
|
+
timer.unref?.()
|
|
1652
|
+
const interval = setInterval(() => {
|
|
1653
|
+
ensureAvailabilityFresh()
|
|
1654
|
+
}, AVAILABILITY_TTL_MS)
|
|
1655
|
+
interval.unref?.()
|
|
1656
|
+
return () => {
|
|
1657
|
+
clearTimeout(timer)
|
|
1658
|
+
clearInterval(interval)
|
|
1659
|
+
}
|
|
1660
|
+
}, 'dsh-llm-hub: initial and periodic availability probe')
|
|
1635
1661
|
|
|
1636
1662
|
// 把不可用的 provider 从 llm 服务的路由表里摘掉。
|
|
1637
1663
|
//
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plugins/dsh-llm-hub",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "DSH 模型管理层:网关可达性探测、模型目录拉取、DeepSeek + MiniMax/智谱/Moonshot/StepFun 余额与配额、本月用量统计、余额预警、Provider 健康看板,再把本机装好的 codex / claude / agy 注册成子代理(装了才出现)。零运行时依赖,不改动 DSH 安装。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|