@dsh-plugins/dsh-llm-hub 1.4.0 → 1.5.0
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 +131 -0
- package/lib/availability-store.js +168 -0
- package/lib/client.js +330 -11
- package/lib/index.js +536 -109
- package/lib/sorter.js +106 -0
- package/package.json +1 -1
package/lib/sorter.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM Provider 优先级排序与计费模式决策引擎(纯逻辑层,零宿主依赖)。
|
|
3
|
+
*
|
|
4
|
+
* 核心规则:
|
|
5
|
+
* 1. 可用性保障:不可用/熔断的 provider 沉底或过滤;
|
|
6
|
+
* 2. 计费权重:套餐(subscription)排在上面,按量付费(usage)排在下面,
|
|
7
|
+
* 确保从上往下选最容易优先消耗固定周期配额,节省按量现金;
|
|
8
|
+
* 3. 计费识别:中转站默认按量,原厂默认按量,周期限额(5h/周/月/resetsAt/OpenCode Go)默认套餐,
|
|
9
|
+
* 用户显式配置具有最高覆盖权。
|
|
10
|
+
*
|
|
11
|
+
* @module @dsh-plugins/dsh-llm-hub/sorter
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 智能推断或读取某个 Provider 的计费模式。
|
|
16
|
+
* @param {object} [profile] - 配置段对象(含 billingMode、baseURL、provider 等)
|
|
17
|
+
* @param {object} [status] - 运行期状态(含 baseURL、api、billingMode 等)
|
|
18
|
+
* @param {object} [balance] - 余额信封对象(含 kind, items, meaning 等)
|
|
19
|
+
* @returns {'usage' | 'subscription'}
|
|
20
|
+
*/
|
|
21
|
+
export function detectBillingMode(profile, status, balance) {
|
|
22
|
+
if (profile?.billingMode === 'usage' || profile?.billingMode === 'subscription') {
|
|
23
|
+
return profile.billingMode
|
|
24
|
+
}
|
|
25
|
+
if (status?.billingMode === 'usage' || status?.billingMode === 'subscription') {
|
|
26
|
+
return status.billingMode
|
|
27
|
+
}
|
|
28
|
+
if (balance && typeof balance === 'object') {
|
|
29
|
+
if (balance.kind === 'plan' || balance.kind === 'quota') return 'subscription'
|
|
30
|
+
if (Array.isArray(balance.items)) {
|
|
31
|
+
for (const item of balance.items) {
|
|
32
|
+
if (item?.resetsAt) return 'subscription'
|
|
33
|
+
if (typeof item?.weeklyPercent === 'number') return 'subscription'
|
|
34
|
+
if (item?.label && /(5h|hour|hourly|week|month|day|分|时|天|周|月)/i.test(item.label)) return 'subscription'
|
|
35
|
+
if (typeof item?.percent === 'number' && balance.meaning) return 'subscription'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const providerName = String(profile?.provider || profile?.id || status?.provider || '').toLowerCase()
|
|
40
|
+
const baseURL = String(status?.baseURL || profile?.baseURL || '').toLowerCase()
|
|
41
|
+
|
|
42
|
+
// 特殊套餐/订阅服务商智能识别(如 OpenCode Go、各类 Coding Plan 等)
|
|
43
|
+
if (providerName.includes('opencode') || baseURL.includes('opencode.ai') || providerName.includes('coding-plan')) {
|
|
44
|
+
if (balance?.kind !== 'cash' && balance?.kind !== 'deepseek') {
|
|
45
|
+
return 'subscription'
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 中转站(无定时限额的聚合/反代如 modelgo, relay, proxy, oneapi 等)基本都是按量付费
|
|
50
|
+
if (providerName.includes('modelgo') || providerName.includes('relay') || providerName.includes('proxy') || providerName.includes('oneapi') || providerName.includes('newapi') || providerName.includes('gateway')) {
|
|
51
|
+
return 'usage'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 厂商原厂域名或现金余额默认按量付费
|
|
55
|
+
if (baseURL.includes('deepseek.com') || baseURL.includes('bigmodel.cn') || baseURL.includes('openai.com') || baseURL.includes('anthropic.com') || baseURL.includes('volces.com') || baseURL.includes('moonshot.cn') || baseURL.includes('minimax.chat') || baseURL.includes('baichuan-ai.com')) {
|
|
56
|
+
return 'usage'
|
|
57
|
+
}
|
|
58
|
+
if (balance?.kind === 'cash' || balance?.kind === 'deepseek') {
|
|
59
|
+
return 'usage'
|
|
60
|
+
}
|
|
61
|
+
return 'usage'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 对 Provider 列表进行带权稳定排序。
|
|
66
|
+
*
|
|
67
|
+
* @param {Array<object>} providers - 原始 provider 数组(每个对象需含 id)
|
|
68
|
+
* @param {function(string): { available?: boolean, billingMode?: 'usage' | 'subscription' }} getMeta - 获取元信息的回调
|
|
69
|
+
* @param {object} [options]
|
|
70
|
+
* @param {boolean} [options.filterUnavailable=false] - 是否直接剔除不可用的 provider
|
|
71
|
+
* @returns {Array<object>} 排序(及可选过滤)后的新数组
|
|
72
|
+
*/
|
|
73
|
+
export function sortProviders(providers, getMeta, options = {}) {
|
|
74
|
+
if (!Array.isArray(providers)) return []
|
|
75
|
+
const filterUnavailable = options.filterUnavailable === true
|
|
76
|
+
|
|
77
|
+
let list = providers
|
|
78
|
+
if (filterUnavailable) {
|
|
79
|
+
list = list.filter((p) => {
|
|
80
|
+
const meta = getMeta(p?.id)
|
|
81
|
+
return meta?.available !== false
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return [...list].sort((a, b) => {
|
|
86
|
+
const metaA = getMeta(a?.id) || {}
|
|
87
|
+
const metaB = getMeta(b?.id) || {}
|
|
88
|
+
|
|
89
|
+
// 1. 可用性优先级(若未硬过滤):可用排前,不可用排后
|
|
90
|
+
const availA = metaA.available !== false ? 1 : 0
|
|
91
|
+
const availB = metaB.available !== false ? 1 : 0
|
|
92
|
+
if (availA !== availB) {
|
|
93
|
+
return availB - availA
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 2. 计费模式优先级:套餐(subscription)排前,按量(usage)排后
|
|
97
|
+
const scoreA = metaA.billingMode === 'subscription' ? 1 : 0
|
|
98
|
+
const scoreB = metaB.billingMode === 'subscription' ? 1 : 0
|
|
99
|
+
if (scoreA !== scoreB) {
|
|
100
|
+
return scoreB - scoreA
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 3. 相同权重保持原有先后相对顺序(稳定排序)
|
|
104
|
+
return 0
|
|
105
|
+
})
|
|
106
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plugins/dsh-llm-hub",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "DSH 模型管理层:网关可达性探测、模型目录拉取、DeepSeek + MiniMax/智谱/Moonshot/StepFun 余额与配额、本月用量统计、余额预警、Provider 健康看板,再把本机装好的 codex / claude / agy 注册成子代理(装了才出现)。零运行时依赖,不改动 DSH 安装。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|