dsh-plugin-subscriptions 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +79 -5
  2. package/README.zh.md +78 -4
  3. package/lib/auth/rpc.d.ts +64 -13
  4. package/lib/auth/rpc.js +75 -10
  5. package/lib/auth/store.d.ts +75 -17
  6. package/lib/auth/store.js +148 -27
  7. package/lib/client/ImageGenerateToolview.d.ts +1 -1
  8. package/lib/client/SpeedSelect.d.ts +25 -2
  9. package/lib/client/SpeedSelect.js +10 -6
  10. package/lib/client/SubscriptionsSection.d.ts +83 -3
  11. package/lib/client/SubscriptionsSection.js +411 -62
  12. package/lib/client/VideoGenerateToolview.d.ts +1 -1
  13. package/lib/client/index.d.ts +1 -9
  14. package/lib/client/index.js +7 -4
  15. package/lib/client/locales.d.ts +46 -10
  16. package/lib/client/locales.js +46 -10
  17. package/lib/client.js +703 -132
  18. package/lib/client.js.map +1 -1
  19. package/lib/compat.d.ts +36 -0
  20. package/lib/compat.js +20 -0
  21. package/lib/index.d.ts +26 -1
  22. package/lib/index.js +2377 -309
  23. package/lib/model-defaults.d.ts +23 -0
  24. package/lib/model-defaults.js +237 -0
  25. package/lib/providers/accounts.d.ts +102 -0
  26. package/lib/providers/accounts.js +123 -0
  27. package/lib/providers/claude.d.ts +46 -7
  28. package/lib/providers/claude.js +125 -34
  29. package/lib/providers/codex.d.ts +45 -3
  30. package/lib/providers/codex.js +152 -26
  31. package/lib/providers/common.d.ts +87 -6
  32. package/lib/providers/common.js +185 -22
  33. package/lib/providers/copilot.d.ts +32 -3
  34. package/lib/providers/copilot.js +111 -19
  35. package/lib/providers/grok.d.ts +45 -4
  36. package/lib/providers/grok.js +136 -20
  37. package/lib/providers/pool-family.d.ts +56 -0
  38. package/lib/providers/pool-family.js +45 -0
  39. package/lib/providers/pool-health.d.ts +74 -0
  40. package/lib/providers/pool-health.js +148 -0
  41. package/lib/providers/pool-usage.d.ts +78 -0
  42. package/lib/providers/pool-usage.js +185 -0
  43. package/lib/providers/pool.d.ts +107 -0
  44. package/lib/providers/pool.js +371 -0
  45. package/lib/providers/rate-limit.d.ts +192 -0
  46. package/lib/providers/rate-limit.js +338 -0
  47. package/lib/tools/image-generate.d.ts +3 -3
  48. package/lib/tools/image-generate.js +2 -1
  49. package/lib/tools/video-generate.d.ts +2 -2
  50. package/lib/tools/video-generate.js +2 -1
  51. package/lib/tools/x-search.d.ts +2 -2
  52. package/lib/tools/x-search.js +2 -1
  53. package/lib/translate/anthropic.js +5 -4
  54. package/lib/translate/chat-completions.js +5 -4
  55. package/lib/translate/responses.js +5 -4
  56. package/package.json +21 -21
  57. package/lib/providers/antigravity.d.ts +0 -90
  58. package/lib/providers/antigravity.js +0 -392
  59. package/lib/translate/antigravity.d.ts +0 -110
  60. package/lib/translate/antigravity.js +0 -303
package/lib/client.js CHANGED
@@ -39,15 +39,19 @@ const en = {
39
39
  unavailable: "Connection unavailable; subscription status cannot be loaded.",
40
40
  checking: "Checking…",
41
41
  loginInProgress: "Login in progress…",
42
- loggedIn: "Logged in",
43
- loggedInAccount: "Logged in as {account}",
44
- loggedInExpires: "Logged in · expires {date}",
45
- loggedInAccountExpires: "Logged in as {account} · expires {date}",
46
42
  notLoggedIn: "Not logged in",
43
+ loggedInCount: "{count} account(s) connected",
44
+ accountExpires: "expires {date}",
45
+ defaultBadge: "Default",
46
+ setDefault: "Set as default",
47
+ addAccount: "Add account",
48
+ addAccountOAuth: "Browser authorization",
49
+ addAccountKeychain: "Import Claude Code",
50
+ addAccountHint: "Browser authorization signs in whichever account the browser currently uses — switch accounts there first (or use an incognito window with the manual code below) to add a different one.",
47
51
  login: "Log in",
48
52
  cancel: "Cancel",
49
53
  logout: "Log out",
50
- logoutConfirm: "Log out of {provider}?",
54
+ logoutAccountConfirm: "Log out {account} of {provider}?",
51
55
  manualSummary: "Browser flow not working? Paste the callback URL or code",
52
56
  manualPlaceholder: "Paste the callback URL or code",
53
57
  submit: "Submit",
@@ -66,6 +70,20 @@ const en = {
66
70
  usageWindow: "Window",
67
71
  usageResets: "resets {date}",
68
72
  usagePlan: "Plan: {plan}",
73
+ modelDefaultsTitle: "Default reasoning effort",
74
+ modelDefaultsHint: "The model picker preselects this level when you switch to the model; models without levels always follow the provider.",
75
+ modelDefaultsFollowProvider: "Follow provider",
76
+ modelDefaultsNoLevels: "{count} model(s) advertise no reasoning levels and always follow the provider.",
77
+ modelDefaultsLoading: "Loading models…",
78
+ modelDefaultsLoadFailed: "Failed to load models: {message}",
79
+ modelDefaultsRetry: "Retry",
80
+ modelDefaultsSaving: "Saving…",
81
+ modelDefaultsSaveFailedNamed: "Save failed for {model}: {message}",
82
+ modelDefaultsSummary: "{total} model(s) · {configured} overridden",
83
+ modelDefaultsSummaryNone: "{total} model(s) · following the provider",
84
+ modelDefaultsSummaryEmpty: "No model advertises reasoning levels",
85
+ modelDefaultsFilterPlaceholder: "Filter models",
86
+ modelDefaultsFilterEmpty: "No model matches “{query}”.",
69
87
  generating: "Generating image…",
70
88
  image: "image",
71
89
  viewImage: "View image",
@@ -124,15 +142,19 @@ const zh = {
124
142
  unavailable: "连接不可用,无法加载订阅状态。",
125
143
  checking: "查询中…",
126
144
  loginInProgress: "登录中…",
127
- loggedIn: "已登录",
128
- loggedInAccount: "已登录:{account}",
129
- loggedInExpires: "已登录 · 过期时间 {date}",
130
- loggedInAccountExpires: "已登录:{account} · 过期时间 {date}",
131
145
  notLoggedIn: "未登录",
146
+ loggedInCount: "已连接 {count} 个账号",
147
+ accountExpires: "过期时间 {date}",
148
+ defaultBadge: "默认",
149
+ setDefault: "设为默认",
150
+ addAccount: "添加账号",
151
+ addAccountOAuth: "浏览器授权",
152
+ addAccountKeychain: "导入 Claude Code",
153
+ addAccountHint: "浏览器授权以浏览器当前登录的账号为准;要添加不同账号,请先在浏览器里切换账号,或用无痕窗口走下方手动授权码。",
132
154
  login: "登录",
133
155
  cancel: "取消",
134
156
  logout: "退出登录",
135
- logoutConfirm: "确定退出 {provider} 的登录吗?",
157
+ logoutAccountConfirm: "确定退出 {provider} 的账号 {account} 吗?",
136
158
  manualSummary: "浏览器流程无法完成?粘贴回调 URL 或授权码",
137
159
  manualPlaceholder: "粘贴回调 URL 或授权码",
138
160
  submit: "提交",
@@ -151,6 +173,20 @@ const zh = {
151
173
  usageWindow: "窗口",
152
174
  usageResets: "{date} 重置",
153
175
  usagePlan: "计划:{plan}",
176
+ modelDefaultsTitle: "默认推理档",
177
+ modelDefaultsHint: "切换到该模型时,模型选择器会预选此档位;没有推理档的模型始终跟随服务商默认。",
178
+ modelDefaultsFollowProvider: "跟随服务商",
179
+ modelDefaultsNoLevels: "{count} 个模型未声明推理档,始终跟随服务商默认。",
180
+ modelDefaultsLoading: "加载模型中…",
181
+ modelDefaultsLoadFailed: "模型加载失败:{message}",
182
+ modelDefaultsRetry: "重试",
183
+ modelDefaultsSaving: "保存中…",
184
+ modelDefaultsSaveFailedNamed: "{model} 保存失败:{message}",
185
+ modelDefaultsSummary: "{total} 个模型 · {configured} 个已覆盖",
186
+ modelDefaultsSummaryNone: "{total} 个模型 · 全部跟随服务商",
187
+ modelDefaultsSummaryEmpty: "没有模型声明推理档",
188
+ modelDefaultsFilterPlaceholder: "筛选模型",
189
+ modelDefaultsFilterEmpty: "没有匹配「{query}」的模型。",
154
190
  generating: "正在生成图片…",
155
191
  image: "图片",
156
192
  viewImage: "查看图片",
@@ -209,6 +245,17 @@ const zh = {
209
245
  const SUBSCRIPTIONS_AUTH_CHANNEL$2 = "/subscriptions-auth";
210
246
  /** Poll cadence while a provider login attempt is busy. */
211
247
  const POLL_INTERVAL_MS$1 = 2e3;
248
+ /**
249
+ * Model count above which the expanded default-effort list also offers a name
250
+ * filter; below it the list is short enough to scan.
251
+ */
252
+ const MODEL_FILTER_THRESHOLD = 8;
253
+ /**
254
+ * Height cap of the expanded default-effort list, in px. Past it the list
255
+ * scrolls internally so a provider with dozens of models cannot stretch the
256
+ * card (roughly 7 rows, which keeps the next card's header on screen).
257
+ */
258
+ const MODEL_LIST_MAX_HEIGHT = 260;
212
259
  /** Card display metadata, in page order (names are brand names, not translated). */
213
260
  const PROVIDERS = [
214
261
  {
@@ -252,6 +299,14 @@ async function callSubscriptionsAuth(rpc, endpoint, payload) {
252
299
  function messageOf(error) {
253
300
  return error instanceof Error ? error.message : String(error);
254
301
  }
302
+ /** Copy a keyed map without the entries whose key is not in `live`. */
303
+ function dropStale(map, live) {
304
+ const stale = Object.keys(map).filter((key) => !live.has(key));
305
+ if (stale.length === 0) return map;
306
+ const next = { ...map };
307
+ for (const key of stale) delete next[key];
308
+ return next;
309
+ }
255
310
  /**
256
311
  * English-dictionary fallback for a missing inject `t` (standalone renders);
257
312
  * the slot inject always supplies the locale-bound one.
@@ -399,6 +454,36 @@ const styles$4 = {
399
454
  lineHeight: "18px",
400
455
  color: "var(--dsw-alias-label-tertiary)"
401
456
  },
457
+ accountRow: {
458
+ display: "flex",
459
+ flexDirection: "column",
460
+ gap: 6,
461
+ border: "1px solid var(--dsw-alias-border-l2)",
462
+ borderRadius: 8,
463
+ padding: "8px 10px",
464
+ marginTop: 4
465
+ },
466
+ accountHeader: {
467
+ display: "flex",
468
+ alignItems: "center",
469
+ gap: 8
470
+ },
471
+ accountName: {
472
+ fontSize: 13,
473
+ lineHeight: "20px",
474
+ color: "var(--dsw-alias-label-primary)",
475
+ userSelect: "all"
476
+ },
477
+ starButton: {
478
+ border: "none",
479
+ background: "transparent",
480
+ padding: 0,
481
+ font: "inherit",
482
+ fontSize: 14,
483
+ lineHeight: "20px",
484
+ cursor: "pointer",
485
+ color: "var(--dsw-alias-state-warn-label)"
486
+ },
402
487
  usageTrack: {
403
488
  height: 6,
404
489
  borderRadius: 3,
@@ -410,6 +495,90 @@ const styles$4 = {
410
495
  height: "100%",
411
496
  borderRadius: 3
412
497
  },
498
+ defaultEffort: {
499
+ display: "flex",
500
+ flexDirection: "column",
501
+ gap: 6,
502
+ marginTop: 4,
503
+ borderTop: "1px solid var(--dsw-alias-border-l2)",
504
+ paddingTop: 8
505
+ },
506
+ defaultEffortToggle: {
507
+ boxSizing: "border-box",
508
+ display: "flex",
509
+ alignItems: "center",
510
+ gap: 8,
511
+ width: "100%",
512
+ padding: 0,
513
+ border: "none",
514
+ background: "transparent",
515
+ font: "inherit",
516
+ textAlign: "left",
517
+ cursor: "pointer"
518
+ },
519
+ defaultEffortChevron: {
520
+ marginLeft: "auto",
521
+ flexShrink: 0,
522
+ fontSize: 10,
523
+ lineHeight: "18px",
524
+ color: "var(--dsw-alias-label-tertiary)"
525
+ },
526
+ defaultEffortList: {
527
+ display: "flex",
528
+ flexDirection: "column",
529
+ gap: 6,
530
+ maxHeight: MODEL_LIST_MAX_HEIGHT,
531
+ overflowY: "auto",
532
+ paddingRight: 2
533
+ },
534
+ defaultEffortRow: {
535
+ display: "flex",
536
+ alignItems: "center",
537
+ justifyContent: "space-between",
538
+ gap: 8
539
+ },
540
+ defaultEffortName: {
541
+ fontSize: 12,
542
+ lineHeight: "18px",
543
+ color: "var(--dsw-alias-label-primary)",
544
+ overflow: "hidden",
545
+ textOverflow: "ellipsis",
546
+ whiteSpace: "nowrap"
547
+ },
548
+ defaultEffortSaving: {
549
+ marginLeft: "auto",
550
+ flexShrink: 0,
551
+ fontSize: 12,
552
+ lineHeight: "18px",
553
+ color: "var(--dsw-alias-label-tertiary)"
554
+ },
555
+ defaultEffortSelect: {
556
+ maxWidth: 220,
557
+ flexShrink: 0,
558
+ height: 28,
559
+ boxSizing: "border-box",
560
+ border: "1px solid var(--dsw-alias-border-l2)",
561
+ borderRadius: 8,
562
+ padding: "0 8px",
563
+ font: "inherit",
564
+ fontSize: 12,
565
+ lineHeight: "18px",
566
+ background: "var(--dsw-alias-bg-layer-1)",
567
+ color: "var(--dsw-alias-label-primary)"
568
+ },
569
+ defaultEffortFilter: {
570
+ height: 28,
571
+ width: "100%",
572
+ boxSizing: "border-box",
573
+ border: "1px solid var(--dsw-alias-border-l2)",
574
+ borderRadius: 8,
575
+ padding: "0 8px",
576
+ font: "inherit",
577
+ fontSize: 12,
578
+ lineHeight: "18px",
579
+ background: "var(--dsw-alias-bg-layer-1)",
580
+ color: "var(--dsw-alias-label-primary)"
581
+ },
413
582
  manual: {
414
583
  marginTop: 4,
415
584
  fontSize: 12,
@@ -541,10 +710,20 @@ const styles$4 = {
541
710
  /** Status dot color for one provider state. */
542
711
  function dotColor(status) {
543
712
  if (status?.busy === true) return "var(--dsw-alias-state-warn-label)";
544
- if (status?.loggedIn === true) return "var(--dsw-alias-state-success-primary)";
713
+ if ((status?.accounts.length ?? 0) > 0) return "var(--dsw-alias-state-success-primary)";
545
714
  return "var(--dsw-alias-label-dimmed)";
546
715
  }
547
716
  /**
717
+ * Whether a provider has at least one connected account. Multi-account made
718
+ * "logged in" a property of the account list rather than a flag, so every
719
+ * logged-in test goes through this one predicate.
720
+ * @param status - the provider's last reported state, possibly absent.
721
+ * @returns true when the provider serves at least one account.
722
+ */
723
+ function hasAccount(status) {
724
+ return (status?.accounts.length ?? 0) > 0;
725
+ }
726
+ /**
548
727
  * One-line status text for one provider state.
549
728
  * @param t - section translate.
550
729
  * @param status - the provider's last reported state.
@@ -553,15 +732,7 @@ function dotColor(status) {
553
732
  function statusText(t, status) {
554
733
  if (status === void 0) return t("checking");
555
734
  if (status.busy) return t("loginInProgress");
556
- if (status.loggedIn) {
557
- const params = {};
558
- if (status.account !== void 0) params.account = status.account;
559
- if (status.expiresAt !== void 0) params.date = new Date(status.expiresAt).toLocaleString();
560
- if (params.account !== void 0 && params.date !== void 0) return t("loggedInAccountExpires", params);
561
- if (params.account !== void 0) return t("loggedInAccount", params);
562
- if (params.date !== void 0) return t("loggedInExpires", params);
563
- return t("loggedIn");
564
- }
735
+ if (status.accounts.length > 0) return t("loggedInCount", { count: status.accounts.length });
565
736
  return t("notLoggedIn");
566
737
  }
567
738
  /**
@@ -593,6 +764,55 @@ function messageColor(tone) {
593
764
  return tone === "error" ? "var(--dsw-alias-state-error-primary)" : "var(--dsw-alias-state-success-primary)";
594
765
  }
595
766
  /**
767
+ * Derive one provider's default-effort section from its catalog and filter.
768
+ * Pure so the collapsed-header counts and the filter stay testable without a
769
+ * DOM: rows come only from models that advertise levels, the count of the rest
770
+ * rides as one line, and the filter matches display name or model id.
771
+ * @param models - the provider's catalog models, or undefined while loading.
772
+ * @param filter - the raw filter input (trimmed and lowercased here).
773
+ * @returns the section's rows and header counts.
774
+ */
775
+ function deriveModelDefaultsView(models, filter) {
776
+ const all = models ?? [];
777
+ const withEfforts = all.filter((model) => model.efforts.length > 0);
778
+ const query = filter.trim().toLowerCase();
779
+ return {
780
+ shown: query === "" ? withEfforts : withEfforts.filter((model) => model.name.toLowerCase().includes(query) || model.id.toLowerCase().includes(query)),
781
+ total: withEfforts.length,
782
+ overridden: withEfforts.filter((model) => model.configured !== void 0).length,
783
+ withoutEfforts: all.length - withEfforts.length,
784
+ showFilter: withEfforts.length > MODEL_FILTER_THRESHOLD
785
+ };
786
+ }
787
+ /**
788
+ * Whether the default-effort catalog needs (re)fetching.
789
+ *
790
+ * Fetching is gated on an *attempt* signature rather than on the payload
791
+ * being empty: an empty answer is a legitimate result (a narrowed
792
+ * `config.providers`, or a catalog that is momentarily unavailable), and
793
+ * treating it as "not loaded yet" re-ran this effect forever. The signature
794
+ * also covers the accounts, so logging a second provider in refetches
795
+ * instead of leaving that card on the previous answer.
796
+ * @param input - the decision inputs.
797
+ * @returns true when the caller should start a fetch.
798
+ */
799
+ function shouldFetchModelDefaults(input) {
800
+ if (input.failed) return false;
801
+ if (input.loggedIn.length === 0) return false;
802
+ if (!input.open.some((provider) => input.loggedIn.includes(provider))) return false;
803
+ return input.loadedFor !== input.signature;
804
+ }
805
+ /**
806
+ * Stable signature of the accounts a catalog answer depends on. A change
807
+ * means a previous answer is stale (an account arrived or left), so the next
808
+ * open disclosure refetches.
809
+ * @param statuses - the per-provider status snapshot.
810
+ * @returns a signature string, stable across renders with equal accounts.
811
+ */
812
+ function modelDefaultsSignature(statuses) {
813
+ return PROVIDERS.map(({ id }) => `${id}:${(statuses[id]?.accounts ?? []).map((account) => account.key).sort().join(",")}`).join("|");
814
+ }
815
+ /**
596
816
  * The Subscriptions settings page component.
597
817
  * @param props - the slot inject face ({@link SubscriptionsSectionInjected}).
598
818
  * @returns the section body, or a notice while the RPC face is absent.
@@ -611,12 +831,13 @@ function SubscriptionsSection(props) {
611
831
  /** Pending device-flow codes (copilot), shown while the attempt polls. */
612
832
  const [deviceCodes, setDeviceCodes] = (0, react.useState)({});
613
833
  const [copiedCode, setCopiedCode] = (0, react.useState)(void 0);
834
+ /** Usage snapshots keyed `${provider}:${accountKey}` — every account tracks its own windows. */
614
835
  const [usages, setUsages] = (0, react.useState)({});
615
836
  const [usageErrors, setUsageErrors] = (0, react.useState)({});
616
837
  const [usageLoading, setUsageLoading] = (0, react.useState)({});
617
838
  const mountedRef = (0, react.useRef)(true);
618
839
  const pollersRef = (0, react.useRef)(/* @__PURE__ */ new Map());
619
- /** Providers with a `usage` call in flight; guards the auto-fetch effect against re-entry. */
840
+ /** Accounts with a `usage` call in flight; guards the auto-fetch effect against re-entry. */
620
841
  const usageInflightRef = (0, react.useRef)(/* @__PURE__ */ new Set());
621
842
  /** Proxy config as last answered by `proxyGet`/`proxySet`. */
622
843
  const [proxy, setProxy] = (0, react.useState)(void 0);
@@ -633,6 +854,24 @@ function SubscriptionsSection(props) {
633
854
  const [proxyTesting, setProxyTesting] = (0, react.useState)(false);
634
855
  const [proxyMessage, setProxyMessage] = (0, react.useState)(void 0);
635
856
  const [proxyTestResult, setProxyTestResult] = (0, react.useState)(void 0);
857
+ /** Per-model default-effort picker state as answered by `modelDefaults`. */
858
+ const [modelDefaults, setModelDefaults] = (0, react.useState)({});
859
+ const [modelDefaultsLoading, setModelDefaultsLoading] = (0, react.useState)(false);
860
+ const [modelDefaultsLoadError, setModelDefaultsLoadError] = (0, react.useState)(void 0);
861
+ /** One set in flight: the `${provider}/${model}` key. */
862
+ const [modelDefaultsSaving, setModelDefaultsSaving] = (0, react.useState)(void 0);
863
+ /** Per-model save failures, keyed `${provider}/${model}`. */
864
+ const [modelDefaultsSaveErrors, setModelDefaultsSaveErrors] = (0, react.useState)({});
865
+ /** Providers whose default-effort disclosure is open (collapsed by default). */
866
+ const [modelDefaultsOpen, setModelDefaultsOpen] = (0, react.useState)({});
867
+ /** Per-provider name filter of the expanded list. */
868
+ const [modelDefaultsFilters, setModelDefaultsFilters] = (0, react.useState)({});
869
+ /** Account signature the last completed catalog fetch was answered for. */
870
+ const [modelDefaultsLoadedFor, setModelDefaultsLoadedFor] = (0, react.useState)(void 0);
871
+ /** Optimistic in-flight selections, keyed `${provider}/${model}` ('' = follow provider). */
872
+ const [modelDefaultsPending, setModelDefaultsPending] = (0, react.useState)({});
873
+ /** Guard the catalog effect against concurrent loads. */
874
+ const modelDefaultsInflightRef = (0, react.useRef)(false);
636
875
  const setProviderError = (0, react.useCallback)((provider, message) => {
637
876
  if (!mountedRef.current) return;
638
877
  setErrors((prev) => {
@@ -662,7 +901,7 @@ function SubscriptionsSection(props) {
662
901
  setStatuses(response.providers);
663
902
  for (const { id } of PROVIDERS) {
664
903
  const status = response.providers[id];
665
- if (status.loggedIn || !status.busy) {
904
+ if (status.accounts.length > 0 || !status.busy) {
666
905
  stopPolling(id);
667
906
  setDeviceCodes((prev) => {
668
907
  if (prev[id] === void 0) return prev;
@@ -694,68 +933,176 @@ function SubscriptionsSection(props) {
694
933
  pollersRef.current.clear();
695
934
  };
696
935
  }, [refresh, startPolling]);
697
- const loadUsage = (0, react.useCallback)(async (provider) => {
698
- if (rpc === void 0 || usageInflightRef.current.has(provider)) return;
699
- usageInflightRef.current.add(provider);
936
+ const loadUsage = (0, react.useCallback)(async (provider, account, force = false) => {
937
+ const key = `${provider}:${account}`;
938
+ if (rpc === void 0 || usageInflightRef.current.has(key)) return;
939
+ usageInflightRef.current.add(key);
700
940
  setUsageLoading((prev) => ({
701
941
  ...prev,
702
- [provider]: true
942
+ [key]: true
703
943
  }));
704
944
  try {
705
- const usage = await callSubscriptionsAuth(rpc, "usage", { provider });
945
+ const usage = await callSubscriptionsAuth(rpc, "usage", {
946
+ provider,
947
+ account,
948
+ ...force ? { force: true } : {}
949
+ });
706
950
  if (!mountedRef.current) return;
707
951
  setUsages((prev) => ({
708
952
  ...prev,
709
- [provider]: usage
953
+ [key]: usage
710
954
  }));
711
955
  setUsageErrors((prev) => {
712
956
  const next = { ...prev };
713
- delete next[provider];
957
+ delete next[key];
714
958
  return next;
715
959
  });
716
960
  } catch (error) {
717
961
  if (mountedRef.current) setUsageErrors((prev) => ({
718
962
  ...prev,
719
- [provider]: messageOf(error)
963
+ [key]: messageOf(error)
720
964
  }));
721
965
  } finally {
722
- usageInflightRef.current.delete(provider);
966
+ usageInflightRef.current.delete(key);
723
967
  if (mountedRef.current) setUsageLoading((prev) => ({
724
968
  ...prev,
725
- [provider]: false
969
+ [key]: false
726
970
  }));
727
971
  }
728
972
  }, [rpc]);
729
973
  (0, react.useEffect)(() => {
730
- for (const { id } of PROVIDERS) {
731
- const status = statuses[id];
732
- if (status === void 0) continue;
733
- if (status.loggedIn) {
734
- if (usages[id] === void 0 && usageErrors[id] === void 0) loadUsage(id);
735
- } else if (usages[id] !== void 0 || usageErrors[id] !== void 0) {
736
- setUsages((prev) => {
737
- const next = { ...prev };
738
- delete next[id];
739
- return next;
740
- });
741
- setUsageErrors((prev) => {
742
- const next = { ...prev };
743
- delete next[id];
744
- return next;
745
- });
746
- }
974
+ const live = /* @__PURE__ */ new Set();
975
+ for (const { id } of PROVIDERS) for (const account of statuses[id]?.accounts ?? []) {
976
+ const key = `${id}:${account.key}`;
977
+ live.add(key);
978
+ if (usages[key] === void 0 && usageErrors[key] === void 0) loadUsage(id, account.key);
747
979
  }
980
+ setUsages((prev) => dropStale(prev, live));
981
+ setUsageErrors((prev) => dropStale(prev, live));
748
982
  }, [
749
983
  statuses,
750
984
  usages,
751
985
  usageErrors,
752
986
  loadUsage
753
987
  ]);
754
- const login = (0, react.useCallback)(async (provider) => {
988
+ const loadModelDefaultsData = (0, react.useCallback)(async (signature) => {
989
+ if (rpc === void 0 || modelDefaultsInflightRef.current) return;
990
+ modelDefaultsInflightRef.current = true;
991
+ setModelDefaultsLoading(true);
992
+ try {
993
+ const catalog = await callSubscriptionsAuth(rpc, "modelDefaults", {});
994
+ if (!mountedRef.current) return;
995
+ const next = {};
996
+ for (const entry of catalog) next[entry.provider] = entry;
997
+ setModelDefaults(next);
998
+ setModelDefaultsLoadError(void 0);
999
+ setModelDefaultsLoadedFor(signature);
1000
+ setModelDefaultsSaveErrors({});
1001
+ } catch (error) {
1002
+ if (mountedRef.current) setModelDefaultsLoadError(messageOf(error));
1003
+ } finally {
1004
+ modelDefaultsInflightRef.current = false;
1005
+ if (mountedRef.current) setModelDefaultsLoading(false);
1006
+ }
1007
+ }, [rpc]);
1008
+ (0, react.useEffect)(() => {
1009
+ const loggedIn = PROVIDERS.filter(({ id }) => hasAccount(statuses[id])).map(({ id }) => id);
1010
+ const signature = modelDefaultsSignature(statuses);
1011
+ if (loggedIn.length > 0) {
1012
+ if (shouldFetchModelDefaults({
1013
+ loggedIn,
1014
+ open: PROVIDERS.filter(({ id }) => modelDefaultsOpen[id] === true).map(({ id }) => id),
1015
+ loadedFor: modelDefaultsLoadedFor,
1016
+ signature,
1017
+ failed: modelDefaultsLoadError !== void 0
1018
+ })) loadModelDefaultsData(signature);
1019
+ } else if (modelDefaultsLoadedFor !== void 0 || Object.keys(modelDefaults).length > 0 || Object.keys(modelDefaultsOpen).length > 0) {
1020
+ setModelDefaults({});
1021
+ setModelDefaultsSaveErrors({});
1022
+ setModelDefaultsLoadError(void 0);
1023
+ setModelDefaultsLoadedFor(void 0);
1024
+ setModelDefaultsLoading(false);
1025
+ setModelDefaultsOpen({});
1026
+ setModelDefaultsFilters({});
1027
+ }
1028
+ }, [
1029
+ statuses,
1030
+ modelDefaults,
1031
+ modelDefaultsOpen,
1032
+ modelDefaultsLoadError,
1033
+ modelDefaultsLoadedFor,
1034
+ loadModelDefaultsData
1035
+ ]);
1036
+ /** Open or close one provider's default-effort disclosure. */
1037
+ const toggleModelDefaults = (0, react.useCallback)((provider) => {
1038
+ setModelDefaultsOpen((prev) => ({
1039
+ ...prev,
1040
+ [provider]: prev[provider] !== true
1041
+ }));
1042
+ }, []);
1043
+ const setModelDefault = (0, react.useCallback)(async (provider, model, effort) => {
1044
+ if (rpc === void 0) return;
1045
+ const key = `${provider}/${model}`;
1046
+ setModelDefaultsSaving(key);
1047
+ setModelDefaultsPending((prev) => ({
1048
+ ...prev,
1049
+ [key]: effort ?? ""
1050
+ }));
1051
+ setModelDefaultsSaveErrors((prev) => {
1052
+ const next = { ...prev };
1053
+ delete next[key];
1054
+ return next;
1055
+ });
1056
+ try {
1057
+ await callSubscriptionsAuth(rpc, "setModelDefault", {
1058
+ provider,
1059
+ model,
1060
+ ...effort === void 0 ? {} : { effort }
1061
+ });
1062
+ if (!mountedRef.current) return;
1063
+ setModelDefaults((prev) => {
1064
+ const section = prev[provider];
1065
+ if (section === void 0) return prev;
1066
+ return {
1067
+ ...prev,
1068
+ [provider]: {
1069
+ ...section,
1070
+ models: section.models.map((entry) => {
1071
+ if (entry.id !== model) return entry;
1072
+ if (effort !== void 0) return {
1073
+ ...entry,
1074
+ configured: effort
1075
+ };
1076
+ const { configured: _cleared,...rest } = entry;
1077
+ return rest;
1078
+ })
1079
+ }
1080
+ };
1081
+ });
1082
+ } catch (error) {
1083
+ if (mountedRef.current) setModelDefaultsSaveErrors((prev) => ({
1084
+ ...prev,
1085
+ [key]: messageOf(error)
1086
+ }));
1087
+ } finally {
1088
+ if (mountedRef.current) {
1089
+ setModelDefaultsSaving((current) => current === key ? void 0 : current);
1090
+ setModelDefaultsPending((prev) => {
1091
+ const next = { ...prev };
1092
+ delete next[key];
1093
+ return next;
1094
+ });
1095
+ }
1096
+ }
1097
+ }, [rpc]);
1098
+ const login = (0, react.useCallback)(async (provider, method) => {
755
1099
  if (rpc === void 0) return;
756
1100
  setProviderError(provider, void 0);
757
1101
  try {
758
- const response = await callSubscriptionsAuth(rpc, "login", { provider });
1102
+ const response = await callSubscriptionsAuth(rpc, "login", {
1103
+ provider,
1104
+ ...method === void 0 ? {} : { method }
1105
+ });
759
1106
  if (typeof response.authorizeUrl === "string" && response.authorizeUrl === "") {
760
1107
  await refresh();
761
1108
  return;
@@ -765,9 +1112,9 @@ function SubscriptionsSection(props) {
765
1112
  setStatuses((prev) => ({
766
1113
  ...prev,
767
1114
  [provider]: {
1115
+ accounts: prev[provider]?.accounts ?? [],
768
1116
  ...prev[provider],
769
- busy: true,
770
- loggedIn: false
1117
+ busy: true
771
1118
  }
772
1119
  }));
773
1120
  if (typeof response.userCode === "string" && response.userCode.length > 0) setDeviceCodes((prev) => ({
@@ -827,12 +1174,18 @@ function SubscriptionsSection(props) {
827
1174
  setProviderError,
828
1175
  refresh
829
1176
  ]);
830
- const logout = (0, react.useCallback)(async (provider, name) => {
1177
+ const logout = (0, react.useCallback)(async (provider, account, display, name) => {
831
1178
  if (rpc === void 0) return;
832
- if (!window.confirm(t("logoutConfirm", { provider: name }))) return;
1179
+ if (!window.confirm(t("logoutAccountConfirm", {
1180
+ provider: name,
1181
+ account: display
1182
+ }))) return;
833
1183
  setProviderError(provider, void 0);
834
1184
  try {
835
- await callSubscriptionsAuth(rpc, "logout", { provider });
1185
+ await callSubscriptionsAuth(rpc, "logout", {
1186
+ provider,
1187
+ account
1188
+ });
836
1189
  } catch (error) {
837
1190
  setProviderError(provider, messageOf(error));
838
1191
  }
@@ -843,6 +1196,23 @@ function SubscriptionsSection(props) {
843
1196
  setProviderError,
844
1197
  refresh
845
1198
  ]);
1199
+ const setDefault = (0, react.useCallback)(async (provider, account) => {
1200
+ if (rpc === void 0) return;
1201
+ setProviderError(provider, void 0);
1202
+ try {
1203
+ await callSubscriptionsAuth(rpc, "setDefault", {
1204
+ provider,
1205
+ account
1206
+ });
1207
+ } catch (error) {
1208
+ setProviderError(provider, messageOf(error));
1209
+ }
1210
+ await refresh();
1211
+ }, [
1212
+ rpc,
1213
+ setProviderError,
1214
+ refresh
1215
+ ]);
846
1216
  const copyDeviceCode = (0, react.useCallback)((provider, userCode) => {
847
1217
  navigator.clipboard?.writeText(userCode).then(() => {
848
1218
  if (!mountedRef.current) return;
@@ -993,9 +1363,7 @@ function SubscriptionsSection(props) {
993
1363
  const status = statuses[id];
994
1364
  const busy = status?.busy === true;
995
1365
  const deviceCode = deviceCodes[id];
996
- const usage = usages[id];
997
- const usageError = usageErrors[id];
998
- const showUsage = status?.loggedIn === true && usage?.supported !== false && (usage !== void 0 || usageError !== void 0 || usageLoading[id] === true);
1366
+ const accounts = status?.accounts ?? [];
999
1367
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1000
1368
  style: styles$4.card,
1001
1369
  children: [
@@ -1021,10 +1389,119 @@ function SubscriptionsSection(props) {
1021
1389
  style: styles$4.errorLine,
1022
1390
  children: errors[id]
1023
1391
  }),
1392
+ accounts.map((account) => {
1393
+ const usageKey = `${id}:${account.key}`;
1394
+ const usage = usages[usageKey];
1395
+ const usageError = usageErrors[usageKey];
1396
+ const display = account.account ?? account.key;
1397
+ const showUsage = usage?.supported !== false && (usage !== void 0 || usageError !== void 0 || usageLoading[usageKey] === true);
1398
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1399
+ style: styles$4.accountRow,
1400
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1401
+ style: styles$4.accountHeader,
1402
+ children: [
1403
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1404
+ type: "button",
1405
+ style: styles$4.starButton,
1406
+ title: account.isDefault ? t("defaultBadge") : t("setDefault"),
1407
+ onClick: () => {
1408
+ if (!account.isDefault) setDefault(id, account.key);
1409
+ },
1410
+ children: account.isDefault ? "★" : "☆"
1411
+ }),
1412
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1413
+ style: styles$4.accountName,
1414
+ children: display
1415
+ }),
1416
+ account.plan !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1417
+ style: styles$4.usagePlan,
1418
+ children: account.plan
1419
+ }),
1420
+ account.expiresAt !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1421
+ style: styles$4.statusLine,
1422
+ children: t("accountExpires", { date: new Date(account.expiresAt).toLocaleString() })
1423
+ }),
1424
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1425
+ type: "button",
1426
+ style: {
1427
+ ...styles$4.button,
1428
+ marginLeft: "auto",
1429
+ flexShrink: 0
1430
+ },
1431
+ onClick: () => {
1432
+ logout(id, account.key, display, name);
1433
+ },
1434
+ children: t("logout")
1435
+ })
1436
+ ]
1437
+ }), showUsage && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1438
+ style: styles$4.usage,
1439
+ children: [
1440
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1441
+ style: styles$4.usageHeader,
1442
+ children: [
1443
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1444
+ style: styles$4.usageTitle,
1445
+ children: t("usageTitle")
1446
+ }),
1447
+ usage?.plan !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1448
+ style: styles$4.usagePlan,
1449
+ children: t("usagePlan", { plan: usage.plan })
1450
+ }),
1451
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1452
+ type: "button",
1453
+ style: {
1454
+ ...styles$4.usageRefresh,
1455
+ ...usageLoading[usageKey] === true ? {
1456
+ opacity: .5,
1457
+ cursor: "default"
1458
+ } : {}
1459
+ },
1460
+ disabled: usageLoading[usageKey] === true,
1461
+ onClick: () => {
1462
+ loadUsage(id, account.key, true);
1463
+ },
1464
+ children: t("usageRefresh")
1465
+ })
1466
+ ]
1467
+ }),
1468
+ usage === void 0 && usageError === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1469
+ style: styles$4.statusLine,
1470
+ children: t("usageLoading")
1471
+ }),
1472
+ usageError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1473
+ style: styles$4.errorLine,
1474
+ children: t("usageError", { message: usageError })
1475
+ }),
1476
+ usage?.windows !== void 0 && usage.windows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1477
+ style: styles$4.statusLine,
1478
+ children: t("usageEmpty")
1479
+ }),
1480
+ (usage?.windows ?? []).map((window$1, index) => {
1481
+ const percent = Math.min(100, Math.max(0, window$1.usedPercent));
1482
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1483
+ style: styles$4.usageRow,
1484
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1485
+ style: styles$4.usageMeta,
1486
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: usageWindowLabel(t, window$1) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [`${String(Math.round(percent))}%`, window$1.resetsAt !== void 0 && ` · ${t("usageResets", { date: new Date(window$1.resetsAt).toLocaleString() })}`] })]
1487
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1488
+ style: styles$4.usageTrack,
1489
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
1490
+ ...styles$4.usageFill,
1491
+ width: `${String(percent)}%`,
1492
+ background: usageBarColor(percent)
1493
+ } })
1494
+ })]
1495
+ }, index);
1496
+ })
1497
+ ]
1498
+ })]
1499
+ }, account.key);
1500
+ }),
1024
1501
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1025
1502
  style: styles$4.actions,
1026
1503
  children: [
1027
- !busy && status?.loggedIn !== true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1504
+ !busy && accounts.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1028
1505
  type: "button",
1029
1506
  style: styles$4.button,
1030
1507
  onClick: () => {
@@ -1032,86 +1509,172 @@ function SubscriptionsSection(props) {
1032
1509
  },
1033
1510
  children: t("login")
1034
1511
  }),
1035
- busy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1512
+ !busy && accounts.length > 0 && id === "claude" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1036
1513
  type: "button",
1037
1514
  style: styles$4.button,
1038
1515
  onClick: () => {
1039
- cancel(id);
1516
+ login(id, "oauth");
1040
1517
  },
1041
- children: t("cancel")
1518
+ children: t("addAccountOAuth")
1519
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1520
+ type: "button",
1521
+ style: styles$4.button,
1522
+ onClick: () => {
1523
+ login(id, "keychain");
1524
+ },
1525
+ children: t("addAccountKeychain")
1526
+ })] }),
1527
+ !busy && accounts.length > 0 && id !== "claude" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1528
+ type: "button",
1529
+ style: styles$4.button,
1530
+ onClick: () => {
1531
+ login(id);
1532
+ },
1533
+ children: t("addAccount")
1042
1534
  }),
1043
- status?.loggedIn === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1535
+ busy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1044
1536
  type: "button",
1045
1537
  style: styles$4.button,
1046
1538
  onClick: () => {
1047
- logout(id, name);
1539
+ cancel(id);
1048
1540
  },
1049
- children: t("logout")
1541
+ children: t("cancel")
1050
1542
  })
1051
1543
  ]
1052
1544
  }),
1053
- showUsage && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1054
- style: styles$4.usage,
1055
- children: [
1056
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1057
- style: styles$4.usageHeader,
1058
- children: [
1059
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1060
- style: styles$4.usageTitle,
1061
- children: t("usageTitle")
1545
+ !busy && accounts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1546
+ style: styles$4.statusLine,
1547
+ children: t("addAccountHint")
1548
+ }),
1549
+ accounts.length > 0 && (() => {
1550
+ const open = modelDefaultsOpen[id] === true;
1551
+ const catalog = modelDefaults[id];
1552
+ const filter = modelDefaultsFilters[id] ?? "";
1553
+ const view = deriveModelDefaultsView(catalog?.models, filter);
1554
+ const saveErrors = Object.entries(modelDefaultsSaveErrors).filter(([key]) => key.startsWith(`${id}/`));
1555
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1556
+ style: styles$4.defaultEffort,
1557
+ children: [
1558
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1559
+ type: "button",
1560
+ style: styles$4.defaultEffortToggle,
1561
+ "aria-expanded": open,
1562
+ onClick: () => {
1563
+ toggleModelDefaults(id);
1564
+ },
1565
+ children: [
1566
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1567
+ style: styles$4.usageTitle,
1568
+ children: t("modelDefaultsTitle")
1569
+ }),
1570
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1571
+ style: styles$4.usagePlan,
1572
+ children: catalog === void 0 ? modelDefaultsLoading ? t("modelDefaultsLoading") : "" : view.total === 0 ? t("modelDefaultsSummaryEmpty") : view.overridden === 0 ? t("modelDefaultsSummaryNone", { total: view.total }) : t("modelDefaultsSummary", {
1573
+ total: view.total,
1574
+ configured: view.overridden
1575
+ })
1576
+ }),
1577
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1578
+ style: styles$4.defaultEffortChevron,
1579
+ "aria-hidden": "true",
1580
+ children: open ? "▲" : "▼"
1581
+ })
1582
+ ]
1583
+ }),
1584
+ saveErrors.map(([key, message]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1585
+ style: styles$4.errorLine,
1586
+ role: "alert",
1587
+ children: t("modelDefaultsSaveFailedNamed", {
1588
+ model: catalog?.models.find((entry) => `${id}/${entry.id}` === key)?.name ?? key.slice(id.length + 1),
1589
+ message
1590
+ })
1591
+ }, key)),
1592
+ open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1593
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1594
+ style: styles$4.statusLine,
1595
+ children: t("modelDefaultsHint")
1062
1596
  }),
1063
- usage?.plan !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1064
- style: styles$4.usagePlan,
1065
- children: t("usagePlan", { plan: usage.plan })
1597
+ modelDefaultsLoadError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1598
+ style: styles$4.errorLine,
1599
+ children: t("modelDefaultsLoadFailed", { message: modelDefaultsLoadError })
1600
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1601
+ style: styles$4.actions,
1602
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1603
+ type: "button",
1604
+ style: styles$4.button,
1605
+ onClick: () => {
1606
+ setModelDefaultsLoadError(void 0);
1607
+ },
1608
+ children: t("modelDefaultsRetry")
1609
+ })
1610
+ })] }),
1611
+ modelDefaultsLoadError === void 0 && catalog === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1612
+ style: styles$4.statusLine,
1613
+ children: t("modelDefaultsLoading")
1066
1614
  }),
1067
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1068
- type: "button",
1069
- style: {
1070
- ...styles$4.usageRefresh,
1071
- ...usageLoading[id] === true ? {
1072
- opacity: .5,
1073
- cursor: "default"
1074
- } : {}
1075
- },
1076
- disabled: usageLoading[id] === true,
1077
- onClick: () => {
1078
- loadUsage(id);
1079
- },
1080
- children: t("usageRefresh")
1615
+ view.showFilter && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1616
+ style: styles$4.defaultEffortFilter,
1617
+ value: filter,
1618
+ placeholder: t("modelDefaultsFilterPlaceholder"),
1619
+ "aria-label": t("modelDefaultsFilterPlaceholder"),
1620
+ onChange: (event) => {
1621
+ setModelDefaultsFilters((prev) => ({
1622
+ ...prev,
1623
+ [id]: event.target.value
1624
+ }));
1625
+ }
1626
+ }),
1627
+ view.shown.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1628
+ style: styles$4.defaultEffortList,
1629
+ children: view.shown.map((model) => {
1630
+ const rowKey = `${id}/${model.id}`;
1631
+ const saving = modelDefaultsSaving === rowKey;
1632
+ const labelId = `dsh-model-default-${id}-${model.id}`;
1633
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1634
+ style: styles$4.defaultEffortRow,
1635
+ children: [
1636
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1637
+ id: labelId,
1638
+ style: styles$4.defaultEffortName,
1639
+ title: model.id,
1640
+ children: model.name
1641
+ }),
1642
+ saving && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1643
+ style: styles$4.defaultEffortSaving,
1644
+ children: t("modelDefaultsSaving")
1645
+ }),
1646
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1647
+ style: styles$4.defaultEffortSelect,
1648
+ "aria-labelledby": labelId,
1649
+ value: modelDefaultsPending[rowKey] ?? model.configured ?? "",
1650
+ disabled: saving,
1651
+ onChange: (event) => {
1652
+ setModelDefault(id, model.id, event.target.value === "" ? void 0 : event.target.value);
1653
+ },
1654
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1655
+ value: "",
1656
+ children: t("modelDefaultsFollowProvider")
1657
+ }), model.efforts.map((effort) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1658
+ value: effort.id,
1659
+ children: effort.name
1660
+ }, effort.id))]
1661
+ })
1662
+ ]
1663
+ }, model.id);
1664
+ })
1665
+ }),
1666
+ catalog !== void 0 && view.shown.length === 0 && filter.trim() !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1667
+ style: styles$4.statusLine,
1668
+ children: t("modelDefaultsFilterEmpty", { query: filter.trim() })
1669
+ }),
1670
+ view.withoutEfforts > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1671
+ style: styles$4.statusLine,
1672
+ children: t("modelDefaultsNoLevels", { count: view.withoutEfforts })
1081
1673
  })
1082
- ]
1083
- }),
1084
- usage === void 0 && usageError === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1085
- style: styles$4.statusLine,
1086
- children: t("usageLoading")
1087
- }),
1088
- usageError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1089
- style: styles$4.errorLine,
1090
- children: t("usageError", { message: usageError })
1091
- }),
1092
- usage?.windows !== void 0 && usage.windows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1093
- style: styles$4.statusLine,
1094
- children: t("usageEmpty")
1095
- }),
1096
- (usage?.windows ?? []).map((window$1, index) => {
1097
- const percent = Math.min(100, Math.max(0, window$1.usedPercent));
1098
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1099
- style: styles$4.usageRow,
1100
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1101
- style: styles$4.usageMeta,
1102
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: usageWindowLabel(t, window$1) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [`${String(Math.round(percent))}%`, window$1.resetsAt !== void 0 && ` · ${t("usageResets", { date: new Date(window$1.resetsAt).toLocaleString() })}`] })]
1103
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1104
- style: styles$4.usageTrack,
1105
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
1106
- ...styles$4.usageFill,
1107
- width: `${String(percent)}%`,
1108
- background: usageBarColor(percent)
1109
- } })
1110
- })]
1111
- }, index);
1112
- })
1113
- ]
1114
- }),
1674
+ ] })
1675
+ ]
1676
+ });
1677
+ })(),
1115
1678
  busy && deviceCode !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1116
1679
  style: styles$4.deviceCode,
1117
1680
  children: [
@@ -1969,14 +2532,21 @@ function VideoGenerateToolview(props) {
1969
2532
  * known state, so a transient failure never locks the toggle away.
1970
2533
  *
1971
2534
  * `sessionId` is a plain string: slot and command contexts brand it through
1972
- * different dsh-session copies, and only the API-client boundary needs one.
2535
+ * different dsh-session copies, and only the service boundary needs one.
2536
+ *
2537
+ * `models` resolves lazily per call: the ui-model-selection service may
2538
+ * register after this plugin applies, and a shell without it (no model seat
2539
+ * at all) simply keeps the toggle hidden.
1973
2540
  */
1974
- function createSpeedLoader(connection, sessionId) {
2541
+ function createSpeedLoader(connection, models, sessionId) {
1975
2542
  return async () => {
1976
2543
  const state = await callSubscriptionsAuth(connection.rpc, "speed", { sessionId });
1977
- const { result } = await connection.api.sessions.models({ sessionId });
1978
- if (!result.ok) throw new Error(`session.models failed: ${result.error.code}: ${result.error.message}`);
1979
- const current = result.value.current;
2544
+ const directories = models();
2545
+ if (directories === void 0) return {
2546
+ visible: false,
2547
+ tier: state.tier
2548
+ };
2549
+ const { current } = await directories.directoryFor(sessionId).load();
1980
2550
  return {
1981
2551
  visible: current !== null && current.provider === "codex" && state.fastModels.includes(current.model),
1982
2552
  tier: state.tier
@@ -2256,13 +2826,14 @@ function apply(ctx) {
2256
2826
  locale: NS,
2257
2827
  inject: videoToolviewInjected
2258
2828
  }, VideoGenerateToolview));
2829
+ const models = () => ctx.get("modelDirectories");
2259
2830
  ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
2260
2831
  name: "conversation.input.right",
2261
2832
  id: "codex-speed",
2262
2833
  order: 0,
2263
2834
  locale: NS,
2264
2835
  inject: (sessionId) => ({
2265
- loadSpeed: createSpeedLoader(connection, sessionId),
2836
+ loadSpeed: createSpeedLoader(connection, models, sessionId),
2266
2837
  setSpeed: createSpeedSetter(connection, sessionId)
2267
2838
  })
2268
2839
  }, SpeedSelect));
@@ -2275,7 +2846,7 @@ function apply(ctx) {
2275
2846
  ui: {
2276
2847
  kind: "popupSelect",
2277
2848
  options: async (session) => {
2278
- const state = await createSpeedLoader(connection, session.sessionId)();
2849
+ const state = await createSpeedLoader(connection, models, session.sessionId)();
2279
2850
  if (!state.visible) throw new Error(t("commandFastUnavailable"));
2280
2851
  return [{
2281
2852
  id: "standard",