dsh-plugin-subscription-usage 0.0.0-stage → 0.1.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 (167) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +38 -2
  3. package/cordis.patch.yml +5 -0
  4. package/icon.svg +9 -0
  5. package/lib/auth/claude-code-creds.d.ts +23 -0
  6. package/lib/auth/claude-code-creds.js +172 -0
  7. package/lib/auth/device-flow.d.ts +55 -0
  8. package/lib/auth/device-flow.js +177 -0
  9. package/lib/auth/jwt.d.ts +10 -0
  10. package/lib/auth/jwt.js +25 -0
  11. package/lib/auth/oauth-flow.d.ts +91 -0
  12. package/lib/auth/oauth-flow.js +227 -0
  13. package/lib/auth/pkce.d.ts +31 -0
  14. package/lib/auth/pkce.js +35 -0
  15. package/lib/auth/rpc.d.ts +233 -0
  16. package/lib/auth/rpc.js +501 -0
  17. package/lib/auth/store.d.ts +198 -0
  18. package/lib/auth/store.js +473 -0
  19. package/lib/client/Footer.d.ts +3 -0
  20. package/lib/client/Footer.js +73 -0
  21. package/lib/client/Gauge.d.ts +28 -0
  22. package/lib/client/Gauge.js +139 -0
  23. package/lib/client/ImageGallery.d.ts +54 -0
  24. package/lib/client/ImageGallery.js +112 -0
  25. package/lib/client/ImageGenerateToolview.d.ts +48 -0
  26. package/lib/client/ImageGenerateToolview.js +130 -0
  27. package/lib/client/Notice.d.ts +15 -0
  28. package/lib/client/Notice.js +93 -0
  29. package/lib/client/ProviderCard.d.ts +35 -0
  30. package/lib/client/ProviderCard.js +240 -0
  31. package/lib/client/SpeedSelect.d.ts +72 -0
  32. package/lib/client/SpeedSelect.js +214 -0
  33. package/lib/client/Usage.d.ts +7 -0
  34. package/lib/client/Usage.js +83 -0
  35. package/lib/client/VideoGenerateToolview.d.ts +42 -0
  36. package/lib/client/VideoGenerateToolview.js +172 -0
  37. package/lib/client/context.d.ts +16 -0
  38. package/lib/client/context.js +11 -0
  39. package/lib/client/fast-command.d.ts +2 -0
  40. package/lib/client/fast-command.js +4 -0
  41. package/lib/client/hooks.d.ts +2 -0
  42. package/lib/client/hooks.js +10 -0
  43. package/lib/client/index.d.ts +3 -0
  44. package/lib/client/index.js +113 -0
  45. package/lib/client/locales.d.ts +139 -0
  46. package/lib/client/locales.js +267 -0
  47. package/lib/client/meter.d.ts +91 -0
  48. package/lib/client/meter.js +196 -0
  49. package/lib/client/prefs.d.ts +51 -0
  50. package/lib/client/prefs.js +71 -0
  51. package/lib/client/rpc.d.ts +161 -0
  52. package/lib/client/rpc.js +31 -0
  53. package/lib/client/store.d.ts +67 -0
  54. package/lib/client/store.js +129 -0
  55. package/lib/client/styles.d.ts +7 -0
  56. package/lib/client/styles.js +176 -0
  57. package/lib/client.js +3933 -0
  58. package/lib/client.js.map +1 -0
  59. package/lib/compat.d.ts +55 -0
  60. package/lib/compat.js +20 -0
  61. package/lib/http.d.ts +134 -0
  62. package/lib/http.js +431 -0
  63. package/lib/index.d.ts +121 -0
  64. package/lib/index.js +13210 -0
  65. package/lib/json-text.d.ts +2 -0
  66. package/lib/json-text.js +4 -0
  67. package/lib/model-defaults.d.ts +23 -0
  68. package/lib/model-defaults.js +238 -0
  69. package/lib/model-speeds.d.ts +10 -0
  70. package/lib/model-speeds.js +48 -0
  71. package/lib/models-directory.d.ts +17 -0
  72. package/lib/models-directory.js +25 -0
  73. package/lib/provider-settings.d.ts +55 -0
  74. package/lib/provider-settings.js +215 -0
  75. package/lib/providers/account-preferences.d.ts +46 -0
  76. package/lib/providers/account-preferences.js +188 -0
  77. package/lib/providers/accounts.d.ts +113 -0
  78. package/lib/providers/accounts.js +142 -0
  79. package/lib/providers/antigravity-oauth-client.d.ts +7 -0
  80. package/lib/providers/antigravity-oauth-client.js +7 -0
  81. package/lib/providers/antigravity.d.ts +119 -0
  82. package/lib/providers/antigravity.js +552 -0
  83. package/lib/providers/catalog-refresh.d.ts +4 -0
  84. package/lib/providers/catalog-refresh.js +30 -0
  85. package/lib/providers/catalog-store.d.ts +47 -0
  86. package/lib/providers/catalog-store.js +283 -0
  87. package/lib/providers/claude-cli-version.d.ts +15 -0
  88. package/lib/providers/claude-cli-version.js +24 -0
  89. package/lib/providers/claude.d.ts +212 -0
  90. package/lib/providers/claude.js +726 -0
  91. package/lib/providers/codex-client-version.d.ts +8 -0
  92. package/lib/providers/codex-client-version.js +17 -0
  93. package/lib/providers/codex-search.d.ts +29 -0
  94. package/lib/providers/codex-search.js +198 -0
  95. package/lib/providers/codex.d.ts +253 -0
  96. package/lib/providers/codex.js +930 -0
  97. package/lib/providers/common.d.ts +415 -0
  98. package/lib/providers/common.js +679 -0
  99. package/lib/providers/copilot.d.ts +365 -0
  100. package/lib/providers/copilot.js +956 -0
  101. package/lib/providers/grok.d.ts +214 -0
  102. package/lib/providers/grok.js +750 -0
  103. package/lib/providers/image-pool.d.ts +30 -0
  104. package/lib/providers/image-pool.js +89 -0
  105. package/lib/providers/npm-cli-version.d.ts +56 -0
  106. package/lib/providers/npm-cli-version.js +98 -0
  107. package/lib/providers/pool-family.d.ts +56 -0
  108. package/lib/providers/pool-family.js +45 -0
  109. package/lib/providers/pool-health.d.ts +91 -0
  110. package/lib/providers/pool-health.js +164 -0
  111. package/lib/providers/pool-usage.d.ts +106 -0
  112. package/lib/providers/pool-usage.js +302 -0
  113. package/lib/providers/pool.d.ts +115 -0
  114. package/lib/providers/pool.js +461 -0
  115. package/lib/providers/rate-limit.d.ts +192 -0
  116. package/lib/providers/rate-limit.js +338 -0
  117. package/lib/providers/replay.d.ts +4 -0
  118. package/lib/providers/replay.js +37 -0
  119. package/lib/providers/reset-redemption.d.ts +20 -0
  120. package/lib/providers/reset-redemption.js +70 -0
  121. package/lib/tools/image-generate.d.ts +128 -0
  122. package/lib/tools/image-generate.js +438 -0
  123. package/lib/tools/registration.d.ts +15 -0
  124. package/lib/tools/registration.js +24 -0
  125. package/lib/tools/video-generate.d.ts +89 -0
  126. package/lib/tools/video-generate.js +257 -0
  127. package/lib/tools/x-search.d.ts +58 -0
  128. package/lib/tools/x-search.js +197 -0
  129. package/lib/translate/anthropic.d.ts +180 -0
  130. package/lib/translate/anthropic.js +534 -0
  131. package/lib/translate/antigravity-images.d.ts +11 -0
  132. package/lib/translate/antigravity-images.js +62 -0
  133. package/lib/translate/antigravity-schema.d.ts +4 -0
  134. package/lib/translate/antigravity-schema.js +145 -0
  135. package/lib/translate/antigravity-thinking.d.ts +5 -0
  136. package/lib/translate/antigravity-thinking.js +44 -0
  137. package/lib/translate/antigravity.d.ts +120 -0
  138. package/lib/translate/antigravity.js +412 -0
  139. package/lib/translate/chat-completions.d.ts +120 -0
  140. package/lib/translate/chat-completions.js +382 -0
  141. package/lib/translate/resolved.d.ts +98 -0
  142. package/lib/translate/resolved.js +186 -0
  143. package/lib/translate/responses.d.ts +192 -0
  144. package/lib/translate/responses.js +443 -0
  145. package/lib/translate/sse.d.ts +21 -0
  146. package/lib/translate/sse.js +62 -0
  147. package/lib/usage/headers.d.ts +21 -0
  148. package/lib/usage/headers.js +64 -0
  149. package/lib/usage/pace.d.ts +53 -0
  150. package/lib/usage/pace.js +48 -0
  151. package/lib/usage/service.d.ts +64 -0
  152. package/lib/usage/service.js +104 -0
  153. package/lib/usage-providers/common.d.ts +23 -0
  154. package/lib/usage-providers/common.js +28 -0
  155. package/lib/usage-providers/index.d.ts +42 -0
  156. package/lib/usage-providers/index.js +71 -0
  157. package/lib/usage-providers/keys.d.ts +11 -0
  158. package/lib/usage-providers/keys.js +52 -0
  159. package/lib/usage-providers/kimi.d.ts +4 -0
  160. package/lib/usage-providers/kimi.js +52 -0
  161. package/lib/usage-providers/ollama-cloud.d.ts +4 -0
  162. package/lib/usage-providers/ollama-cloud.js +42 -0
  163. package/lib/usage-providers/zai.d.ts +5 -0
  164. package/lib/usage-providers/zai.js +53 -0
  165. package/locale/en.json +6 -0
  166. package/locale/zh.json +6 -0
  167. package/package.json +115 -4
@@ -0,0 +1,196 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { displayPercent, pace } from '../usage/pace.js';
3
+ import { format } from './locales.js';
4
+ /** Translate with placeholders. */
5
+ export function tf(t, key, params) {
6
+ return format(t(key), params);
7
+ }
8
+ const PATHS = {
9
+ refresh: _jsxs(_Fragment, { children: [_jsx("path", { d: "M13.5 8a5.5 5.5 0 1 1-1.6-3.9" }), _jsx("path", { d: "M13.5 2.5v3h-3" })] }),
10
+ alert: _jsxs(_Fragment, { children: [_jsx("path", { d: "M8 1.8 15 14H1z" }), _jsx("path", { d: "M8 6.3v3.4M8 11.8v.01" })] }),
11
+ check: _jsx("path", { d: "m3.5 8.3 3 3 6-6.6" }),
12
+ star: _jsx("path", { d: "m8 2 1.8 3.8 4.2.5-3.1 2.9.8 4.1L8 11.3 4.3 13.3l.8-4.1L2 6.3l4.2-.5z", fill: "currentColor" }),
13
+ bolt: _jsx("path", { d: "M9 1.8 3.5 9H8l-1 5.2L12.5 7H8z", fill: "currentColor", stroke: "none" }),
14
+ pin: _jsxs(_Fragment, { children: [_jsx("path", { d: "M6 2.5h4l-.6 4 2.1 2.2v1H4.5v-1l2.1-2.2z" }), _jsx("path", { d: "M8 9.7v3.8" })] }),
15
+ pinFilled: _jsxs(_Fragment, { children: [_jsx("path", { d: "M6 2.5h4l-.6 4 2.1 2.2v1H4.5v-1l2.1-2.2z", fill: "currentColor" }), _jsx("path", { d: "M8 9.7v3.8" })] }),
16
+ gear: _jsxs(_Fragment, { children: [_jsx("circle", { cx: "8", cy: "8", r: "2.2" }), _jsx("path", { d: "M8 1.8v1.6M8 12.6v1.6M1.8 8h1.6M12.6 8h1.6M3.6 3.6l1.1 1.1M11.3 11.3l1.1 1.1M3.6 12.4l1.1-1.1M11.3 4.7l1.1-1.1" })] }),
17
+ ext: _jsx("path", { d: "M9 3h4v4M13 3 7.5 8.5M12 9.5V13H3V4h3.5" }),
18
+ copy: _jsxs(_Fragment, { children: [_jsx("rect", { x: "5", y: "5", width: "8.5", height: "8.5", rx: "1.5" }), _jsx("path", { d: "M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5" })] }),
19
+ x: _jsx("path", { d: "m4 4 8 8M12 4l-8 8" }),
20
+ right: _jsx("path", { d: "m6 4 4 4-4 4" }),
21
+ down: _jsx("path", { d: "m5 6.5 3 3 3-3" }),
22
+ meter: _jsxs(_Fragment, { children: [_jsx("path", { d: "M2.5 11a5.5 5.5 0 0 1 11 0" }), _jsx("path", { d: "m8 11 2.6-3.2" })] }),
23
+ sparkle: _jsx("path", { d: "M8 1.5 9.4 6.6 14.5 8 9.4 9.4 8 14.5 6.6 9.4 1.5 8 6.6 6.6z", fill: "currentColor", stroke: "none" }),
24
+ };
25
+ /** One 16-unit stroke icon. */
26
+ export function Icon({ name, size = 14, className }) {
27
+ return (_jsx("svg", { className: className, width: size, height: size, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", style: { flex: 'none', display: 'block' }, children: PATHS[name] }));
28
+ }
29
+ const H = 3_600_000;
30
+ const D = 24 * H;
31
+ /** "3h 6m", "2d 12h", "42m". */
32
+ export function shortDuration(ms) {
33
+ // Round once, then split, so 4h 59.6m reads 5h rather than 4h 60m.
34
+ const total = Math.max(1, Math.round(ms / 60_000));
35
+ const days = Math.floor(total / 1440);
36
+ const hours = Math.floor((total % 1440) / 60);
37
+ const minutes = total % 60;
38
+ if (days > 0)
39
+ return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
40
+ if (hours > 0)
41
+ return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
42
+ return `${Math.max(1, minutes)}m`;
43
+ }
44
+ /**
45
+ * A short point in time in the browser's locale: "6:04 PM" today, "Wed" within
46
+ * the week, "12 Nov" beyond. The day alone keeps the time column narrow; the
47
+ * tooltip carries the exact reset time.
48
+ */
49
+ export function clockTime(at, now) {
50
+ const date = new Date(at);
51
+ if (date.toDateString() === new Date(now).toDateString()) {
52
+ return date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
53
+ }
54
+ if (Math.abs(at - now) < 6 * D)
55
+ return date.toLocaleDateString(undefined, { weekday: 'short' });
56
+ return date.toLocaleDateString(undefined, { day: 'numeric', month: 'short' });
57
+ }
58
+ /** Weekday and time, for tooltips. */
59
+ export function fullTime(at) {
60
+ return new Date(at).toLocaleString(undefined, { weekday: 'short', hour: 'numeric', minute: '2-digit' });
61
+ }
62
+ /** The window's length as "5h", "7d" or "30d", rounded to a whole unit; undefined when unknown. */
63
+ export function lengthLabel(durationMs) {
64
+ if (durationMs === undefined || !Number.isFinite(durationMs) || durationMs <= 0)
65
+ return undefined;
66
+ const hours = Math.round(durationMs / 3_600_000);
67
+ if (hours < 1)
68
+ return `${Math.max(1, Math.round(durationMs / 60_000))}m`;
69
+ return hours < 48 ? `${hours}h` : `${Math.round(hours / 24)}d`;
70
+ }
71
+ /** "5h", "7d", "30d": the window's own length, else its kind, else "limit". */
72
+ export function kindLabel(t, kind, durationMs) {
73
+ const length = lengthLabel(durationMs);
74
+ if (length !== undefined)
75
+ return length;
76
+ return kind === 'session' ? t('window5h') : kind === 'weekly' ? t('window7d') : t('windowOther');
77
+ }
78
+ /** The length label, with a model scope when the window has one. A per-model quota is named by its scope alone. */
79
+ export function windowLabel(t, window) {
80
+ if (window.scope === undefined)
81
+ return kindLabel(t, window.kind, window.durationMs);
82
+ return window.kind === 'other' ? window.scope : `${kindLabel(t, window.kind, window.durationMs)} ${window.scope}`;
83
+ }
84
+ const FAMILY_NAMES = { gemini: 'Gemini', claude: 'Claude', gpt: 'GPT' };
85
+ /** "claude-opus-4-6-thinking" becomes "Claude"; "tab_flash_lite" becomes "Tab". */
86
+ function modelFamily(id) {
87
+ const head = id.split(/[-_]/)[0] ?? id;
88
+ return FAMILY_NAMES[head.toLowerCase()] ?? head.charAt(0).toUpperCase() + head.slice(1);
89
+ }
90
+ /**
91
+ * Per-model windows that report the same usage and reset share one quota pool
92
+ * (Antigravity reports one window per model), so they merge into one row named
93
+ * by model family. A per-model window at 0% with no reset carries no information
94
+ * and is hidden. Unscoped windows pass through unchanged.
95
+ */
96
+ export function shownWindows(windows) {
97
+ const out = [];
98
+ const groups = new Map();
99
+ for (const window of windows ?? []) {
100
+ if (window.scope === undefined || window.kind !== 'other') {
101
+ out.push(window);
102
+ continue;
103
+ }
104
+ if (window.resetsAt === undefined && window.usedPercent === 0)
105
+ continue;
106
+ const minute = window.resetsAt === undefined ? '' : String(Math.round(window.resetsAt / 60_000));
107
+ const key = `${Math.round(window.usedPercent)}/${minute}`;
108
+ const group = groups.get(key);
109
+ if (group === undefined) {
110
+ const entry = { window, models: [window.scope] };
111
+ groups.set(key, entry);
112
+ }
113
+ else {
114
+ group.models.push(window.scope);
115
+ }
116
+ }
117
+ for (const { window, models } of groups.values()) {
118
+ const families = [...new Set(models.map(modelFamily))];
119
+ out.push({ ...window, scope: families.join(' · '), models });
120
+ }
121
+ return out;
122
+ }
123
+ /**
124
+ * What compact surfaces show: unscoped windows, session first. A provider that
125
+ * only reports per-model windows (Antigravity) shows its two most used instead.
126
+ */
127
+ export function primaryWindows(windows) {
128
+ const order = { session: 0, weekly: 1, other: 2 };
129
+ const all = shownWindows(windows);
130
+ const unscoped = all.filter(w => w.scope === undefined).sort((a, b) => order[a.kind] - order[b.kind]);
131
+ if (unscoped.length > 0)
132
+ return unscoped;
133
+ return [...all].sort((a, b) => b.usedPercent - a.usedPercent).slice(0, 2);
134
+ }
135
+ /** The pace bar: fill is quota, hatch is the forecast, color is the pace state. Drains in `left` mode. */
136
+ export function Bar({ window, p, display, height = 6 }) {
137
+ const used = p.state === 'unknown' ? 0 : p.usedPercent;
138
+ const left = display === 'left';
139
+ const fill = left ? 100 - used : used;
140
+ let projection = null;
141
+ if (p.state === 'over' || p.state === 'tight') {
142
+ const end = Math.min(100, p.projectedPercent);
143
+ projection = _jsx("span", { className: "dsm-proj", style: left ? { left: `${100 - end}%`, width: `${end - used}%` } : { left: `${used}%`, width: `${end - used}%` } });
144
+ }
145
+ return (_jsxs("span", { className: `dsm-bar dsm-${p.state}`, style: { height }, role: "meter", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": displayPercent(window, display) ?? 0, children: [_jsx("span", { className: "dsm-fill", style: { width: `${fill}%` } }), projection] }));
146
+ }
147
+ function valueText(window, display) {
148
+ const value = displayPercent(window, display);
149
+ return value === undefined ? '–' : `${value}%`;
150
+ }
151
+ /** The time column: when this changes next. */
152
+ function timeText(t, window, p, now) {
153
+ if (p.state === 'out' && window.resetsAt !== undefined)
154
+ return tf(t, 'backAt', { time: clockTime(window.resetsAt, now) });
155
+ if (p.state === 'over')
156
+ return tf(t, 'emptyAt', { time: clockTime(p.emptyAt, now) });
157
+ return window.resetsAt === undefined ? '' : tf(t, 'resetsIn', { time: shortDuration(Math.max(0, window.resetsAt - now)) });
158
+ }
159
+ function tooltip(t, window, p, now) {
160
+ if (p.state === 'unknown')
161
+ return t('notReported');
162
+ const used = Math.round(p.usedPercent);
163
+ const lines = [...window.models ?? [], tf(t, 'tipUsedLeft', { used, left: 100 - used })];
164
+ if (window.used !== undefined && window.limit !== undefined) {
165
+ lines.push(tf(t, 'tipCount', { used: Math.round(window.used), limit: window.limit }));
166
+ }
167
+ if ((p.state === 'calm' || p.state === 'tight' || p.state === 'over') && p.elapsed !== undefined) {
168
+ lines.push(tf(t, 'tipElapsed', { percent: Math.round(p.elapsed * 100) }));
169
+ }
170
+ if (p.state === 'tight')
171
+ lines.push(tf(t, 'tipPace', { percent: Math.round(p.projectedPercent) }));
172
+ if (p.state === 'over')
173
+ lines.push(tf(t, 'emptyAt', { time: fullTime(p.emptyAt) }));
174
+ if (window.resetsAt !== undefined)
175
+ lines.push(tf(t, 'tipResets', { time: fullTime(window.resetsAt) }));
176
+ return lines.join('\n');
177
+ }
178
+ /** One window on the shared grid: label | bar | value | time. */
179
+ export function WindowRow({ t, window, display, now }) {
180
+ const p = pace(window, now);
181
+ const tip = tooltip(t, window, p, now);
182
+ // The row is `display: contents` so its cells sit on the shared grid; each cell carries the tooltip.
183
+ return (_jsxs("div", { className: `dsm-w dsm-${p.state}`, children: [_jsx("span", { className: "dsm-wl", title: window.scope === undefined ? tip : `${windowLabel(t, window)}\n${tip}`, children: windowLabel(t, window) }), _jsx("span", { title: tip, children: _jsx(Bar, { window: window, p: p, display: display }) }), _jsx("span", { className: "dsm-wv", title: tip, children: valueText(window, display) }), _jsx("span", { className: "dsm-wt", title: tip, children: timeText(t, window, p, now) })] }));
184
+ }
185
+ /** Label, small bar and value, for summary rows and the gauge. */
186
+ export function Mini({ t, window, display, now }) {
187
+ const p = pace(window, now);
188
+ return (_jsxs("span", { className: `dsm-mini dsm-${p.state}`, title: window.scope === undefined ? tooltip(t, window, p, now) : `${window.scope}\n${tooltip(t, window, p, now)}`, children: [_jsx("i", { children: window.kind === 'other' && window.scope !== undefined ? window.scope : kindLabel(t, window.kind, window.durationMs) }), _jsx(Bar, { window: window, p: p, display: display, height: 4 }), _jsx("b", { children: valueText(window, display) })] }));
189
+ }
190
+ /** The mini meters of an account's unscoped windows. */
191
+ export function Minis({ t, windows, display, now }) {
192
+ const shown = primaryWindows(windows);
193
+ if (shown.length === 0)
194
+ return null;
195
+ return _jsx("span", { className: "dsm-minis", children: shown.map((w, i) => _jsx(Mini, { t: t, window: w, display: display, now: now }, i)) });
196
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Usage preferences live in this plugin's volatile `usage` Config field, so
3
+ * they persist in the profile and reach every tab. The Client reads them
4
+ * through the settings forms service and validates the section where it
5
+ * enters.
6
+ */
7
+ import type { UsageProviderId as ProviderId } from './rpc.js';
8
+ export type { ProviderId };
9
+ export interface UsagePreferencesView {
10
+ /** Show usage under the composer: the current model's provider plus `pinned`. */
11
+ composer: boolean;
12
+ pinned: readonly ProviderId[];
13
+ display: 'left' | 'used';
14
+ paceWarnings: boolean;
15
+ /** Whether writes reach the profile. */
16
+ writable: boolean;
17
+ }
18
+ export declare const DEFAULT_PREFS: UsagePreferencesView;
19
+ /** The slice of a settings ConfigForm this store uses. */
20
+ export interface ConfigFormLike {
21
+ getSnapshot(): {
22
+ status: string;
23
+ value: unknown;
24
+ writable: boolean;
25
+ };
26
+ subscribe(listener: () => void): () => void;
27
+ mutate(ops: readonly ({
28
+ op: 'set';
29
+ path: string[];
30
+ value: unknown;
31
+ } | {
32
+ op: 'unset';
33
+ path: string[];
34
+ })[]): Promise<boolean>;
35
+ }
36
+ /** Narrow the namespace section's `usage` field, falling back field by field. */
37
+ export declare function parsePrefs(section: unknown, writable: boolean): UsagePreferencesView;
38
+ export declare class PrefsStore {
39
+ private snapshot;
40
+ private readonly listeners;
41
+ private unsubscribe;
42
+ private form;
43
+ getSnapshot: () => UsagePreferencesView;
44
+ subscribe: (listener: () => void) => (() => void);
45
+ /** Bind the form once the settings service is available; returns the unbinder. */
46
+ bind(form: ConfigFormLike): () => void;
47
+ /** Change some preferences; the shown value updates at once and settles on the Host's answer. */
48
+ set(patch: Partial<Omit<UsagePreferencesView, 'writable'>>): Promise<boolean>;
49
+ togglePinned(provider: ProviderId): Promise<boolean>;
50
+ private publish;
51
+ }
@@ -0,0 +1,71 @@
1
+ import { isUsageProviderId } from './rpc.js';
2
+ export const DEFAULT_PREFS = { composer: true, pinned: [], display: 'left', paceWarnings: true, writable: false };
3
+ /** Narrow the namespace section's `usage` field, falling back field by field. */
4
+ export function parsePrefs(section, writable) {
5
+ const usage = section !== null && typeof section === 'object' && 'usage' in section ? section.usage : undefined;
6
+ if (usage === null || typeof usage !== 'object')
7
+ return { ...DEFAULT_PREFS, writable };
8
+ const read = (key) => (key in usage ? usage[key] : undefined);
9
+ const composer = read('composer');
10
+ // Profiles saved before `composer` existed carry a three-way `gauge` mode.
11
+ const legacyGauge = read('gauge');
12
+ const display = read('display');
13
+ const pinned = read('pinned');
14
+ const pace = read('paceWarnings');
15
+ return {
16
+ composer: typeof composer === 'boolean' ? composer : legacyGauge !== 'off',
17
+ pinned: Array.isArray(pinned) ? pinned.filter(isUsageProviderId) : [],
18
+ display: display === 'used' ? 'used' : 'left',
19
+ paceWarnings: typeof pace === 'boolean' ? pace : true,
20
+ writable,
21
+ };
22
+ }
23
+ export class PrefsStore {
24
+ snapshot = DEFAULT_PREFS;
25
+ listeners = new Set();
26
+ unsubscribe;
27
+ form;
28
+ getSnapshot = () => this.snapshot;
29
+ subscribe = (listener) => {
30
+ this.listeners.add(listener);
31
+ return () => { this.listeners.delete(listener); };
32
+ };
33
+ /** Bind the form once the settings service is available; returns the unbinder. */
34
+ bind(form) {
35
+ this.form = form;
36
+ const sync = () => {
37
+ const view = form.getSnapshot();
38
+ if (view.status !== 'ready')
39
+ return;
40
+ this.publish(parsePrefs(view.value, view.writable));
41
+ };
42
+ this.unsubscribe = form.subscribe(sync);
43
+ sync();
44
+ return () => {
45
+ this.unsubscribe?.();
46
+ this.form = undefined;
47
+ };
48
+ }
49
+ /** Change some preferences; the shown value updates at once and settles on the Host's answer. */
50
+ async set(patch) {
51
+ const next = { ...this.snapshot, ...patch };
52
+ this.publish(next);
53
+ if (this.form === undefined)
54
+ return false;
55
+ const ops = Object.entries(patch).map(([key, value]) => ({ op: 'set', path: ['usage', key], value }));
56
+ // `composer` replaces the legacy `gauge` mode; drop it on the first write.
57
+ ops.push({ op: 'unset', path: ['usage', 'gauge'] });
58
+ return this.form.mutate(ops);
59
+ }
60
+ togglePinned(provider) {
61
+ const pinned = this.snapshot.pinned.includes(provider)
62
+ ? this.snapshot.pinned.filter(p => p !== provider)
63
+ : [...this.snapshot.pinned, provider];
64
+ return this.set({ pinned });
65
+ }
66
+ publish(next) {
67
+ this.snapshot = next;
68
+ for (const listener of this.listeners)
69
+ listener();
70
+ }
71
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Browser calls to the Host's `/api/subscriptions-auth.*` routes, and the
3
+ * shapes those routes return. Only types cross from the Host modules, so the
4
+ * bundle stays free of Node imports.
5
+ */
6
+ import type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client';
7
+ import type { AccountStatus, ProviderStatus, ProviderAccountCatalog } from '../auth/rpc.js';
8
+ import type { ProviderId } from '../auth/store.js';
9
+ import type { KeyProviderId, KeyProviderView } from '../usage-providers/index.js';
10
+ import type { ProviderPreferences, SubscriptionTool } from '../provider-settings.js';
11
+ import type { DisplayUsage, UsageReadReason } from '../usage/service.js';
12
+ import type { ProxyConfigView, ProxyInput, ProxyTestResult } from '../http.js';
13
+ export type { KeyProviderId, KeyProviderView, AccountStatus, ProviderStatus, ProviderId, ProviderPreferences, DisplayUsage, UsageReadReason, ProxyConfigView, ProxyTestResult };
14
+ /** Every provider this plugin can route, in Models-page order. Names and metering come from `status`. */
15
+ export declare const PROVIDERS: readonly ProviderId[];
16
+ /** Providers that read usage with a pasted key, in Usage-page order. Mirrors the Host's `KEY_PROVIDER_IDS`. */
17
+ export declare const KEY_PROVIDERS: readonly KeyProviderId[];
18
+ /** Any provider the Usage page and gauge can show. */
19
+ export type UsageProviderId = ProviderId | KeyProviderId;
20
+ export declare const USAGE_PROVIDERS: readonly UsageProviderId[];
21
+ export declare const isUsageProviderId: (value: unknown) => value is UsageProviderId;
22
+ export declare const isProviderId: (value: unknown) => value is ProviderId;
23
+ /** The provider's short name from `status`, else its id. */
24
+ export declare const shortName: (status: StatusValue | undefined, provider: UsageProviderId) => string;
25
+ /** Whether the Host reports usage for this provider. */
26
+ export declare const isMetered: (status: StatusValue | undefined, provider: UsageProviderId) => boolean;
27
+ /** `status` response. */
28
+ export interface StatusValue {
29
+ usageRevision: number;
30
+ /** Every key provider with whether a key is stored; configured ones also appear in `providers`. Absent from a Host that predates key providers. */
31
+ keyProviders?: KeyProviderView[];
32
+ providers: Partial<Record<UsageProviderId, ProviderStatus>>;
33
+ }
34
+ /** One model row of `providerSettings`. */
35
+ export interface ProviderModelRow {
36
+ id: string;
37
+ name: string;
38
+ contextWindow?: number;
39
+ efforts: {
40
+ id: string;
41
+ name: string;
42
+ }[];
43
+ configured?: string;
44
+ }
45
+ /** `providerSettings` response. */
46
+ export interface ProviderSettingsValue {
47
+ provider: ProviderId;
48
+ settings: ProviderPreferences;
49
+ models: ProviderModelRow[];
50
+ tools: readonly SubscriptionTool[];
51
+ accounts: ProviderAccountCatalog[];
52
+ }
53
+ /** The Host's business error text. */
54
+ export declare class SubscriptionsAuthError extends Error {
55
+ }
56
+ /** Typed endpoint table: payload in, value out. */
57
+ export interface Endpoints {
58
+ status: [{
59
+ sessionId?: string;
60
+ }, StatusValue];
61
+ usage: [{
62
+ provider: UsageProviderId;
63
+ account: string;
64
+ reason: UsageReadReason;
65
+ }, DisplayUsage];
66
+ keysSet: [{
67
+ provider: KeyProviderId;
68
+ key: string;
69
+ }, {
70
+ ok: true;
71
+ }];
72
+ keysRemove: [{
73
+ provider: KeyProviderId;
74
+ }, {
75
+ ok: true;
76
+ }];
77
+ login: [{
78
+ provider: ProviderId;
79
+ method?: 'oauth' | 'keychain';
80
+ }, {
81
+ authorizeUrl: string;
82
+ userCode?: string;
83
+ }];
84
+ manual: [{
85
+ provider: ProviderId;
86
+ input: string;
87
+ }, {
88
+ ok: true;
89
+ }];
90
+ cancel: [{
91
+ provider: ProviderId;
92
+ }, {
93
+ ok: true;
94
+ }];
95
+ logout: [{
96
+ provider: ProviderId;
97
+ account: string;
98
+ }, {
99
+ ok: true;
100
+ }];
101
+ setDefault: [{
102
+ provider: ProviderId;
103
+ account: string;
104
+ }, {
105
+ ok: true;
106
+ }];
107
+ providerSettings: [{
108
+ provider: ProviderId;
109
+ force?: boolean;
110
+ }, ProviderSettingsValue];
111
+ setProviderSettings: [{
112
+ provider: ProviderId;
113
+ settings: ProviderPreferences;
114
+ }, {
115
+ ok: true;
116
+ }];
117
+ setModelDefault: [{
118
+ provider: ProviderId;
119
+ model: string;
120
+ effort?: string;
121
+ }, {
122
+ ok: true;
123
+ }];
124
+ proxyGet: [Record<string, never>, ProxyConfigView];
125
+ proxySet: [ProxyInput, ProxyConfigView];
126
+ proxyTest: [{
127
+ url?: string;
128
+ proxy?: {
129
+ url: string;
130
+ };
131
+ }, ProxyTestResult];
132
+ image: [Record<string, unknown>, {
133
+ mediaType: string;
134
+ dataBase64: string;
135
+ }];
136
+ video: [{
137
+ name: string;
138
+ }, {
139
+ mediaType: string;
140
+ dataBase64: string;
141
+ }];
142
+ speed: [{
143
+ sessionId: string;
144
+ model?: string;
145
+ }, {
146
+ tier: 'standard' | 'fast';
147
+ fastModels: string[];
148
+ }];
149
+ setSpeed: [{
150
+ sessionId: string;
151
+ tier: 'standard' | 'fast';
152
+ model?: string;
153
+ }, {
154
+ ok: true;
155
+ }];
156
+ }
157
+ /**
158
+ * Call one endpoint and unwrap its business result.
159
+ * @throws SubscriptionsAuthError with the Host's message on transport or business failure.
160
+ */
161
+ export declare function call<E extends keyof Endpoints>(rpc: ConnectionHandle['rpc'], endpoint: E, payload: Endpoints[E][0]): Promise<Endpoints[E][1]>;
@@ -0,0 +1,31 @@
1
+ /** Every provider this plugin can route, in Models-page order. Names and metering come from `status`. */
2
+ export const PROVIDERS = ['codex', 'claude', 'grok', 'copilot', 'antigravity'];
3
+ /** Providers that read usage with a pasted key, in Usage-page order. Mirrors the Host's `KEY_PROVIDER_IDS`. */
4
+ export const KEY_PROVIDERS = ['zai', 'ollama-cloud', 'kimi'];
5
+ export const USAGE_PROVIDERS = [...PROVIDERS, ...KEY_PROVIDERS];
6
+ export const isUsageProviderId = (value) => typeof value === 'string' && USAGE_PROVIDERS.includes(value);
7
+ export const isProviderId = (value) => typeof value === 'string' && PROVIDERS.includes(value);
8
+ /** The provider's short name from `status`, else its id. */
9
+ export const shortName = (status, provider) => status?.providers[provider]?.shortName ?? provider;
10
+ /** Whether the Host reports usage for this provider. */
11
+ export const isMetered = (status, provider) => status?.providers[provider]?.metered === true;
12
+ /** The Host's business error text. */
13
+ export class SubscriptionsAuthError extends Error {
14
+ }
15
+ /**
16
+ * Call one endpoint and unwrap its business result.
17
+ * @throws SubscriptionsAuthError with the Host's message on transport or business failure.
18
+ */
19
+ export async function call(rpc, endpoint, payload) {
20
+ let result;
21
+ try {
22
+ result = await rpc.call('/api', `subscriptions-auth.${endpoint}`, payload);
23
+ }
24
+ catch (error) {
25
+ throw new SubscriptionsAuthError(error instanceof Error ? error.message : String(error));
26
+ }
27
+ if (!result.ok)
28
+ throw new SubscriptionsAuthError(result.error.message);
29
+ // The Host route owns this shape; the endpoint table mirrors it.
30
+ return result.value;
31
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * One shared store per page for login status and usage, read by the Models
3
+ * cards, the composer gauge, and its popover. It never fetches on its own
4
+ * beyond the policy below; the Host decides whether a read reaches a provider.
5
+ *
6
+ * - `status` is read on start, every 5 minutes while the page is visible, after
7
+ * a sign-in or account change, and after each turn the gauge observes.
8
+ * - `usage` is re-read when `status.usageRevision` moves (a reply carried new
9
+ * headers, or another surface fetched), when the popover opens (`open`), on
10
+ * the Refresh button (`manual`), and every 5 minutes while visible (`poll`).
11
+ */
12
+ import type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client';
13
+ import type { AccountStatus, DisplayUsage, UsageProviderId as ProviderId, ProviderStatus, StatusValue, UsageReadReason } from './rpc.js';
14
+ export declare const POLL_MS: number;
15
+ /** Usage of one account, or the failure that left it empty. */
16
+ export type UsageEntry = {
17
+ kind: 'loading';
18
+ } | {
19
+ kind: 'ready';
20
+ usage: DisplayUsage;
21
+ receivedAt: number;
22
+ } | {
23
+ kind: 'error';
24
+ message: string;
25
+ };
26
+ export interface StoreSnapshot {
27
+ status: StatusValue | undefined;
28
+ statusError: string | undefined;
29
+ /** Keyed `${provider}/${account}`. */
30
+ usage: ReadonlyMap<string, UsageEntry>;
31
+ /** Accounts with a read in flight, keyed like `usage`. */
32
+ refreshing: ReadonlySet<string>;
33
+ }
34
+ export declare const usageKey: (provider: ProviderId, account: string) => string;
35
+ export declare class SubscriptionsStore {
36
+ private readonly rpc;
37
+ private snapshot;
38
+ private readonly listeners;
39
+ private lastRevision;
40
+ private statusInFlight;
41
+ private timer;
42
+ /** The session the gauge is showing, so `status` reports its serving account. */
43
+ private sessionId;
44
+ constructor(rpc: ConnectionHandle['rpc']);
45
+ getSnapshot: () => StoreSnapshot;
46
+ subscribe: (listener: () => void) => (() => void);
47
+ /** Report the session whose serving account the `status` read should name. */
48
+ focusSession(sessionId: string | undefined): void;
49
+ /** Re-read login status; usage follows when the Host's revision moved. */
50
+ refreshStatus(): Promise<void>;
51
+ /** Read every signed-in account's usage under one reason. */
52
+ refreshUsage(reason: UsageReadReason, only?: ProviderId): Promise<void>;
53
+ private readStatus;
54
+ private readUsage;
55
+ private setUsage;
56
+ private publish;
57
+ private readonly onVisibility;
58
+ private start;
59
+ private stop;
60
+ }
61
+ /** The account a provider is using now: the one that served last, else the default, else the first. */
62
+ export declare function activeAccountKey(status: ProviderStatus | undefined): string | undefined;
63
+ /** The active account shown in full, and the others that fold behind one row. */
64
+ export declare function splitAccounts(status: ProviderStatus): {
65
+ active: AccountStatus;
66
+ rest: AccountStatus[];
67
+ } | undefined;