@goodandready/dsh-subscriptions 0.7.24 → 0.7.26
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/lib/accounts-view.js +2 -0
- package/lib/accounts.js +6 -0
- package/lib/client.js +203 -10
- package/lib/config-schema.js +4 -0
- package/lib/index.js +2 -2
- package/lib/rotate.js +31 -1
- package/lib/stream-rotate.js +1 -0
- package/lib/subscriptions.js +1 -0
- package/package.json +1 -1
package/lib/accounts-view.js
CHANGED
|
@@ -165,6 +165,8 @@ export async function createAccountsView({ live, store, fetch, pmL, pmE, concurr
|
|
|
165
165
|
latencyGrade: info.latencyGrade ?? null,
|
|
166
166
|
lastDoctorVerdict: info.lastDoctorVerdict || null,
|
|
167
167
|
availableModels: info.availableModels || null,
|
|
168
|
+
poolEnabled: slot.poolEnabled !== false,
|
|
169
|
+
...(Array.isArray(slot.poolModels) ? { poolModels: slot.poolModels } : {}),
|
|
168
170
|
capabilities,
|
|
169
171
|
billing,
|
|
170
172
|
renewsAt: billing.renewsAt,
|
package/lib/accounts.js
CHANGED
|
@@ -73,6 +73,10 @@ export function normalizeSlots(slots) {
|
|
|
73
73
|
...(slot.proxyUrl ? { proxyUrl: String(slot.proxyUrl) } : {}),
|
|
74
74
|
...(slot.expiresAt ? { expiresAt: Number(slot.expiresAt) } : {}),
|
|
75
75
|
...(slot.maxConcurrency !== undefined ? { maxConcurrency: Number(slot.maxConcurrency) } : {}),
|
|
76
|
+
...(slot.poolEnabled === false ? { poolEnabled: false } : {}),
|
|
77
|
+
...(Array.isArray(slot.poolModels)
|
|
78
|
+
? { poolModels: [...new Set(slot.poolModels.map((id) => String(id).trim()).filter(Boolean))] }
|
|
79
|
+
: {}),
|
|
76
80
|
})
|
|
77
81
|
}
|
|
78
82
|
return out
|
|
@@ -384,6 +388,8 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
|
|
|
384
388
|
tags: Array.isArray(slot.tags) ? slot.tags : [],
|
|
385
389
|
weight: Number(slot.weight) || 0,
|
|
386
390
|
isPro: !!slot.isPro,
|
|
391
|
+
poolEnabled: slot.poolEnabled !== false,
|
|
392
|
+
...(Array.isArray(slot.poolModels) ? { poolModels: slot.poolModels } : {}),
|
|
387
393
|
})
|
|
388
394
|
}
|
|
389
395
|
return out
|
package/lib/client.js
CHANGED
|
@@ -186,6 +186,16 @@ window.__ModuleLoader__.load({
|
|
|
186
186
|
"toastCooldownRecovered": "is now available (cooldown expired)",
|
|
187
187
|
"modelsCatalogTitle": "Curated Subscription Models",
|
|
188
188
|
"modelsCatalogDesc": "Available model catalog across active subscriptions in DeepSeek Harness.",
|
|
189
|
+
"modelSearchPlaceholder": "Search models by id, name or alias",
|
|
190
|
+
"modelSortLabel": "Sort models",
|
|
191
|
+
"modelSortName": "Name",
|
|
192
|
+
"modelSortProvider": "Provider",
|
|
193
|
+
"modelSortContext": "Context window",
|
|
194
|
+
"modelSortAvailability": "Availability",
|
|
195
|
+
"modelListCounts": "{shown} of {matched} shown · {hidden} hidden · {total} total",
|
|
196
|
+
"modelShowMore": "Show more",
|
|
197
|
+
"modelShowAll": "Show all",
|
|
198
|
+
"modelNoMatches": "No models match this search.",
|
|
189
199
|
"rotationDecisionsTitle": "Rotation & Routing Decisions",
|
|
190
200
|
"rotationDecisionsDesc": "Real-time decision trace explaining account selection, skips, and fallback reasons.",
|
|
191
201
|
"rotationAttempt": "attempt",
|
|
@@ -274,6 +284,15 @@ window.__ModuleLoader__.load({
|
|
|
274
284
|
"vipOnlyHint": "Reserve this account for VIP-tagged requests.",
|
|
275
285
|
"isPro": "Pro",
|
|
276
286
|
"isProHint": "Treat this account as Pro for weighted priority.",
|
|
287
|
+
"poolEnabled": "Use in automatic pool",
|
|
288
|
+
"poolEnabledHint": "When off, this account is not selected automatically; it stays available for explicit use only.",
|
|
289
|
+
"poolModelsTitle": "Pool models",
|
|
290
|
+
"poolModelsAutomatic": "Automatic",
|
|
291
|
+
"poolModelsAll": "Select all",
|
|
292
|
+
"poolModelsNone": "None",
|
|
293
|
+
"poolModelsSelected": "selected",
|
|
294
|
+
"poolModelsHint": "Automatic allows every current and future model. None removes this account from model routing. An explicit list permits only checked model ids.",
|
|
295
|
+
"poolModelsNoCatalog": "No model catalog yet; sync models or use Automatic.",
|
|
277
296
|
"tags": "Tags",
|
|
278
297
|
"tagsPlaceholder": "team, prod",
|
|
279
298
|
"weight": "Weight",
|
|
@@ -550,6 +569,16 @@ window.__ModuleLoader__.load({
|
|
|
550
569
|
"toastCooldownRecovered": "已解除冷却恢复可用",
|
|
551
570
|
"modelsCatalogTitle": "精选订阅模型",
|
|
552
571
|
"modelsCatalogDesc": "当前 DeepSeek Harness 活跃订阅下可用模型一览。",
|
|
572
|
+
"modelSearchPlaceholder": "搜索模型 ID、名称或别名",
|
|
573
|
+
"modelSortLabel": "模型排序",
|
|
574
|
+
"modelSortName": "名称",
|
|
575
|
+
"modelSortProvider": "服务商",
|
|
576
|
+
"modelSortContext": "上下文窗口",
|
|
577
|
+
"modelSortAvailability": "可用性",
|
|
578
|
+
"modelListCounts": "已显示 {shown} / {matched} · 隐藏 {hidden} · 共 {total} 个",
|
|
579
|
+
"modelShowMore": "显示更多",
|
|
580
|
+
"modelShowAll": "显示全部",
|
|
581
|
+
"modelNoMatches": "没有匹配的模型。",
|
|
553
582
|
"rotationDecisionsTitle": "轮询调度与路由决策",
|
|
554
583
|
"rotationDecisionsDesc": "实时决策追踪,透明展示每次请求的账号选择、跳过与回退原因。",
|
|
555
584
|
"rotationAttempt": "次尝试",
|
|
@@ -638,6 +667,15 @@ window.__ModuleLoader__.load({
|
|
|
638
667
|
"vipOnlyHint": "将此账户保留给 VIP 请求。",
|
|
639
668
|
"isPro": "Pro",
|
|
640
669
|
"isProHint": "按 Pro 账户参与加权优先级。",
|
|
670
|
+
"poolEnabled": "参与自动池",
|
|
671
|
+
"poolEnabledHint": "关闭后该账户不会被自动选择,仅保留给显式使用。",
|
|
672
|
+
"poolModelsTitle": "池模型",
|
|
673
|
+
"poolModelsAutomatic": "自动",
|
|
674
|
+
"poolModelsAll": "全选",
|
|
675
|
+
"poolModelsNone": "无",
|
|
676
|
+
"poolModelsSelected": "已选",
|
|
677
|
+
"poolModelsHint": "自动允许当前及未来全部模型。无会将该账户移出模型调度。显式列表只允许勾选的模型 ID。",
|
|
678
|
+
"poolModelsNoCatalog": "暂无模型目录;请同步模型或使用自动。",
|
|
641
679
|
"tags": "标签",
|
|
642
680
|
"tagsPlaceholder": "team, prod",
|
|
643
681
|
"weight": "权重",
|
|
@@ -948,7 +986,52 @@ window.__ModuleLoader__.load({
|
|
|
948
986
|
}
|
|
949
987
|
const Chevron = ChevronIcon || FallbackChevron
|
|
950
988
|
|
|
989
|
+
// #839 model-list helpers
|
|
990
|
+
function modelListSearchText(model) {
|
|
991
|
+
const parts = [model && model.id, model && model.name, model && model.alias, model && model.provider, model && model.providerName]
|
|
992
|
+
if (model && Array.isArray(model.aliases)) parts.push.apply(parts, model.aliases)
|
|
993
|
+
return parts.filter(Boolean).join(' ').toLowerCase()
|
|
994
|
+
}
|
|
951
995
|
|
|
996
|
+
function filterModelList(models, query) {
|
|
997
|
+
const list = Array.isArray(models) ? models : []
|
|
998
|
+
const q = String(query || '').trim().toLowerCase()
|
|
999
|
+
if (!q) return list.slice()
|
|
1000
|
+
return list.filter((model) => modelListSearchText(model).includes(q))
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
function modelAvailabilityRank(model) {
|
|
1004
|
+
const status = String(model && model.pool && model.pool.status || 'unconfigured').toLowerCase()
|
|
1005
|
+
const order = { available: 0, degraded: 1, cooldown: 2, quarantined: 3, exhausted: 4, unconfigured: 5 }
|
|
1006
|
+
return order[status] != null ? order[status] : 6
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function sortModelList(models, sortKey) {
|
|
1010
|
+
const list = Array.isArray(models) ? models.slice() : []
|
|
1011
|
+
const key = String(sortKey || 'name')
|
|
1012
|
+
const byName = (a, b) => String(a && (a.name || a.id) || '').localeCompare(String(b && (b.name || b.id) || ''), undefined, { numeric: true, sensitivity: 'base' })
|
|
1013
|
+
list.sort((a, b) => {
|
|
1014
|
+
if (key === 'provider') {
|
|
1015
|
+
const byProvider = String(a && (a.providerName || a.provider) || '').localeCompare(String(b && (b.providerName || b.provider) || ''), undefined, { sensitivity: 'base' })
|
|
1016
|
+
if (byProvider) return byProvider
|
|
1017
|
+
} else if (key === 'context') {
|
|
1018
|
+
const delta = Number(b && b.contextWindow || 0) - Number(a && a.contextWindow || 0)
|
|
1019
|
+
if (delta) return delta
|
|
1020
|
+
} else if (key === 'availability') {
|
|
1021
|
+
const delta = modelAvailabilityRank(a) - modelAvailabilityRank(b)
|
|
1022
|
+
if (delta) return delta
|
|
1023
|
+
}
|
|
1024
|
+
return byName(a, b)
|
|
1025
|
+
})
|
|
1026
|
+
return list
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
function modelListCounts(all, shown) {
|
|
1030
|
+
const total = Array.isArray(all) ? all.length : 0
|
|
1031
|
+
const visible = Array.isArray(shown) ? shown.length : 0
|
|
1032
|
+
return { total, shown: visible, hidden: Math.max(0, total - visible) }
|
|
1033
|
+
}
|
|
1034
|
+
// #839 end model-list helpers
|
|
952
1035
|
|
|
953
1036
|
function SubsSection(props) {
|
|
954
1037
|
// The translator comes from the slot because its registration declares locale.
|
|
@@ -961,6 +1044,9 @@ window.__ModuleLoader__.load({
|
|
|
961
1044
|
const [accounts, setAccounts] = React.useState([])
|
|
962
1045
|
const [providers, setProviders] = React.useState([])
|
|
963
1046
|
const [modelsCatalog, setModelsCatalog] = React.useState([])
|
|
1047
|
+
const [modelQuery, setModelQuery] = React.useState('')
|
|
1048
|
+
const [modelSort, setModelSort] = React.useState('name')
|
|
1049
|
+
const [modelLimit, setModelLimit] = React.useState(40)
|
|
964
1050
|
const [paste, setPaste] = React.useState({})
|
|
965
1051
|
const [device, setDevice] = React.useState({}) // #90 key -> {state,userCode,authUrl,intervalMs,status}
|
|
966
1052
|
const [loginLinks, setLoginLinks] = React.useState({}) // #595 provider:index -> fallback auth URL when a popup is blocked
|
|
@@ -1676,6 +1762,20 @@ window.__ModuleLoader__.load({
|
|
|
1676
1762
|
|
|
1677
1763
|
const connectedCount = accounts.filter((a) => a.configured).length
|
|
1678
1764
|
|
|
1765
|
+
const catalogAll = Array.isArray(modelsCatalog) && modelsCatalog.length ? modelsCatalog : [
|
|
1766
|
+
{ id: 'gpt-4o', contextLabel: '128k', capabilities: ['vision'], pool: { status: t('sourceAvailable') } },
|
|
1767
|
+
{ id: 'o1 / o3-mini', contextLabel: '200k', capabilities: ['reasoning'], pool: { status: t('sourceAvailable') } },
|
|
1768
|
+
{ id: 'claude-3-7-sonnet', contextLabel: '200k', capabilities: ['reasoning'], pool: { status: t('sourceAvailable') } },
|
|
1769
|
+
{ id: 'claude-3-5-sonnet', contextLabel: '200k', capabilities: ['coding'], pool: { status: t('sourceAvailable') } },
|
|
1770
|
+
{ id: 'grok-beta', contextLabel: '128k', capabilities: ['fast'], pool: { status: t('sourceAvailable') } },
|
|
1771
|
+
{ id: 'gemini-2.5-pro', contextLabel: '1M', capabilities: ['vision'], pool: { status: t('sourceAvailable') } },
|
|
1772
|
+
{ id: 'kimi-k1.5', contextLabel: '128k', capabilities: ['math'], pool: { status: t('sourceAvailable') } },
|
|
1773
|
+
{ id: 'glm-4-plus', contextLabel: '128k', capabilities: ['chinese'], pool: { status: t('sourceAvailable') } },
|
|
1774
|
+
]
|
|
1775
|
+
const catalogSorted = sortModelList(filterModelList(catalogAll, modelQuery), modelSort)
|
|
1776
|
+
const catalogShown = catalogSorted.slice(0, modelLimit)
|
|
1777
|
+
const catalogCounts = modelListCounts(catalogAll, catalogShown)
|
|
1778
|
+
|
|
1679
1779
|
return h('div', { className: 'dsub-wrap', 'data-tab': tab },
|
|
1680
1780
|
h('div', { className: 'dsub-header-bar' },
|
|
1681
1781
|
h('div', { className: 'dsub-row', style: { marginTop: 0 } },
|
|
@@ -2197,17 +2297,36 @@ h('div', { className: 'dsub-row' },
|
|
|
2197
2297
|
h('details', { className: 'dsub-group', 'data-pane': 'models' },
|
|
2198
2298
|
h('summary', { className: 'dsub-groupHead' }, '🎯 ' + t('modelsCatalogTitle')),
|
|
2199
2299
|
h('div', { className: 'dsub-sub', style: { marginTop: '4px' } }, t('modelsCatalogDesc')),
|
|
2300
|
+
h('div', { className: 'dsub-row', style: { marginTop: 8, gap: 6, alignItems: 'center' } },
|
|
2301
|
+
h('input', {
|
|
2302
|
+
className: 'dsub-inp dsub-grow',
|
|
2303
|
+
value: modelQuery,
|
|
2304
|
+
placeholder: t('modelSearchPlaceholder'),
|
|
2305
|
+
'aria-label': t('modelSearchPlaceholder'),
|
|
2306
|
+
onChange: (e) => { setModelQuery(e.target.value); setModelLimit(40) },
|
|
2307
|
+
}),
|
|
2308
|
+
h('select', {
|
|
2309
|
+
className: 'dsub-select',
|
|
2310
|
+
value: modelSort,
|
|
2311
|
+
title: t('modelSortLabel'),
|
|
2312
|
+
'aria-label': t('modelSortLabel'),
|
|
2313
|
+
onChange: (e) => { setModelSort(e.target.value); setModelLimit(40) },
|
|
2314
|
+
},
|
|
2315
|
+
h('option', { value: 'name' }, t('modelSortName')),
|
|
2316
|
+
h('option', { value: 'provider' }, t('modelSortProvider')),
|
|
2317
|
+
h('option', { value: 'context' }, t('modelSortContext')),
|
|
2318
|
+
h('option', { value: 'availability' }, t('modelSortAvailability')),
|
|
2319
|
+
),
|
|
2320
|
+
),
|
|
2321
|
+
h('div', { className: 'dsub-sub', style: { marginTop: 4 } },
|
|
2322
|
+
t('modelListCounts')
|
|
2323
|
+
.replace('{shown}', String(catalogCounts.shown))
|
|
2324
|
+
.replace('{matched}', String(catalogSorted.length))
|
|
2325
|
+
.replace('{hidden}', String(catalogCounts.hidden))
|
|
2326
|
+
.replace('{total}', String(catalogCounts.total))
|
|
2327
|
+
),
|
|
2200
2328
|
h('div', { className: 'dsub-model-grid' },
|
|
2201
|
-
|
|
2202
|
-
{ id: 'gpt-4o', contextLabel: '128k', capabilities: ['vision'], pool: { status: t('sourceAvailable') } },
|
|
2203
|
-
{ id: 'o1 / o3-mini', contextLabel: '200k', capabilities: ['reasoning'], pool: { status: t('sourceAvailable') } },
|
|
2204
|
-
{ id: 'claude-3-7-sonnet', contextLabel: '200k', capabilities: ['reasoning'], pool: { status: t('sourceAvailable') } },
|
|
2205
|
-
{ id: 'claude-3-5-sonnet', contextLabel: '200k', capabilities: ['coding'], pool: { status: t('sourceAvailable') } },
|
|
2206
|
-
{ id: 'grok-beta', contextLabel: '128k', capabilities: ['fast'], pool: { status: t('sourceAvailable') } },
|
|
2207
|
-
{ id: 'gemini-2.5-pro', contextLabel: '1M', capabilities: ['vision'], pool: { status: t('sourceAvailable') } },
|
|
2208
|
-
{ id: 'kimi-k1.5', contextLabel: '128k', capabilities: ['math'], pool: { status: t('sourceAvailable') } },
|
|
2209
|
-
{ id: 'glm-4-plus', contextLabel: '128k', capabilities: ['chinese'], pool: { status: t('sourceAvailable') } },
|
|
2210
|
-
]).map((m, idx) => {
|
|
2329
|
+
catalogShown.map((m, idx) => {
|
|
2211
2330
|
const poolStatus = m.pool ? m.pool.status : 'unconfigured'
|
|
2212
2331
|
const statusClass = 'dsub-model-status-' + poolStatus
|
|
2213
2332
|
let poolLabel = ''
|
|
@@ -2244,6 +2363,17 @@ h('div', { className: 'dsub-row' },
|
|
|
2244
2363
|
)
|
|
2245
2364
|
})
|
|
2246
2365
|
),
|
|
2366
|
+
catalogSorted.length === 0 ? h('div', { className: 'dsub-sub', style: { marginTop: 6 } }, t('modelNoMatches')) : null,
|
|
2367
|
+
catalogCounts.shown < catalogSorted.length ? h('div', { className: 'dsub-row', style: { marginTop: 6, gap: 6 } },
|
|
2368
|
+
h('button', {
|
|
2369
|
+
type: 'button', className: 'dsub-mini',
|
|
2370
|
+
onClick: () => setModelLimit((n) => n + 40),
|
|
2371
|
+
}, t('modelShowMore')),
|
|
2372
|
+
h('button', {
|
|
2373
|
+
type: 'button', className: 'dsub-mini',
|
|
2374
|
+
onClick: () => setModelLimit(catalogSorted.length),
|
|
2375
|
+
}, t('modelShowAll')),
|
|
2376
|
+
) : null,
|
|
2247
2377
|
),
|
|
2248
2378
|
h('details', { className: 'dsub-group', 'data-pane': 'models' },
|
|
2249
2379
|
h('summary', { className: 'dsub-groupHead' }, '🧠 ' + t('reasoningEffortTitle')),
|
|
@@ -2612,6 +2742,17 @@ h('div', { className: 'dsub-row' },
|
|
|
2612
2742
|
const account = accountOf(row.slot.provider, row.slot.index)
|
|
2613
2743
|
const key = row.slot.provider + ':' + row.slot.index
|
|
2614
2744
|
const on = !!account.configured
|
|
2745
|
+
const poolEnabled = row.slot.poolEnabled !== false
|
|
2746
|
+
const poolModels = Array.isArray(row.slot.poolModels) ? row.slot.poolModels : null
|
|
2747
|
+
const modelIdOf = (m) => (typeof m === 'string' ? m : (m && (m.id || m.slug || m.name)))
|
|
2748
|
+
const poolModelIds = Array.from(new Set(
|
|
2749
|
+
(Array.isArray(modelsCatalog) ? modelsCatalog : [])
|
|
2750
|
+
.filter((m) => m && m.provider === row.slot.provider)
|
|
2751
|
+
.map((m) => modelIdOf(m))
|
|
2752
|
+
.concat(Array.isArray(account.availableModels) ? account.availableModels.map(modelIdOf) : [])
|
|
2753
|
+
.concat(poolModels || [])
|
|
2754
|
+
.filter(Boolean)
|
|
2755
|
+
)).sort()
|
|
2615
2756
|
const avatarSrc = safeExternalUrl(account.avatarUrl, true)
|
|
2616
2757
|
const avatarEl = (avatarSrc ? h('img', {
|
|
2617
2758
|
src: avatarSrc,
|
|
@@ -2911,6 +3052,58 @@ h('div', { className: 'dsub-row' },
|
|
|
2911
3052
|
}),
|
|
2912
3053
|
' ' + t('isPro')
|
|
2913
3054
|
),
|
|
3055
|
+
h('label', { className: 'dsub-ref-tag', title: t('poolEnabledHint') },
|
|
3056
|
+
h('input', {
|
|
3057
|
+
type: 'checkbox',
|
|
3058
|
+
checked: poolEnabled,
|
|
3059
|
+
onChange: (e) => updateSlot(row.slot.provider, row.slot.index, { poolEnabled: e.target.checked }),
|
|
3060
|
+
}),
|
|
3061
|
+
' ' + t('poolEnabled')
|
|
3062
|
+
),
|
|
3063
|
+
),
|
|
3064
|
+
h('details', { className: 'dsub-details' },
|
|
3065
|
+
h('summary', { className: 'dsub-summary' },
|
|
3066
|
+
'🎯 ' + t('poolModelsTitle') + ' · ' + (poolModels ? (poolModels.length + ' ' + t('poolModelsSelected')) : t('poolModelsAutomatic'))
|
|
3067
|
+
),
|
|
3068
|
+
h('div', { className: 'dsub-sub' }, t('poolModelsHint')),
|
|
3069
|
+
h('div', { className: 'dsub-row' },
|
|
3070
|
+
h('button', {
|
|
3071
|
+
type: 'button', className: 'dsub-mini' + (!poolModels ? ' dsub-btnActive' : ''),
|
|
3072
|
+
onClick: () => updateSlot(row.slot.provider, row.slot.index, { poolModels: undefined }),
|
|
3073
|
+
}, t('poolModelsAutomatic')),
|
|
3074
|
+
h('button', {
|
|
3075
|
+
type: 'button', className: 'dsub-mini',
|
|
3076
|
+
onClick: () => updateSlot(row.slot.provider, row.slot.index, { poolModels: poolModelIds.slice() }),
|
|
3077
|
+
}, t('poolModelsAll')),
|
|
3078
|
+
h('button', {
|
|
3079
|
+
type: 'button', className: 'dsub-mini' + (poolModels && poolModels.length === 0 ? ' dsub-btnActive' : ''),
|
|
3080
|
+
onClick: () => updateSlot(row.slot.provider, row.slot.index, { poolModels: [] }),
|
|
3081
|
+
}, t('poolModelsNone')),
|
|
3082
|
+
),
|
|
3083
|
+
poolModelIds.length ? h('div', { className: 'dsub-row', style: { flexWrap: 'wrap', gap: 6 } },
|
|
3084
|
+
poolModelIds.map((modelId) => {
|
|
3085
|
+
const checked = poolModels ? poolModels.includes(modelId) : true
|
|
3086
|
+
return h('label', { key: modelId, className: 'dsub-ref-tag', title: modelId },
|
|
3087
|
+
h('input', {
|
|
3088
|
+
type: 'checkbox',
|
|
3089
|
+
checked,
|
|
3090
|
+
onChange: () => {
|
|
3091
|
+
let next
|
|
3092
|
+
if (poolModels) {
|
|
3093
|
+
next = checked
|
|
3094
|
+
? poolModels.filter((id) => id !== modelId)
|
|
3095
|
+
: poolModels.concat([modelId])
|
|
3096
|
+
} else {
|
|
3097
|
+
next = poolModelIds.filter((id) => id !== modelId)
|
|
3098
|
+
}
|
|
3099
|
+
updateSlot(row.slot.provider, row.slot.index, { poolModels: next })
|
|
3100
|
+
},
|
|
3101
|
+
}),
|
|
3102
|
+
' ',
|
|
3103
|
+
h('code', null, modelId)
|
|
3104
|
+
)
|
|
3105
|
+
})
|
|
3106
|
+
) : h('div', { className: 'dsub-sub' }, t('poolModelsNoCatalog')),
|
|
2914
3107
|
),
|
|
2915
3108
|
h('div', { className: 'dsub-row' },
|
|
2916
3109
|
h('span', { className: 'dsub-ref-tag' }, t('tags')),
|
package/lib/config-schema.js
CHANGED
|
@@ -34,6 +34,10 @@ export const Slot = z.object({
|
|
|
34
34
|
.description('#171 Weighted rotation priority. 0 = derive from isPro.'),
|
|
35
35
|
isPro: z.boolean().default(false)
|
|
36
36
|
.description('#171 Treat this account as Pro for weighted rotation.'),
|
|
37
|
+
poolEnabled: z.boolean().default(true)
|
|
38
|
+
.description('#795 Include this account in automatic pool selection. false keeps it available for explicit/manual use only.'),
|
|
39
|
+
poolModels: z.array(z.string()).default(undefined)
|
|
40
|
+
.description('#795 Model allowlist for automatic pool selection. Absent = every model, empty = none, explicit list = only those ids.'),
|
|
37
41
|
})
|
|
38
42
|
|
|
39
43
|
export const defaultSlots = PROVIDERS.map((provider) => ({ provider, index: 1, label: '' }))
|
package/lib/index.js
CHANGED
|
@@ -27,7 +27,7 @@ import { createDiagnosticsReport } from './diagnostics.js'
|
|
|
27
27
|
import { createAccountsView } from './accounts-view.js'
|
|
28
28
|
import { inspectScopes } from './scope-inspector.js'
|
|
29
29
|
import { createAdapterManager } from './adapter-manager.js'
|
|
30
|
-
import { pickAccount, isAuthError } from './rotate.js'
|
|
30
|
+
import { pickAccount, poolBlockReason, isAuthError } from './rotate.js'
|
|
31
31
|
import { notifySessionExpired, getRecentAlerts, dispatchNotificationDigest, setWebhookSecret, setWebhookAllowPrivate, setWebhookFormat } from './alerts.js'
|
|
32
32
|
import { isDigestDue } from './notification-scheduler.js'
|
|
33
33
|
import { clearSessionPins } from './session-pin.js'
|
|
@@ -394,7 +394,7 @@ export function apply(ctx, rawConfig) {
|
|
|
394
394
|
drainBeforeReset: live().drainBeforeReset,
|
|
395
395
|
drainSafetyPercent: live().drainSafetyPercent,
|
|
396
396
|
})
|
|
397
|
-
const slot = eligible || (accounts || []).find((row) => row && row.ref && row.hasToken)
|
|
397
|
+
const slot = eligible || (accounts || []).find((row) => row && row.ref && row.hasToken && !poolBlockReason(row, {}))
|
|
398
398
|
if (!slot) throw new Error(`not logged in to ${provider}: sign in in the Subscriptions section`)
|
|
399
399
|
const raw = await store.resolveRaw(slot.ref)
|
|
400
400
|
if (!raw) throw new Error(`not logged in to ${provider}: sign in in the Subscriptions section`)
|
package/lib/rotate.js
CHANGED
|
@@ -79,6 +79,19 @@ export function getAccountRemainingPercent(acc) {
|
|
|
79
79
|
return null
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
export function poolBlockReason(account, opts) {
|
|
83
|
+
if (!account) return null
|
|
84
|
+
if (account.poolEnabled === false) return 'pool_disabled'
|
|
85
|
+
if (Array.isArray(account.poolModels)) {
|
|
86
|
+
const model = String(opts && opts.model || '').trim().toLowerCase()
|
|
87
|
+
if (model) {
|
|
88
|
+
const allowed = account.poolModels.some((id) => String(id).trim().toLowerCase() === model)
|
|
89
|
+
if (!allowed) return 'model_not_allowed'
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
|
|
82
95
|
export function pickAccountWithTrace(accounts, nowMs, opts) {
|
|
83
96
|
const list = Array.isArray(accounts) ? accounts : []
|
|
84
97
|
const now = Number(nowMs) || 0
|
|
@@ -119,7 +132,16 @@ export function pickAccountWithTrace(accounts, nowMs, opts) {
|
|
|
119
132
|
const pinnedRef = getPinnedAccountRef(targetSession, now)
|
|
120
133
|
if (pinnedRef) {
|
|
121
134
|
const pinned = list.find((a) => (a.ref || a.id) === pinnedRef)
|
|
122
|
-
|
|
135
|
+
const pinnedBlock = pinned ? poolBlockReason(pinned, opts) : null
|
|
136
|
+
if (pinned && pinnedBlock) {
|
|
137
|
+
unpinSession(targetSession, pinnedBlock)
|
|
138
|
+
evaluations.push({
|
|
139
|
+
ref: pinnedRef,
|
|
140
|
+
eligible: false,
|
|
141
|
+
reason: pinnedBlock,
|
|
142
|
+
pinned: true,
|
|
143
|
+
})
|
|
144
|
+
} else if (pinned && pinned.hasToken && !isQuarantined(pinned, now)) {
|
|
123
145
|
const isCooldown = pinned.cooldownUntil && Number(pinned.cooldownUntil) > now && cooldownBlocks(pinned, opts && opts.family)
|
|
124
146
|
const isPinnedZero = Boolean(pinned.quota && (
|
|
125
147
|
(pinned.quota.remaining != null && Number(pinned.quota.remaining) <= 0) ||
|
|
@@ -191,6 +213,14 @@ export function pickAccountWithTrace(accounts, nowMs, opts) {
|
|
|
191
213
|
continue
|
|
192
214
|
}
|
|
193
215
|
|
|
216
|
+
// #795: pool participation and per-account model allowlists are checked
|
|
217
|
+
// before weighting, quota tiers and session pinning.
|
|
218
|
+
const blockedByPool = poolBlockReason(acc, opts)
|
|
219
|
+
if (blockedByPool) {
|
|
220
|
+
evaluations.push({ ref, eligible: false, tier: null, reason: blockedByPool })
|
|
221
|
+
continue
|
|
222
|
+
}
|
|
223
|
+
|
|
194
224
|
if (!opts || opts.autoPacing !== false) {
|
|
195
225
|
if (acc.pacingRisk === undefined) {
|
|
196
226
|
const rem = acc.quota?.remainingPercent ?? (acc.quota?.usedPercent != null ? (100 - Number(acc.quota.usedPercent)) : (acc.usagePercent != null ? (100 - Number(acc.usagePercent)) : null))
|
package/lib/stream-rotate.js
CHANGED
|
@@ -51,6 +51,7 @@ export async function* streamWithRotation({
|
|
|
51
51
|
while (true) {
|
|
52
52
|
const pickRes = pickAccountWithTrace(pool, nowMs(), {
|
|
53
53
|
switchAtRemaining,
|
|
54
|
+
model: options && options.model,
|
|
54
55
|
family: modelFamily(options && options.provider, options && options.model),
|
|
55
56
|
sessionId: options && options.sessionId,
|
|
56
57
|
tag: options && options.tag,
|
package/lib/subscriptions.js
CHANGED
|
@@ -147,6 +147,7 @@ export function createSubscriptionsService(deps) {
|
|
|
147
147
|
while (true) {
|
|
148
148
|
const pickRes = pickAccountWithTrace(pool, Date.now(), {
|
|
149
149
|
switchAtRemaining: thr,
|
|
150
|
+
model: body && body.model,
|
|
150
151
|
family: modelFamily(provider, body && body.model),
|
|
151
152
|
drainBeforeReset: typeof deps.drainBeforeReset === 'function' ? deps.drainBeforeReset() : deps.drainBeforeReset,
|
|
152
153
|
drainSafetyPercent: typeof deps.drainSafetyPercent === 'function' ? deps.drainSafetyPercent() : deps.drainSafetyPercent,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.26",
|
|
4
4
|
"description": "Use ChatGPT Codex, Claude, Grok, Antigravity, Kimi, GLM, Cursor, Kiro, Copilot, Qwen, ERNIE, Spark, JetBrains, Perplexity, Replit, and Cody subscriptions as DeepSeek Harness LLM providers via OAuth.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|