@goodandready/dsh-subscriptions 0.6.4 → 0.6.6
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 +7 -0
- package/README.ru.md +7 -0
- package/lib/blob.js +0 -8
- package/lib/client.js +191 -15
- package/lib/history.js +78 -2
- package/lib/http.js +4 -1
- package/lib/index.js +5 -0
- package/lib/quarantine.js +0 -7
- package/lib/ratelimit.js +0 -8
- package/lib/refs.js +5 -0
- package/lib/relative-time.js +6 -0
- package/lib/routes/status.js +125 -7
- package/lib/session-pin.js +11 -0
- package/lib/vendors/antigravity.js +3 -1
- package/lib/vendors/grok.js +2 -1
- package/package.json +9 -6
- package/lib/ratelimit-parser.js +0 -24
- package/lib/ui-styles.js +0 -119
package/README.md
CHANGED
|
@@ -186,6 +186,13 @@ const res = await ctx.subscriptions.request('codex', '/backend-api/codex/images/
|
|
|
186
186
|
* **Frosted Panel**: Hovering reveals a `backdrop-filter: blur(28px)` panel listing every account with usage bars. Clicking the bubble refreshes quota data; data also refreshes every 60 s.
|
|
187
187
|
* **Settings Card Polish (`v0.4.18`)**: Chevron switched to the core `IconChevronDownOutline14` primitive; explicit settings snapshot states (loading / unavailable + Retry) guard against phantom input.
|
|
188
188
|
|
|
189
|
+
### 16. ⚡ ClineBot-Inspired UI & Hardened Telemetry (`v0.6.6`)
|
|
190
|
+
* **Live Status Badge Bar**: Instant latency check to the host (`Host online (XX ms)`), real-time connected account indicators, and pool size at a glance.
|
|
191
|
+
* **One-Click Smoke Test (Ping)**: Test live connectivity to the active upstream subscription provider and measure real round-trip latency.
|
|
192
|
+
* **Session Telemetry Dashboard**: Visual stat cards displaying successful/total requests, average latency, session success rate, and last request activity.
|
|
193
|
+
* **Curated Models Catalog**: Quick preview of supported model families with context windows and capability tags (Vision, Reasoning, Hybrid).
|
|
194
|
+
* **Non-Blocking Storage**: `HistoryStore` debounces disk persistence asynchronously to prevent blocking the Node.js event loop during high-throughput streaming.
|
|
195
|
+
|
|
189
196
|
---
|
|
190
197
|
|
|
191
198
|
## 📦 Quick Installation
|
package/README.ru.md
CHANGED
|
@@ -162,6 +162,13 @@ const res = await ctx.subscriptions.request('codex', '/backend-api/codex/images/
|
|
|
162
162
|
* **Reasoning vs Standard**: 429 на reasoning-модели (claude `*thinking*`, grok `*reasoning*`, все codex-модели) охлаждает только семейство reasoning этого аккаунта — standard-модели того же аккаунта продолжают работать. Legacy-кулдауны (от старых версий) по-прежнему блокируют весь аккаунт до истечения.
|
|
163
163
|
* **Скрыть устаревшие модели (`hideDeprecatedModels`)**: убирает из нативного селектора id с `test`/`preview`/`dev`/`alpha`/`beta`/`legacy` (действует и на живые каталоги, и на статичный фолбэк).
|
|
164
164
|
|
|
165
|
+
### 12. ⚡ Дизайн в стиле ClineBot и неблокирующая телеметрия (`v0.6.6`)
|
|
166
|
+
* **Строка статуса с бейджами**: Мгновенный замер задержки хоста (`Host online (XX ms)`), количество активных подключений и размер пула.
|
|
167
|
+
* **Интерактивный Smoke Test (Ping)**: Проверка связи с провайдером в один клик с измерением реального времени отклика в миллисекундах.
|
|
168
|
+
* **Панель телеметрии сессии**: 4 карточки метрик (успешных/всего запросов, средняя задержка, процент успешности, последняя активность).
|
|
169
|
+
* **Каталог моделей подписок**: Компактный список поддерживаемых моделей с размером контекста (128k, 200k, 1M) и тегами возможностей.
|
|
170
|
+
* **Асинхронная история**: Неблокирующий дебаунсинг записи `HistoryStore`, исключающий просадки event loop при интенсивном стриминге.
|
|
171
|
+
|
|
165
172
|
---
|
|
166
173
|
|
|
167
174
|
## 📦 Быстрая установка
|
package/lib/blob.js
CHANGED
package/lib/client.js
CHANGED
|
@@ -54,6 +54,10 @@ window.__ModuleLoader__.load({
|
|
|
54
54
|
'.dsub-chevOpen{transform:rotate(180deg)}' +
|
|
55
55
|
'.dsub-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:12px}' +
|
|
56
56
|
'.dsub-block{margin-top:10px}' +
|
|
57
|
+
'.dsub-group{margin-top:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:0 14px}' +
|
|
58
|
+
'.dsub-group[open]{padding-bottom:6px}' +
|
|
59
|
+
'.dsub-groupHead{cursor:pointer;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);padding:10px 0;list-style:none}' +
|
|
60
|
+
'.dsub-groupHead::-webkit-details-marker{display:none}' +
|
|
57
61
|
'.dsub-row{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-top:8px}' +
|
|
58
62
|
'.dsub-grow{flex:1 1 180px;min-width:140px}' +
|
|
59
63
|
'.dsub-helpBox{margin-top:8px;padding:12px 14px;border-radius:8px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);font-size:12px;line-height:1.6}' +
|
|
@@ -107,6 +111,27 @@ window.__ModuleLoader__.load({
|
|
|
107
111
|
'.dsub-details{margin-top:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px 12px;background:var(--dsw-alias-bg-layer-2)}' +
|
|
108
112
|
'.dsub-summary{cursor:pointer;font-weight:600;font-size:12.5px;color:var(--dsw-alias-label-secondary);user-select:none}' +
|
|
109
113
|
'.dsub-summary:hover{color:var(--dsw-alias-label-primary)}' +
|
|
114
|
+
'.dsub-header-bar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;padding:8px 0 12px;margin-bottom:8px;border-bottom:1px solid var(--dsw-alias-border-l2)}' +
|
|
115
|
+
'.dsub-badge{font-size:12px;padding:3px 10px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);display:inline-flex;align-items:center;gap:5px;font-weight:500}' +
|
|
116
|
+
'.dsub-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:rgba(16,185,129,0.08)}' +
|
|
117
|
+
'.dsub-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:rgba(245,158,11,0.08)}' +
|
|
118
|
+
'.dsub-badge-bad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);background:rgba(239,68,68,0.08)}' +
|
|
119
|
+
'.dsub-badge-neutral{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}' +
|
|
120
|
+
'.dsub-section-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:14px 16px;display:flex;flex-direction:column;gap:10px;margin-top:12px}' +
|
|
121
|
+
'.dsub-section-title{font-size:14.5px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between;gap:8px}' +
|
|
122
|
+
'.dsub-section-desc{font-size:12px;color:var(--dsw-alias-label-secondary);margin-top:-4px;line-height:1.4}' +
|
|
123
|
+
'.dsub-grid-2{display:grid;grid-template-columns:repeat(auto-fit, minmax(200px, 1fr));gap:10px}' +
|
|
124
|
+
'.dsub-stat-box{padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);display:flex;flex-direction:column;gap:3px}' +
|
|
125
|
+
'.dsub-stat-val{font-size:16.5px;font-weight:700;color:var(--dsw-alias-label-primary)}' +
|
|
126
|
+
'.dsub-stat-lbl{font-size:11.5px;color:var(--dsw-alias-label-secondary)}' +
|
|
127
|
+
'.dsub-alert-ok{padding:8px 12px;border-radius:8px;background:rgba(16,185,129,0.09);color:var(--dsw-alias-state-success-primary);font-size:12.5px;border:1px solid rgba(16,185,129,0.25)}' +
|
|
128
|
+
'.dsub-alert-bad{padding:8px 12px;border-radius:8px;background:rgba(239,68,68,0.09);color:var(--dsw-alias-state-error-primary);font-size:12.5px;border:1px solid rgba(239,68,68,0.25)}' +
|
|
129
|
+
'.dsub-btn-primary{background:var(--dsw-alias-label-primary) !important;color:var(--dsw-alias-bg-layer-3) !important;border-color:transparent !important;font-weight:600}' +
|
|
130
|
+
'.dsub-btn-primary:hover:not(:disabled){opacity:0.9}' +
|
|
131
|
+
'.dsub-model-grid{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}' +
|
|
132
|
+
'.dsub-model-chip{display:inline-flex;align-items:center;gap:6px;padding:3px 8px;border-radius:6px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);font-size:11.5px;color:var(--dsw-alias-label-primary)}' +
|
|
133
|
+
'.dsub-model-chip code{font-family:ui-monospace,monospace;font-weight:600}' +
|
|
134
|
+
'.dsub-model-tag{font-size:10px;padding:1px 4px;border-radius:3px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font-weight:600}' +
|
|
110
135
|
'.dsub-heroBars{display:flex;flex-direction:column;gap:8px;margin-top:10px}' +
|
|
111
136
|
'.dsub-barLabelRow{display:flex;justify-content:space-between;font-size:11.5px;color:var(--dsw-alias-label-secondary);margin-bottom:3px}' +
|
|
112
137
|
'.dsub-barTrack{height:6px;border-radius:3px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}' +
|
|
@@ -310,6 +335,12 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
310
335
|
'intro': 'Log in with a consumer subscription. Tokens stay on the host credentials store. The browser never reads them back. After Connect, if the provider lands on localhost or a vendor page, paste the redirected URL or the code here.',
|
|
311
336
|
'useOrigin': 'Use this Web UI origin as OAuth redirect_uri',
|
|
312
337
|
'useOriginHint': 'Leave off unless you registered your own OAuth client for this origin. Vendor CLI clients usually require their published redirect URI plus the paste step.',
|
|
338
|
+
'closeModal': 'Close',
|
|
339
|
+
'groupAuth': 'Sign-in and redirect',
|
|
340
|
+
'groupPrivacy': 'Privacy and model list',
|
|
341
|
+
'groupRotation': 'Rotation and limits',
|
|
342
|
+
'groupProvider': 'Provider behaviour',
|
|
343
|
+
'groupFallback': 'Local fallback',
|
|
313
344
|
'privacyMask': 'Mask emails and account identifiers',
|
|
314
345
|
'privacyMaskHint': 'For demos and screen sharing: emails show as j***n@example.com.',
|
|
315
346
|
'autoLoopback': 'Auto Loopback',
|
|
@@ -343,6 +374,21 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
343
374
|
'probeIntervalMinHint': 'Interval for background checks to refresh account status and quota balance.',
|
|
344
375
|
'notifyLimits': 'Notify on rate limits',
|
|
345
376
|
'notifyLimitsHint': 'Show notification alerts when account usage reaches quota ceiling.',
|
|
377
|
+
'hostOnline': 'Host online',
|
|
378
|
+
'hostOffline': 'Host unreachable',
|
|
379
|
+
'statsTitle': 'Session Telemetry',
|
|
380
|
+
'statsRequests': 'Successful / Total Requests',
|
|
381
|
+
'statsLatency': 'Avg Response Latency',
|
|
382
|
+
'statsSuccessRate': 'Session Success Rate',
|
|
383
|
+
'statsLastActivity': 'Last Activity',
|
|
384
|
+
'smokeTitle': 'Connection Smoke Test (Ping)',
|
|
385
|
+
'smokeDesc': 'Test live upstream connectivity and measure round-trip latency to the subscription provider.',
|
|
386
|
+
'runSmoke': '⚡ Run Smoke Test (Ping)',
|
|
387
|
+
'smokeTesting': 'Pinging…',
|
|
388
|
+
'smokeOk': 'Smoke test passed',
|
|
389
|
+
'smokeFail': 'Smoke test failed',
|
|
390
|
+
'modelsCatalogTitle': 'Curated Subscription Models',
|
|
391
|
+
'modelsCatalogDesc': 'Available model catalog across active subscriptions in DeepSeek Harness.',
|
|
346
392
|
'resetCredits': 'Reset credits',
|
|
347
393
|
'resetCreditsHint': 'ChatGPT reset cards: emergency reset for the 5-hour window. Requires a 5s confirmation.',
|
|
348
394
|
'resetCreditsAction': 'Reset ChatGPT Limit',
|
|
@@ -539,6 +585,10 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
539
585
|
function badgeFor(account, t) {
|
|
540
586
|
if (!account || !account.configured) return t('notConnected')
|
|
541
587
|
if (account.validationUrl) return t('verifyAccount')
|
|
588
|
+
// #303: a quarantined account says why it is parked.
|
|
589
|
+
if (account.quarantineReason && account.quarantineUntil && account.quarantineUntil > Date.now()) {
|
|
590
|
+
return account.quarantineReason === 'VERIFY' ? t('verifyAccount') : String(account.quarantineReason)
|
|
591
|
+
}
|
|
542
592
|
if (account.cooldownUntil && account.cooldownUntil > Date.now()) return t('coolingDown')
|
|
543
593
|
if (account.usagePercent != null && account.usagePercent >= 100) return t('usageFull')
|
|
544
594
|
return t('connected')
|
|
@@ -636,6 +686,32 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
636
686
|
.catch((e) => setDiag('error: ' + String(e && e.message || e)))
|
|
637
687
|
}
|
|
638
688
|
const [checkRes, setCheckRes] = React.useState({})
|
|
689
|
+
const [hostPing, setHostPing] = React.useState({ ok: true, latencyMs: null })
|
|
690
|
+
const [telemetry, setTelemetry] = React.useState(null)
|
|
691
|
+
const [smoke, setSmoke] = React.useState({ testing: false, result: null })
|
|
692
|
+
|
|
693
|
+
const reloadTelemetry = () => {
|
|
694
|
+
fetch('/dsh-subscriptions/telemetry', { cache: 'no-store' })
|
|
695
|
+
.then((r) => r.json())
|
|
696
|
+
.then((d) => { if (d && d.ok && d.telemetry) setTelemetry(d.telemetry) })
|
|
697
|
+
.catch(() => {})
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const runSmokeTest = async (provider, index) => {
|
|
701
|
+
setSmoke({ testing: true, result: null })
|
|
702
|
+
try {
|
|
703
|
+
const res = await fetch('/dsh-subscriptions/smoke', {
|
|
704
|
+
method: 'POST',
|
|
705
|
+
headers: { 'Content-Type': 'application/json' },
|
|
706
|
+
body: JSON.stringify({ provider, index }),
|
|
707
|
+
})
|
|
708
|
+
const d = await res.json()
|
|
709
|
+
setSmoke({ testing: false, result: d })
|
|
710
|
+
reloadTelemetry()
|
|
711
|
+
} catch (e) {
|
|
712
|
+
setSmoke({ testing: false, result: { ok: false, error: { message: String(e && e.message || e) } } })
|
|
713
|
+
}
|
|
714
|
+
}
|
|
639
715
|
const [checking, setChecking] = React.useState({})
|
|
640
716
|
const [saved, setSaved] = React.useState(false)
|
|
641
717
|
const [tokenDraft, setTokenDraft] = React.useState({})
|
|
@@ -649,9 +725,21 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
649
725
|
setProviders((data && data.providers) || [])
|
|
650
726
|
}
|
|
651
727
|
|
|
652
|
-
const reload = () =>
|
|
653
|
-
|
|
654
|
-
|
|
728
|
+
const reload = () => {
|
|
729
|
+
const t0 = Date.now()
|
|
730
|
+
return fetch('/dsh-subscriptions/config', { cache: 'no-store' })
|
|
731
|
+
.then((res) => res.json())
|
|
732
|
+
.then((data) => {
|
|
733
|
+
setHostPing({ ok: true, latencyMs: Date.now() - t0 })
|
|
734
|
+
applyPayload(data)
|
|
735
|
+
reloadTelemetry()
|
|
736
|
+
return data
|
|
737
|
+
})
|
|
738
|
+
.catch((err) => {
|
|
739
|
+
setHostPing({ ok: false, latencyMs: null })
|
|
740
|
+
throw err
|
|
741
|
+
})
|
|
742
|
+
}
|
|
655
743
|
|
|
656
744
|
React.useEffect(() => {
|
|
657
745
|
let alive = true
|
|
@@ -857,11 +945,65 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
857
945
|
{ id: 'antigravity', name: 'Antigravity' },
|
|
858
946
|
]
|
|
859
947
|
|
|
948
|
+
const connectedCount = accounts.filter((a) => a.configured).length
|
|
949
|
+
|
|
860
950
|
return React.createElement('div', { className: 'dsub-wrap' },
|
|
951
|
+
React.createElement('div', { className: 'dsub-header-bar' },
|
|
952
|
+
React.createElement('div', { className: 'dsub-row', style: { marginTop: 0 } },
|
|
953
|
+
React.createElement('span', {
|
|
954
|
+
className: 'dsub-badge ' + (hostPing.ok ? 'dsub-badge-ok' : 'dsub-badge-bad')
|
|
955
|
+
}, hostPing.ok
|
|
956
|
+
? (hostPing.latencyMs != null ? 'Host online (' + hostPing.latencyMs + ' ms)' : t('hostOnline'))
|
|
957
|
+
: t('hostOffline')
|
|
958
|
+
),
|
|
959
|
+
React.createElement('span', {
|
|
960
|
+
className: 'dsub-badge ' + (connectedCount > 0 ? 'dsub-badge-ok' : 'dsub-badge-warn')
|
|
961
|
+
}, connectedCount > 0
|
|
962
|
+
? (connectedCount + ' ' + t('subsLogged'))
|
|
963
|
+
: t('subsNotLogged')
|
|
964
|
+
),
|
|
965
|
+
React.createElement('span', {
|
|
966
|
+
className: 'dsub-badge dsub-badge-neutral'
|
|
967
|
+
}, 'Pool: ' + accounts.length + ' accounts'),
|
|
968
|
+
),
|
|
969
|
+
React.createElement('button', {
|
|
970
|
+
type: 'button',
|
|
971
|
+
className: 'dsub-mini dsub-btn-primary',
|
|
972
|
+
disabled: smoke.testing,
|
|
973
|
+
onClick: () => runSmokeTest(),
|
|
974
|
+
}, smoke.testing ? t('smokeTesting') : t('runSmoke')),
|
|
975
|
+
),
|
|
976
|
+
smoke.result ? React.createElement('div', {
|
|
977
|
+
className: smoke.result.ok ? 'dsub-alert-ok' : 'dsub-alert-bad',
|
|
978
|
+
style: { marginBottom: '12px' }
|
|
979
|
+
}, smoke.result.ok
|
|
980
|
+
? ('🟢 ' + t('smokeOk') + ': ' + (smoke.result.provider || 'Provider').toUpperCase() + ' · ' + (smoke.result.latencyMs != null ? smoke.result.latencyMs + ' ms' : 'OK') + (smoke.result.label ? ' (' + smoke.result.label + ')' : ''))
|
|
981
|
+
: ('❌ ' + t('smokeFail') + ': ' + (smoke.result.error && (smoke.result.error.message || smoke.result.error.code) || 'Unknown error'))
|
|
982
|
+
) : null,
|
|
983
|
+
telemetry && telemetry.totalRequests > 0 ? React.createElement('div', { className: 'dsub-grid-2', style: { marginBottom: '14px' } },
|
|
984
|
+
React.createElement('div', { className: 'dsub-stat-box' },
|
|
985
|
+
React.createElement('div', { className: 'dsub-stat-val' }, telemetry.successRequests + ' / ' + telemetry.totalRequests),
|
|
986
|
+
React.createElement('div', { className: 'dsub-stat-lbl' }, t('statsRequests')),
|
|
987
|
+
),
|
|
988
|
+
React.createElement('div', { className: 'dsub-stat-box' },
|
|
989
|
+
React.createElement('div', { className: 'dsub-stat-val' }, (telemetry.avgLatencyMs || 0) + ' ms'),
|
|
990
|
+
React.createElement('div', { className: 'dsub-stat-lbl' }, t('statsLatency')),
|
|
991
|
+
),
|
|
992
|
+
React.createElement('div', { className: 'dsub-stat-box' },
|
|
993
|
+
React.createElement('div', { className: 'dsub-stat-val' }, (telemetry.successRate || 100) + '%'),
|
|
994
|
+
React.createElement('div', { className: 'dsub-stat-lbl' }, t('statsSuccessRate')),
|
|
995
|
+
),
|
|
996
|
+
React.createElement('div', { className: 'dsub-stat-box' },
|
|
997
|
+
React.createElement('div', { className: 'dsub-stat-val' }, telemetry.lastRequestAt ? Math.round((Date.now() - telemetry.lastRequestAt) / 60000) + 'm ago' : '—'),
|
|
998
|
+
React.createElement('div', { className: 'dsub-stat-lbl' }, t('statsLastActivity')),
|
|
999
|
+
),
|
|
1000
|
+
) : null,
|
|
861
1001
|
React.createElement('div', { className: 'dsub-block' },
|
|
862
1002
|
React.createElement('div', { className: 'dsub-h' }, t('title')),
|
|
863
1003
|
React.createElement('div', { className: 'dsub-sub' },
|
|
864
1004
|
t('intro')),
|
|
1005
|
+
React.createElement('details', { className: 'dsub-group', open: true },
|
|
1006
|
+
React.createElement('summary', { className: 'dsub-groupHead' }, t('groupAuth')),
|
|
865
1007
|
React.createElement('label', { className: 'dsub-row' },
|
|
866
1008
|
React.createElement('input', {
|
|
867
1009
|
type: 'checkbox',
|
|
@@ -875,23 +1017,26 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
875
1017
|
React.createElement('label', { className: 'dsub-row' },
|
|
876
1018
|
React.createElement('input', {
|
|
877
1019
|
type: 'checkbox',
|
|
878
|
-
checked: !!draft.
|
|
879
|
-
onChange: (e) => setDraft((d) => Object.assign({}, d, {
|
|
1020
|
+
checked: !!draft.autoLoopback,
|
|
1021
|
+
onChange: (e) => setDraft((d) => Object.assign({}, d, { autoLoopback: e.target.checked })),
|
|
880
1022
|
}),
|
|
881
|
-
React.createElement('span', null, t('
|
|
1023
|
+
React.createElement('span', null, t('autoLoopback')),
|
|
882
1024
|
),
|
|
883
1025
|
React.createElement('div', { className: 'dsub-sub' },
|
|
884
|
-
t('
|
|
1026
|
+
t('autoLoopbackHint')),
|
|
1027
|
+
),
|
|
1028
|
+
React.createElement('details', { className: 'dsub-group', open: true },
|
|
1029
|
+
React.createElement('summary', { className: 'dsub-groupHead' }, t('groupPrivacy')),
|
|
885
1030
|
React.createElement('label', { className: 'dsub-row' },
|
|
886
1031
|
React.createElement('input', {
|
|
887
1032
|
type: 'checkbox',
|
|
888
|
-
checked: !!draft.
|
|
889
|
-
onChange: (e) => setDraft((d) => Object.assign({}, d, {
|
|
1033
|
+
checked: !!draft.privacyMask,
|
|
1034
|
+
onChange: (e) => setDraft((d) => Object.assign({}, d, { privacyMask: e.target.checked })),
|
|
890
1035
|
}),
|
|
891
|
-
React.createElement('span', null, t('
|
|
1036
|
+
React.createElement('span', null, t('privacyMask')),
|
|
892
1037
|
),
|
|
893
1038
|
React.createElement('div', { className: 'dsub-sub' },
|
|
894
|
-
t('
|
|
1039
|
+
t('privacyMaskHint')),
|
|
895
1040
|
React.createElement('label', { className: 'dsub-row' },
|
|
896
1041
|
React.createElement('input', {
|
|
897
1042
|
type: 'checkbox',
|
|
@@ -902,6 +1047,9 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
902
1047
|
),
|
|
903
1048
|
React.createElement('div', { className: 'dsub-sub' },
|
|
904
1049
|
t('hideDeprecatedHint')),
|
|
1050
|
+
),
|
|
1051
|
+
React.createElement('details', { className: 'dsub-group', open: true },
|
|
1052
|
+
React.createElement('summary', { className: 'dsub-groupHead' }, t('groupRotation')),
|
|
905
1053
|
React.createElement('div', { className: 'dsub-row' },
|
|
906
1054
|
React.createElement('span', { className: 'dsub-h' }, t('composerQuota')),
|
|
907
1055
|
React.createElement('select', {
|
|
@@ -931,6 +1079,9 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
931
1079
|
),
|
|
932
1080
|
React.createElement('div', { className: 'dsub-sub' },
|
|
933
1081
|
t('expiryNotifyDaysHint')),
|
|
1082
|
+
),
|
|
1083
|
+
React.createElement('details', { className: 'dsub-group', open: true },
|
|
1084
|
+
React.createElement('summary', { className: 'dsub-groupHead' }, t('groupProvider')),
|
|
934
1085
|
React.createElement('label', { className: 'dsub-row' },
|
|
935
1086
|
React.createElement('input', {
|
|
936
1087
|
type: 'checkbox',
|
|
@@ -956,6 +1107,9 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
956
1107
|
),
|
|
957
1108
|
React.createElement('div', { className: 'dsub-sub' },
|
|
958
1109
|
t('codexVerbosityHint')),
|
|
1110
|
+
),
|
|
1111
|
+
React.createElement('details', { className: 'dsub-group', open: true },
|
|
1112
|
+
React.createElement('summary', { className: 'dsub-groupHead' }, t('groupFallback')),
|
|
959
1113
|
React.createElement('label', { className: 'dsub-row' },
|
|
960
1114
|
React.createElement('input', {
|
|
961
1115
|
type: 'checkbox',
|
|
@@ -982,6 +1136,21 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
982
1136
|
onChange: (e) => setDraft((d) => Object.assign({}, d, { ollamaFallbackModel: e.target.value })),
|
|
983
1137
|
}),
|
|
984
1138
|
) : null,
|
|
1139
|
+
),
|
|
1140
|
+
React.createElement('details', { className: 'dsub-group' },
|
|
1141
|
+
React.createElement('summary', { className: 'dsub-groupHead' }, '🎯 ' + t('modelsCatalogTitle')),
|
|
1142
|
+
React.createElement('div', { className: 'dsub-sub', style: { marginTop: '4px' } }, t('modelsCatalogDesc')),
|
|
1143
|
+
React.createElement('div', { className: 'dsub-model-grid' },
|
|
1144
|
+
React.createElement('div', { className: 'dsub-model-chip' }, React.createElement('code', null, 'gpt-4o'), React.createElement('span', { className: 'dsub-model-tag' }, '128k · Vision')),
|
|
1145
|
+
React.createElement('div', { className: 'dsub-model-chip' }, React.createElement('code', null, 'o1 / o3-mini'), React.createElement('span', { className: 'dsub-model-tag' }, '200k · Reasoning')),
|
|
1146
|
+
React.createElement('div', { className: 'dsub-model-chip' }, React.createElement('code', null, 'claude-3-7-sonnet'), React.createElement('span', { className: 'dsub-model-tag' }, '200k · Hybrid')),
|
|
1147
|
+
React.createElement('div', { className: 'dsub-model-chip' }, React.createElement('code', null, 'claude-3-5-sonnet'), React.createElement('span', { className: 'dsub-model-tag' }, '200k · Coding')),
|
|
1148
|
+
React.createElement('div', { className: 'dsub-model-chip' }, React.createElement('code', null, 'grok-beta'), React.createElement('span', { className: 'dsub-model-tag' }, '128k · Speed')),
|
|
1149
|
+
React.createElement('div', { className: 'dsub-model-chip' }, React.createElement('code', null, 'gemini-2.5-pro'), React.createElement('span', { className: 'dsub-model-tag' }, '1M · Context')),
|
|
1150
|
+
React.createElement('div', { className: 'dsub-model-chip' }, React.createElement('code', null, 'kimi-k1.5'), React.createElement('span', { className: 'dsub-model-tag' }, '128k · Math')),
|
|
1151
|
+
React.createElement('div', { className: 'dsub-model-chip' }, React.createElement('code', null, 'glm-4-plus'), React.createElement('span', { className: 'dsub-model-tag' }, '128k · Chinese')),
|
|
1152
|
+
),
|
|
1153
|
+
),
|
|
985
1154
|
React.createElement('details', { className: 'dsub-details' },
|
|
986
1155
|
React.createElement('summary', { className: 'dsub-summary' }, t('advancedSettings')),
|
|
987
1156
|
React.createElement('div', { className: 'dsub-block' },
|
|
@@ -1691,15 +1860,20 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1691
1860
|
const rows = state.accounts.map((a, i) => {
|
|
1692
1861
|
const isLogged = state.logged.includes(a.provider)
|
|
1693
1862
|
const cooled = a.cooldownUntil && a.cooldownUntil > now
|
|
1863
|
+
const quarantined = Boolean(a.quarantineReason) && Number(a.quarantineUntil || 0) > now
|
|
1694
1864
|
const pct = a.usagePercent != null ? Math.round(a.usagePercent) : null
|
|
1695
1865
|
const b = brandBadge(a.provider)
|
|
1696
1866
|
const isActiveThis = state.active && state.active.provider === a.provider && state.active.index === a.index
|
|
1697
1867
|
|
|
1698
1868
|
let statusClass = 'dsub-statusOff'
|
|
1699
1869
|
let statusText = t('subsNotLogged')
|
|
1700
|
-
if (
|
|
1870
|
+
if (quarantined) {
|
|
1871
|
+
statusClass = 'dsub-statusBad'
|
|
1872
|
+
statusText = a.quarantineReason === 'VERIFY' ? t('verifyAccount') : String(a.quarantineReason)
|
|
1873
|
+
} else if (cooled) {
|
|
1701
1874
|
statusClass = 'dsub-statusWarn'
|
|
1702
|
-
|
|
1875
|
+
// Only some model families may be cooled down - say which ones.
|
|
1876
|
+
statusText = t('subsCooldown') + (Array.isArray(a.cooldownFamilies) && a.cooldownFamilies.length ? ' · ' + a.cooldownFamilies.join('/') : '')
|
|
1703
1877
|
} else if (isLogged) {
|
|
1704
1878
|
if (pct >= 90) { statusClass = 'dsub-statusBad'; statusText = '90%+ ' + t('subsUsed') }
|
|
1705
1879
|
else if (pct >= 50) { statusClass = 'dsub-statusWarn'; statusText = pct + '% ' + t('subsUsed') }
|
|
@@ -1715,7 +1889,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1715
1889
|
isActiveThis ? React.createElement('span', { className: 'dsub-pillTag', style: { background: 'color-mix(in srgb,var(--dsw-alias-state-success-primary) 15%,transparent)', color: 'var(--dsw-alias-state-success-primary)' } }, 'ACTIVE') : null,
|
|
1716
1890
|
a.proxy ? React.createElement('span', { className: 'dsub-dim', title: a.proxy }, '🌐 proxy') : null,
|
|
1717
1891
|
),
|
|
1718
|
-
React.createElement('span', { className: 'dsub-accStatusTag ' + statusClass }, statusText),
|
|
1892
|
+
React.createElement('span', { className: 'dsub-accStatusTag ' + statusClass, role: 'status', 'aria-live': 'polite' }, statusText),
|
|
1719
1893
|
),
|
|
1720
1894
|
(isLogged && pct != null) ? React.createElement('div', { className: 'dsub-barTrack', style: { marginTop: 4, height: 4 } },
|
|
1721
1895
|
React.createElement('div', {
|
|
@@ -1746,7 +1920,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1746
1920
|
className: 'dsub-modalWrap',
|
|
1747
1921
|
onClick: (e) => { if (e.target === e.currentTarget) setState((s) => Object.assign({}, s, { open: false })) },
|
|
1748
1922
|
},
|
|
1749
|
-
React.createElement('div', { className: 'dsub-modal', role: 'dialog', 'aria-label': t('subsModalTitle') },
|
|
1923
|
+
React.createElement('div', { className: 'dsub-modal', role: 'dialog', 'aria-modal': 'true', 'aria-label': t('subsModalTitle') },
|
|
1750
1924
|
React.createElement('div', { className: 'dsub-modalHead' },
|
|
1751
1925
|
React.createElement('div', { className: 'dsub-modalTitleWrap' },
|
|
1752
1926
|
React.createElement('div', { className: 'dsub-modalIcon' }, '⚡'),
|
|
@@ -1765,6 +1939,8 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1765
1939
|
React.createElement('button', {
|
|
1766
1940
|
type: 'button',
|
|
1767
1941
|
className: 'dsub-mini',
|
|
1942
|
+
autoFocus: true,
|
|
1943
|
+
'aria-label': t('closeModal'),
|
|
1768
1944
|
onClick: () => setState((s) => Object.assign({}, s, { open: false })),
|
|
1769
1945
|
}, '✕'),
|
|
1770
1946
|
),
|
package/lib/history.js
CHANGED
|
@@ -4,17 +4,24 @@ import { homedir } from 'node:os'
|
|
|
4
4
|
|
|
5
5
|
// #65: request and cost history. Stored in
|
|
6
6
|
// ~/.dsh/storages/dsh-subscriptions/history.json (JSON array, newest first).
|
|
7
|
-
// All IO is best-effort
|
|
7
|
+
// All IO is best-effort: a write failure must never crash the harness.
|
|
8
8
|
|
|
9
9
|
export const DEFAULT_HISTORY_DIR = join(homedir(), '.dsh', 'storages', 'dsh-subscriptions')
|
|
10
10
|
|
|
11
11
|
export class HistoryStore {
|
|
12
|
-
constructor(dir = DEFAULT_HISTORY_DIR, ttlMs = 7 * 24 * 60 * 60 * 1000) {
|
|
12
|
+
constructor(dir = DEFAULT_HISTORY_DIR, ttlMs = 7 * 24 * 60 * 60 * 1000, debounceMs = 0) {
|
|
13
13
|
this.dir = dir
|
|
14
14
|
this.ttlMs = ttlMs
|
|
15
|
+
this.debounceMs = debounceMs
|
|
15
16
|
this.path = join(dir, 'history.json')
|
|
16
17
|
this.rows = []
|
|
18
|
+
this._persistTimer = null
|
|
17
19
|
this._load()
|
|
20
|
+
|
|
21
|
+
if (typeof process !== 'undefined' && typeof process.on === 'function') {
|
|
22
|
+
this._exitHandler = () => this.flush()
|
|
23
|
+
process.on('beforeExit', this._exitHandler)
|
|
24
|
+
}
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
_load() {
|
|
@@ -36,6 +43,24 @@ export class HistoryStore {
|
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
_persist() {
|
|
46
|
+
if (this.debounceMs > 0) {
|
|
47
|
+
if (this._persistTimer) return
|
|
48
|
+
this._persistTimer = setTimeout(() => {
|
|
49
|
+
this._persistTimer = null
|
|
50
|
+
this.flush()
|
|
51
|
+
}, this.debounceMs)
|
|
52
|
+
if (typeof this._persistTimer.unref === 'function') this._persistTimer.unref()
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
this.flush()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Write pending updates to disk immediately. */
|
|
59
|
+
flush() {
|
|
60
|
+
if (this._persistTimer) {
|
|
61
|
+
clearTimeout(this._persistTimer)
|
|
62
|
+
this._persistTimer = null
|
|
63
|
+
}
|
|
39
64
|
try {
|
|
40
65
|
mkdirSync(this.dir, { recursive: true })
|
|
41
66
|
writeFileSync(this.path, JSON.stringify(this.rows))
|
|
@@ -63,4 +88,55 @@ export class HistoryStore {
|
|
|
63
88
|
size() {
|
|
64
89
|
return this.rows.length
|
|
65
90
|
}
|
|
91
|
+
|
|
92
|
+
/** Summarize session and 24-hour telemetry for UI stat cards. */
|
|
93
|
+
telemetrySummary() {
|
|
94
|
+
const total = this.rows.length
|
|
95
|
+
if (total === 0) {
|
|
96
|
+
return {
|
|
97
|
+
totalRequests: 0,
|
|
98
|
+
requests24h: 0,
|
|
99
|
+
successRequests: 0,
|
|
100
|
+
errorRequests: 0,
|
|
101
|
+
successRate: 100,
|
|
102
|
+
avgLatencyMs: 0,
|
|
103
|
+
lastLatencyMs: 0,
|
|
104
|
+
lastRequestAt: null,
|
|
105
|
+
lastProvider: null,
|
|
106
|
+
lastModel: null,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
let success = 0
|
|
110
|
+
let error = 0
|
|
111
|
+
let totalMs = 0
|
|
112
|
+
let latencyCount = 0
|
|
113
|
+
const now = Date.now()
|
|
114
|
+
const last24h = now - 24 * 60 * 60 * 1000
|
|
115
|
+
let count24h = 0
|
|
116
|
+
|
|
117
|
+
for (const r of this.rows) {
|
|
118
|
+
if (r.ts >= last24h) count24h++
|
|
119
|
+
const st = Number(r.status || 0)
|
|
120
|
+
if (st >= 200 && st < 400) success++
|
|
121
|
+
else if (st >= 400) error++
|
|
122
|
+
if (typeof r.ms === 'number' && r.ms > 0) {
|
|
123
|
+
totalMs += r.ms
|
|
124
|
+
latencyCount++
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const first = this.rows[0] // newest first
|
|
129
|
+
return {
|
|
130
|
+
totalRequests: total,
|
|
131
|
+
requests24h: count24h,
|
|
132
|
+
successRequests: success,
|
|
133
|
+
errorRequests: error,
|
|
134
|
+
successRate: total > 0 ? Math.round((success / total) * 100) : 100,
|
|
135
|
+
avgLatencyMs: latencyCount > 0 ? Math.round(totalMs / latencyCount) : 0,
|
|
136
|
+
lastLatencyMs: (first && typeof first.ms === 'number') ? first.ms : 0,
|
|
137
|
+
lastRequestAt: (first && first.ts) || null,
|
|
138
|
+
lastProvider: (first && first.provider) || null,
|
|
139
|
+
lastModel: (first && first.model) || null,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
66
142
|
}
|
package/lib/http.js
CHANGED
|
@@ -30,7 +30,10 @@ export function readBody(req, maxBytes = 64 * 1024) {
|
|
|
30
30
|
})
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* @param {import('node:http').IncomingMessage} request
|
|
35
|
+
* @returns {boolean}
|
|
36
|
+
* Reject cross-site writes. Do not require loopback: Web UI is used over LAN and reverse proxies. */
|
|
34
37
|
export function isTrustedSettingsRequest(request) {
|
|
35
38
|
return request.headers['sec-fetch-site'] !== 'cross-site'
|
|
36
39
|
}
|
package/lib/index.js
CHANGED
|
@@ -440,6 +440,11 @@ export function apply(ctx, config) {
|
|
|
440
440
|
configured: info.configured,
|
|
441
441
|
writable: info.writable,
|
|
442
442
|
cooldownUntil: info.cooldownUntil,
|
|
443
|
+
// #303: expose the cooldown scope and quarantine so the card can
|
|
444
|
+
// explain why only some models are blocked.
|
|
445
|
+
cooldownFamilies: info.cooldownFamilies || null,
|
|
446
|
+
quarantineUntil: info.quarantineUntil || 0,
|
|
447
|
+
quarantineReason: info.quarantineReason || null,
|
|
443
448
|
usagePercent: info.usagePercent,
|
|
444
449
|
quota: info.quota || null,
|
|
445
450
|
usage: info.usage || null,
|
package/lib/quarantine.js
CHANGED
|
@@ -29,10 +29,3 @@ export function isQuarantined(account, nowMs = Date.now()) {
|
|
|
29
29
|
return Number(account.quarantineUntil) > Number(nowMs)
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export function releaseFromQuarantine(account) {
|
|
33
|
-
const next = { ...account }
|
|
34
|
-
delete next.quarantineReason
|
|
35
|
-
delete next.quarantineUntil
|
|
36
|
-
if (next.status === 'quarantine') next.status = 'active'
|
|
37
|
-
return next
|
|
38
|
-
}
|
package/lib/ratelimit.js
CHANGED
|
@@ -208,11 +208,3 @@ export function quotaSnapshot(provider, headers, bodyJson, nowMs = Date.now()) {
|
|
|
208
208
|
return { remaining, limit, resetAt: resetAt ?? null, usedPercent, measuredAt: nowMs }
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
export function formatQuota(q) {
|
|
212
|
-
if (!q) return ""
|
|
213
|
-
const parts = []
|
|
214
|
-
if (q.remaining!=null && q.limit!=null) parts.push(q.remaining+"/"+q.limit)
|
|
215
|
-
else if (q.remaining!=null) parts.push("left "+q.remaining)
|
|
216
|
-
if (q.resetAt) parts.push("reset "+new Date(q.resetAt).toLocaleString())
|
|
217
|
-
return parts.join(" · ")
|
|
218
|
-
}
|
package/lib/refs.js
CHANGED
|
@@ -61,6 +61,11 @@ export function displayName(provider) {
|
|
|
61
61
|
return DISPLAY[provider] || displayExtra[provider] || provider
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* @param {string} provider
|
|
66
|
+
* @param {number} index
|
|
67
|
+
* @returns {string} provider-scoped credential ref, e.g. CODEX_OAUTH_1
|
|
68
|
+
*/
|
|
64
69
|
export function oauthRef(provider, index) {
|
|
65
70
|
const n = Number(index)
|
|
66
71
|
if (!Number.isInteger(n) || n < 1) throw new Error('account index must be an integer >= 1')
|
package/lib/relative-time.js
CHANGED
|
@@ -26,6 +26,12 @@ function fill(template, n) {
|
|
|
26
26
|
return String(template).replace('{n}', String(n))
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* @param {number} resetAt epoch ms of the quota window reset
|
|
31
|
+
* @param {string} [lang]
|
|
32
|
+
* @param {number} [now]
|
|
33
|
+
* @returns {string} human-readable remaining time, down to the minute
|
|
34
|
+
*/
|
|
29
35
|
export function formatRelativeReset(resetAt, lang = 'ru', now = Date.now()) {
|
|
30
36
|
if (typeof resetAt !== 'number' || !Number.isFinite(resetAt) || resetAt <= 0) return ''
|
|
31
37
|
const delta = resetAt - now
|
package/lib/routes/status.js
CHANGED
|
@@ -32,7 +32,7 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
32
32
|
ctx.effect(() => ctx.webServer.register({
|
|
33
33
|
kind: 'exact',
|
|
34
34
|
path: '/dsh-subscriptions/config',
|
|
35
|
-
handler: async (req, res) => {
|
|
35
|
+
handler: safeJsonHandler(async (req, res) => {
|
|
36
36
|
if (req.method === 'GET') {
|
|
37
37
|
writeJson(res, 200, await configResponse())
|
|
38
38
|
return
|
|
@@ -67,10 +67,7 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
67
67
|
} catch (e) {
|
|
68
68
|
writeJson(res, 400, { ok: false, error: { code: 'save', message: String(e && e.message || e) } })
|
|
69
69
|
}
|
|
70
|
-
},
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
}),
|
|
74
71
|
}), 'dsh-subscriptions: /config')
|
|
75
72
|
|
|
76
73
|
|
|
@@ -239,7 +236,7 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
239
236
|
ctx.effect(() => ctx.webServer.register({
|
|
240
237
|
kind: 'exact',
|
|
241
238
|
path: '/dsh-subscriptions/history',
|
|
242
|
-
handler: async (req, res) => {
|
|
239
|
+
handler: safeJsonHandler(async (req, res) => {
|
|
243
240
|
if (req.method !== 'GET') {
|
|
244
241
|
writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET only' } })
|
|
245
242
|
return
|
|
@@ -251,6 +248,127 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
251
248
|
}
|
|
252
249
|
const limit = Math.min(Number(queryOf(req).get('limit') || '10'), 100)
|
|
253
250
|
writeJson(res, 200, { ok: true, total: history.size(), items: history.recent(limit) })
|
|
254
|
-
},
|
|
251
|
+
}),
|
|
255
252
|
}), 'dsh-subscriptions: /history')
|
|
253
|
+
|
|
254
|
+
// Telemetry summary route for UI stat cards (requests, latency, success rate)
|
|
255
|
+
ctx.effect(() => ctx.webServer.register({
|
|
256
|
+
kind: 'exact',
|
|
257
|
+
path: '/dsh-subscriptions/telemetry',
|
|
258
|
+
handler: safeJsonHandler(async (req, res) => {
|
|
259
|
+
if (req.method !== 'GET') {
|
|
260
|
+
writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET only' } })
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
const summary = typeof history.telemetrySummary === 'function'
|
|
264
|
+
? history.telemetrySummary()
|
|
265
|
+
: { totalRequests: history.size(), requests24h: 0, successRequests: 0, errorRequests: 0, successRate: 100, avgLatencyMs: 0 }
|
|
266
|
+
writeJson(res, 200, { ok: true, telemetry: summary })
|
|
267
|
+
}),
|
|
268
|
+
}), 'dsh-subscriptions: /telemetry')
|
|
269
|
+
|
|
270
|
+
// Live smoke ping test for active subscription providers
|
|
271
|
+
ctx.effect(() => ctx.webServer.register({
|
|
272
|
+
kind: 'exact',
|
|
273
|
+
path: '/dsh-subscriptions/smoke',
|
|
274
|
+
handler: safeJsonHandler(async (req, res) => {
|
|
275
|
+
if (req.method !== 'POST') {
|
|
276
|
+
writeJson(res, 405, { ok: false, error: { code: 'method', message: 'POST only' } })
|
|
277
|
+
return
|
|
278
|
+
}
|
|
279
|
+
let payload = {}
|
|
280
|
+
try { payload = JSON.parse((await readBody(req, 64 * 1024)).toString('utf8') || '{}') } catch {}
|
|
281
|
+
|
|
282
|
+
let provider = payload.provider
|
|
283
|
+
let index = payload.index || 1
|
|
284
|
+
|
|
285
|
+
if (!provider) {
|
|
286
|
+
const accounts = await accountsView()
|
|
287
|
+
const connected = accounts.find((a) => a.configured)
|
|
288
|
+
if (connected) {
|
|
289
|
+
provider = connected.provider
|
|
290
|
+
index = connected.index || 1
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!provider || !isProvider(provider)) {
|
|
295
|
+
writeJson(res, 400, { ok: false, error: { code: 'no_connected_accounts', message: 'No connected subscription account found to test' } })
|
|
296
|
+
return
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const ref = oauthRef(provider, index)
|
|
300
|
+
const info = await store.describeRef(ref)
|
|
301
|
+
if (!info.configured) {
|
|
302
|
+
writeJson(res, 400, { ok: false, provider, index, ref, error: { code: 'not_connected', message: 'Account is not connected' } })
|
|
303
|
+
return
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const t0 = Date.now()
|
|
307
|
+
let blob
|
|
308
|
+
try {
|
|
309
|
+
blob = await store.loadBlob(ref)
|
|
310
|
+
blob = await store.ensureFresh(provider, blob, ref)
|
|
311
|
+
} catch (e) {
|
|
312
|
+
const latencyMs = Date.now() - t0
|
|
313
|
+
history.add({ provider, ref, path: "/smoke", status: 401, ms: latencyMs })
|
|
314
|
+
writeJson(res, 502, { ok: false, provider, index, ref, latencyMs, error: { code: 'auth_failed', message: String(e && e.message || e) } })
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const vendor = getVendor(provider)
|
|
319
|
+
if (typeof vendor.check === 'function') {
|
|
320
|
+
let capturedQuota = null
|
|
321
|
+
const probeFetch = async (url, init) => {
|
|
322
|
+
const res2 = await fetch(url, init)
|
|
323
|
+
try {
|
|
324
|
+
const snap = quotaSnapshot(provider, res2.headers, null, Date.now())
|
|
325
|
+
if (snap) { capturedQuota = snap; store.rememberQuota(ref, snap) }
|
|
326
|
+
} catch {}
|
|
327
|
+
return res2
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
await vendor.check(blob, vendorConfig(provider, live()), probeFetch)
|
|
331
|
+
const latencyMs = Date.now() - t0
|
|
332
|
+
history.add({ provider, ref, path: "/smoke", status: 200, ms: latencyMs })
|
|
333
|
+
writeJson(res, 200, {
|
|
334
|
+
ok: true,
|
|
335
|
+
provider,
|
|
336
|
+
index,
|
|
337
|
+
ref,
|
|
338
|
+
latencyMs,
|
|
339
|
+
label: pmL(blob.label),
|
|
340
|
+
email: pmE(blob.email),
|
|
341
|
+
quota: capturedQuota || info.quota || null,
|
|
342
|
+
usagePercent: info.usagePercent ?? null,
|
|
343
|
+
})
|
|
344
|
+
return
|
|
345
|
+
} catch (e) {
|
|
346
|
+
const latencyMs = Date.now() - t0
|
|
347
|
+
history.add({ provider, ref, path: "/smoke", status: 502, ms: latencyMs })
|
|
348
|
+
writeJson(res, 502, {
|
|
349
|
+
ok: false,
|
|
350
|
+
provider,
|
|
351
|
+
index,
|
|
352
|
+
ref,
|
|
353
|
+
latencyMs,
|
|
354
|
+
error: { code: e && e.code ? e.code : 'VENDOR', message: String(e && e.message || e).slice(0, 300) },
|
|
355
|
+
})
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const latencyMs = Date.now() - t0
|
|
361
|
+
history.add({ provider, ref, path: "/smoke", status: 200, ms: latencyMs })
|
|
362
|
+
writeJson(res, 200, {
|
|
363
|
+
ok: true,
|
|
364
|
+
provider,
|
|
365
|
+
index,
|
|
366
|
+
ref,
|
|
367
|
+
latencyMs,
|
|
368
|
+
label: pmL(blob.label),
|
|
369
|
+
email: pmE(blob.email),
|
|
370
|
+
quota: info.quota || null,
|
|
371
|
+
})
|
|
372
|
+
}),
|
|
373
|
+
}), 'dsh-subscriptions: /smoke')
|
|
256
374
|
}
|
package/lib/session-pin.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
const sessionPins = new Map()
|
|
2
2
|
const DEFAULT_TTL_MS = 30 * 60 * 1000 // 30 mins
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Bind a session id to an account ref for ttlMs (default 30 minutes).
|
|
6
|
+
* @param {string|undefined} sessionId
|
|
7
|
+
* @param {string|undefined} accountRef
|
|
8
|
+
* @param {number} [ttlMs]
|
|
9
|
+
* @returns {void}
|
|
10
|
+
*/
|
|
4
11
|
export function pinSession(sessionId, accountRef, ttlMs = DEFAULT_TTL_MS) {
|
|
5
12
|
if (!sessionId || !accountRef) return
|
|
6
13
|
sessionPins.set(sessionId, {
|
|
@@ -9,6 +16,10 @@ export function pinSession(sessionId, accountRef, ttlMs = DEFAULT_TTL_MS) {
|
|
|
9
16
|
})
|
|
10
17
|
}
|
|
11
18
|
|
|
19
|
+
/**
|
|
20
|
+
* @param {string|undefined} sessionId
|
|
21
|
+
* @returns {string|null} the bound account ref, or null when absent/expired
|
|
22
|
+
*/
|
|
12
23
|
export function getPinnedAccountRef(sessionId) {
|
|
13
24
|
if (!sessionId) return null
|
|
14
25
|
const entry = sessionPins.get(sessionId)
|
|
@@ -150,7 +150,9 @@ export async function usage(blob, _cfg, fetchImpl) {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
export async function* streamOnce({ blob, options, fetchImpl, headers, config, signal, saveBlob }) {
|
|
153
|
-
|
|
153
|
+
// Abort must cover the project pre-flight as well as the stream.
|
|
154
|
+
const preflight = (u, i) => fetchImpl(u, { ...(i || {}), signal })
|
|
155
|
+
const ready = await withProject(blob, preflight, saveBlob)
|
|
154
156
|
const { envelope, sessionId } = streamEnvelope({
|
|
155
157
|
projectId: ready.projectId,
|
|
156
158
|
model: options.model,
|
package/lib/vendors/grok.js
CHANGED
|
@@ -236,7 +236,8 @@ export async function* streamOnce({ blob, options, fetchImpl, headers, config, s
|
|
|
236
236
|
if (!body.instructions) delete body.instructions
|
|
237
237
|
// grok manages reasoning itself (cli catalog aware); drop the codex-level field
|
|
238
238
|
delete body.reasoning
|
|
239
|
-
|
|
239
|
+
// Thread the caller AbortSignal through the catalog pre-flight too.
|
|
240
|
+
const catalog = await cliCatalogCached(blob, config, (u, i) => fetchImpl(u, { ...(i || {}), signal })).catch(() => new Map())
|
|
240
241
|
const reasoning = grokReasoningBody(options.model, options.reasoningEffort, catalog)
|
|
241
242
|
if (reasoning) body.reasoning = reasoning
|
|
242
243
|
body = grokBodyForModel(options.model, body)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
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",
|
|
@@ -34,9 +34,6 @@
|
|
|
34
34
|
"bugs": {
|
|
35
35
|
"url": "https://github.com/GooDAnDReaDY/dsh-subscriptions/issues"
|
|
36
36
|
},
|
|
37
|
-
"scripts": {
|
|
38
|
-
"test": "eslint lib/ && node --test test/*.test.mjs"
|
|
39
|
-
},
|
|
40
37
|
"dsh": {
|
|
41
38
|
"bundle": {
|
|
42
39
|
"patch": "./cordis.patch.yml"
|
|
@@ -62,6 +59,12 @@
|
|
|
62
59
|
"undici": "^8.10.1"
|
|
63
60
|
},
|
|
64
61
|
"devDependencies": {
|
|
65
|
-
"@deepseek-ai/dsh-llm": "0.1.0-rc.8"
|
|
62
|
+
"@deepseek-ai/dsh-llm": "0.1.0-rc.8",
|
|
63
|
+
"@types/node": "^22.20.2",
|
|
64
|
+
"typescript": "^7.0.2"
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"test": "eslint lib/ && node --test test/*.test.mjs",
|
|
68
|
+
"typecheck": "tsc -p tsconfig.checkjs.json"
|
|
66
69
|
}
|
|
67
|
-
}
|
|
70
|
+
}
|
package/lib/ratelimit-parser.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export function parseRateLimitHeaders(headers) {
|
|
2
|
-
if (!headers || typeof headers !== 'object') return null
|
|
3
|
-
|
|
4
|
-
const get = (key) => {
|
|
5
|
-
const k = Object.keys(headers).find((h) => h.toLowerCase() === key.toLowerCase())
|
|
6
|
-
return k ? headers[k] : null
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const remainingTokens = get('x-ratelimit-remaining-tokens')
|
|
10
|
-
const resetTokens = get('x-ratelimit-reset-tokens')
|
|
11
|
-
const remainingRequests = get('x-ratelimit-remaining-requests')
|
|
12
|
-
const resetRequests = get('x-ratelimit-reset-requests')
|
|
13
|
-
|
|
14
|
-
if (!remainingTokens && !resetTokens && !remainingRequests && !resetRequests) {
|
|
15
|
-
return null
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
remainingTokens: remainingTokens != null ? Number(remainingTokens) : null,
|
|
20
|
-
resetTokensTime: resetTokens || null,
|
|
21
|
-
remainingRequests: remainingRequests != null ? Number(remainingRequests) : null,
|
|
22
|
-
resetRequestsTime: resetRequests || null
|
|
23
|
-
}
|
|
24
|
-
}
|
package/lib/ui-styles.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
export const CSS =
|
|
2
|
-
'.dsub-card{list-style:none;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;transition:border-color .16s,background .16s}' +
|
|
3
|
-
'.dsub-card:hover{border-color:var(--dsw-alias-label-dimmed)}' +
|
|
4
|
-
'.dsub-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}' +
|
|
5
|
-
'.dsub-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:18px 20px;display:flex}' +
|
|
6
|
-
'.dsub-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}' +
|
|
7
|
-
'.dsub-headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}' +
|
|
8
|
-
'.dsub-name{color:var(--dsw-alias-label-primary);font-size:16px;font-weight:600;line-height:1.4}' +
|
|
9
|
-
'.dsub-description{color:var(--dsw-alias-label-secondary);font-size:13px}' +
|
|
10
|
-
'.dsub-chev{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}' +
|
|
11
|
-
'.dsub-chevOpen{transform:rotate(180deg)}' +
|
|
12
|
-
'.dsub-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 20px;padding-bottom:12px}' +
|
|
13
|
-
'.dsub-block{margin-top:10px}' +
|
|
14
|
-
'.dsub-row{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-top:8px}' +
|
|
15
|
-
'.dsub-grow{flex:1 1 180px;min-width:140px}' +
|
|
16
|
-
'.dsub-helpBox{margin-top:8px;padding:12px 14px;border-radius:8px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);font-size:12px;line-height:1.6}' +
|
|
17
|
-
'.dsub-helpHead{display:flex;align-items:center;justify-content:space-between;font-weight:600;margin-bottom:8px;color:var(--dsw-alias-brand-primary)}' +
|
|
18
|
-
'.dsub-helpStep{margin-bottom:6px;color:var(--dsw-alias-label-primary)}' +
|
|
19
|
-
'.dsub-btnActive{background:var(--dsw-alias-brand-primary);color:#fff;border-color:var(--dsw-alias-brand-primary)}' +
|
|
20
|
-
'.dsub-h{font-size:13.5px;font-weight:600;color:var(--dsw-alias-label-primary)}' +
|
|
21
|
-
'.dsub-sub{font-size:12px;color:var(--dsw-alias-label-secondary)}' +
|
|
22
|
-
'.dsub-dim{font-size:11.5px;color:var(--dsw-alias-label-tertiary)}' +
|
|
23
|
-
'.dsub-mini{appearance:none;font:inherit;font-size:12px;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:3px 8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);transition:all .15s}' +
|
|
24
|
-
'.dsub-mini:hover{background:var(--dsw-alias-bg-layer-1);border-color:var(--dsw-alias-label-dimmed)}' +
|
|
25
|
-
'.dsub-ok{font-size:12px;color:var(--dsw-alias-state-success-primary)}' +
|
|
26
|
-
'.dsub-bad{font-size:12px;color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;word-break:break-word;max-width:100%}' +
|
|
27
|
-
'.dsub-hint{overflow-wrap:anywhere;word-break:break-word;max-width:100%}' +
|
|
28
|
-
'.dsub-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 14px;font-size:13px;font-weight:500;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);transition:all .15s}' +
|
|
29
|
-
'.dsub-btn:hover{background:var(--dsw-alias-bg-layer-1);border-color:var(--dsw-alias-label-dimmed)}' +
|
|
30
|
-
'.dsub-inp{height:36px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;min-width:180px}' +
|
|
31
|
-
'.dsub-inp:focus{outline:none;border-color:var(--dsw-alias-brand-primary)}' +
|
|
32
|
-
'.dsub-box{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:12px 14px;margin-top:8px;background:var(--dsw-alias-bg-layer-2)}' +
|
|
33
|
-
'.dsub-cooldown{display:inline-block;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-state-warning-primary);color:#000;font-size:11px;font-weight:600}' +
|
|
34
|
-
'.dsub-bar{position:relative;height:10px;border-radius:999px;background:var(--dsw-alias-bg-layer-1);overflow:hidden;flex:1;min-width:80px}' +
|
|
35
|
-
'.dsub-barFill{height:100%;border-radius:3px;background:var(--dsw-alias-state-success-primary);transition:width .2s}' +
|
|
36
|
-
'.dsub-barWarn .dsub-barFill{background:var(--dsw-alias-state-warning-primary)}' +
|
|
37
|
-
'.dsub-barFull .dsub-barFill{background:var(--dsw-alias-state-error-primary)}' +
|
|
38
|
-
'.dsub-barRow{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--dsw-alias-label-secondary)}' +
|
|
39
|
-
'.dsub-manual{display:flex;flex-direction:column;gap:6px;margin-top:4px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l2)}' +
|
|
40
|
-
'.dsub-verify{font-size:12px;color:var(--dsw-alias-state-warning-primary)}' +
|
|
41
|
-
'.dsub-verify a{color:var(--dsw-alias-brand-primary)}' +
|
|
42
|
-
'.dsub-pill{display:inline-flex;align-items:center;gap:7px;height:28px;padding:0 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-size:12px;font-weight:600;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,.15),inset 0 1px 0 rgba(255,255,255,.06);transition:all .16s ease;user-select:none}' +
|
|
43
|
-
'.dsub-pill:hover{background:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-tertiary);transform:translateY(-1px);box-shadow:0 3px 8px rgba(0,0,0,.25),inset 0 1px 0 rgba(255,255,255,.1)}' +
|
|
44
|
-
'.dsub-pill:active{transform:translateY(0)}' +
|
|
45
|
-
'.dsub-pillTag{padding:1px 5px;border-radius:4px;font-size:10.5px;font-weight:700;letter-spacing:.02em;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary)}' +
|
|
46
|
-
'.dsub-led{width:7px;height:7px;border-radius:50%;flex:none;transition:all .2s ease}' +
|
|
47
|
-
'.dsub-ledOk{background:var(--dsw-alias-state-success-primary,#10b981);box-shadow:0 0 6px color-mix(in srgb,var(--dsw-alias-state-success-primary) 70%,transparent)}' +
|
|
48
|
-
'.dsub-ledWarn{background:var(--dsw-alias-state-warning-primary,#f59e0b);box-shadow:0 0 6px color-mix(in srgb,var(--dsw-alias-state-warning-primary) 70%,transparent)}' +
|
|
49
|
-
'.dsub-ledBad{background:var(--dsw-alias-state-error-primary,#ef4444);box-shadow:0 0 6px color-mix(in srgb,var(--dsw-alias-state-error-primary) 70%,transparent)}' +
|
|
50
|
-
'.dsub-ledOff{background:var(--dsw-alias-label-tertiary,#6b7280)}' +
|
|
51
|
-
'.dsub-modalWrap{position:fixed;inset:0;z-index:10020;display:grid;place-items:center;background:rgba(0,0,0,.65);backdrop-filter:blur(10px);animation:dsubFadeIn .16s ease-out}' +
|
|
52
|
-
'.dsub-modal{width:min(520px,94vw);max-height:min(85vh,720px);overflow-y:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:16px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);box-shadow:0 24px 64px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.07);padding:20px;animation:dsubScaleIn .18s cubic-bezier(.16,1,.3,1)}' +
|
|
53
|
-
'@keyframes dsubFadeIn{from{opacity:0}to{opacity:1}}' +
|
|
54
|
-
'@keyframes dsubScaleIn{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}' +
|
|
55
|
-
'.dsub-modalHead{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-bottom:14px;border-bottom:1px solid var(--dsw-alias-border-l2)}' +
|
|
56
|
-
'.dsub-modalTitleWrap{display:flex;align-items:center;gap:10px}' +
|
|
57
|
-
'.dsub-modalIcon{width:30px;height:30px;border-radius:8px;background:var(--dsw-alias-bg-layer-1);display:grid;place-items:center;color:var(--dsw-alias-brand-primary);font-size:14px}' +
|
|
58
|
-
'.dsub-modalBadge{font-size:11px;font-weight:600;padding:2px 7px;border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary)}' +
|
|
59
|
-
'.dsub-heroCard{margin-top:14px;padding:14px 16px;border-radius:12px;background:linear-gradient(135deg,var(--dsw-alias-bg-layer-2) 0%,var(--dsw-alias-bg-layer-3) 100%);border:1px solid var(--dsw-alias-border-l1);box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}' +
|
|
60
|
-
'.dsub-heroHead{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}' +
|
|
61
|
-
'.dsub-heroTitle{font-size:14px;font-weight:700;display:flex;align-items:center;gap:6px}' +
|
|
62
|
-
'.dsub-heroModel{font-size:11.5px;color:var(--dsw-alias-label-secondary);font-family:ui-monospace,monospace}' +
|
|
63
|
-
'.dsub-heroBars{display:flex;flex-direction:column;gap:8px;margin-top:10px}' +
|
|
64
|
-
'.dsub-barLabelRow{display:flex;justify-content:space-between;font-size:11.5px;color:var(--dsw-alias-label-secondary);margin-bottom:3px}' +
|
|
65
|
-
'.dsub-barTrack{height:10px;border-radius:999px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}' +
|
|
66
|
-
'.dsub-barFillGrad{height:100%;border-radius:3px;transition:width .3s ease}' +
|
|
67
|
-
'.dsub-panel-box{margin-top:10px;padding:14px 16px;border-radius:10px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);display:flex;flex-direction:column;gap:8px}' +
|
|
68
|
-
'.dsub-panel-warn{border-color:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 40%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 5%,transparent)}' +
|
|
69
|
-
'.dsub-panel-title{font-size:16px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between}' +
|
|
70
|
-
'.dsub-panel-desc{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.4}' +
|
|
71
|
-
'.dsub-usage-grid{display:flex;flex-direction:column;gap:8px;margin-top:4px}' +
|
|
72
|
-
'.dsub-usage-card{display:flex;flex-direction:column;gap:5px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}' +
|
|
73
|
-
'.dsub-usage-head{display:flex;justify-content:space-between;align-items:center;font-size:12px;font-weight:500;color:var(--dsw-alias-label-primary)}' +
|
|
74
|
-
'.dsub-usage-track{width:100%;height:8px;border-radius:999px;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);overflow:hidden}' +
|
|
75
|
-
'.dsub-usage-fill{height:100%;border-radius:999px;transition:width .3s ease}' +
|
|
76
|
-
'.dsub-usage-meta{display:flex;justify-content:space-between;align-items:center;font-size:11px;color:var(--dsw-alias-label-secondary)}' +
|
|
77
|
-
'.dsub-tag-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 8%,transparent);padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600;border-style:solid;border-width:1px}' +
|
|
78
|
-
'.dsub-tag-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 8%,transparent);padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600;border-style:solid;border-width:1px}' +
|
|
79
|
-
'.dsub-tag-bad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent);padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600;border-style:solid;border-width:1px}' +
|
|
80
|
-
'.dsub-slot-meta-row{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:12px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l2);font-size:12px}' +
|
|
81
|
-
'.dsub-ref-tag{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;color:var(--dsw-alias-label-secondary)}' +
|
|
82
|
-
'.dsub-ref-tag code{padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);font-family:ui-monospace,monospace;color:var(--dsw-alias-label-primary);font-size:11px}' +
|
|
83
|
-
'.dsub-btn-danger{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 40%,transparent)!important;color:var(--dsw-alias-state-error-primary)!important}' +
|
|
84
|
-
'.dsub-btn-danger:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent)!important}' +
|
|
85
|
-
'.dsub-poolTitle{margin-top:16px;margin-bottom:8px;font-size:11.5px;font-weight:700;color:var(--dsw-alias-label-secondary);text-transform:uppercase;letter-spacing:.05em}' +
|
|
86
|
-
'.dsub-accountCard{display:flex;align-items:center;gap:12px;padding:10px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2);margin-top:6px;transition:border-color .15s,background .15s}' +
|
|
87
|
-
'.dsub-accountCard:hover{background:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-tertiary)}' +
|
|
88
|
-
'.dsub-brandBadge{width:32px;height:32px;border-radius:8px;display:grid;place-items:center;font-size:12px;font-weight:700;flex-shrink:0}' +
|
|
89
|
-
'.dsub-brandCodex{background:rgba(16,185,129,.15);color:#10b981;border:1px solid rgba(16,185,129,.3)}' +
|
|
90
|
-
'.dsub-brandClaude{background:rgba(245,158,11,.15);color:#f59e0b;border:1px solid rgba(245,158,11,.3)}' +
|
|
91
|
-
'.dsub-brandGrok{background:rgba(168,85,247,.15);color:#c084fc;border:1px solid rgba(168,85,247,.3)}' +
|
|
92
|
-
'.dsub-brandAgy{background:rgba(59,130,246,.15);color:#60a5fa;border:1px solid rgba(59,130,246,.3)}' +
|
|
93
|
-
'.dsub-brandOllama{background:rgba(107,114,128,.15);color:#9ca3af;border:1px solid rgba(107,114,128,.3)}' +
|
|
94
|
-
'.dsub-brandKimi{background:rgba(236,72,153,.15);color:#f472b6;border:1px solid rgba(236,72,153,.3)}' +
|
|
95
|
-
'.dsub-brandGlm{background:rgba(20,184,166,.15);color:#2dd4bf;border:1px solid rgba(20,184,166,.3)}' +
|
|
96
|
-
'.dsub-planBadge{display:inline-flex;align-items:center;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px;background:rgba(99,102,241,.15);color:#818cf8;border:1px solid rgba(99,102,241,.3);margin-left:6px}' +
|
|
97
|
-
|
|
98
|
-
'.dsub-accInfo{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}' +
|
|
99
|
-
'.dsub-accNameRow{display:flex;align-items:center;justify-content:space-between;gap:8px}' +
|
|
100
|
-
'.dsub-accName{font-size:13px;font-weight:600}' +
|
|
101
|
-
'.dsub-accStatusTag{font-size:10.5px;font-weight:600;padding:1px 6px;border-radius:4px}' +
|
|
102
|
-
'.dsub-statusOk{background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 15%,transparent);color:var(--dsw-alias-state-success-primary)}' +
|
|
103
|
-
'.dsub-statusWarn{background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 15%,transparent);color:var(--dsw-alias-state-warning-primary)}' +
|
|
104
|
-
'.dsub-statusBad{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 15%,transparent);color:var(--dsw-alias-state-error-primary)}' +
|
|
105
|
-
'.dsub-statusOff{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-tertiary)}' +
|
|
106
|
-
'.dsub-modalFoot{display:flex;align-items:center;justify-content:space-between;margin-top:16px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2)}' +
|
|
107
|
-
'.dsub-btnPrimary{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:8px;font-size:12px;font-weight:600;background:var(--dsw-alias-brand-primary,#3b82f6);color:#fff;border:0;cursor:pointer;transition:opacity .15s}' +
|
|
108
|
-
'.dsub-btnPrimary:hover{opacity:.9}' +
|
|
109
|
-
'.dsub-btnSec{display:inline-flex;align-items:center;gap:5px;padding:5px 10px;border-radius:7px;font-size:11.5px;font-weight:500;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;text-decoration:none;transition:all .15s}' +
|
|
110
|
-
'.dsub-btnSec:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-tertiary)}' +
|
|
111
|
-
'.dsub-cq{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary);padding:0 6px}' +
|
|
112
|
-
'.dsub-cqBar{position:relative;width:40px;height:5px;border-radius:3px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}' +
|
|
113
|
-
'.dsub-cqBarFill{height:100%;border-radius:3px;background:var(--dsw-alias-state-success-primary)}' +
|
|
114
|
-
'.dsub-cqBarWarn .dsub-cqBarFill{background:var(--dsw-alias-state-warning-primary)}' +
|
|
115
|
-
'.dsub-cqBarFull .dsub-cqBarFill{background:var(--dsw-alias-state-error-primary)}' +
|
|
116
|
-
'.dsub-cqB{font-variant-numeric:tabular-nums;font-weight:600;color:var(--dsw-alias-label-primary)}' +
|
|
117
|
-
'.dsub-diag{font:11px/1.5 ui-monospace,SFMono-Regular,monospace;max-height:260px;overflow:auto;white-space:pre-wrap;word-break:break-word;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:10px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary)}'
|
|
118
|
-
|
|
119
|
-
const cssId = 'dsh-subscriptions/settings.module.css'
|