@goodandready/dsh-subscriptions 0.6.87 → 0.6.89
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.js +4 -0
- package/lib/client.js +77 -256
- package/lib/config-schema.js +8 -0
- package/lib/http.js +16 -59
- package/lib/providers.js +64 -0
- package/lib/refs.js +9 -65
- package/lib/vendor-factory.js +3 -3
- package/lib/vendors/codex.js +5 -5
- package/lib/vendors/cody.js +1 -4
- package/lib/vendors/copilot.js +4 -0
- package/lib/vendors/ernie.js +0 -1
- package/lib/vendors/grok.js +1 -98
- package/lib/vendors/index.js +7 -3
- package/lib/vendors/perplexity.js +3 -2
- package/lib/vendors/replit.js +1 -4
- package/package.json +1 -1
package/lib/accounts.js
CHANGED
|
@@ -380,6 +380,10 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
|
|
|
380
380
|
pacePerHour: info.pacePerHour || 0,
|
|
381
381
|
maxConcurrency: slot.maxConcurrency !== undefined ? slot.maxConcurrency : (curConfig.accountMaxConcurrency || 1),
|
|
382
382
|
maxQueueSize: slot.maxQueueSize > 0 ? Number(slot.maxQueueSize) : (curConfig.maxQueueSize || 20),
|
|
383
|
+
vipOnly: !!slot.vipOnly,
|
|
384
|
+
tags: Array.isArray(slot.tags) ? slot.tags : [],
|
|
385
|
+
weight: Number(slot.weight) || 0,
|
|
386
|
+
isPro: !!slot.isPro,
|
|
383
387
|
})
|
|
384
388
|
}
|
|
385
389
|
return out
|
package/lib/client.js
CHANGED
|
@@ -83,18 +83,13 @@ window.__ModuleLoader__.load({
|
|
|
83
83
|
"syncModels": "Sync Models",
|
|
84
84
|
"syncModelsTitle": "Discover and synchronize available models from vendor API",
|
|
85
85
|
"modelsSynced": "Models synchronized",
|
|
86
|
-
"doctorDiagnosis": "Doctor Verdict",
|
|
87
86
|
"recommendation": "Recommendation",
|
|
88
|
-
"pingTitle": "Measure network latency to provider API",
|
|
89
87
|
"notConnected": "Not connected",
|
|
90
88
|
"verifyAccount": "Verify account",
|
|
91
89
|
"coolingDown": "Cooling down",
|
|
92
90
|
"usageFull": "Usage 100%",
|
|
93
91
|
"connected": "Connected",
|
|
94
92
|
"title": "Subscriptions",
|
|
95
|
-
"cardIntro": "Subscription accounts, rotation and limits.",
|
|
96
|
-
"show": "Show",
|
|
97
|
-
"hide": "Hide",
|
|
98
93
|
"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.",
|
|
99
94
|
"useOrigin": "Use this Web UI origin as OAuth redirect_uri",
|
|
100
95
|
"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.",
|
|
@@ -135,8 +130,6 @@ window.__ModuleLoader__.load({
|
|
|
135
130
|
"copilotCliPathHint": "Optional path to copilot or github-copilot-cli binary.",
|
|
136
131
|
"ollamaFallback": "Ollama local fallback",
|
|
137
132
|
"ollamaFallbackHint": "Automatically route queries to a local Ollama instance when cloud quota is exhausted.",
|
|
138
|
-
"ollamaBaseUrl": "Ollama Base URL",
|
|
139
|
-
"ollamaFallbackModel": "Ollama Fallback Model",
|
|
140
133
|
"advancedSettings": "Advanced Settings & Monitoring",
|
|
141
134
|
"cooldownMs": "Failure cooldown (minutes)",
|
|
142
135
|
"cooldownMsHint": "How long to pause a failing or rate-limited account before retrying.",
|
|
@@ -146,12 +139,10 @@ window.__ModuleLoader__.load({
|
|
|
146
139
|
"notifyLimitsHint": "Show notification alerts when account usage reaches quota ceiling.",
|
|
147
140
|
"hostOnline": "Host online",
|
|
148
141
|
"hostOffline": "Host unreachable",
|
|
149
|
-
"statsTitle": "Session Telemetry",
|
|
150
142
|
"statsRequests": "Successful / Total Requests",
|
|
151
143
|
"statsLatency": "Avg Response Latency",
|
|
152
144
|
"statsSuccessRate": "Session Success Rate",
|
|
153
145
|
"statsLastActivity": "Last Activity",
|
|
154
|
-
"smokeTitle": "Connection Smoke Test (Ping)",
|
|
155
146
|
"smokeDesc": "Test live upstream connectivity and measure round-trip latency to the subscription provider.",
|
|
156
147
|
"runSmoke": "⚡ Run Smoke Test (Ping)",
|
|
157
148
|
"smokeTesting": "Pinging…",
|
|
@@ -189,10 +180,7 @@ window.__ModuleLoader__.load({
|
|
|
189
180
|
"capabilitiesHint": "Subscription capabilities, context window and model features",
|
|
190
181
|
"orPasteVaultPayload": "or paste vault payload below",
|
|
191
182
|
"workspaceTitle": "Organization / Workspace",
|
|
192
|
-
"workspaceSelect": "Select active organization/workspace",
|
|
193
|
-
"personalWorkspace": "Personal Default",
|
|
194
183
|
"windowResetsAt": "Window resets at",
|
|
195
|
-
"quotaRecovery": "Quota recovery",
|
|
196
184
|
"toastCooldownRecovered": "is now available (cooldown expired)",
|
|
197
185
|
"modelsCatalogTitle": "Curated Subscription Models",
|
|
198
186
|
"modelsCatalogDesc": "Available model catalog across active subscriptions in DeepSeek Harness.",
|
|
@@ -211,7 +199,6 @@ window.__ModuleLoader__.load({
|
|
|
211
199
|
"resetCreditsHint": "ChatGPT reset cards: emergency reset for the 5-hour window. Requires a 5s confirmation.",
|
|
212
200
|
"resetCreditsAction": "Reset ChatGPT Limit",
|
|
213
201
|
"usageLimitsTitle": "Usage Limits",
|
|
214
|
-
"slotDetailsTitle": "Slot Details",
|
|
215
202
|
"resetAvailable": "Reset attempts available",
|
|
216
203
|
"resetExpires": "earliest expires",
|
|
217
204
|
"resetAck": "I understand one attempt will be consumed",
|
|
@@ -232,23 +219,17 @@ window.__ModuleLoader__.load({
|
|
|
232
219
|
"tokensSaved": "Saved Tokens",
|
|
233
220
|
"subsPill": "SUBS",
|
|
234
221
|
"subsModalTitle": "Subscription Hub",
|
|
235
|
-
"subsActiveHero": "Active Subscription",
|
|
236
222
|
"subsPoolTitle": "Provider Accounts",
|
|
237
223
|
"subsRefresh": "Refresh",
|
|
238
224
|
"subsOpenSettings": "All Settings →",
|
|
239
|
-
"subsFastMode": "Fast Mode 1.5x",
|
|
240
|
-
"subsHealthy": "Healthy",
|
|
241
225
|
"subsFree": "free",
|
|
242
226
|
"subsUsed": "used",
|
|
243
|
-
"subsModalClose": "Close",
|
|
244
227
|
"subsLogged": "connected",
|
|
245
228
|
"subsNotLogged": "not connected",
|
|
246
229
|
"subsCooldown": "cooldown",
|
|
247
|
-
"hudTitle": "Subscription balance",
|
|
248
230
|
"settingsLoading": "Loading settings…",
|
|
249
231
|
"settingsUnavailable": "Settings are unavailable",
|
|
250
232
|
"settingsRetry": "Retry",
|
|
251
|
-
"hudHint": "drag · edge docks · click refresh",
|
|
252
233
|
"fcCalibrating": "calibrating…",
|
|
253
234
|
"fcIdle": "no usage",
|
|
254
235
|
"fcHours": "h",
|
|
@@ -276,23 +257,14 @@ window.__ModuleLoader__.load({
|
|
|
276
257
|
"addAccount": "+ Add account",
|
|
277
258
|
"save": "Save",
|
|
278
259
|
"saved": "Saved",
|
|
279
|
-
"loading": "Loading…",
|
|
280
|
-
"unavailable": "Settings are unavailable",
|
|
281
260
|
"accountLabel": "Account",
|
|
282
261
|
"plan": "Plan",
|
|
283
262
|
"storedAs": "Stored as",
|
|
284
|
-
"switchLabel": "Provider",
|
|
285
|
-
"chipActive": "Subscriptions",
|
|
286
|
-
"expiryLabel": "expires",
|
|
287
263
|
"slashLogin": "Login to a subscription provider",
|
|
288
264
|
"slashLogout": "Log out a subscription provider",
|
|
289
|
-
"slashStatus": "Subscription status",
|
|
290
|
-
"none": "none",
|
|
291
265
|
"forecast": "≈",
|
|
292
266
|
"windowPrimary": "Primary window",
|
|
293
267
|
"windowSecondary": "Secondary window",
|
|
294
|
-
"resetLabel": "reset",
|
|
295
|
-
"check": "Check",
|
|
296
268
|
"checking": "Checking…",
|
|
297
269
|
"importToken": "Save token",
|
|
298
270
|
"importTokenPlace": "Paste token or API key",
|
|
@@ -302,7 +274,12 @@ window.__ModuleLoader__.load({
|
|
|
302
274
|
"howToGetToken": "❓ Instructions",
|
|
303
275
|
"howToGetTokenTitle": "How to get token / API key for this provider",
|
|
304
276
|
"instructionsTitle": "How to obtain token",
|
|
305
|
-
"
|
|
277
|
+
"lang": "en",
|
|
278
|
+
"cardIntro": "Subscription accounts, rotation and limits.",
|
|
279
|
+
"loading": "Loading…",
|
|
280
|
+
"unavailable": "Settings are unavailable",
|
|
281
|
+
"error": "Error",
|
|
282
|
+
"settingsError": "Settings error",
|
|
306
283
|
"cancel": "Cancel",
|
|
307
284
|
"quota": "Quota",
|
|
308
285
|
"subs5hWindow": "5h window",
|
|
@@ -313,8 +290,6 @@ window.__ModuleLoader__.load({
|
|
|
313
290
|
"sessionRevokedBadge": "Revoked / Expired",
|
|
314
291
|
"sessionRevokedWarning": "Session expired / revoked — re-authorization required",
|
|
315
292
|
"manualTitle": "If the browser did not come back",
|
|
316
|
-
"updateChecking": "Checking updates…",
|
|
317
|
-
"updateUpToDate": "Up to date",
|
|
318
293
|
"updateAvailable": "Update available",
|
|
319
294
|
"updateNow": "Update",
|
|
320
295
|
"updating": "Updating…",
|
|
@@ -339,7 +314,6 @@ window.__ModuleLoader__.load({
|
|
|
339
314
|
"backupImportAction": "Import",
|
|
340
315
|
"backupPassphrase": "Vault Passphrase (min 12 chars)",
|
|
341
316
|
"backupExportHint": "Encrypt connected subscription accounts and slots with AES-256-GCM. ⚠️ Contains sensitive OAuth refresh tokens; use a strong password (min 12 chars).",
|
|
342
|
-
"vaultTokenWarning": "⚠️ Contains sensitive OAuth refresh tokens",
|
|
343
317
|
"backupImportHint": "Restore encrypted subscription accounts and credentials from a vault payload.",
|
|
344
318
|
"backupPayloadPlaceholder": "Paste DSHE... encrypted string here",
|
|
345
319
|
"backupExportOk": "Backup exported successfully",
|
|
@@ -354,10 +328,7 @@ window.__ModuleLoader__.load({
|
|
|
354
328
|
"resetsIn": "Resets in",
|
|
355
329
|
"toastQuarantined": "Account placed in quarantine",
|
|
356
330
|
"toastReleased": "Account active and healthy",
|
|
357
|
-
"toastQuotaWarning": "Quota threshold exceeded",
|
|
358
|
-
"enableToasts": "In-app toast alerts",
|
|
359
331
|
"headlessTip": "Headless / Remote Server?",
|
|
360
|
-
"headlessSshCommand": "SSH Port Forward Command:",
|
|
361
332
|
"headlessCopySsh": "Copy SSH Command",
|
|
362
333
|
"headlessCopied": "Copied!",
|
|
363
334
|
"headlessScanQr": "Scan with Mobile to Authenticate:",
|
|
@@ -395,23 +366,14 @@ window.__ModuleLoader__.load({
|
|
|
395
366
|
"wizardAll": "Account Wizard",
|
|
396
367
|
"wizardIncludeNetwork": "Include active network checks (proxy ping & model catalog discovery)",
|
|
397
368
|
"wizardRunning": "Running diagnostic checklist...",
|
|
398
|
-
"wizardStepCreds": "Credentials & Expiry",
|
|
399
|
-
"wizardStepProxy": "Proxy & Gateway",
|
|
400
|
-
"wizardStepToken": "Token Validation",
|
|
401
|
-
"wizardStepCatalog": "Model Catalog Reachability",
|
|
402
369
|
"wizardHealthy": "Healthy",
|
|
403
370
|
"wizardDegraded": "Degraded / Warnings",
|
|
404
371
|
"wizardFailing": "Issues Detected",
|
|
405
372
|
"wizardRecommendations": "Recommendations & Next Steps",
|
|
406
373
|
"wizardCopy": "Copy Masked Diagnostic Report",
|
|
407
374
|
"cachePinnedBadge": "Cache Pinned",
|
|
408
|
-
"field.enableToasts": "Show popup toast notifications on quarantine or quota events",
|
|
409
375
|
"reasoningEffortTitle": "Reasoning Effort Control",
|
|
410
376
|
"reasoningEffortDesc": "Configure reasoning depth for models with extended thinking capabilities.",
|
|
411
|
-
"reasoningLevelLow": "Low",
|
|
412
|
-
"reasoningLevelMed": "Medium",
|
|
413
|
-
"reasoningLevelHigh": "High",
|
|
414
|
-
"reasoningLevelMax": "Max",
|
|
415
377
|
"reasoningLevelOff": "Off"
|
|
416
378
|
};
|
|
417
379
|
|
|
@@ -419,18 +381,13 @@ window.__ModuleLoader__.load({
|
|
|
419
381
|
"syncModels": "同步模型",
|
|
420
382
|
"syncModelsTitle": "从服务商 API 实时发现并同步可用模型列表",
|
|
421
383
|
"modelsSynced": "模型已同步",
|
|
422
|
-
"doctorDiagnosis": "诊断结论",
|
|
423
384
|
"recommendation": "修复建议",
|
|
424
|
-
"pingTitle": "测试到服务商 API 的网络往返延迟",
|
|
425
385
|
"notConnected": "未连接",
|
|
426
386
|
"verifyAccount": "需要验证账户",
|
|
427
387
|
"coolingDown": "冷却等待中",
|
|
428
388
|
"usageFull": "用量已达 100%",
|
|
429
389
|
"connected": "已连接",
|
|
430
390
|
"title": "个人订阅管理",
|
|
431
|
-
"cardIntro": "AI 服务订阅账户、自动轮换与速率限制管理。",
|
|
432
|
-
"show": "展开",
|
|
433
|
-
"hide": "收起",
|
|
434
391
|
"intro": "通过个人订阅登录。凭据安全保存在服务器端凭据库中,浏览器不回读敏感令牌。点击“连接”后,若页面跳转至 localhost 或服务商页面,请将重定向网址或授权代码粘贴到下方。",
|
|
435
392
|
"useOrigin": "将当前 Web UI 地址作为 OAuth 重定向地址 (redirect_uri)",
|
|
436
393
|
"useOriginHint": "除非您为当前域名申请了专属 OAuth 客户端,否则请保持关闭。官方 CLI 客户端通常需使用其公开重定向地址并配合粘贴代码流程。",
|
|
@@ -471,8 +428,6 @@ window.__ModuleLoader__.load({
|
|
|
471
428
|
"copilotCliPathHint": "可选的 copilot 或 github-copilot-cli 二进制文件路径。",
|
|
472
429
|
"ollamaFallback": "Ollama 本地模型自动兜底",
|
|
473
430
|
"ollamaFallbackHint": "当云端配额耗尽或服务商故障时,自动将请求转发给本地 Ollama 实例。",
|
|
474
|
-
"ollamaBaseUrl": "Ollama 服务基础 URL",
|
|
475
|
-
"ollamaFallbackModel": "Ollama 兜底模型名称",
|
|
476
431
|
"advancedSettings": "高级配置与监控",
|
|
477
432
|
"cooldownMs": "故障与限流冷却时间(分钟)",
|
|
478
433
|
"cooldownMsHint": "当账户遇到 429 或故障时暂停该账户的分钟数。",
|
|
@@ -482,12 +437,10 @@ window.__ModuleLoader__.load({
|
|
|
482
437
|
"notifyLimitsHint": "当账户使用量达到上限时触发系统通知提醒。",
|
|
483
438
|
"hostOnline": "主机在线",
|
|
484
439
|
"hostOffline": "主机无法连接",
|
|
485
|
-
"statsTitle": "会话遥测统计",
|
|
486
440
|
"statsRequests": "成功 / 总请求数",
|
|
487
441
|
"statsLatency": "平均响应延迟",
|
|
488
442
|
"statsSuccessRate": "会话成功率",
|
|
489
443
|
"statsLastActivity": "最近活跃时间",
|
|
490
|
-
"smokeTitle": "链路连通性冒烟测试 (Ping)",
|
|
491
444
|
"smokeDesc": "向当前订阅服务商发起实时探活并测量双向网络往返延迟 (RTT)。",
|
|
492
445
|
"runSmoke": "⚡ 执行冒烟探活 (Ping)",
|
|
493
446
|
"smokeTesting": "正在探活…",
|
|
@@ -525,10 +478,7 @@ window.__ModuleLoader__.load({
|
|
|
525
478
|
"capabilitiesHint": "订阅账号模型上下文窗口与特权特性",
|
|
526
479
|
"orPasteVaultPayload": "或在下方粘贴保险库文本",
|
|
527
480
|
"workspaceTitle": "工作区与组织",
|
|
528
|
-
"workspaceSelect": "选择生效的工作区或组织",
|
|
529
|
-
"personalWorkspace": "个人默认",
|
|
530
481
|
"windowResetsAt": "额度重置于",
|
|
531
|
-
"quotaRecovery": "额度恢复",
|
|
532
482
|
"toastCooldownRecovered": "已解除冷却恢复可用",
|
|
533
483
|
"modelsCatalogTitle": "精选订阅模型",
|
|
534
484
|
"modelsCatalogDesc": "当前 DeepSeek Harness 活跃订阅下可用模型一览。",
|
|
@@ -547,7 +497,6 @@ window.__ModuleLoader__.load({
|
|
|
547
497
|
"resetCreditsHint": "ChatGPT 重置卡:紧急重置 5 小时额度窗口。扣除前需等待 5 秒倒计时确认。",
|
|
548
498
|
"resetCreditsAction": "重置 ChatGPT 限制",
|
|
549
499
|
"usageLimitsTitle": "用量限制",
|
|
550
|
-
"slotDetailsTitle": "槽位详情",
|
|
551
500
|
"resetAvailable": "可用重置次数",
|
|
552
501
|
"resetExpires": "最早到期时间",
|
|
553
502
|
"resetAck": "我确认将消耗一次重置机会",
|
|
@@ -568,23 +517,17 @@ window.__ModuleLoader__.load({
|
|
|
568
517
|
"tokensSaved": "已节省 Token",
|
|
569
518
|
"subsPill": "订阅",
|
|
570
519
|
"subsModalTitle": "订阅管理中心",
|
|
571
|
-
"subsActiveHero": "当前主用订阅",
|
|
572
520
|
"subsPoolTitle": "服务商账户池",
|
|
573
521
|
"subsRefresh": "刷新",
|
|
574
522
|
"subsOpenSettings": "全部设置 →",
|
|
575
|
-
"subsFastMode": "加速模式 1.5x",
|
|
576
|
-
"subsHealthy": "健康运行中",
|
|
577
523
|
"subsFree": "空闲可用",
|
|
578
524
|
"subsUsed": "已用",
|
|
579
|
-
"subsModalClose": "关闭",
|
|
580
525
|
"subsLogged": "已连接",
|
|
581
526
|
"subsNotLogged": "未连接",
|
|
582
527
|
"subsCooldown": "冷却中",
|
|
583
|
-
"hudTitle": "订阅额度余量",
|
|
584
528
|
"settingsLoading": "正在加载设置…",
|
|
585
529
|
"settingsUnavailable": "设置加载失败",
|
|
586
530
|
"settingsRetry": "重试",
|
|
587
|
-
"hudHint": "可拖拽 · 边缘停靠 · 点击刷新",
|
|
588
531
|
"fcCalibrating": "校准中…",
|
|
589
532
|
"fcIdle": "暂无消耗",
|
|
590
533
|
"fcHours": "小时",
|
|
@@ -612,23 +555,14 @@ window.__ModuleLoader__.load({
|
|
|
612
555
|
"addAccount": "+ 添加账户",
|
|
613
556
|
"save": "保存",
|
|
614
557
|
"saved": "已保存",
|
|
615
|
-
"loading": "加载中…",
|
|
616
|
-
"unavailable": "设置不可用",
|
|
617
558
|
"accountLabel": "账户",
|
|
618
559
|
"plan": "套餐方案",
|
|
619
560
|
"storedAs": "存储标识",
|
|
620
|
-
"switchLabel": "切换服务商",
|
|
621
|
-
"chipActive": "已启用订阅",
|
|
622
|
-
"expiryLabel": "到期时间",
|
|
623
561
|
"slashLogin": "登录订阅服务商",
|
|
624
562
|
"slashLogout": "登出订阅服务商",
|
|
625
|
-
"slashStatus": "查看订阅状态",
|
|
626
|
-
"none": "无",
|
|
627
563
|
"forecast": "≈",
|
|
628
564
|
"windowPrimary": "主配额窗口",
|
|
629
565
|
"windowSecondary": "次配额窗口",
|
|
630
|
-
"resetLabel": "重置倒计时",
|
|
631
|
-
"check": "检测",
|
|
632
566
|
"checking": "检测中…",
|
|
633
567
|
"importToken": "保存令牌",
|
|
634
568
|
"importTokenPlace": "粘贴令牌或 API Key",
|
|
@@ -638,7 +572,12 @@ window.__ModuleLoader__.load({
|
|
|
638
572
|
"howToGetToken": "❓ 获取指引",
|
|
639
573
|
"howToGetTokenTitle": "如何获取此服务商的令牌或 API 密钥",
|
|
640
574
|
"instructionsTitle": "获取令牌指引",
|
|
641
|
-
"
|
|
575
|
+
"lang": "zh",
|
|
576
|
+
"cardIntro": "AI 服务订阅账户、自动轮换与速率限制管理。",
|
|
577
|
+
"loading": "加载中…",
|
|
578
|
+
"unavailable": "设置不可用",
|
|
579
|
+
"error": "错误",
|
|
580
|
+
"settingsError": "设置错误",
|
|
642
581
|
"cancel": "取消",
|
|
643
582
|
"quota": "配额",
|
|
644
583
|
"subs5hWindow": "5小时窗口",
|
|
@@ -649,8 +588,6 @@ window.__ModuleLoader__.load({
|
|
|
649
588
|
"sessionRevokedBadge": "已失效 / 过期",
|
|
650
589
|
"sessionRevokedWarning": "会话已过期或失效 — 需要重新授权",
|
|
651
590
|
"manualTitle": "若浏览器未能自动跳转回调",
|
|
652
|
-
"updateChecking": "检查更新中…",
|
|
653
|
-
"updateUpToDate": "已是最新版本",
|
|
654
591
|
"updateAvailable": "有新版本可用",
|
|
655
592
|
"updateNow": "立即更新",
|
|
656
593
|
"updating": "正在更新…",
|
|
@@ -675,7 +612,6 @@ window.__ModuleLoader__.load({
|
|
|
675
612
|
"backupImportAction": "导入",
|
|
676
613
|
"backupPassphrase": "凭据库密码(至少 12 位)",
|
|
677
614
|
"backupExportHint": "使用 AES-256-GCM 安全加密导出所有订阅账户与配置。⚠️ 包含敏感的 OAuth 刷新令牌,请使用至少 12 位的强密码保护。",
|
|
678
|
-
"vaultTokenWarning": "⚠️ 包含敏感的 OAuth 刷新令牌",
|
|
679
615
|
"backupImportHint": "从已加密的备份内容中导入并恢复订阅账户与凭据。",
|
|
680
616
|
"backupPayloadPlaceholder": "在此粘贴 DSHE... 加密内容",
|
|
681
617
|
"backupExportOk": "备份导出成功",
|
|
@@ -690,7 +626,6 @@ window.__ModuleLoader__.load({
|
|
|
690
626
|
"resetsIn": "重置倒计时",
|
|
691
627
|
"toastQuarantined": "账户进入隔离期",
|
|
692
628
|
"headlessTip": "无头环境 / 远程服务器?",
|
|
693
|
-
"headlessSshCommand": "SSH 端口转发命令:",
|
|
694
629
|
"headlessCopySsh": "复制 SSH 命令",
|
|
695
630
|
"headlessCopied": "已复制!",
|
|
696
631
|
"headlessScanQr": "使用手机扫描以登录:",
|
|
@@ -703,8 +638,6 @@ window.__ModuleLoader__.load({
|
|
|
703
638
|
"vaultSyncLastPush": "上次推送:",
|
|
704
639
|
"vaultSyncLastPull": "上次拉取:",
|
|
705
640
|
"toastReleased": "账户已解除隔离恢复使用",
|
|
706
|
-
"toastQuotaWarning": "额度预警",
|
|
707
|
-
"enableToasts": "应用内弹窗提醒",
|
|
708
641
|
"groupQueue": "并发限制与请求队列",
|
|
709
642
|
"enableQueue": "启用并发请求排队",
|
|
710
643
|
"enableQueueHint": "当账户达到并发上限时将多余请求排队等待,避免直接触发 429 报错",
|
|
@@ -731,23 +664,14 @@ window.__ModuleLoader__.load({
|
|
|
731
664
|
"wizardAll": "诊断向导",
|
|
732
665
|
"wizardIncludeNetwork": "包含活跃网络探测(代理连通性与模型目录探测)",
|
|
733
666
|
"wizardRunning": "正在执行诊断检查清单...",
|
|
734
|
-
"wizardStepCreds": "凭据完整性与有效期",
|
|
735
|
-
"wizardStepProxy": "网络代理与网关",
|
|
736
|
-
"wizardStepToken": "令牌有效性验证",
|
|
737
|
-
"wizardStepCatalog": "模型目录可达性",
|
|
738
667
|
"wizardHealthy": "健康正常",
|
|
739
668
|
"wizardDegraded": "降级预警",
|
|
740
669
|
"wizardFailing": "发现故障",
|
|
741
670
|
"wizardRecommendations": "排查建议与修复步骤",
|
|
742
671
|
"wizardCopy": "复制脱敏诊断报告",
|
|
743
672
|
"cachePinnedBadge": "缓存粘性",
|
|
744
|
-
"field.enableToasts": "当账户发生隔离或额度达到警戒线时显示即时弹窗提醒",
|
|
745
673
|
"reasoningEffortTitle": "推理强度控制",
|
|
746
674
|
"reasoningEffortDesc": "为具备扩展思考能力的模型配置推理深度与思考预算。",
|
|
747
|
-
"reasoningLevelLow": "低",
|
|
748
|
-
"reasoningLevelMed": "中",
|
|
749
|
-
"reasoningLevelHigh": "高",
|
|
750
|
-
"reasoningLevelMax": "最高",
|
|
751
675
|
"reasoningLevelOff": "关闭"
|
|
752
676
|
};
|
|
753
677
|
// #51: live countdown to the quota window reset (account.quota.resetAt).
|
|
@@ -802,6 +726,27 @@ window.__ModuleLoader__.load({
|
|
|
802
726
|
return label ? h('span', { className: 'dsub-planBadge' }, label) : null
|
|
803
727
|
}
|
|
804
728
|
|
|
729
|
+
function pad2(n) {
|
|
730
|
+
return String(n).padStart(2, '0')
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function formatMmSs(totalSeconds) {
|
|
734
|
+
const total = Math.max(0, Math.ceil(Number(totalSeconds) || 0))
|
|
735
|
+
return pad2(Math.floor(total / 60)) + ':' + pad2(total % 60)
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function barTone(pct, normalColor = 'var(--dsw-alias-state-success-primary)') {
|
|
739
|
+
const p = Number(pct) || 0
|
|
740
|
+
if (p >= 90) return { cls: 'dsub-barFull', tag: 'dsub-tag-bad', color: 'var(--dsw-alias-state-error-primary)' }
|
|
741
|
+
if (p >= 70) return { cls: 'dsub-barWarn', tag: 'dsub-tag-warn', color: 'var(--dsw-alias-state-warn-primary)' }
|
|
742
|
+
return { cls: '', tag: 'dsub-tag-ok', color: normalColor }
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function ledClass(pct) {
|
|
746
|
+
const p = Number(pct) || 0
|
|
747
|
+
return p >= 90 ? 'dsub-ledBad' : (p >= 70 ? 'dsub-ledWarn' : 'dsub-ledOk')
|
|
748
|
+
}
|
|
749
|
+
|
|
805
750
|
function formatRelativeReset(resetAt, lang, now) {
|
|
806
751
|
if (!resetAt || !Number.isFinite(resetAt) || resetAt <= 0) return ''
|
|
807
752
|
var delta = resetAt - (now || Date.now())
|
|
@@ -828,17 +773,14 @@ window.__ModuleLoader__.load({
|
|
|
828
773
|
if (ms <= 0) return null
|
|
829
774
|
const total = Math.floor(ms / 1000)
|
|
830
775
|
const d = new Date(props.resetAt)
|
|
831
|
-
const
|
|
832
|
-
const timeStr = pad(d.getHours()) + ':' + pad(d.getMinutes())
|
|
776
|
+
const timeStr = pad2(d.getHours()) + ':' + pad2(d.getMinutes())
|
|
833
777
|
const relStr = formatRelativeReset(props.resetAt, t('lang'), now)
|
|
834
778
|
if (total > 3600) {
|
|
835
779
|
return h('span', { className: 'dsub-sub', style: { fontVariantNumeric: 'tabular-nums' } },
|
|
836
780
|
'⏳ ' + t('windowResetsAt') + ' ' + timeStr + ' (' + relStr + ')')
|
|
837
781
|
}
|
|
838
|
-
const m = Math.floor(total / 60)
|
|
839
|
-
const sec = total % 60
|
|
840
782
|
return h('span', { className: 'dsub-sub', style: { fontVariantNumeric: 'tabular-nums' } },
|
|
841
|
-
'⏳ ' + t('windowResetsAt') + ' ' + timeStr + ' (' + relStr + ' · ' +
|
|
783
|
+
'⏳ ' + t('windowResetsAt') + ' ' + timeStr + ' (' + relStr + ' · ' + formatMmSs(total) + ')')
|
|
842
784
|
}
|
|
843
785
|
|
|
844
786
|
function badgeFor(account, t) {
|
|
@@ -852,10 +794,7 @@ window.__ModuleLoader__.load({
|
|
|
852
794
|
}
|
|
853
795
|
if (account.cooldownUntil && account.cooldownUntil > Date.now()) {
|
|
854
796
|
const leftSec = Math.max(0, Math.ceil((account.cooldownUntil - Date.now()) / 1000))
|
|
855
|
-
|
|
856
|
-
const s = leftSec % 60
|
|
857
|
-
const pad = (n) => String(n).padStart(2, "0")
|
|
858
|
-
return t("coolingDown") + " (" + pad(m) + ":" + pad(s) + ")"
|
|
797
|
+
return t("coolingDown") + " (" + formatMmSs(leftSec) + ")"
|
|
859
798
|
}
|
|
860
799
|
if (account.usagePercent != null && account.usagePercent >= 100) return t('usageFull')
|
|
861
800
|
return t('connected')
|
|
@@ -1009,7 +948,7 @@ window.__ModuleLoader__.load({
|
|
|
1009
948
|
fetch('/dsh-subscriptions/vault/sync/status')
|
|
1010
949
|
.then((r) => r.json())
|
|
1011
950
|
.then((d) => { if (d && d.ok) setVaultSyncInfo(d.status) })
|
|
1012
|
-
.catch(() =>
|
|
951
|
+
.catch((e) => setBackupErr(cleanErrorMessage(e && e.message ? e.message : e)))
|
|
1013
952
|
}
|
|
1014
953
|
|
|
1015
954
|
const doVaultSyncPush = async () => {
|
|
@@ -2622,7 +2561,7 @@ window.__ModuleLoader__.load({
|
|
|
2622
2561
|
stepsEn: ['Paste your token or API key into the field above and click "Save token".'],
|
|
2623
2562
|
};
|
|
2624
2563
|
// Determine language safely from current locale (fallback to zh if Chinese or en)
|
|
2625
|
-
const isZh = t('
|
|
2564
|
+
const isZh = t('lang') === 'zh';
|
|
2626
2565
|
const steps = (isZh && info.stepsZh) ? info.stepsZh : (info.stepsEn || []);
|
|
2627
2566
|
return h('div', { className: 'dsub-helpBox' },
|
|
2628
2567
|
h('div', { className: 'dsub-helpHead' },
|
|
@@ -2843,8 +2782,9 @@ window.__ModuleLoader__.load({
|
|
|
2843
2782
|
|
|
2844
2783
|
function renderItem(label, pct, detail){
|
|
2845
2784
|
var p = Math.min(100, Math.max(0, Math.round(pct)))
|
|
2846
|
-
var
|
|
2847
|
-
var
|
|
2785
|
+
var tone = barTone(p)
|
|
2786
|
+
var tagCls = tone.tag
|
|
2787
|
+
var fillBg = tone.color
|
|
2848
2788
|
return h('div', { key: label, className: 'dsub-usage-card' },
|
|
2849
2789
|
h('div', { className: 'dsub-usage-head' },
|
|
2850
2790
|
h('span', null, label),
|
|
@@ -2888,7 +2828,7 @@ window.__ModuleLoader__.load({
|
|
|
2888
2828
|
var agoStr = null
|
|
2889
2829
|
if (at) {
|
|
2890
2830
|
var m = Math.round((Date.now() - at) / 60000)
|
|
2891
|
-
if (m >= 1) agoStr = m + 'm ' + (t('
|
|
2831
|
+
if (m >= 1) agoStr = m + 'm ' + (t('lang') === 'zh' ? '前' : 'ago')
|
|
2892
2832
|
}
|
|
2893
2833
|
|
|
2894
2834
|
return h('div', { className: 'dsub-panel-box' },
|
|
@@ -3164,47 +3104,9 @@ window.__ModuleLoader__.load({
|
|
|
3164
3104
|
expiryNotifyDays: (data && data.expiryNotifyDays) || 7,
|
|
3165
3105
|
composerQuota: (data && data.composerQuota) || 'off',
|
|
3166
3106
|
active: (data && data.active) || null,
|
|
3107
|
+
error: null,
|
|
3167
3108
|
}
|
|
3168
|
-
} catch { return { logged: [], usage: 0, expiresAt: {}, labels: {}, expiryNotifyDays: 7, fastMode: false, composerQuota: 'off', active: null } }
|
|
3169
|
-
}
|
|
3170
|
-
|
|
3171
|
-
// #84: compact quota indicator of the active subscription in the input area.
|
|
3172
|
-
// The runway forecast uses a rolling window of /status samples.
|
|
3173
|
-
const fcSamples = { windows: {} }
|
|
3174
|
-
|
|
3175
|
-
function fcObserve(key, remaining, now) {
|
|
3176
|
-
const windows = fcSamples.windows
|
|
3177
|
-
const prev = windows[key]
|
|
3178
|
-
const samples = (prev && prev.samples) || []
|
|
3179
|
-
const last = samples[samples.length - 1]
|
|
3180
|
-
if (!last || (now > last.at && (Math.abs(remaining - last.pct) >= 0.1 || now - last.at >= 15 * 60 * 1000))) {
|
|
3181
|
-
samples.push({ at: now, pct: remaining })
|
|
3182
|
-
}
|
|
3183
|
-
windows[key] = { samples: samples.filter((x) => x.at >= now - 24 * 60 * 60 * 1000).slice(-192) }
|
|
3184
|
-
}
|
|
3185
|
-
|
|
3186
|
-
function fcEstimate(key, remaining) {
|
|
3187
|
-
const rec = fcSamples.windows[key]
|
|
3188
|
-
if (!rec) return { status: 'calibrating' }
|
|
3189
|
-
const samples = rec.samples
|
|
3190
|
-
if (samples.length < 3) return { status: 'calibrating' }
|
|
3191
|
-
const first = samples[0]
|
|
3192
|
-
const last = samples[samples.length - 1]
|
|
3193
|
-
const span = last.at - first.at
|
|
3194
|
-
const consumed = first.pct - last.pct
|
|
3195
|
-
if (span < 30 * 60 * 1000 || consumed < 1) return { status: 'calibrating' }
|
|
3196
|
-
const t0 = first.at
|
|
3197
|
-
let sw = 0, sx = 0, sy = 0, sxx = 0, sxy = 0
|
|
3198
|
-
for (const x of samples) {
|
|
3199
|
-
const wx = (x.at - t0) / 3600000
|
|
3200
|
-
const wy = first.pct - x.pct
|
|
3201
|
-
const w = Math.exp((x.at - last.at) / (6 * 3600000))
|
|
3202
|
-
sw += w; sx += w * wx; sy += w * wy; sxx += w * wx * wx; sxy += w * wx * wy
|
|
3203
|
-
}
|
|
3204
|
-
const den = sw * sxx - sx * sx
|
|
3205
|
-
const pace = den > 0 ? (sw * sxy - sx * sy) / den : 0
|
|
3206
|
-
if (!Number.isFinite(pace) || pace < 0.02) return { status: 'idle' }
|
|
3207
|
-
return { status: 'ready', runwaySeconds: Math.round((remaining / pace) * 3600) }
|
|
3109
|
+
} catch { return { logged: [], usage: 0, expiresAt: {}, labels: {}, expiryNotifyDays: 7, fastMode: false, composerQuota: 'off', active: null, error: 'status unavailable' } }
|
|
3208
3110
|
}
|
|
3209
3111
|
|
|
3210
3112
|
function ComposerQuota(props) {
|
|
@@ -3213,15 +3115,25 @@ window.__ModuleLoader__.load({
|
|
|
3213
3115
|
React.useEffect(() => {
|
|
3214
3116
|
let alive = true
|
|
3215
3117
|
const pull = () => {
|
|
3216
|
-
|
|
3118
|
+
Promise.all([
|
|
3119
|
+
refreshLoggedIn(),
|
|
3120
|
+
fetch('/dsh-subscriptions/analytics', { cache: 'no-store' })
|
|
3121
|
+
.then((res) => res.json())
|
|
3122
|
+
.catch(() => ({})),
|
|
3123
|
+
]).then(([r, analyticsPayload]) => {
|
|
3217
3124
|
if (!alive) return
|
|
3218
3125
|
const mode = r.composerQuota || 'off'
|
|
3219
3126
|
const a = r.active
|
|
3220
3127
|
if (mode !== 'off' && a && a.provider && a.provider !== 'ollama' && a.usagePercent != null) {
|
|
3221
3128
|
const remaining = Math.max(0, 100 - a.usagePercent)
|
|
3222
|
-
const
|
|
3223
|
-
|
|
3224
|
-
|
|
3129
|
+
const forecast = (analyticsPayload && analyticsPayload.analytics && analyticsPayload.analytics.forecast) || []
|
|
3130
|
+
const row = forecast.find((f) => f.provider === a.provider && (!f.ref || !a.ref || f.ref === a.ref))
|
|
3131
|
+
let est = { status: 'calibrating' }
|
|
3132
|
+
if (row) {
|
|
3133
|
+
est = row.hoursToExhaustion == null
|
|
3134
|
+
? { status: 'idle' }
|
|
3135
|
+
: { status: 'forecast', runwaySeconds: Math.max(0, Number(row.hoursToExhaustion) * 3600) }
|
|
3136
|
+
}
|
|
3225
3137
|
setState({ mode, active: a, remaining, est })
|
|
3226
3138
|
} else {
|
|
3227
3139
|
setState({ mode, active: a, remaining: null, est: null })
|
|
@@ -3277,102 +3189,6 @@ window.__ModuleLoader__.load({
|
|
|
3277
3189
|
return { label: (p.charAt(0).toUpperCase() || 'P'), cls: 'dsub-brandCodex', icon: '●' }
|
|
3278
3190
|
}
|
|
3279
3191
|
|
|
3280
|
-
const PLUGIN_LABELS = new Set(['plugins', '插件', 'plugin'])
|
|
3281
|
-
|
|
3282
|
-
function resolvePluginLabels(customCtx) {
|
|
3283
|
-
const labels = new Set(PLUGIN_LABELS)
|
|
3284
|
-
const loc = (customCtx && customCtx.locale) || (typeof window !== 'undefined' && window.__dshCtx && window.__dshCtx.locale)
|
|
3285
|
-
if (loc && typeof loc.bind === 'function') {
|
|
3286
|
-
try {
|
|
3287
|
-
const sp = loc.bind('settings.plugins')
|
|
3288
|
-
const nav = typeof sp === 'function' ? sp('nav') : null
|
|
3289
|
-
if (nav && typeof nav === 'string') labels.add(nav.trim().toLowerCase())
|
|
3290
|
-
} catch { /* bestEffort */ }
|
|
3291
|
-
try {
|
|
3292
|
-
const pm = loc.bind('pluginManager')
|
|
3293
|
-
const panel = typeof pm === 'function' ? (pm('panel') || pm('title')) : null
|
|
3294
|
-
if (panel && typeof panel === 'string') labels.add(panel.trim().toLowerCase())
|
|
3295
|
-
} catch { /* bestEffort */ }
|
|
3296
|
-
}
|
|
3297
|
-
return labels
|
|
3298
|
-
}
|
|
3299
|
-
|
|
3300
|
-
function matchesPluginLabel(el, customLabels) {
|
|
3301
|
-
if (!el) return false
|
|
3302
|
-
const dataTab = (el.getAttribute('data-tab') || '').trim().toLowerCase()
|
|
3303
|
-
const dataSection = (el.getAttribute('data-section') || '').trim().toLowerCase()
|
|
3304
|
-
const dataKey = (el.getAttribute('data-key') || '').trim().toLowerCase()
|
|
3305
|
-
const id = (el.getAttribute('id') || '').trim().toLowerCase()
|
|
3306
|
-
if (dataTab === 'plugins' || dataSection === 'plugins' || dataKey === 'plugins' || id === 'plugins') return true
|
|
3307
|
-
|
|
3308
|
-
if (el.querySelector && el.querySelector('path[d*="M10.3232"]')) return true
|
|
3309
|
-
|
|
3310
|
-
const txt = (el.textContent || '').trim().toLowerCase()
|
|
3311
|
-
const aria = (el.getAttribute('aria-label') || '').trim().toLowerCase()
|
|
3312
|
-
const title = (el.getAttribute('title') || '').trim().toLowerCase()
|
|
3313
|
-
const labels = customLabels || PLUGIN_LABELS
|
|
3314
|
-
return labels.has(txt) || labels.has(aria) || labels.has(title) || labels.has(dataTab)
|
|
3315
|
-
}
|
|
3316
|
-
|
|
3317
|
-
function findAndClickCard(doc) {
|
|
3318
|
-
for (const key of [PKG, 'dsh-subscriptions', '@goodandready/dsh-subscriptions']) {
|
|
3319
|
-
const card = doc.querySelector(`[data-plugin-package="${key}"]`) || doc.querySelector(`[data-plugin-item="${ROW_ID}"]`)
|
|
3320
|
-
if (card) {
|
|
3321
|
-
const open = [...card.querySelectorAll('button')].filter((b) => b.getAttribute('role') !== 'switch')
|
|
3322
|
-
const target = open.length >= 1 ? open[0] : card
|
|
3323
|
-
if (typeof target.click === 'function') {
|
|
3324
|
-
target.click()
|
|
3325
|
-
return true
|
|
3326
|
-
}
|
|
3327
|
-
}
|
|
3328
|
-
}
|
|
3329
|
-
return false
|
|
3330
|
-
}
|
|
3331
|
-
|
|
3332
|
-
function openPluginSettings(doc = document, schedule = (run, ms) => window.setTimeout(run, ms), navCtx) {
|
|
3333
|
-
try {
|
|
3334
|
-
if (findAndClickCard(doc)) return
|
|
3335
|
-
|
|
3336
|
-
const activeLabels = resolvePluginLabels(navCtx)
|
|
3337
|
-
const checkEl = (el) => matchesPluginLabel(el, activeLabels)
|
|
3338
|
-
|
|
3339
|
-
const buttons = [...doc.querySelectorAll('button, a, div[role="button"], div[role="tab"]')]
|
|
3340
|
-
const panel = buttons.find(checkEl)
|
|
3341
|
-
if (panel) {
|
|
3342
|
-
panel.click()
|
|
3343
|
-
const poll = (left) => {
|
|
3344
|
-
if (findAndClickCard(doc)) return
|
|
3345
|
-
if (left > 0) schedule(() => poll(left - 1), 100)
|
|
3346
|
-
}
|
|
3347
|
-
schedule(() => poll(40), 100)
|
|
3348
|
-
return
|
|
3349
|
-
}
|
|
3350
|
-
|
|
3351
|
-
const triggers = buttons.filter((b) => b.getAttribute('aria-haspopup') === 'dialog' && b.hasAttribute('aria-expanded'))
|
|
3352
|
-
if (triggers.length > 0) {
|
|
3353
|
-
const trigger = triggers.find((b) => b.getAttribute('aria-expanded') !== 'true') || triggers[0]
|
|
3354
|
-
if (trigger && typeof trigger.click === 'function') {
|
|
3355
|
-
trigger.click()
|
|
3356
|
-
const pollSection = (left) => {
|
|
3357
|
-
try {
|
|
3358
|
-
const section = [...doc.querySelectorAll('button, a, div[role="tab"], [role="button"]')].find(checkEl)
|
|
3359
|
-
if (section && typeof section.click === 'function') {
|
|
3360
|
-
section.click()
|
|
3361
|
-
const poll = (cLeft) => {
|
|
3362
|
-
if (findAndClickCard(doc)) return
|
|
3363
|
-
if (cLeft > 0) schedule(() => poll(cLeft - 1), 100)
|
|
3364
|
-
}
|
|
3365
|
-
schedule(() => poll(40), 100)
|
|
3366
|
-
return
|
|
3367
|
-
}
|
|
3368
|
-
} catch { /* bestEffort */ }
|
|
3369
|
-
if (left > 0) schedule(() => pollSection(left - 1), 80)
|
|
3370
|
-
}
|
|
3371
|
-
schedule(() => pollSection(20), 80)
|
|
3372
|
-
}
|
|
3373
|
-
}
|
|
3374
|
-
} catch { /* bestEffort */ }
|
|
3375
|
-
}
|
|
3376
3192
|
function SubsPill(props) {
|
|
3377
3193
|
const t = (props && props.t) || ((k) => k)
|
|
3378
3194
|
const [state, setState] = React.useState({ logged: [], usage: 0, accounts: [], open: false, active: null, loading: false })
|
|
@@ -3382,6 +3198,13 @@ window.__ModuleLoader__.load({
|
|
|
3382
3198
|
const dismissToast = (id) => {
|
|
3383
3199
|
setToasts((prev) => prev.filter((t) => t.id !== id))
|
|
3384
3200
|
}
|
|
3201
|
+
const reportError = (message) => {
|
|
3202
|
+
const text = cleanErrorMessage(message)
|
|
3203
|
+
if (!text) return
|
|
3204
|
+
setToasts((prev) => prev.some((toast) => toast.message === text)
|
|
3205
|
+
? prev
|
|
3206
|
+
: [{ id: 'err_' + Date.now(), message: text, cls: 'dsub-toast-bad' }, ...prev].slice(0, 3))
|
|
3207
|
+
}
|
|
3385
3208
|
|
|
3386
3209
|
const pollAlerts = () => {
|
|
3387
3210
|
fetch('/dsh-subscriptions/alerts?limit=5', { cache: 'no-store' })
|
|
@@ -3401,22 +3224,25 @@ window.__ModuleLoader__.load({
|
|
|
3401
3224
|
}
|
|
3402
3225
|
}
|
|
3403
3226
|
})
|
|
3404
|
-
.catch(() =>
|
|
3227
|
+
.catch((e) => reportError(t('hostOffline') + ': ' + String(e && e.message || e)))
|
|
3405
3228
|
}
|
|
3406
3229
|
|
|
3407
3230
|
const pull = () => {
|
|
3408
3231
|
setState((s) => Object.assign({}, s, { loading: true }))
|
|
3409
3232
|
refreshLoggedIn().then((r) => {
|
|
3233
|
+
if (r.error) reportError(t('hostOffline') + ': ' + r.error)
|
|
3410
3234
|
const logged = r.logged || []
|
|
3411
3235
|
fetch('/dsh-subscriptions/config', { cache: 'no-store' })
|
|
3412
3236
|
.then((res) => res.json())
|
|
3413
3237
|
.then((cfg) => {
|
|
3414
3238
|
setState((s) => Object.assign({}, s, { logged, usage: r.usage, accounts: (cfg && cfg.accounts) || [], active: r.active, loading: false }))
|
|
3415
3239
|
})
|
|
3416
|
-
.catch(() => {
|
|
3240
|
+
.catch((e) => {
|
|
3241
|
+
reportError(t('hostOffline') + ': ' + String(e && e.message || e))
|
|
3417
3242
|
setState((s) => Object.assign({}, s, { logged, usage: r.usage, accounts: [], active: r.active, loading: false }))
|
|
3418
3243
|
})
|
|
3419
|
-
}).catch(() => {
|
|
3244
|
+
}).catch((e) => {
|
|
3245
|
+
reportError(t('hostOffline') + ': ' + String(e && e.message || e))
|
|
3420
3246
|
setState((s) => Object.assign({}, s, { loading: false }))
|
|
3421
3247
|
})
|
|
3422
3248
|
}
|
|
@@ -3437,7 +3263,7 @@ window.__ModuleLoader__.load({
|
|
|
3437
3263
|
|
|
3438
3264
|
const n = state.logged.length
|
|
3439
3265
|
const total = state.accounts.length || n
|
|
3440
|
-
const led = n === 0 ? 'dsub-ledOff' : (state.usage
|
|
3266
|
+
const led = n === 0 ? 'dsub-ledOff' : ledClass(state.usage)
|
|
3441
3267
|
const now = Date.now()
|
|
3442
3268
|
|
|
3443
3269
|
// Header Pill Label
|
|
@@ -3487,7 +3313,7 @@ window.__ModuleLoader__.load({
|
|
|
3487
3313
|
className: 'dsub-barFillGrad',
|
|
3488
3314
|
style: {
|
|
3489
3315
|
width: Math.min(100, Math.max(0, primPct)) + '%',
|
|
3490
|
-
background:
|
|
3316
|
+
background: barTone(primPct).color,
|
|
3491
3317
|
},
|
|
3492
3318
|
}),
|
|
3493
3319
|
),
|
|
@@ -3502,7 +3328,7 @@ window.__ModuleLoader__.load({
|
|
|
3502
3328
|
className: 'dsub-barFillGrad',
|
|
3503
3329
|
style: {
|
|
3504
3330
|
width: Math.min(100, Math.max(0, secPct)) + '%',
|
|
3505
|
-
background:
|
|
3331
|
+
background: barTone(secPct, 'var(--dsw-alias-brand-primary)').color,
|
|
3506
3332
|
},
|
|
3507
3333
|
}),
|
|
3508
3334
|
),
|
|
@@ -3546,11 +3372,8 @@ window.__ModuleLoader__.load({
|
|
|
3546
3372
|
} else if (cooled) {
|
|
3547
3373
|
statusClass = 'dsub-statusWarn'
|
|
3548
3374
|
const leftSec = Math.max(0, Math.ceil((a.cooldownUntil - now) / 1000))
|
|
3549
|
-
const m = Math.floor(leftSec / 60)
|
|
3550
|
-
const s = leftSec % 60
|
|
3551
|
-
const pad = (n) => String(n).padStart(2, '0')
|
|
3552
3375
|
const fams = Array.isArray(a.cooldownFamilies) && a.cooldownFamilies.length ? ' · ' + a.cooldownFamilies.join('/') : ''
|
|
3553
|
-
statusText = t('subsCooldown') + ' (' +
|
|
3376
|
+
statusText = t('subsCooldown') + ' (' + formatMmSs(leftSec) + ')' + fams
|
|
3554
3377
|
} else if (isLogged) {
|
|
3555
3378
|
if (pct >= 90) { statusClass = 'dsub-statusBad'; statusText = '90%+ ' + t('subsUsed') }
|
|
3556
3379
|
else if (pct >= 50) { statusClass = 'dsub-statusWarn'; statusText = pct + '% ' + t('subsUsed') }
|
|
@@ -3573,7 +3396,7 @@ window.__ModuleLoader__.load({
|
|
|
3573
3396
|
className: 'dsub-barFillGrad',
|
|
3574
3397
|
style: {
|
|
3575
3398
|
width: Math.min(100, Math.max(0, pct)) + '%',
|
|
3576
|
-
background:
|
|
3399
|
+
background: barTone(pct).color,
|
|
3577
3400
|
},
|
|
3578
3401
|
}),
|
|
3579
3402
|
) : null,
|
|
@@ -3658,8 +3481,6 @@ window.__ModuleLoader__.load({
|
|
|
3658
3481
|
layout.selectPanel('plugins')
|
|
3659
3482
|
}
|
|
3660
3483
|
} catch { /* bestEffort */ }
|
|
3661
|
-
|
|
3662
|
-
openPluginSettings(document, undefined, clientCtx)
|
|
3663
3484
|
},
|
|
3664
3485
|
}, t('subsOpenSettings')),
|
|
3665
3486
|
),
|
|
@@ -3767,7 +3588,7 @@ window.__ModuleLoader__.load({
|
|
|
3767
3588
|
}, 'dsh-subscriptions: slash commands')
|
|
3768
3589
|
}
|
|
3769
3590
|
|
|
3770
|
-
exports.inject = ['slots', 'locale'
|
|
3591
|
+
exports.inject = ['slots', 'locale']
|
|
3771
3592
|
exports.apply = function apply(ctx) {
|
|
3772
3593
|
if (typeof window !== 'undefined') {
|
|
3773
3594
|
window.__dshCtx = ctx
|
package/lib/config-schema.js
CHANGED
|
@@ -26,6 +26,14 @@ export const Slot = z.object({
|
|
|
26
26
|
.description('#590 Optional monthly subscription cost in USD for analytics. Defaults to 20.'),
|
|
27
27
|
maxQueueSize: z.number().default(0)
|
|
28
28
|
.description('#592 Per-account queue depth before overflow. 0 = use the global maxQueueSize.'),
|
|
29
|
+
vipOnly: z.boolean().default(false)
|
|
30
|
+
.description('#175 Reserve this account for VIP requests only.'),
|
|
31
|
+
tags: z.array(z.string()).default([])
|
|
32
|
+
.description('#173 Tags used by tag-filtered rotation.'),
|
|
33
|
+
weight: z.number().default(0)
|
|
34
|
+
.description('#171 Weighted rotation priority. 0 = derive from isPro.'),
|
|
35
|
+
isPro: z.boolean().default(false)
|
|
36
|
+
.description('#171 Treat this account as Pro for weighted rotation.'),
|
|
29
37
|
})
|
|
30
38
|
|
|
31
39
|
export const defaultSlots = PROVIDERS.map((provider) => ({ provider, index: 1, label: '' }))
|
package/lib/http.js
CHANGED
|
@@ -282,8 +282,23 @@ export function isSameOrigin(url1, url2) {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
export function headerSafeValue(value) {
|
|
286
|
+
return String(value == null ? '' : value)
|
|
287
|
+
.replace(/[\r\n]+/g, ' ')
|
|
288
|
+
.replace(/[^\t\x20-\x7e\x80-\xff]/g, '')
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function sanitizeHeaderValues(headers) {
|
|
292
|
+
const out = {}
|
|
293
|
+
for (const [key, value] of Object.entries(headers || {})) {
|
|
294
|
+
if (value === undefined || value === null) continue
|
|
295
|
+
out[key] = headerSafeValue(value)
|
|
296
|
+
}
|
|
297
|
+
return out
|
|
298
|
+
}
|
|
299
|
+
|
|
285
300
|
export function sanitizeRequestHeaders(url, init = {}, options = {}) {
|
|
286
|
-
const headers =
|
|
301
|
+
const headers = sanitizeHeaderValues(init.headers || {})
|
|
287
302
|
if (!url) return headers
|
|
288
303
|
try {
|
|
289
304
|
const parsed = new URL(url)
|
|
@@ -351,64 +366,6 @@ export async function fetchWithTimeout(fetchImpl, url, init = {}, { timeoutMs =
|
|
|
351
366
|
return res
|
|
352
367
|
}
|
|
353
368
|
if (typeof timer?.unref === 'function') timer.unref()
|
|
354
|
-
const origJson = typeof res.json === 'function' ? res.json.bind(res) : null
|
|
355
|
-
const origText = typeof res.text === 'function' ? res.text.bind(res) : null
|
|
356
|
-
if (origJson) {
|
|
357
|
-
res.json = () => {
|
|
358
|
-
if (typeof timer?.ref === 'function') timer.ref()
|
|
359
|
-
if (controller.signal.aborted) {
|
|
360
|
-
return Promise.reject(new DOMException('The operation was aborted due to timeout', 'TimeoutError'))
|
|
361
|
-
}
|
|
362
|
-
return new Promise((resolve, reject) => {
|
|
363
|
-
const onAbort = () => reject(new DOMException('The operation was aborted due to timeout', 'TimeoutError'))
|
|
364
|
-
controller.signal.addEventListener('abort', onAbort, { once: true })
|
|
365
|
-
origJson().then(
|
|
366
|
-
(val) => {
|
|
367
|
-
controller.signal.removeEventListener('abort', onAbort)
|
|
368
|
-
clearTimeout(timer)
|
|
369
|
-
resolve(val)
|
|
370
|
-
},
|
|
371
|
-
(err) => {
|
|
372
|
-
controller.signal.removeEventListener('abort', onAbort)
|
|
373
|
-
clearTimeout(timer)
|
|
374
|
-
if (controller.signal.aborted) {
|
|
375
|
-
reject(new DOMException('The operation was aborted due to timeout', 'TimeoutError'))
|
|
376
|
-
} else {
|
|
377
|
-
reject(err)
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
)
|
|
381
|
-
})
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
if (origText) {
|
|
385
|
-
res.text = () => {
|
|
386
|
-
if (typeof timer?.ref === 'function') timer.ref()
|
|
387
|
-
if (controller.signal.aborted) {
|
|
388
|
-
return Promise.reject(new DOMException('The operation was aborted due to timeout', 'TimeoutError'))
|
|
389
|
-
}
|
|
390
|
-
return new Promise((resolve, reject) => {
|
|
391
|
-
const onAbort = () => reject(new DOMException('The operation was aborted due to timeout', 'TimeoutError'))
|
|
392
|
-
controller.signal.addEventListener('abort', onAbort, { once: true })
|
|
393
|
-
origText().then(
|
|
394
|
-
(val) => {
|
|
395
|
-
controller.signal.removeEventListener('abort', onAbort)
|
|
396
|
-
clearTimeout(timer)
|
|
397
|
-
resolve(val)
|
|
398
|
-
},
|
|
399
|
-
(err) => {
|
|
400
|
-
controller.signal.removeEventListener('abort', onAbort)
|
|
401
|
-
clearTimeout(timer)
|
|
402
|
-
if (controller.signal.aborted) {
|
|
403
|
-
reject(new DOMException('The operation was aborted due to timeout', 'TimeoutError'))
|
|
404
|
-
} else {
|
|
405
|
-
reject(err)
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
)
|
|
409
|
-
})
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
369
|
return res
|
|
413
370
|
} catch (err) {
|
|
414
371
|
clearTimeout(timer)
|
package/lib/providers.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export const BUILTIN_PROVIDERS = Object.freeze([
|
|
2
|
+
'codex',
|
|
3
|
+
'claude',
|
|
4
|
+
'grok',
|
|
5
|
+
'antigravity',
|
|
6
|
+
'kimi',
|
|
7
|
+
'glm',
|
|
8
|
+
'cursor',
|
|
9
|
+
'kiro',
|
|
10
|
+
'copilot',
|
|
11
|
+
'qwen',
|
|
12
|
+
'ernie',
|
|
13
|
+
'spark',
|
|
14
|
+
'jetbrains',
|
|
15
|
+
'perplexity',
|
|
16
|
+
'replit',
|
|
17
|
+
'cody',
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
const DISPLAY = {
|
|
21
|
+
codex: 'ChatGPT Codex',
|
|
22
|
+
claude: 'Claude',
|
|
23
|
+
grok: 'Grok',
|
|
24
|
+
antigravity: 'Antigravity',
|
|
25
|
+
kimi: 'Moonshot Kimi',
|
|
26
|
+
glm: 'Zhipu GLM',
|
|
27
|
+
cursor: 'Cursor',
|
|
28
|
+
kiro: 'AWS Kiro',
|
|
29
|
+
copilot: 'GitHub Copilot',
|
|
30
|
+
perplexity: 'Perplexity Pro',
|
|
31
|
+
jetbrains: 'JetBrains AI',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const dynamicIds = new Set()
|
|
35
|
+
const displayExtra = {}
|
|
36
|
+
|
|
37
|
+
export let PROVIDERS = [...BUILTIN_PROVIDERS]
|
|
38
|
+
|
|
39
|
+
export function registerCustomProviderIds(ids) {
|
|
40
|
+
let changed = false
|
|
41
|
+
for (const id of ids || []) {
|
|
42
|
+
if (typeof id === 'string' && /^[a-z][a-z0-9_]*$/.test(id) && !dynamicIds.has(id)) {
|
|
43
|
+
dynamicIds.add(id)
|
|
44
|
+
changed = true
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (changed) PROVIDERS = listProviders()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function listProviders() {
|
|
51
|
+
return [...BUILTIN_PROVIDERS, ...dynamicIds]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function registerDisplayName(id, name) {
|
|
55
|
+
if (id && name) displayExtra[id] = name
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isProvider(value) {
|
|
59
|
+
return BUILTIN_PROVIDERS.includes(value) || dynamicIds.has(value)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function displayName(provider) {
|
|
63
|
+
return DISPLAY[provider] || displayExtra[provider] || provider
|
|
64
|
+
}
|
package/lib/refs.js
CHANGED
|
@@ -1,68 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
'kiro',
|
|
11
|
-
'copilot',
|
|
12
|
-
'qwen',
|
|
13
|
-
'ernie',
|
|
14
|
-
'spark',
|
|
15
|
-
'jetbrains',
|
|
16
|
-
'perplexity',
|
|
17
|
-
'replit',
|
|
18
|
-
'cody',
|
|
19
|
-
])
|
|
20
|
-
|
|
21
|
-
const dynamicIds = new Set()
|
|
22
|
-
const displayExtra = {}
|
|
23
|
-
|
|
24
|
-
export let PROVIDERS = [...BUILTIN_PROVIDERS]
|
|
25
|
-
|
|
26
|
-
export function registerCustomProviderIds(ids) {
|
|
27
|
-
let changed = false
|
|
28
|
-
for (const id of ids || []) {
|
|
29
|
-
if (typeof id === 'string' && /^[a-z][a-z0-9_]*$/.test(id) && !dynamicIds.has(id)) {
|
|
30
|
-
dynamicIds.add(id)
|
|
31
|
-
changed = true
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
if (changed) PROVIDERS = listProviders()
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function listProviders() {
|
|
38
|
-
return [...BUILTIN_PROVIDERS, ...dynamicIds]
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function registerDisplayName(id, name) {
|
|
42
|
-
if (id && name) displayExtra[id] = name
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function isProvider(value) {
|
|
46
|
-
return BUILTIN_PROVIDERS.includes(value) || dynamicIds.has(value)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const DISPLAY = {
|
|
50
|
-
codex: 'ChatGPT Codex',
|
|
51
|
-
claude: 'Claude',
|
|
52
|
-
grok: 'Grok',
|
|
53
|
-
antigravity: 'Antigravity',
|
|
54
|
-
kimi: 'Moonshot Kimi',
|
|
55
|
-
glm: 'Zhipu GLM',
|
|
56
|
-
cursor: 'Cursor',
|
|
57
|
-
kiro: 'AWS Kiro',
|
|
58
|
-
copilot: 'GitHub Copilot',
|
|
59
|
-
perplexity: 'Perplexity Pro',
|
|
60
|
-
jetbrains: 'JetBrains AI',
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function displayName(provider) {
|
|
64
|
-
return DISPLAY[provider] || displayExtra[provider] || provider
|
|
65
|
-
}
|
|
1
|
+
export {
|
|
2
|
+
BUILTIN_PROVIDERS,
|
|
3
|
+
PROVIDERS,
|
|
4
|
+
registerCustomProviderIds,
|
|
5
|
+
listProviders,
|
|
6
|
+
registerDisplayName,
|
|
7
|
+
displayName,
|
|
8
|
+
isProvider,
|
|
9
|
+
} from './providers.js'
|
|
66
10
|
|
|
67
11
|
/**
|
|
68
12
|
* @param {string} provider
|
package/lib/vendor-factory.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fetchWithTimeout } from "./http.js"
|
|
1
|
+
import { fetchWithTimeout, sanitizeHeaderValues } from "./http.js"
|
|
2
2
|
// Vendor factory from a declarative profile.
|
|
3
3
|
//
|
|
4
4
|
// Covers the OpenAI-Responses-compatible subscription family: PKCE auth,
|
|
@@ -30,11 +30,11 @@ export function createVendorFromProfile(profile) {
|
|
|
30
30
|
const tokenStyle = profile.tokenStyle === "json" ? "json" : "form"
|
|
31
31
|
|
|
32
32
|
function identityHeaders(blob, extra) {
|
|
33
|
-
return {
|
|
33
|
+
return sanitizeHeaderValues({
|
|
34
34
|
Authorization: `Bearer ${blob.accessToken}`,
|
|
35
35
|
...(profile.headers || {}),
|
|
36
36
|
...(extra || {}),
|
|
37
|
-
}
|
|
37
|
+
})
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
return {
|
package/lib/vendors/codex.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isTimeoutError, fetchWithTimeout } from "../http.js"
|
|
1
|
+
import { isTimeoutError, fetchWithTimeout, headerSafeValue } from "../http.js"
|
|
2
2
|
import { randomUUID, createHash } from 'node:crypto'
|
|
3
3
|
import { buildAuthorizeUrl } from '../oauth.js'
|
|
4
4
|
import { codexResponsesBody, modelCatalog } from '../messages.js'
|
|
@@ -61,10 +61,10 @@ export function authorizeUrl(cfg, pkce) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
function identityHeaders(blob, config, extra) {
|
|
64
|
-
const originator = config.originator || 'codex_cli_rs'
|
|
65
|
-
const activeOrgId = blob.selectedOrgId || blob.accountId || ''
|
|
64
|
+
const originator = headerSafeValue(config.originator || 'codex_cli_rs')
|
|
65
|
+
const activeOrgId = headerSafeValue(blob.selectedOrgId || blob.accountId || '')
|
|
66
66
|
return {
|
|
67
|
-
Authorization: `Bearer ${blob.accessToken}`,
|
|
67
|
+
Authorization: `Bearer ${headerSafeValue(blob.accessToken)}`,
|
|
68
68
|
originator,
|
|
69
69
|
'chatgpt-account-id': activeOrgId,
|
|
70
70
|
'ChatGPT-Account-ID': activeOrgId,
|
|
@@ -260,7 +260,7 @@ export async function* streamOnce({ blob, options, fetchImpl, headers, config, s
|
|
|
260
260
|
Accept: 'text/event-stream',
|
|
261
261
|
'session-id': sessionId,
|
|
262
262
|
'x-client-request-id': sessionId,
|
|
263
|
-
...(isFast ? { 'x-codex-routing-hint': `model=${(options && options.model) || 'gpt-5.6-luna'};tier=priority` } : {}),
|
|
263
|
+
...(isFast ? { 'x-codex-routing-hint': `model=${headerSafeValue((options && options.model) || 'gpt-5.6-luna')};tier=priority` } : {}),
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
const res = await fetchImpl(`${base}/responses`, {
|
package/lib/vendors/cody.js
CHANGED
|
@@ -39,10 +39,7 @@ export async function listModels() {
|
|
|
39
39
|
return CODY_MODELS
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export async function usage(
|
|
43
|
-
if (blob && (blob.accessToken || blob.token)) {
|
|
44
|
-
return { usedPercent: null, plan: 'Cody Pro', timestamp: Date.now() }
|
|
45
|
-
}
|
|
42
|
+
export async function usage() {
|
|
46
43
|
return null
|
|
47
44
|
}
|
|
48
45
|
|
package/lib/vendors/copilot.js
CHANGED
|
@@ -81,6 +81,7 @@ export async function listModels(blob, cfg, fetchImpl) {
|
|
|
81
81
|
const impl = fetchImpl || fetch
|
|
82
82
|
const fallback = modelCatalog(id, (cfg && cfg.models) || COPILOT_MODELS)
|
|
83
83
|
if (!blob) return fallback
|
|
84
|
+
blob = { ...blob }
|
|
84
85
|
const githubToken = blob.refreshToken || blob.githubToken
|
|
85
86
|
let copilotToken = blob.accessToken
|
|
86
87
|
try {
|
|
@@ -279,6 +280,8 @@ export async function usage(blob, config, fetchImpl) {
|
|
|
279
280
|
const usedPercent = user && typeof user.used_percent === "number" && Number.isFinite(user.used_percent)
|
|
280
281
|
? user.used_percent
|
|
281
282
|
: null
|
|
283
|
+
|
|
284
|
+
if (usedPercent == null) return null
|
|
282
285
|
return {
|
|
283
286
|
usedPercent,
|
|
284
287
|
plan: user && user.plan ? (user.plan.name || String(user.plan)) : "Copilot",
|
|
@@ -290,6 +293,7 @@ export async function usage(blob, config, fetchImpl) {
|
|
|
290
293
|
}
|
|
291
294
|
|
|
292
295
|
export async function* streamOnce({ blob, options, fetchImpl, headers, config, signal, spawnImpl }) {
|
|
296
|
+
blob = blob ? { ...blob } : blob
|
|
293
297
|
const mode = (config && config.copilotMode) || (blob && blob.copilotMode) || 'auto'
|
|
294
298
|
const customPath = (config && config.copilotCliPath) || (blob && blob.copilotCliPath) || ''
|
|
295
299
|
|
package/lib/vendors/ernie.js
CHANGED
|
@@ -74,7 +74,6 @@ export async function* streamOnce({ blob, options, fetchImpl, headers, config, s
|
|
|
74
74
|
const auth = await refreshErnieToken(blob.apiKey, blob.secretKey, impl)
|
|
75
75
|
if (auth && auth.access_token) {
|
|
76
76
|
token = auth.access_token
|
|
77
|
-
blob.accessToken = token
|
|
78
77
|
}
|
|
79
78
|
}
|
|
80
79
|
if (!token) throw new LlmError('Baidu ERNIE requires access token or API Key + Secret Key', 'AUTH')
|
package/lib/vendors/grok.js
CHANGED
|
@@ -262,26 +262,7 @@ export async function usage(blob, cfg, fetchImpl) {
|
|
|
262
262
|
headers: { ...identityHeaders(blob, cfg), Accept: 'application/json' },
|
|
263
263
|
})
|
|
264
264
|
const json = await readJson(res)
|
|
265
|
-
|
|
266
|
-
if (pct == null) {
|
|
267
|
-
// Fallback to gRPC-web GetGrokCreditsConfig
|
|
268
|
-
try {
|
|
269
|
-
const gRes = await impl('https://grok.com/rest/app-chat/get-grok-credits-config', {
|
|
270
|
-
method: 'POST',
|
|
271
|
-
headers: {
|
|
272
|
-
...identityHeaders(blob, cfg),
|
|
273
|
-
'Content-Type': 'application/grpc-web+proto',
|
|
274
|
-
'X-User-Agent': 'grpc-web-javascript/0.1',
|
|
275
|
-
},
|
|
276
|
-
body: Buffer.from([0, 0, 0, 0, 0]),
|
|
277
|
-
})
|
|
278
|
-
if (gRes.ok) {
|
|
279
|
-
const buf = await gRes.arrayBuffer()
|
|
280
|
-
const decoded = decodeGrokCreditsFrame(Buffer.from(buf))
|
|
281
|
-
if (decoded != null) pct = decoded
|
|
282
|
-
}
|
|
283
|
-
} catch { /* ignore grok usage capture error */ }
|
|
284
|
-
}
|
|
265
|
+
const pct = grokBillingPercent(json)
|
|
285
266
|
const snap = asUsageSnapshot(pct)
|
|
286
267
|
if (snap) snap.windows = usageWindows(json)
|
|
287
268
|
return snap
|
|
@@ -394,81 +375,3 @@ export async function* streamOnce({ blob, options, fetchImpl, headers, config, s
|
|
|
394
375
|
if (!res.ok) throw httpError(res.status, await readErrorBody(res))
|
|
395
376
|
yield* codexResponsesStream(res.body)
|
|
396
377
|
}
|
|
397
|
-
|
|
398
|
-
function readVarint(bytes, offset) {
|
|
399
|
-
let value = 0, shift = 0, index = offset
|
|
400
|
-
while (index < bytes.length) {
|
|
401
|
-
const byte = bytes[index]
|
|
402
|
-
index += 1
|
|
403
|
-
value += (byte & 0x7f) * (2 ** shift)
|
|
404
|
-
if ((byte & 0x80) === 0) return { value, next: index }
|
|
405
|
-
shift += 7
|
|
406
|
-
if (shift > 63) return undefined
|
|
407
|
-
}
|
|
408
|
-
return undefined
|
|
409
|
-
}
|
|
410
|
-
function readLength(bytes, offset, size) {
|
|
411
|
-
if (offset + size > bytes.length) return undefined
|
|
412
|
-
return { bytes: bytes.subarray(offset, offset + size), next: offset + size }
|
|
413
|
-
}
|
|
414
|
-
function decodeFields(bytes) {
|
|
415
|
-
const fields = new Map()
|
|
416
|
-
let offset = 0
|
|
417
|
-
while (offset < bytes.length) {
|
|
418
|
-
const tag = readVarint(bytes, offset)
|
|
419
|
-
if (!tag) break
|
|
420
|
-
const fieldNumber = Math.floor(tag.value / 8)
|
|
421
|
-
const wireType = tag.value % 8
|
|
422
|
-
offset = tag.next
|
|
423
|
-
if (fieldNumber <= 0) return undefined
|
|
424
|
-
let value
|
|
425
|
-
if (wireType === 0) {
|
|
426
|
-
const next = readVarint(bytes, offset)
|
|
427
|
-
if (!next) return undefined
|
|
428
|
-
value = { wireType, value: next.value }
|
|
429
|
-
offset = next.next
|
|
430
|
-
} else if (wireType === 5) {
|
|
431
|
-
const next = readLength(bytes, offset, 4)
|
|
432
|
-
if (!next) return undefined
|
|
433
|
-
value = { wireType, bytes: next.bytes }
|
|
434
|
-
offset = next.next
|
|
435
|
-
} else if (wireType === 1) {
|
|
436
|
-
const next = readLength(bytes, offset, 8)
|
|
437
|
-
if (!next) return undefined
|
|
438
|
-
value = { wireType, bytes: next.bytes }
|
|
439
|
-
offset = next.next
|
|
440
|
-
} else if (wireType === 2) {
|
|
441
|
-
const length = readVarint(bytes, offset)
|
|
442
|
-
if (!length) return undefined
|
|
443
|
-
const next = readLength(bytes, length.next, length.value)
|
|
444
|
-
if (!next) return undefined
|
|
445
|
-
value = { wireType, bytes: next.bytes }
|
|
446
|
-
offset = next.next
|
|
447
|
-
} else return undefined
|
|
448
|
-
if (!fields.has(fieldNumber)) fields.set(fieldNumber, value)
|
|
449
|
-
}
|
|
450
|
-
return fields
|
|
451
|
-
}
|
|
452
|
-
function float32Le(field) {
|
|
453
|
-
if (!field || field.wireType !== 5 || !field.bytes || field.bytes.length < 4) return undefined
|
|
454
|
-
const val = Buffer.from(field.bytes).readFloatLE(0)
|
|
455
|
-
return Number.isFinite(val) ? val : undefined
|
|
456
|
-
}
|
|
457
|
-
export function decodeGrokCreditsFrame(buffer) {
|
|
458
|
-
try {
|
|
459
|
-
const bytes = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer)
|
|
460
|
-
if (!bytes.length) return undefined
|
|
461
|
-
const payload = bytes.length > 5 && bytes[0] === 0 ? bytes.subarray(5) : bytes
|
|
462
|
-
const top = decodeFields(payload)
|
|
463
|
-
if (!top) return undefined
|
|
464
|
-
const nested = top.get(1)?.wireType === 2 ? decodeFields(top.get(1).bytes) : undefined
|
|
465
|
-
const credits = nested ?? top
|
|
466
|
-
if (!credits) return undefined
|
|
467
|
-
const ratio = float32Le(credits.get(1))
|
|
468
|
-
if (ratio == null) return undefined
|
|
469
|
-
const pct = ratio <= 1 ? Math.round(ratio * 100) : Math.round(ratio)
|
|
470
|
-
return Math.max(0, Math.min(100, pct))
|
|
471
|
-
} catch {
|
|
472
|
-
return undefined
|
|
473
|
-
}
|
|
474
|
-
}
|
package/lib/vendors/index.js
CHANGED
|
@@ -14,8 +14,9 @@ import * as grok from './grok.js'
|
|
|
14
14
|
import * as antigravity from './antigravity.js'
|
|
15
15
|
import * as kimi from './kimi.js'
|
|
16
16
|
import * as glm from './glm.js'
|
|
17
|
+
import { BUILTIN_PROVIDERS, isProvider as isRegisteredProvider, registerCustomProviderIds } from '../providers.js'
|
|
17
18
|
|
|
18
|
-
const
|
|
19
|
+
const BUILTIN_MODULES = {
|
|
19
20
|
codex,
|
|
20
21
|
claude,
|
|
21
22
|
grok,
|
|
@@ -31,14 +32,17 @@ const builtins = {
|
|
|
31
32
|
jetbrains,
|
|
32
33
|
perplexity,
|
|
33
34
|
replit,
|
|
34
|
-
cody
|
|
35
|
+
cody,
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
const builtins = Object.fromEntries(BUILTIN_PROVIDERS.map((id) => [id, BUILTIN_MODULES[id]]))
|
|
39
|
+
|
|
37
40
|
const customVendors = new Map()
|
|
38
41
|
|
|
39
42
|
export function registerCustomVendor(vendor) {
|
|
40
43
|
if (!vendor || !vendor.id) throw new Error('custom vendor needs id')
|
|
41
44
|
customVendors.set(vendor.id, vendor)
|
|
45
|
+
registerCustomProviderIds([vendor.id])
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
export function clearCustomVendors() {
|
|
@@ -62,7 +66,7 @@ const vendors = new Proxy({}, {
|
|
|
62
66
|
})
|
|
63
67
|
|
|
64
68
|
export function isProvider(value) {
|
|
65
|
-
return Boolean(vendors[value])
|
|
69
|
+
return isRegisteredProvider(value) && Boolean(vendors[value])
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
export function getVendor(provider) {
|
|
@@ -171,15 +171,16 @@ export async function usage(blob, _cfg, fetchImpl) {
|
|
|
171
171
|
})
|
|
172
172
|
if (res.ok) {
|
|
173
173
|
const d = await readJson(res)
|
|
174
|
+
if (typeof d.used_percent !== 'number') return null
|
|
174
175
|
return {
|
|
175
|
-
usedPercent:
|
|
176
|
+
usedPercent: d.used_percent,
|
|
176
177
|
plan: d.plan || blob.plan || 'Perplexity Pro',
|
|
177
178
|
timestamp: Date.now(),
|
|
178
179
|
quota: d.quota ? { used: d.quota.used, limit: d.quota.limit, resetAt: d.quota.reset_at } : undefined,
|
|
179
180
|
}
|
|
180
181
|
}
|
|
181
182
|
} catch { /* usage endpoint is optional */ }
|
|
182
|
-
return
|
|
183
|
+
return null
|
|
183
184
|
}
|
|
184
185
|
|
|
185
186
|
export async function* streamOnce({ blob, options, fetchImpl, headers, config, signal }) {
|
package/lib/vendors/replit.js
CHANGED
|
@@ -34,10 +34,7 @@ export async function listModels() {
|
|
|
34
34
|
return REPLIT_MODELS
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
export async function usage(
|
|
38
|
-
if (blob && (blob.accessToken || blob.token)) {
|
|
39
|
-
return { usedPercent: null, plan: 'Replit Core', timestamp: Date.now() }
|
|
40
|
-
}
|
|
37
|
+
export async function usage() {
|
|
41
38
|
return null
|
|
42
39
|
}
|
|
43
40
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.89",
|
|
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",
|