dsh-subscriptions 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -180
- package/lib/auth/claude-code-creds.js +31 -9
- package/lib/auth/device-flow.js +16 -9
- package/lib/auth/oauth-flow.js +13 -10
- package/lib/auth/rpc.js +74 -23
- package/lib/auth/store.js +80 -31
- package/lib/client/CursorCard.d.ts +1 -1
- package/lib/client/CursorCard.js +50 -16
- package/lib/client/ExternalUsageCards.d.ts +1 -1
- package/lib/client/ExternalUsageCards.js +37 -16
- package/lib/client/ImageGallery.d.ts +2 -2
- package/lib/client/ImageGallery.js +92 -33
- package/lib/client/ImageGenerateToolview.js +50 -14
- package/lib/client/OllamaCard.d.ts +1 -1
- package/lib/client/OllamaCard.js +26 -8
- package/lib/client/OllamaSettingsManager.js +113 -30
- package/lib/client/ProviderAccountManager.d.ts +1 -1
- package/lib/client/ProviderAccountManager.js +164 -49
- package/lib/client/ProviderModelEditor.js +107 -26
- package/lib/client/SpeedSelect.d.ts +62 -14
- package/lib/client/SpeedSelect.js +257 -46
- package/lib/client/SubscriptionUsageBadge.d.ts +4 -10
- package/lib/client/SubscriptionUsageBadge.js +300 -112
- package/lib/client/SubscriptionsSection.js +258 -85
- package/lib/client/UsageBadgeDisplaySetting.js +35 -12
- package/lib/client/UsageMeter.d.ts +1 -1
- package/lib/client/UsageMeter.js +62 -24
- package/lib/client/VideoGenerateToolview.js +69 -17
- package/lib/client/account-preferences.d.ts +8 -0
- package/lib/client/account-preferences.js +20 -5
- package/lib/client/host-contract.d.ts +11 -2
- package/lib/client/host-contract.js +20 -3
- package/lib/client/index.d.ts +5 -5
- package/lib/client/index.js +34 -12
- package/lib/client/locales.d.ts +10 -0
- package/lib/client/locales.js +61 -51
- package/lib/client/ollama-draft.js +21 -12
- package/lib/client/reset-action.js +6 -4
- package/lib/client/reset-credits-view.d.ts +1 -1
- package/lib/client/reset-credits-view.js +66 -17
- package/lib/client/subscription-card-styles.js +94 -24
- package/lib/client/usage-color-preferences.js +4 -2
- package/lib/client/usage-pace.js +30 -12
- package/lib/client.js +287 -47
- package/lib/client.js.map +1 -1
- package/lib/http.js +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.js +118 -64
- package/lib/model-defaults.js +4 -2
- package/lib/provider-settings.d.ts +12 -0
- package/lib/provider-settings.js +58 -16
- package/lib/providers/account-preferences.js +51 -22
- package/lib/providers/accounts.js +15 -9
- package/lib/providers/antigravity-oauth-client.js +2 -2
- package/lib/providers/antigravity.js +141 -68
- package/lib/providers/catalog-store.js +120 -41
- package/lib/providers/claude-cli-version.js +3 -3
- package/lib/providers/claude.d.ts +9 -2
- package/lib/providers/claude.js +153 -90
- package/lib/providers/codex-client-version.js +3 -3
- package/lib/providers/codex-search.js +40 -13
- package/lib/providers/codex.js +196 -112
- package/lib/providers/common.js +71 -39
- package/lib/providers/copilot.js +143 -82
- package/lib/providers/cursor-adapter.js +42 -18
- package/lib/providers/cursor-auth.d.ts +1 -1
- package/lib/providers/cursor-auth.js +69 -24
- package/lib/providers/cursor-model-settings.js +14 -5
- package/lib/providers/cursor-usage.js +27 -13
- package/lib/providers/external-usage-controller.js +14 -5
- package/lib/providers/external-usage.js +59 -23
- package/lib/providers/grok.js +152 -89
- package/lib/providers/image-pool.js +29 -11
- package/lib/providers/minimax-usage.js +31 -11
- package/lib/providers/npm-cli-version.js +10 -3
- package/lib/providers/ollama-settings.js +24 -8
- package/lib/providers/ollama-web.js +29 -11
- package/lib/providers/ollama.js +129 -66
- package/lib/providers/pool-family.js +11 -3
- package/lib/providers/pool-health.js +28 -8
- package/lib/providers/pool-usage.js +16 -9
- package/lib/providers/pool.d.ts +8 -0
- package/lib/providers/pool.js +81 -42
- package/lib/providers/rate-limit.js +8 -2
- package/lib/providers/replay.js +40 -17
- package/lib/providers/reset-redemption.js +18 -6
- package/lib/tools/image-generate.js +123 -65
- package/lib/tools/registration.js +5 -2
- package/lib/tools/video-generate.js +51 -31
- package/lib/tools/x-search.js +50 -22
- package/lib/translate/anthropic.js +85 -37
- package/lib/translate/antigravity-schema.js +71 -28
- package/lib/translate/antigravity-thinking.js +22 -9
- package/lib/translate/antigravity.js +141 -50
- package/lib/translate/chat-completions.js +67 -23
- package/lib/translate/resolved.js +55 -21
- package/lib/translate/responses.js +69 -29
- package/lib/translate/sse.js +4 -1
- package/package.json +26 -4
package/lib/client/usage-pace.js
CHANGED
|
@@ -21,7 +21,7 @@ export function resetCountdownParts(ms) {
|
|
|
21
21
|
{ unit: 'hour', count: Math.floor((total % 1440) / 60) },
|
|
22
22
|
{ unit: 'minute', count: total % 60 },
|
|
23
23
|
];
|
|
24
|
-
return parts.filter(part => part.count > 0).slice(0, 2);
|
|
24
|
+
return parts.filter((part) => part.count > 0).slice(0, 2);
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Whether the reported percentage still describes the window now. Only a fresh
|
|
@@ -30,10 +30,14 @@ export function resetCountdownParts(ms) {
|
|
|
30
30
|
* drawn: where the window stands in time never depends on the reading.
|
|
31
31
|
*/
|
|
32
32
|
export function isUsageFresh(window, observation, now = Date.now()) {
|
|
33
|
-
return validUsage(window.usedPercent) &&
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
return (validUsage(window.usedPercent) &&
|
|
34
|
+
observation.stale !== true &&
|
|
35
|
+
observation.observedAt !== undefined &&
|
|
36
|
+
Number.isFinite(observation.observedAt) &&
|
|
37
|
+
observation.observedAt <= now &&
|
|
38
|
+
now - observation.observedAt <= USAGE_FRESHNESS_MS &&
|
|
39
|
+
(window.resetsAt === undefined ||
|
|
40
|
+
(Number.isFinite(window.resetsAt) && window.resetsAt > now)));
|
|
37
41
|
}
|
|
38
42
|
/**
|
|
39
43
|
* How far through its window the clock is, from the window's own timing alone.
|
|
@@ -48,10 +52,19 @@ export function elapsedPercent(window, now = Date.now()) {
|
|
|
48
52
|
if (end === undefined || !Number.isFinite(end) || end <= now)
|
|
49
53
|
return undefined;
|
|
50
54
|
const duration = window.windowDurationMs;
|
|
51
|
-
const start = window.startsAt ??
|
|
52
|
-
|
|
55
|
+
const start = window.startsAt ??
|
|
56
|
+
(window.fixedWindow === true &&
|
|
57
|
+
duration !== undefined &&
|
|
58
|
+
Number.isFinite(duration) &&
|
|
59
|
+
duration > 0
|
|
60
|
+
? end - duration
|
|
61
|
+
: undefined);
|
|
62
|
+
if (start === undefined ||
|
|
63
|
+
!Number.isFinite(start) ||
|
|
64
|
+
start >= end ||
|
|
65
|
+
start > now)
|
|
53
66
|
return undefined;
|
|
54
|
-
return Math.min(100, Math.max(0, (now - start) / (end - start) * 100));
|
|
67
|
+
return Math.min(100, Math.max(0, ((now - start) / (end - start)) * 100));
|
|
55
68
|
}
|
|
56
69
|
export function usageColorState(used, elapsed, preset, fresh) {
|
|
57
70
|
if (!validUsage(used))
|
|
@@ -65,8 +78,10 @@ export function usageColorState(used, elapsed, preset, fresh) {
|
|
|
65
78
|
}
|
|
66
79
|
export function usageBarColor(used, elapsed, preset = 'standard', fresh = true) {
|
|
67
80
|
const state = usageColorState(used, elapsed, preset, fresh);
|
|
68
|
-
return state === 'red'
|
|
69
|
-
|
|
81
|
+
return state === 'red'
|
|
82
|
+
? 'var(--dsw-alias-state-error-primary)'
|
|
83
|
+
: state === 'yellow'
|
|
84
|
+
? 'var(--dsw-alias-state-warn-label)'
|
|
70
85
|
: 'var(--dsw-alias-state-success-primary)';
|
|
71
86
|
}
|
|
72
87
|
/**
|
|
@@ -80,7 +95,10 @@ export function displayStartPercent(window, now = Date.now()) {
|
|
|
80
95
|
const start = window.displayStartsAt;
|
|
81
96
|
if (end === undefined || !Number.isFinite(end) || end <= now)
|
|
82
97
|
return undefined;
|
|
83
|
-
if (start === undefined ||
|
|
98
|
+
if (start === undefined ||
|
|
99
|
+
!Number.isFinite(start) ||
|
|
100
|
+
start >= end ||
|
|
101
|
+
start > now)
|
|
84
102
|
return undefined;
|
|
85
|
-
return Math.min(100, Math.max(0, (now - start) / (end - start) * 100));
|
|
103
|
+
return Math.min(100, Math.max(0, ((now - start) / (end - start)) * 100));
|
|
86
104
|
}
|
package/lib/client.js
CHANGED
|
@@ -72,6 +72,11 @@ const en = {
|
|
|
72
72
|
modelsWebSearch: "Web search",
|
|
73
73
|
modelsSave: "Save changes",
|
|
74
74
|
modelsUnavailable: "Currently unavailable",
|
|
75
|
+
claudeCacheTtl: "Prompt cache lifetime",
|
|
76
|
+
claudeCacheTtlFollowConfig: "Follow config",
|
|
77
|
+
claudeCacheTtl5m: "5 minutes",
|
|
78
|
+
claudeCacheTtl1h: "1 hour",
|
|
79
|
+
claudeCacheTtlHint: "Overrides the claudePromptCacheTtl config for this Claude route; Follow config uses the configured value. Saved changes apply from the next request. One hour keeps cached prefixes longer but writes them at a higher input price; compaction and session-title requests stay at five minutes.",
|
|
75
80
|
nav: "Subscriptions",
|
|
76
81
|
intro: "Log a subscription provider in or out. Login opens the provider’s authorization page in a new tab; headless setups can paste the callback URL or code instead.",
|
|
77
82
|
unavailable: "Connection unavailable; subscription status cannot be loaded.",
|
|
@@ -307,6 +312,11 @@ const zh = {
|
|
|
307
312
|
modelsWebSearch: "网页搜索",
|
|
308
313
|
modelsSave: "保存更改",
|
|
309
314
|
modelsUnavailable: "当前不可用",
|
|
315
|
+
claudeCacheTtl: "提示词缓存时长",
|
|
316
|
+
claudeCacheTtlFollowConfig: "跟随配置",
|
|
317
|
+
claudeCacheTtl5m: "5 分钟",
|
|
318
|
+
claudeCacheTtl1h: "1 小时",
|
|
319
|
+
claudeCacheTtlHint: "覆盖此 Claude 路由的 claudePromptCacheTtl 配置;选择「跟随配置」时使用配置值。保存后自下一次请求生效。1 小时可更久保留缓存前缀,但写入按更高的输入价格计费;压缩与会话标题请求仍为 5 分钟。",
|
|
310
320
|
nav: "订阅",
|
|
311
321
|
intro: "在此登录或退出订阅服务商。点击登录会在新标签页打开服务商的授权页面;无浏览器环境可改为粘贴回调 URL 或授权码。",
|
|
312
322
|
unavailable: "连接不可用,无法加载订阅状态。",
|
|
@@ -576,6 +586,19 @@ function mergeLatestAccounts(draft, latest) {
|
|
|
576
586
|
...latest.accounts === void 0 ? {} : { accounts: latest.accounts }
|
|
577
587
|
};
|
|
578
588
|
}
|
|
589
|
+
/**
|
|
590
|
+
* The account manager owns the Claude prompt-cache TTL picker. The model
|
|
591
|
+
* editor's draft carries the TTL it loaded, so the dialog always replaces it
|
|
592
|
+
* with the picker's choice when touched, or the freshly re-read value
|
|
593
|
+
* otherwise.
|
|
594
|
+
*/
|
|
595
|
+
function mergePromptCacheTtl(settings, promptCacheTtl) {
|
|
596
|
+
const { promptCacheTtl: _stale,...rest } = settings;
|
|
597
|
+
return promptCacheTtl === void 0 ? rest : {
|
|
598
|
+
...rest,
|
|
599
|
+
promptCacheTtl
|
|
600
|
+
};
|
|
601
|
+
}
|
|
579
602
|
/** Only touched accounts replace their latest preferences; other settings survive. */
|
|
580
603
|
function mergeAccountChanges(latest, changes) {
|
|
581
604
|
return {
|
|
@@ -683,7 +706,8 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
683
706
|
draft,
|
|
684
707
|
contexts,
|
|
685
708
|
efforts,
|
|
686
|
-
provider
|
|
709
|
+
provider,
|
|
710
|
+
t
|
|
687
711
|
]);
|
|
688
712
|
const allModels = catalog?.models ?? [];
|
|
689
713
|
const known = new Set(allModels.map((model) => model.id));
|
|
@@ -1149,6 +1173,8 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1149
1173
|
const [saveError, setSaveError] = (0, react.useState)("");
|
|
1150
1174
|
const [attempt, setAttempt] = (0, react.useState)(0);
|
|
1151
1175
|
const [modelsDirty, setModelsDirty] = (0, react.useState)(false);
|
|
1176
|
+
const [ttlDraft, setTtlDraft] = (0, react.useState)();
|
|
1177
|
+
const [ttlDirty, setTtlDirty] = (0, react.useState)(false);
|
|
1152
1178
|
const alive = (0, react.useRef)(true);
|
|
1153
1179
|
const saveLock = (0, react.useRef)(false);
|
|
1154
1180
|
const editor = (0, react.useRef)(null);
|
|
@@ -1168,7 +1194,11 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1168
1194
|
setLoading(true);
|
|
1169
1195
|
setError("");
|
|
1170
1196
|
callSubscriptionsAuth(rpc, provider === "cursor-subscription" ? "cursorSettings" : "providerSettings", { provider }).then((data) => {
|
|
1171
|
-
if (current)
|
|
1197
|
+
if (current) {
|
|
1198
|
+
setCatalog(data);
|
|
1199
|
+
setTtlDraft(data.settings.promptCacheTtl);
|
|
1200
|
+
setTtlDirty(false);
|
|
1201
|
+
}
|
|
1172
1202
|
}).catch((error$1) => {
|
|
1173
1203
|
if (current) setError(t("accountsLoadFailed", { message: error$1 instanceof Error ? error$1.message : String(error$1) }));
|
|
1174
1204
|
}).finally(() => {
|
|
@@ -1216,7 +1246,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1216
1246
|
if (provider === "cursor-subscription") await callSubscriptionsAuth(rpc, "cursorSetSettings", { settings: models.settings ?? latest.settings });
|
|
1217
1247
|
else await callSubscriptionsAuth(rpc, "setProviderSettings", {
|
|
1218
1248
|
provider,
|
|
1219
|
-
settings: mergeAccountChanges(models.settings === void 0 ? latest.settings : mergeLatestAccounts(models.settings, latest.settings), changes)
|
|
1249
|
+
settings: mergeAccountChanges(mergePromptCacheTtl(models.settings === void 0 ? latest.settings : mergeLatestAccounts(models.settings, latest.settings), ttlDirty ? ttlDraft : latest.settings.promptCacheTtl), changes)
|
|
1220
1250
|
});
|
|
1221
1251
|
if (alive.current) onClose();
|
|
1222
1252
|
} catch (error$1) {
|
|
@@ -1392,7 +1422,8 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1392
1422
|
},
|
|
1393
1423
|
children: [
|
|
1394
1424
|
t("accountsModels"),
|
|
1395
|
-
" ·
|
|
1425
|
+
" ·",
|
|
1426
|
+
" ",
|
|
1396
1427
|
preferences.poolModels === void 0 ? t("accountsModelsAll") : t("accountsModelsCount", { count: preferences.poolModels.length })
|
|
1397
1428
|
]
|
|
1398
1429
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1484,6 +1515,52 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1484
1515
|
disabled: saving,
|
|
1485
1516
|
onDirtyChange: setModelsDirty
|
|
1486
1517
|
}),
|
|
1518
|
+
provider === "claude" && catalog && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1519
|
+
style: {
|
|
1520
|
+
...stack$1,
|
|
1521
|
+
gap: 6,
|
|
1522
|
+
borderTop: border$1,
|
|
1523
|
+
marginTop: 12,
|
|
1524
|
+
paddingTop: 12
|
|
1525
|
+
},
|
|
1526
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1527
|
+
style: {
|
|
1528
|
+
...stack$1,
|
|
1529
|
+
gap: 6
|
|
1530
|
+
},
|
|
1531
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1532
|
+
className: "dsh-subscription-label",
|
|
1533
|
+
children: t("claudeCacheTtl")
|
|
1534
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1535
|
+
"aria-label": t("claudeCacheTtl"),
|
|
1536
|
+
style: { width: 220 },
|
|
1537
|
+
value: ttlDraft ?? "",
|
|
1538
|
+
disabled: saving,
|
|
1539
|
+
onChange: (event) => {
|
|
1540
|
+
const value = event.target.value;
|
|
1541
|
+
setTtlDraft(value === "" ? void 0 : value);
|
|
1542
|
+
setTtlDirty(true);
|
|
1543
|
+
},
|
|
1544
|
+
children: [
|
|
1545
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1546
|
+
value: "",
|
|
1547
|
+
children: t("claudeCacheTtlFollowConfig")
|
|
1548
|
+
}),
|
|
1549
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1550
|
+
value: "5m",
|
|
1551
|
+
children: t("claudeCacheTtl5m")
|
|
1552
|
+
}),
|
|
1553
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1554
|
+
value: "1h",
|
|
1555
|
+
children: t("claudeCacheTtl1h")
|
|
1556
|
+
})
|
|
1557
|
+
]
|
|
1558
|
+
})]
|
|
1559
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1560
|
+
className: "dsh-subscription-hint",
|
|
1561
|
+
children: t("claudeCacheTtlHint")
|
|
1562
|
+
})]
|
|
1563
|
+
}),
|
|
1487
1564
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
1488
1565
|
style: {
|
|
1489
1566
|
...stack$1,
|
|
@@ -1510,7 +1587,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1510
1587
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1511
1588
|
type: "button",
|
|
1512
1589
|
className: "dsh-subscription-primary",
|
|
1513
|
-
disabled: loading || saving || !Object.keys(changes).length && !modelsDirty,
|
|
1590
|
+
disabled: loading || saving || !Object.keys(changes).length && !modelsDirty && !ttlDirty,
|
|
1514
1591
|
onClick: () => {
|
|
1515
1592
|
save();
|
|
1516
1593
|
},
|
|
@@ -3588,7 +3665,12 @@ function ResetCreditsDisclosure({ mode, credits, t, now = Date.now(), nextCredit
|
|
|
3588
3665
|
}),
|
|
3589
3666
|
mode === "grants" && state === "notUsable" && credit.cooldownUntil !== void 0 && credit.cooldownUntil > now && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3590
3667
|
"aria-hidden": "true",
|
|
3591
|
-
children: [
|
|
3668
|
+
children: [
|
|
3669
|
+
t("resetCreditCooldownUntil", { date: new Date(credit.cooldownUntil).toLocaleString() }),
|
|
3670
|
+
" ",
|
|
3671
|
+
"·",
|
|
3672
|
+
" "
|
|
3673
|
+
]
|
|
3592
3674
|
}),
|
|
3593
3675
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3594
3676
|
"aria-hidden": mode === "grants" ? true : void 0,
|
|
@@ -3734,7 +3816,7 @@ const styles$6 = {
|
|
|
3734
3816
|
//#endregion
|
|
3735
3817
|
//#region src/client/SubscriptionsSection.tsx
|
|
3736
3818
|
/** Poll cadence while a provider login attempt is busy. */
|
|
3737
|
-
const POLL_INTERVAL_MS
|
|
3819
|
+
const POLL_INTERVAL_MS = 2e3;
|
|
3738
3820
|
const CLIENT_VERSION_SOURCES = {
|
|
3739
3821
|
npm: "clientVersionNpm",
|
|
3740
3822
|
local: "clientVersionLocal",
|
|
@@ -4063,7 +4145,7 @@ function SubscriptionsSection(props) {
|
|
|
4063
4145
|
if (pollersRef.current.has(provider)) return;
|
|
4064
4146
|
pollersRef.current.set(provider, setInterval(() => {
|
|
4065
4147
|
refresh();
|
|
4066
|
-
}, POLL_INTERVAL_MS
|
|
4148
|
+
}, POLL_INTERVAL_MS));
|
|
4067
4149
|
}, [refresh]);
|
|
4068
4150
|
(0, react.useEffect)(() => {
|
|
4069
4151
|
mountedRef.current = true;
|
|
@@ -4225,7 +4307,8 @@ function SubscriptionsSection(props) {
|
|
|
4225
4307
|
rpc,
|
|
4226
4308
|
t,
|
|
4227
4309
|
setProviderError,
|
|
4228
|
-
startPolling
|
|
4310
|
+
startPolling,
|
|
4311
|
+
refresh
|
|
4229
4312
|
]);
|
|
4230
4313
|
const cancel = (0, react.useCallback)(async (provider) => {
|
|
4231
4314
|
if (rpc === void 0) return;
|
|
@@ -4972,7 +5055,8 @@ const HOST_CONTRACT = {
|
|
|
4972
5055
|
slots: "@deepseek-ai/dsh-client-ui-renderer"
|
|
4973
5056
|
},
|
|
4974
5057
|
markers: { composerStats: {
|
|
4975
|
-
|
|
5058
|
+
row: "data-composer-stats",
|
|
5059
|
+
dock: "data-composer-stat",
|
|
4976
5060
|
package: "@deepseek-ai/dsh-client-ui-chat"
|
|
4977
5061
|
} },
|
|
4978
5062
|
diagnostics: {
|
|
@@ -5025,6 +5109,10 @@ const HOST_CONTRACT = {
|
|
|
5025
5109
|
]
|
|
5026
5110
|
}
|
|
5027
5111
|
};
|
|
5112
|
+
/** Attribute selector for a host DOM marker (`data-composer-stats` → `[data-composer-stats]`). */
|
|
5113
|
+
function markerSelector(attribute) {
|
|
5114
|
+
return `[${attribute}]`;
|
|
5115
|
+
}
|
|
5028
5116
|
|
|
5029
5117
|
//#endregion
|
|
5030
5118
|
//#region src/client/host-icons.ts
|
|
@@ -5435,9 +5523,10 @@ function VideoGenerateToolview(props) {
|
|
|
5435
5523
|
//#region src/client/SpeedSelect.tsx
|
|
5436
5524
|
/**
|
|
5437
5525
|
* The `loadSpeed` half of the inject face: the plugin's own speed state plus
|
|
5438
|
-
* the host's current model selection (the visibility gate). A
|
|
5439
|
-
*
|
|
5440
|
-
*
|
|
5526
|
+
* the host's current model selection (the visibility gate). A session whose
|
|
5527
|
+
* model selection is unavailable (an addressed subagent) answers hidden.
|
|
5528
|
+
* Any other model-directory failure throws, so the caller keeps its last
|
|
5529
|
+
* known state instead of locking the toggle away.
|
|
5441
5530
|
*
|
|
5442
5531
|
* `sessionId` is a plain string: slot and command contexts brand it through
|
|
5443
5532
|
* different dsh-session copies, and only the service boundary needs one.
|
|
@@ -5454,13 +5543,126 @@ function createSpeedLoader(connection, models, sessionId) {
|
|
|
5454
5543
|
visible: false,
|
|
5455
5544
|
tier: state.tier
|
|
5456
5545
|
};
|
|
5457
|
-
|
|
5546
|
+
let current;
|
|
5547
|
+
try {
|
|
5548
|
+
current = (await directories.directoryFor(sessionId).load()).current;
|
|
5549
|
+
} catch (error) {
|
|
5550
|
+
if (!modelSelectionUnavailable(error)) throw error;
|
|
5551
|
+
return {
|
|
5552
|
+
visible: false,
|
|
5553
|
+
tier: state.tier
|
|
5554
|
+
};
|
|
5555
|
+
}
|
|
5458
5556
|
return {
|
|
5459
5557
|
visible: current !== null && current.provider === "codex" && state.fastModels.includes(current.model),
|
|
5460
5558
|
tier: state.tier
|
|
5461
5559
|
};
|
|
5462
5560
|
};
|
|
5463
5561
|
}
|
|
5562
|
+
/** Host copy for a session that has no model seat (an addressed subagent). */
|
|
5563
|
+
const MODEL_SELECTION_UNAVAILABLE = "model selection is unavailable";
|
|
5564
|
+
function modelSelectionUnavailable(error) {
|
|
5565
|
+
return (error instanceof Error ? error.message : String(error)).includes(MODEL_SELECTION_UNAVAILABLE);
|
|
5566
|
+
}
|
|
5567
|
+
/** Stable identity of a model selection. Empty when nothing is selected. */
|
|
5568
|
+
function modelSelectionKey(current) {
|
|
5569
|
+
if (current == null) return "";
|
|
5570
|
+
return JSON.stringify([current.provider, current.model]);
|
|
5571
|
+
}
|
|
5572
|
+
/**
|
|
5573
|
+
* How often a host without a directory store is re-read, and how often a
|
|
5574
|
+
* missing directory service is retried. Neither interval calls
|
|
5575
|
+
* `subscriptions-auth.speed`.
|
|
5576
|
+
*/
|
|
5577
|
+
const MODEL_DIRECTORY_POLL_INTERVAL_MS = 3e3;
|
|
5578
|
+
/**
|
|
5579
|
+
* Mount reads of `speed`, including the first attempt. Further failures stop
|
|
5580
|
+
* instead of polling for the life of the composer.
|
|
5581
|
+
*/
|
|
5582
|
+
const SPEED_MOUNT_READ_ATTEMPTS = 4;
|
|
5583
|
+
/** Fired after `/fast` writes a tier, so an open composer re-reads `speed`. */
|
|
5584
|
+
const SPEED_CHANGED_EVENT = "dsh:subscriptions:speed-changed";
|
|
5585
|
+
/** Tell mounted Speed controls that this page changed a session tier. */
|
|
5586
|
+
function notifySpeedChanged() {
|
|
5587
|
+
if (typeof window === "undefined") return;
|
|
5588
|
+
window.dispatchEvent(new Event(SPEED_CHANGED_EVENT));
|
|
5589
|
+
}
|
|
5590
|
+
/**
|
|
5591
|
+
* Subscribe to the session's selected model. A directory store notifies the
|
|
5592
|
+
* listener only when `provider` or `model` changes. A directory with no store
|
|
5593
|
+
* is re-read on {@link MODEL_DIRECTORY_POLL_INTERVAL_MS}; a service that is
|
|
5594
|
+
* not registered yet, or whose `directoryFor` throws, is retried on that same
|
|
5595
|
+
* interval. The first successful attach after a miss notifies once, so a
|
|
5596
|
+
* mount that ran before the service existed reads `speed` again.
|
|
5597
|
+
*/
|
|
5598
|
+
function createModelSelectionWatch(models, sessionId) {
|
|
5599
|
+
return (listener) => {
|
|
5600
|
+
let stopped = false;
|
|
5601
|
+
let unsubscribeStore;
|
|
5602
|
+
let timer;
|
|
5603
|
+
let mode = "seek";
|
|
5604
|
+
let lastKey;
|
|
5605
|
+
const stopTimer = () => {
|
|
5606
|
+
if (timer === void 0) return;
|
|
5607
|
+
clearInterval(timer);
|
|
5608
|
+
timer = void 0;
|
|
5609
|
+
};
|
|
5610
|
+
const changed = (current) => {
|
|
5611
|
+
const next = modelSelectionKey(current);
|
|
5612
|
+
if (lastKey === void 0) {
|
|
5613
|
+
lastKey = next;
|
|
5614
|
+
return false;
|
|
5615
|
+
}
|
|
5616
|
+
if (next === lastKey) return false;
|
|
5617
|
+
lastKey = next;
|
|
5618
|
+
return true;
|
|
5619
|
+
};
|
|
5620
|
+
const attach = (late) => {
|
|
5621
|
+
if (stopped || mode !== "seek") return;
|
|
5622
|
+
let directory;
|
|
5623
|
+
try {
|
|
5624
|
+
directory = models()?.directoryFor(sessionId);
|
|
5625
|
+
} catch {
|
|
5626
|
+
return;
|
|
5627
|
+
}
|
|
5628
|
+
if (directory === void 0) return;
|
|
5629
|
+
const store = directory.store;
|
|
5630
|
+
if (store !== void 0) {
|
|
5631
|
+
mode = "store";
|
|
5632
|
+
stopTimer();
|
|
5633
|
+
lastKey = modelSelectionKey(store.getSnapshot().current);
|
|
5634
|
+
unsubscribeStore = store.subscribe(() => {
|
|
5635
|
+
if (!stopped && changed(store.getSnapshot().current)) listener();
|
|
5636
|
+
});
|
|
5637
|
+
if (late) listener();
|
|
5638
|
+
return;
|
|
5639
|
+
}
|
|
5640
|
+
mode = "poll";
|
|
5641
|
+
stopTimer();
|
|
5642
|
+
let force = late;
|
|
5643
|
+
const seed = () => {
|
|
5644
|
+
directory.load().then((state) => {
|
|
5645
|
+
if (stopped || mode !== "poll") return;
|
|
5646
|
+
const identityChanged = changed(state.current);
|
|
5647
|
+
const forced = force;
|
|
5648
|
+
force = false;
|
|
5649
|
+
if (identityChanged || forced) listener();
|
|
5650
|
+
}, () => {});
|
|
5651
|
+
};
|
|
5652
|
+
seed();
|
|
5653
|
+
timer = setInterval(seed, MODEL_DIRECTORY_POLL_INTERVAL_MS);
|
|
5654
|
+
};
|
|
5655
|
+
attach(false);
|
|
5656
|
+
if (mode === "seek") timer = setInterval(() => {
|
|
5657
|
+
attach(true);
|
|
5658
|
+
}, MODEL_DIRECTORY_POLL_INTERVAL_MS);
|
|
5659
|
+
return () => {
|
|
5660
|
+
stopped = true;
|
|
5661
|
+
unsubscribeStore?.();
|
|
5662
|
+
stopTimer();
|
|
5663
|
+
};
|
|
5664
|
+
};
|
|
5665
|
+
}
|
|
5464
5666
|
/** The `setSpeed` half of the inject face: boolean outcome for the component's busy state. */
|
|
5465
5667
|
function createSpeedSetter(connection, sessionId) {
|
|
5466
5668
|
return (tier) => callSubscriptionsAuth(connection.rpc, "setSpeed", {
|
|
@@ -5476,20 +5678,15 @@ const TIERS = ["standard", "fast"];
|
|
|
5476
5678
|
/**
|
|
5477
5679
|
* The composer Speed control: a trigger reading `速度 · 快速`/`速度 · 标准`
|
|
5478
5680
|
* that opens a two-row menu (standard/fast with descriptions, check mark on
|
|
5479
|
-
* the current tier).
|
|
5480
|
-
*
|
|
5681
|
+
* the current tier). It reads `subscriptions-auth.speed` on mount, when the
|
|
5682
|
+
* menu opens, when {@link SpeedSelectInjected.watchModel} reports a new
|
|
5683
|
+
* selected model, and when {@link notifySpeedChanged} fires. Until the first
|
|
5684
|
+
* read succeeds, a failure is retried on
|
|
5685
|
+
* {@link MODEL_DIRECTORY_POLL_INTERVAL_MS}, at most
|
|
5686
|
+
* {@link SPEED_MOUNT_READ_ATTEMPTS} times; after that, a failed read keeps
|
|
5687
|
+
* the last known state.
|
|
5481
5688
|
*/
|
|
5482
|
-
|
|
5483
|
-
const POLL_INTERVAL_MS = 3e3;
|
|
5484
|
-
/**
|
|
5485
|
-
* The composer Speed control: a trigger reading `速度 · 快速`/`速度 · 标准`
|
|
5486
|
-
* that opens a two-row menu (standard/fast with descriptions, check mark on
|
|
5487
|
-
* the current tier). The host pushes nothing on a model switch, so the
|
|
5488
|
-
* control re-reads on a slow poll with a single-flight guard; a failed read
|
|
5489
|
-
* keeps the last known state, so a transient RPC failure can never lock the
|
|
5490
|
-
* toggle away (the earlier mount-only load had no recovery path).
|
|
5491
|
-
*/
|
|
5492
|
-
function SpeedSelect({ loadSpeed, setSpeed, t }) {
|
|
5689
|
+
function SpeedSelect({ loadSpeed, setSpeed, watchModel, t }) {
|
|
5493
5690
|
const translate = t ?? fallbackTranslate$1;
|
|
5494
5691
|
const [state, setState] = (0, react.useState)(null);
|
|
5495
5692
|
const [open, setOpen] = (0, react.useState)(false);
|
|
@@ -5497,25 +5694,58 @@ function SpeedSelect({ loadSpeed, setSpeed, t }) {
|
|
|
5497
5694
|
const rootRef = (0, react.useRef)(null);
|
|
5498
5695
|
const loadRef = (0, react.useRef)(loadSpeed);
|
|
5499
5696
|
loadRef.current = loadSpeed;
|
|
5697
|
+
const watchRef = (0, react.useRef)(watchModel);
|
|
5698
|
+
watchRef.current = watchModel;
|
|
5500
5699
|
(0, react.useEffect)(() => {
|
|
5501
5700
|
if (loadRef.current === void 0) return;
|
|
5502
5701
|
let cancelled = false;
|
|
5503
5702
|
let inflight = false;
|
|
5703
|
+
let again = false;
|
|
5704
|
+
let loadedOnce = false;
|
|
5705
|
+
let failures = 0;
|
|
5706
|
+
let retry;
|
|
5707
|
+
const stopRetry = () => {
|
|
5708
|
+
if (retry === void 0) return;
|
|
5709
|
+
clearInterval(retry);
|
|
5710
|
+
retry = void 0;
|
|
5711
|
+
};
|
|
5504
5712
|
const reload = () => {
|
|
5505
5713
|
const load = loadRef.current;
|
|
5506
|
-
if (load === void 0
|
|
5714
|
+
if (load === void 0) return;
|
|
5715
|
+
if (inflight) {
|
|
5716
|
+
again = true;
|
|
5717
|
+
return;
|
|
5718
|
+
}
|
|
5507
5719
|
inflight = true;
|
|
5508
5720
|
load().then((loaded) => {
|
|
5509
|
-
if (
|
|
5510
|
-
|
|
5721
|
+
if (cancelled) return;
|
|
5722
|
+
loadedOnce = true;
|
|
5723
|
+
stopRetry();
|
|
5724
|
+
setState(loaded);
|
|
5725
|
+
}, () => {
|
|
5726
|
+
failures += 1;
|
|
5727
|
+
if (failures >= SPEED_MOUNT_READ_ATTEMPTS) stopRetry();
|
|
5728
|
+
}).finally(() => {
|
|
5511
5729
|
inflight = false;
|
|
5730
|
+
if (cancelled || !again) return;
|
|
5731
|
+
again = false;
|
|
5732
|
+
reload();
|
|
5512
5733
|
});
|
|
5513
5734
|
};
|
|
5514
5735
|
reload();
|
|
5515
|
-
|
|
5736
|
+
retry = setInterval(() => {
|
|
5737
|
+
if (!loadedOnce) reload();
|
|
5738
|
+
}, MODEL_DIRECTORY_POLL_INTERVAL_MS);
|
|
5739
|
+
const unsubscribe = watchRef.current?.(reload);
|
|
5740
|
+
const onSpeedChanged = () => {
|
|
5741
|
+
reload();
|
|
5742
|
+
};
|
|
5743
|
+
window.addEventListener(SPEED_CHANGED_EVENT, onSpeedChanged);
|
|
5516
5744
|
return () => {
|
|
5517
5745
|
cancelled = true;
|
|
5518
|
-
|
|
5746
|
+
unsubscribe?.();
|
|
5747
|
+
stopRetry();
|
|
5748
|
+
window.removeEventListener(SPEED_CHANGED_EVENT, onSpeedChanged);
|
|
5519
5749
|
};
|
|
5520
5750
|
}, []);
|
|
5521
5751
|
(0, react.useEffect)(() => {
|
|
@@ -5711,7 +5941,7 @@ const PROVIDER_NAMES = {
|
|
|
5711
5941
|
"cursor-subscription": "Cursor",
|
|
5712
5942
|
"opencode-go": "OpenCode Go",
|
|
5713
5943
|
"kimi-coding": "Kimi Code",
|
|
5714
|
-
|
|
5944
|
+
minimax: "MiniMax",
|
|
5715
5945
|
"minimax-cn": "MiniMax CN",
|
|
5716
5946
|
"ollama-cloud": "Ollama Cloud"
|
|
5717
5947
|
};
|
|
@@ -5946,6 +6176,8 @@ function usageWindowLabel(t, window$1) {
|
|
|
5946
6176
|
* every provider's accounts and their windows. Returns null when no data is
|
|
5947
6177
|
* available.
|
|
5948
6178
|
*/
|
|
6179
|
+
const STATS_ROW_SELECTOR = markerSelector(HOST_CONTRACT.markers.composerStats.row);
|
|
6180
|
+
const STATS_DOCK_SELECTOR = markerSelector(HOST_CONTRACT.markers.composerStats.dock);
|
|
5949
6181
|
function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
5950
6182
|
const translate = t ?? fallbackTranslate;
|
|
5951
6183
|
const [displays, setDisplays] = (0, react.useState)([]);
|
|
@@ -6105,15 +6337,18 @@ function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
|
6105
6337
|
if (seat$1 === null) return;
|
|
6106
6338
|
const scope = statsScopeOf(seat$1);
|
|
6107
6339
|
if (scope === null) return;
|
|
6108
|
-
const find = () => scope.querySelector(
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
setStatsRow(
|
|
6112
|
-
|
|
6340
|
+
const find = () => scope.querySelector(STATS_ROW_SELECTOR);
|
|
6341
|
+
const settle = () => {
|
|
6342
|
+
const row = find();
|
|
6343
|
+
setStatsRow(row);
|
|
6344
|
+
if (row === null && scope.querySelector(STATS_DOCK_SELECTOR) !== null) observer.disconnect();
|
|
6345
|
+
};
|
|
6346
|
+
const observer = new MutationObserver(settle);
|
|
6113
6347
|
observer.observe(scope, {
|
|
6114
6348
|
childList: true,
|
|
6115
6349
|
subtree: true
|
|
6116
6350
|
});
|
|
6351
|
+
settle();
|
|
6117
6352
|
return () => {
|
|
6118
6353
|
observer.disconnect();
|
|
6119
6354
|
};
|
|
@@ -6237,15 +6472,19 @@ function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
|
6237
6472
|
});
|
|
6238
6473
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [seat, statsRow !== null && statsRow.isConnected ? (0, react_dom.createPortal)(pill, statsRow) : pill] });
|
|
6239
6474
|
}
|
|
6475
|
+
/** How far statsScopeOf walks looking for `composerStats.row`. */
|
|
6476
|
+
const LEGACY_STATS_SCOPE_DEPTH = 4;
|
|
6240
6477
|
/**
|
|
6241
|
-
*
|
|
6242
|
-
*
|
|
6243
|
-
*
|
|
6478
|
+
* Scope for the legacy stats row. The nearest ancestor within
|
|
6479
|
+
* LEGACY_STATS_SCOPE_DEPTH that contains `composerStats.row`, so a badge
|
|
6480
|
+
* does not adopt another composer's pills. When none does, the seat's
|
|
6481
|
+
* parent: the slot outlet, which holds sibling `composerStats.dock` anchors
|
|
6482
|
+
* on hosts whose pills are dock entries.
|
|
6244
6483
|
*/
|
|
6245
6484
|
function statsScopeOf(seat) {
|
|
6246
6485
|
let node = seat.parentElement;
|
|
6247
|
-
for (let depth = 0; node !== null && depth <
|
|
6248
|
-
if (node.querySelector(
|
|
6486
|
+
for (let depth = 0; node !== null && depth < LEGACY_STATS_SCOPE_DEPTH; depth++) {
|
|
6487
|
+
if (node.querySelector(STATS_ROW_SELECTOR) !== null) return node;
|
|
6249
6488
|
node = node.parentElement;
|
|
6250
6489
|
}
|
|
6251
6490
|
return seat.parentElement;
|
|
@@ -6260,10 +6499,10 @@ function AccountMeta({ account, translate }) {
|
|
|
6260
6499
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
6261
6500
|
style: styles.providerMeta,
|
|
6262
6501
|
title: account.account,
|
|
6263
|
-
children: [account.isDefault && /* @__PURE__ */ (0, react_jsx_runtime.
|
|
6502
|
+
children: [account.isDefault && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
6264
6503
|
style: styles.defaultStar,
|
|
6265
6504
|
"aria-label": "default",
|
|
6266
|
-
children: "★ "
|
|
6505
|
+
children: ["★", " "]
|
|
6267
6506
|
}), parts.join(" · ")]
|
|
6268
6507
|
});
|
|
6269
6508
|
}
|
|
@@ -6545,7 +6784,8 @@ function apply(ctx) {
|
|
|
6545
6784
|
locale: NS,
|
|
6546
6785
|
inject: (sessionId) => ({
|
|
6547
6786
|
loadSpeed: createSpeedLoader(connection, models, sessionId),
|
|
6548
|
-
setSpeed: createSpeedSetter(connection, sessionId)
|
|
6787
|
+
setSpeed: createSpeedSetter(connection, sessionId),
|
|
6788
|
+
watchModel: createModelSelectionWatch(models, sessionId)
|
|
6549
6789
|
})
|
|
6550
6790
|
}, SpeedSelect));
|
|
6551
6791
|
ctx.slots.inject("conversation.composer.dock", () => ctx.slots.register({
|
|
@@ -6583,7 +6823,7 @@ function apply(ctx) {
|
|
|
6583
6823
|
}));
|
|
6584
6824
|
},
|
|
6585
6825
|
onSelect: async (option, session) => {
|
|
6586
|
-
await createSpeedSetter(connection, session.sessionId)(option.id);
|
|
6826
|
+
if (await createSpeedSetter(connection, session.sessionId)(option.id)) notifySpeedChanged();
|
|
6587
6827
|
}
|
|
6588
6828
|
}
|
|
6589
6829
|
}), "dsh-subscriptions: /fast contribution");
|