dsh-plugin-subscription-usage 0.0.0-stage → 0.2.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 (169) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +50 -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 +13399 -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 +16 -0
  124. package/lib/tools/registration.js +25 -0
  125. package/lib/tools/usage.d.ts +62 -0
  126. package/lib/tools/usage.js +89 -0
  127. package/lib/tools/video-generate.d.ts +89 -0
  128. package/lib/tools/video-generate.js +257 -0
  129. package/lib/tools/x-search.d.ts +58 -0
  130. package/lib/tools/x-search.js +197 -0
  131. package/lib/translate/anthropic.d.ts +180 -0
  132. package/lib/translate/anthropic.js +534 -0
  133. package/lib/translate/antigravity-images.d.ts +11 -0
  134. package/lib/translate/antigravity-images.js +62 -0
  135. package/lib/translate/antigravity-schema.d.ts +4 -0
  136. package/lib/translate/antigravity-schema.js +145 -0
  137. package/lib/translate/antigravity-thinking.d.ts +5 -0
  138. package/lib/translate/antigravity-thinking.js +44 -0
  139. package/lib/translate/antigravity.d.ts +120 -0
  140. package/lib/translate/antigravity.js +412 -0
  141. package/lib/translate/chat-completions.d.ts +120 -0
  142. package/lib/translate/chat-completions.js +382 -0
  143. package/lib/translate/resolved.d.ts +98 -0
  144. package/lib/translate/resolved.js +186 -0
  145. package/lib/translate/responses.d.ts +192 -0
  146. package/lib/translate/responses.js +443 -0
  147. package/lib/translate/sse.d.ts +21 -0
  148. package/lib/translate/sse.js +62 -0
  149. package/lib/usage/headers.d.ts +21 -0
  150. package/lib/usage/headers.js +64 -0
  151. package/lib/usage/pace.d.ts +53 -0
  152. package/lib/usage/pace.js +48 -0
  153. package/lib/usage/service.d.ts +64 -0
  154. package/lib/usage/service.js +104 -0
  155. package/lib/usage-providers/common.d.ts +23 -0
  156. package/lib/usage-providers/common.js +28 -0
  157. package/lib/usage-providers/index.d.ts +42 -0
  158. package/lib/usage-providers/index.js +71 -0
  159. package/lib/usage-providers/keys.d.ts +11 -0
  160. package/lib/usage-providers/keys.js +52 -0
  161. package/lib/usage-providers/kimi.d.ts +4 -0
  162. package/lib/usage-providers/kimi.js +52 -0
  163. package/lib/usage-providers/ollama-cloud.d.ts +4 -0
  164. package/lib/usage-providers/ollama-cloud.js +42 -0
  165. package/lib/usage-providers/zai.d.ts +5 -0
  166. package/lib/usage-providers/zai.js +53 -0
  167. package/locale/en.json +6 -0
  168. package/locale/zh.json +6 -0
  169. package/package.json +115 -4
@@ -0,0 +1,93 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Frame-wide pace notice in `shell.overlay`: one line when a window will
4
+ * empty before it resets, or has run out. Each crossing shows once and stays
5
+ * dismissed for that window, keyed by provider, account, window and reset.
6
+ */
7
+ import { useEffect, useMemo, useState } from 'react';
8
+ import { pace } from '../usage/pace.js';
9
+ import { isMetered, USAGE_PROVIDERS, shortName } from './rpc.js';
10
+ import { usageKey } from './store.js';
11
+ import { clockTime, Icon, tf, windowLabel } from './meter.js';
12
+ import { useNow } from './hooks.js';
13
+ import { usePrefs, useStore } from './context.js';
14
+ const DISMISSED_KEY = 'dsh-plugin-subscription-usage.dismissed-notices';
15
+ /** Dismissals survive reloads; one entry per window crossing. */
16
+ function readDismissed() {
17
+ try {
18
+ const raw = JSON.parse(localStorage.getItem(DISMISSED_KEY) ?? '[]');
19
+ return new Set(Array.isArray(raw) ? raw.filter((id) => typeof id === 'string') : []);
20
+ }
21
+ catch {
22
+ return new Set();
23
+ }
24
+ }
25
+ /**
26
+ * The most urgent notice across the serving accounts, or undefined.
27
+ * Only serving or default accounts warn; an idle account's pace is not the user's problem yet.
28
+ */
29
+ export function currentNotice(t, snapshot, now, dismissed) {
30
+ const candidates = [];
31
+ for (const provider of USAGE_PROVIDERS) {
32
+ if (!isMetered(snapshot.status, provider))
33
+ continue;
34
+ const status = snapshot.status?.providers[provider];
35
+ if (status === undefined)
36
+ continue;
37
+ for (const account of status.accounts) {
38
+ if (account.key !== status.servingAccount && !account.isDefault)
39
+ continue;
40
+ const entry = snapshot.usage.get(usageKey(provider, account.key));
41
+ if (entry?.kind !== 'ready')
42
+ continue;
43
+ for (const window of entry.usage.windows ?? []) {
44
+ const notice = noticeFor(t, shortName(snapshot.status, provider), provider, account.key, window, now);
45
+ if (notice !== undefined && !dismissed.has(notice.id))
46
+ candidates.push(notice);
47
+ }
48
+ }
49
+ }
50
+ candidates.sort((a, b) => a.rank - b.rank);
51
+ const top = candidates[0];
52
+ return top === undefined ? undefined : { id: top.id, tone: top.tone, text: top.text };
53
+ }
54
+ function noticeFor(t, name, provider, account, window, now) {
55
+ const p = pace(window, now);
56
+ const id = `${provider}/${account}/${window.kind}/${window.scope ?? ''}/${window.resetsAt ?? ''}`;
57
+ const label = windowLabel(t, window);
58
+ if (p.state === 'out')
59
+ return { id: `${id}/out`, tone: 'error', rank: 0, text: tf(t, 'noticeOut', { provider: name, window: label }) };
60
+ if (p.state === 'over') {
61
+ return { id: `${id}/over`, tone: 'warn', rank: p.emptyAt - now, text: tf(t, 'noticeEmpty', { provider: name, window: label, time: clockTime(p.emptyAt, now) }) };
62
+ }
63
+ return undefined;
64
+ }
65
+ export function Notice(props) {
66
+ const { store, prefs, t } = props;
67
+ if (store === undefined || prefs === undefined || t === undefined)
68
+ return null;
69
+ return _jsx(NoticeBody, { store: store, prefs: prefs, t: t });
70
+ }
71
+ function NoticeBody({ store, prefs, t }) {
72
+ const snapshot = useStore(store);
73
+ const view = usePrefs(prefs);
74
+ const now = useNow(60_000);
75
+ const [dismissed, setDismissed] = useState(readDismissed);
76
+ const notice = useMemo(() => view.paceWarnings ? currentNotice(t, snapshot, now, dismissed) : undefined, [view.paceWarnings, t, snapshot, now, dismissed]);
77
+ useEffect(() => {
78
+ const onStorage = (event) => { if (event.key === DISMISSED_KEY)
79
+ setDismissed(readDismissed()); };
80
+ window.addEventListener('storage', onStorage);
81
+ return () => { window.removeEventListener('storage', onStorage); };
82
+ }, []);
83
+ if (notice === undefined)
84
+ return null;
85
+ const dismiss = () => {
86
+ const next = new Set(dismissed);
87
+ next.add(notice.id);
88
+ // Old windows never return; keep the list short.
89
+ localStorage.setItem(DISMISSED_KEY, JSON.stringify([...next].slice(-50)));
90
+ setDismissed(next);
91
+ };
92
+ return (_jsx("div", { className: "dsm-notice-host", style: { pointerEvents: 'auto' }, children: _jsxs("div", { className: `dsm-notice${notice.tone === 'error' ? ' dsm-err' : ''}`, role: "status", "aria-live": "polite", children: [_jsx(Icon, { name: "alert" }), _jsx("span", { children: notice.text }), _jsx("button", { type: "button", className: "dsm-btn dsm-icon", "aria-label": t('dismiss'), title: t('dismiss'), onClick: dismiss, children: _jsx(Icon, { name: "x" }) })] }) }));
93
+ }
@@ -0,0 +1,35 @@
1
+ import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import type { AccountStatus, UsageProviderId, ProviderStatus } from './rpc.js';
3
+ import type { StoreSnapshot } from './store.js';
4
+ import type { T } from './meter.js';
5
+ import type { SurfaceInjected } from './context.js';
6
+ import type { UsagePreferencesView } from './prefs.js';
7
+ export type ProviderCardProps = PropsRuntime<'settings.models.provider-card'> & Partial<SurfaceInjected>;
8
+ export declare function ProviderCard(props: ProviderCardProps): import("react").JSX.Element | null;
9
+ /** Plan tag, default star, and serving bolt. */
10
+ export declare function AccountTags({ t, account, status }: {
11
+ t: T;
12
+ account: AccountStatus;
13
+ status: ProviderStatus;
14
+ }): import("react").JSX.Element;
15
+ /** "live", "4m ago", or a stale warning, at the end of an account header. */
16
+ export declare function Freshness({ t, snapshot, provider, account, now }: {
17
+ t: T;
18
+ snapshot: StoreSnapshot;
19
+ provider: UsageProviderId;
20
+ account: string;
21
+ now: number;
22
+ }): import("react").JSX.Element | null;
23
+ /** One account: header, then its windows on the shared grid. */
24
+ export declare function Account({ t, provider, account, status, snapshot, prefs, now, onSignIn, compact, hideUsage }: {
25
+ t: T;
26
+ provider: UsageProviderId;
27
+ account: AccountStatus;
28
+ status: ProviderStatus;
29
+ snapshot: StoreSnapshot;
30
+ prefs: UsagePreferencesView;
31
+ now: number;
32
+ onSignIn?: () => void;
33
+ compact?: boolean;
34
+ hideUsage?: boolean;
35
+ }): import("react").JSX.Element;
@@ -0,0 +1,240 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * The body of one subscription row on the Models page, rendered in the
4
+ * `settings.models.provider-card` slot. The active account shows in full;
5
+ * other accounts fold behind one summary row; Manage opens Accounts, Models
6
+ * and Tools inline.
7
+ */
8
+ import { useEffect, useRef, useState } from 'react';
9
+ import { call, isMetered, isProviderId } from './rpc.js';
10
+ import { splitAccounts, usageKey } from './store.js';
11
+ import { Icon, shortDuration, shownWindows, tf, WindowRow } from './meter.js';
12
+ import { useNow } from './hooks.js';
13
+ import { usePrefs, useStore } from './context.js';
14
+ /** How often a busy sign-in re-reads status until the account lands. */
15
+ const SIGN_IN_POLL_MS = 2000;
16
+ export function ProviderCard(props) {
17
+ const { store, prefs, rpc, t } = props;
18
+ const provider = props.provider.provider;
19
+ if (store === undefined || prefs === undefined || rpc === undefined || t === undefined || !isProviderId(provider))
20
+ return null;
21
+ return _jsx(CardBody, { provider: provider, store: store, prefs: prefs, rpc: rpc, t: t });
22
+ }
23
+ function CardBody({ provider, store, prefs, rpc, t }) {
24
+ const snapshot = useStore(store);
25
+ const prefView = usePrefs(prefs);
26
+ const now = useNow(30_000);
27
+ const status = snapshot.status?.providers[provider];
28
+ const [mode, setMode] = useState({ kind: 'view' });
29
+ const [error, setError] = useState();
30
+ const [moreOpen, setMoreOpen] = useState(false);
31
+ // A busy sign-in polls status until the account lands or the attempt ends.
32
+ const busy = status?.busy === true;
33
+ useEffect(() => {
34
+ if (mode.kind !== 'signin' && !busy)
35
+ return;
36
+ const timer = setInterval(() => { void store.refreshStatus(); }, SIGN_IN_POLL_MS);
37
+ return () => { clearInterval(timer); };
38
+ }, [mode.kind, busy, store]);
39
+ const accountCount = status?.accounts.length ?? 0;
40
+ const startCount = useRef(accountCount);
41
+ useEffect(() => {
42
+ if (mode.kind === 'signin' && !busy && accountCount !== startCount.current)
43
+ setMode({ kind: 'view' });
44
+ }, [mode.kind, busy, accountCount]);
45
+ useEffect(() => {
46
+ if (status?.detail !== undefined)
47
+ setError(status.detail);
48
+ }, [status?.detail]);
49
+ const signIn = async (method) => {
50
+ setError(undefined);
51
+ startCount.current = accountCount;
52
+ try {
53
+ const response = await call(rpc, 'login', { provider, ...method === undefined ? {} : { method } });
54
+ if (response.authorizeUrl === '') {
55
+ await store.refreshStatus();
56
+ return;
57
+ }
58
+ if (response.userCode !== undefined && response.userCode !== '') {
59
+ setMode({ kind: 'signin', userCode: response.userCode, url: response.authorizeUrl });
60
+ }
61
+ else {
62
+ window.open(response.authorizeUrl, '_blank', 'noopener');
63
+ setMode({ kind: 'signin' });
64
+ }
65
+ void store.refreshStatus();
66
+ }
67
+ catch (failure) {
68
+ setError(failure instanceof Error ? failure.message : String(failure));
69
+ }
70
+ };
71
+ const cancel = async () => {
72
+ setMode({ kind: 'view' });
73
+ await call(rpc, 'cancel', { provider }).catch(() => undefined);
74
+ await store.refreshStatus();
75
+ };
76
+ if (status === undefined) {
77
+ return snapshot.statusError === undefined ? null : _jsx("div", { className: "dsm-sub", children: _jsx("span", { className: "dsm-card-error", children: snapshot.statusError }) });
78
+ }
79
+ if (mode.kind === 'signin') {
80
+ return (_jsxs("div", { className: "dsm-sub", children: [_jsx(SignIn, { t: t, provider: provider, mode: mode, rpc: rpc, onCancel: () => { void cancel(); }, onError: setError }), error === undefined ? null : _jsx("span", { className: "dsm-card-error", role: "alert", children: error })] }));
81
+ }
82
+ if (status.accounts.length === 0) {
83
+ return (_jsxs("div", { className: "dsm-sub", children: [_jsxs("div", { className: "dsm-foot", children: [_jsx("button", { type: "button", className: "dsm-btn dsm-primary", onClick: () => { void signIn(); }, children: t('signIn') }), provider === 'claude'
84
+ ? _jsx("button", { type: "button", className: "dsm-btn dsm-ghost", onClick: () => { void signIn('keychain'); }, children: t('claudeImport') })
85
+ : _jsx("span", { className: "dsm-muted", children: t('noApiKey') })] }), error === undefined ? null : _jsx("span", { className: "dsm-card-error", role: "alert", children: error })] }));
86
+ }
87
+ const split = splitAccounts(status);
88
+ if (split === undefined)
89
+ return null;
90
+ const { active, rest } = split;
91
+ return (_jsxs("div", { className: "dsm-sub", children: [_jsx(Account, { t: t, provider: provider, account: active, status: status, snapshot: snapshot, prefs: prefView, now: now, hideUsage: true, onSignIn: () => { void signIn(); } }), rest.length === 0 ? null : (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "dsm-fold", "aria-expanded": moreOpen, onClick: () => { setMoreOpen(open => !open); }, children: _jsxs("span", { className: "dsm-fold-name", children: [_jsx(Icon, { name: "right", size: 12, className: "dsm-chev" }), _jsx("span", { children: rest.length === 1 ? label(rest[0]) : tf(t, 'moreAccounts', { count: rest.length }) }), rest.length === 1 ? _jsx(AccountTags, { t: t, account: rest[0], status: status }) : null] }) }), moreOpen ? (_jsx("div", { className: "dsm-fold-body", children: rest.map(account => _jsx(Account, { t: t, provider: provider, account: account, status: status, snapshot: snapshot, prefs: prefView, now: now, hideUsage: true, onSignIn: () => { void signIn(); } }, account.key)) })) : null] })), mode.kind === 'manage'
92
+ ? _jsx(ManagePanel, { t: t, provider: provider, status: status, rpc: rpc, onClose: () => { setMode({ kind: 'view' }); void store.refreshStatus(); }, onAddAccount: () => { void signIn(); } })
93
+ : (_jsxs("div", { className: "dsm-foot", children: [_jsx("button", { type: "button", className: "dsm-btn", onClick: () => { setMode({ kind: 'manage' }); }, children: t('manage') }), _jsx(PickerCount, { t: t, status: status })] })), error === undefined ? null : _jsx("span", { className: "dsm-card-error", role: "alert", children: error })] }));
94
+ }
95
+ const label = (account) => account.account ?? account.key;
96
+ function usageOf(snapshot, provider, account) {
97
+ const entry = snapshot.usage.get(usageKey(provider, account));
98
+ return entry?.kind === 'ready' ? entry.usage : undefined;
99
+ }
100
+ /** Plan tag, default star, and serving bolt. */
101
+ export function AccountTags({ t, account, status }) {
102
+ return (_jsxs(_Fragment, { children: [account.plan === undefined ? null : _jsx("span", { className: "dsm-tag", children: account.plan }), account.isDefault ? _jsx("span", { className: "dsm-glyph dsm-star", title: t('defaultAccount'), "aria-label": t('defaultAccount'), children: _jsx(Icon, { name: "star", size: 12 }) }) : null, status.servingAccount === account.key
103
+ ? _jsx("span", { className: "dsm-glyph dsm-live", title: t('servingAccount'), "aria-label": t('servingAccount'), children: _jsx(Icon, { name: "bolt", size: 12 }) })
104
+ : null] }));
105
+ }
106
+ /** "live", "4m ago", or a stale warning, at the end of an account header. */
107
+ export function Freshness({ t, snapshot, provider, account, now }) {
108
+ if (!isMetered(snapshot.status, provider))
109
+ return _jsx("span", { className: "dsm-fresh", title: t('noUsageDataTitle'), children: t('noUsageData') });
110
+ const entry = snapshot.usage.get(usageKey(provider, account));
111
+ if (entry === undefined || entry.kind === 'loading')
112
+ return null;
113
+ if (entry.kind === 'error')
114
+ return _jsxs("span", { className: "dsm-fresh dsm-stale", title: entry.message, children: [_jsx(Icon, { name: "alert", size: 12 }), t('usageUnavailable')] });
115
+ const usage = entry.usage;
116
+ const fetchedAt = usage.fetchedAt ?? entry.receivedAt;
117
+ if (usage.refreshFailed === true || usage.stale === true && usage.retryAt !== undefined) {
118
+ const when = usage.retryAt === undefined ? '' : tf(t, 'resetsIn', { time: shortDuration(Math.max(0, usage.retryAt - now)) });
119
+ return (_jsxs("span", { className: "dsm-fresh dsm-stale", title: tf(t, 'staleTitle', { when }), children: [_jsx(Icon, { name: "alert", size: 12 }), tf(t, 'staleShort', { time: shortDuration(Math.max(0, now - fetchedAt)) })] }));
120
+ }
121
+ if (usage.source === 'live' && now - fetchedAt < 10 * 60_000) {
122
+ return _jsxs("span", { className: "dsm-fresh", title: t('liveTitle'), children: [_jsx("span", { className: "dsm-live-dot" }), t('live')] });
123
+ }
124
+ return _jsx("span", { className: "dsm-fresh", children: tf(t, 'agoShort', { time: shortDuration(Math.max(0, now - fetchedAt)) }) });
125
+ }
126
+ /** One account: header, then its windows on the shared grid. */
127
+ export function Account({ t, provider, account, status, snapshot, prefs, now, onSignIn, compact, hideUsage }) {
128
+ const usage = usageOf(snapshot, provider, account.key);
129
+ const expired = usage === undefined && snapshot.usage.get(usageKey(provider, account.key))?.kind === 'error'
130
+ && account.expiresAt !== undefined && account.expiresAt < now;
131
+ return (_jsxs("div", { className: compact === true ? 'dsm-pacct' : 'dsm-acct', children: [_jsxs("div", { className: "dsm-acct-head", children: [_jsx("span", { className: "dsm-acct-name", children: label(account) }), _jsx(AccountTags, { t: t, account: account, status: status }), hideUsage === true ? null : _jsx(Freshness, { t: t, snapshot: snapshot, provider: provider, account: account.key, now: now })] }), expired && onSignIn !== undefined ? (_jsxs("div", { className: "dsm-alert", role: "alert", children: [_jsx(Icon, { name: "alert" }), _jsx("span", { children: t('signInExpired') }), _jsx("button", { type: "button", className: "dsm-btn dsm-primary", onClick: onSignIn, children: t('signInAgain') })] })) : hideUsage === true || usage?.windows === undefined || usage.windows.length === 0 ? null : (_jsx("div", { className: "dsm-wins", children: shownWindows(usage.windows).map((window, index) => _jsx(WindowRow, { t: t, window: window, display: prefs.display, now: now }, index)) }))] }));
132
+ }
133
+ function PickerCount({ t, status }) {
134
+ const total = status.catalogSize;
135
+ if (total === undefined)
136
+ return null;
137
+ const visible = status.visibleModels === undefined ? total : status.visibleModels.length;
138
+ return _jsx("span", { className: "dsm-muted dsm-num", title: t('modelsCountTitle'), children: tf(t, 'modelsCount', { visible, total }) });
139
+ }
140
+ function SignIn({ t, provider, mode, rpc, onCancel, onError }) {
141
+ const [draft, setDraft] = useState('');
142
+ if (mode.userCode !== undefined) {
143
+ return (_jsxs("div", { className: "dsm-signin", children: [_jsxs("ol", { className: "dsm-steps", children: [_jsxs("li", { "data-s": "done", children: [_jsx(Icon, { name: "check", size: 12 }), t('stepCodeIssued')] }), _jsxs("li", { "data-s": "now", children: [_jsx("span", { className: "dsm-spin" }), t('stepWaitingGithub')] })] }), _jsxs("div", { className: "dsm-foot", style: { flexWrap: 'wrap' }, children: [_jsx("span", { className: "dsm-code", children: mode.userCode }), _jsx("button", { type: "button", className: "dsm-btn dsm-icon", "aria-label": t('copyCode'), title: t('copyCode'), onClick: () => { void navigator.clipboard?.writeText(mode.userCode ?? ''); }, children: _jsx(Icon, { name: "copy" }) }), mode.url === undefined ? null : (_jsxs("a", { className: "dsm-btn", href: mode.url, target: "_blank", rel: "noopener noreferrer", children: [new URL(mode.url).host + new URL(mode.url).pathname, _jsx(Icon, { name: "ext", size: 12 })] })), _jsx("button", { type: "button", className: "dsm-btn dsm-ghost", style: { marginLeft: 'auto' }, onClick: onCancel, children: t('cancel') })] })] }));
144
+ }
145
+ const submit = async () => {
146
+ const input = draft.trim();
147
+ if (input === '')
148
+ return;
149
+ onError(undefined);
150
+ try {
151
+ await call(rpc, 'manual', { provider, input });
152
+ setDraft('');
153
+ }
154
+ catch (failure) {
155
+ onError(failure instanceof Error ? failure.message : String(failure));
156
+ }
157
+ };
158
+ return (_jsxs("div", { className: "dsm-signin", children: [_jsxs("ol", { className: "dsm-steps", children: [_jsxs("li", { "data-s": "done", children: [_jsx(Icon, { name: "check", size: 12 }), t('stepTabOpened')] }), _jsxs("li", { "data-s": "now", children: [_jsx("span", { className: "dsm-spin" }), t('stepApprove')] }), _jsxs("li", { children: [_jsx(Icon, { name: "check", size: 12 }), t('stepLoadModels')] })] }), _jsxs("div", { className: "dsm-foot", style: { alignItems: 'flex-start' }, children: [_jsxs("details", { className: "dsm-fallback", style: { flex: 1 }, children: [_jsxs("summary", { children: [_jsx(Icon, { name: "down", size: 12 }), t('pasteRedirect')] }), _jsxs("form", { className: "dsm-foot", onSubmit: event => { event.preventDefault(); void submit(); }, children: [_jsx("input", { className: "dsm-input", value: draft, placeholder: t('pastePlaceholder'), onChange: event => { setDraft(event.target.value); } }), _jsx("button", { type: "submit", className: "dsm-btn", disabled: draft.trim() === '', children: t('continue') })] })] }), _jsx("button", { type: "button", className: "dsm-btn dsm-ghost", onClick: onCancel, children: t('cancel') })] })] }));
159
+ }
160
+ const TOOL_LABEL = { web_search: 'toolWebSearch', image_generate: 'toolImage', video_generate: 'toolVideo', x_search: 'toolXSearch' };
161
+ function ManagePanel({ t, provider, status, rpc, onClose, onAddAccount }) {
162
+ const [tab, setTab] = useState('accounts');
163
+ const [loaded, setLoaded] = useState();
164
+ const [draft, setDraft] = useState();
165
+ const [efforts, setEfforts] = useState({});
166
+ const [search, setSearch] = useState('');
167
+ const [saving, setSaving] = useState(false);
168
+ const [error, setError] = useState();
169
+ useEffect(() => {
170
+ let live = true;
171
+ call(rpc, 'providerSettings', { provider }).then(value => {
172
+ if (!live)
173
+ return;
174
+ setLoaded(value);
175
+ setDraft(value.settings);
176
+ }, (failure) => { if (live)
177
+ setError(tf(t, 'loadFailed', { message: failure instanceof Error ? failure.message : String(failure) })); });
178
+ return () => { live = false; };
179
+ }, [rpc, provider, t]);
180
+ const accountAction = async (endpoint, account) => {
181
+ setError(undefined);
182
+ try {
183
+ await call(rpc, endpoint, { provider, account });
184
+ onClose();
185
+ }
186
+ catch (failure) {
187
+ setError(failure instanceof Error ? failure.message : String(failure));
188
+ }
189
+ };
190
+ const save = async () => {
191
+ if (draft === undefined || loaded === undefined)
192
+ return;
193
+ setSaving(true);
194
+ setError(undefined);
195
+ try {
196
+ await call(rpc, 'setProviderSettings', { provider, settings: draft });
197
+ for (const [model, effort] of Object.entries(efforts)) {
198
+ await call(rpc, 'setModelDefault', { provider, model, ...effort === undefined ? {} : { effort } });
199
+ }
200
+ onClose();
201
+ }
202
+ catch (failure) {
203
+ setError(tf(t, 'saveFailed', { message: failure instanceof Error ? failure.message : String(failure) }));
204
+ }
205
+ finally {
206
+ setSaving(false);
207
+ }
208
+ };
209
+ const models = loaded?.models ?? [];
210
+ const allIds = models.map(m => m.id);
211
+ const visible = new Set(draft?.visibleModels ?? allIds);
212
+ const automatic = draft?.visibleModels === undefined;
213
+ const setVisible = (ids) => {
214
+ setDraft(current => {
215
+ const next = { ...current };
216
+ if (ids === undefined)
217
+ delete next.visibleModels;
218
+ else
219
+ next.visibleModels = ids;
220
+ return next;
221
+ });
222
+ };
223
+ const accountPrefs = draft?.accounts ?? {};
224
+ const setPool = (account, enabled) => {
225
+ setDraft(current => ({ ...current, accounts: { ...current?.accounts, [account]: { ...current?.accounts?.[account], poolEnabled: enabled } } }));
226
+ };
227
+ const toolsOn = draft?.tools ?? {};
228
+ const shown = models.filter(m => search === '' || m.id.toLowerCase().includes(search.toLowerCase()) || m.name.toLowerCase().includes(search.toLowerCase()));
229
+ return (_jsxs("div", { className: "dsm-panel", children: [_jsx("div", { className: "dsm-tabs", role: "tablist", children: ['accounts', 'models', 'tools'].filter(id => id !== 'tools' || (loaded?.tools.length ?? 0) > 0).map(id => (_jsx("button", { type: "button", role: "tab", className: "dsm-tab", "aria-selected": tab === id, onClick: () => { setTab(id); }, children: t(id === 'accounts' ? 'tabAccounts' : id === 'models' ? 'tabModels' : 'tabTools') }, id))) }), tab === 'accounts' ? (_jsxs(_Fragment, { children: [status.accounts.map(account => (_jsxs("div", { className: "dsm-row", children: [_jsxs("span", { className: "dsm-grow", children: [_jsx("span", { className: "dsm-ellipsis", children: label(account) }), _jsx(AccountTags, { t: t, account: account, status: status })] }), status.accounts.length > 1 ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "dsm-note", children: t('pool') }), _jsx("button", { type: "button", role: "switch", className: "dsm-switch", "aria-checked": accountPrefs[account.key]?.poolEnabled !== false, "aria-label": tf(t, 'poolInclude', { account: label(account) }), onClick: () => { setPool(account.key, accountPrefs[account.key]?.poolEnabled === false); } })] })) : null, account.isDefault ? null : (_jsx("button", { type: "button", className: "dsm-btn dsm-icon", title: t('makeDefault'), "aria-label": t('makeDefault'), onClick: () => { void accountAction('setDefault', account.key); }, children: _jsx(Icon, { name: "star" }) })), _jsx("button", { type: "button", className: "dsm-btn dsm-danger", onClick: () => { void accountAction('logout', account.key); }, children: t('signOut') })] }, account.key))), _jsx("div", { className: "dsm-row", children: _jsxs("button", { type: "button", className: "dsm-btn", onClick: onAddAccount, children: ["+ ", t('addAccount')] }) })] })) : null, tab === 'models' ? (_jsxs(_Fragment, { children: [_jsx("input", { className: "dsm-input", value: search, placeholder: tf(t, 'searchModels', { count: models.length }), onChange: event => { setSearch(event.target.value); } }), _jsxs("div", { className: "dsm-row", children: [_jsx("span", { className: "dsm-grow", children: t('autoShowNew') }), _jsx("button", { type: "button", role: "switch", className: "dsm-switch", "aria-checked": automatic, "aria-label": t('autoShowNew'), onClick: () => { setVisible(automatic ? allIds : undefined); } })] }), _jsxs("div", { className: "dsm-row", children: [_jsx("button", { type: "button", className: "dsm-btn", onClick: () => { setVisible(allIds); }, children: t('selectAll') }), _jsx("button", { type: "button", className: "dsm-btn", onClick: () => { setVisible([]); }, children: t('deselectAll') })] }), _jsx("div", { className: "dsm-list", children: shown.map(model => (_jsx(ModelRow, { t: t, model: model, on: visible.has(model.id), effort: model.id in efforts ? efforts[model.id] : model.configured, onToggle: () => {
230
+ const next = new Set(visible);
231
+ if (next.has(model.id))
232
+ next.delete(model.id);
233
+ else
234
+ next.add(model.id);
235
+ setVisible(allIds.filter(id => next.has(id)));
236
+ }, onEffort: effort => { setEfforts(current => ({ ...current, [model.id]: effort })); } }, model.id))) }), _jsx("span", { className: "dsm-note", children: tf(t, 'pickerCount', { visible: visible.size, total: models.length }) })] })) : null, tab === 'tools' ? (_jsxs(_Fragment, { children: [(loaded?.tools ?? []).map(tool => (_jsxs("div", { className: "dsm-row", children: [_jsx("span", { className: "dsm-grow", children: t(TOOL_LABEL[tool]) }), _jsx("button", { type: "button", role: "switch", className: "dsm-switch", "aria-checked": toolsOn[tool] !== false, "aria-label": t(TOOL_LABEL[tool]), onClick: () => { setDraft(current => ({ ...current, tools: { ...current?.tools, [tool]: current?.tools?.[tool] === false } })); } })] }, tool))), _jsx("span", { className: "dsm-note", children: t('toolsNote') })] })) : null, _jsxs("div", { className: "dsm-panel-foot", children: [error === undefined ? null : _jsx("span", { className: "dsm-card-error", role: "alert", children: error }), _jsx("button", { type: "button", className: "dsm-btn dsm-ghost", onClick: onClose, children: t('cancel') }), _jsx("button", { type: "button", className: "dsm-btn dsm-primary", disabled: saving || draft === undefined, onClick: () => { void save(); }, children: saving ? t('saving') : t('save') })] })] }));
237
+ }
238
+ function ModelRow({ t, model, on, effort, onToggle, onEffort }) {
239
+ return (_jsxs("div", { className: "dsm-row", children: [_jsx("button", { type: "button", role: "switch", className: "dsm-switch", "aria-checked": on, "aria-label": tf(t, 'showInPicker', { model: model.id }), onClick: onToggle }), _jsxs("span", { className: "dsm-grow", children: [_jsx("span", { className: "dsm-ellipsis dsm-num", children: model.id }), model.contextWindow === undefined ? null : _jsxs("span", { className: "dsm-note", children: [Math.round(model.contextWindow / 1000), "K"] })] }), model.efforts.length === 0 ? null : (_jsxs("select", { className: "dsm-select", "aria-label": tf(t, 'effortFor', { model: model.id }), value: effort ?? '', onChange: event => { onEffort(event.target.value === '' ? undefined : event.target.value); }, children: [_jsx("option", { value: "", children: t('effortAuto') }), model.efforts.map(level => _jsx("option", { value: level.id, children: level.name }, level.id))] }))] }));
240
+ }
@@ -0,0 +1,72 @@
1
+ import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client';
3
+ /** One session's speed choice: standard routing or the fast (priority) tier. */
4
+ export type SpeedTier = 'standard' | 'fast';
5
+ /** `speed` endpoint value, mirrored from the node half. */
6
+ export interface SpeedState {
7
+ tier: SpeedTier;
8
+ fastModels: string[];
9
+ }
10
+ /** What {@link SpeedSelect} renders from: visibility plus the current tier. */
11
+ export interface SpeedSelectState {
12
+ visible: boolean;
13
+ model?: string;
14
+ tier: SpeedTier;
15
+ }
16
+ /**
17
+ * Minimal structural face of ui-model-selection's `ctx.modelDirectories`
18
+ * service (the sanctioned cross-plugin channel: cordis services, not value
19
+ * imports — same mirroring discipline as ToolCallOwnerProps). Both dsh lines
20
+ * ship it with this shape; it replaces rc.2's `connection.api.sessions.models`
21
+ * read, which the 0.1.2-alpha removed along with the whole `.api` face.
22
+ */
23
+ export interface ModelDirectoriesLike {
24
+ /** Resolve one session's shared model directory (throws for unknown sessions). */
25
+ directoryFor(sessionId: string): {
26
+ /** Load the directory; `current` is the session's effective model selection. */
27
+ load(): Promise<{
28
+ current: {
29
+ provider: string;
30
+ model: string;
31
+ } | null;
32
+ }>;
33
+ };
34
+ }
35
+ /** Injected dependencies of {@link SpeedSelect} (slot `inject`, session-bound). */
36
+ export interface SpeedSelectInjected {
37
+ /** Load the session's speed state; `visible` false keeps the control hidden. */
38
+ loadSpeed: () => Promise<SpeedSelectState>;
39
+ /** Set the session's speed tier; resolves false when the write failed. */
40
+ setSpeed: (tier: SpeedTier, displayedModel?: string) => Promise<boolean>;
41
+ }
42
+ /**
43
+ * Props delivered by the slot outlet: the framework session kit and InputZone
44
+ * owner share (unused — everything arrives session-bound through the inject
45
+ * face), the injected callbacks, and the locale seat.
46
+ */
47
+ export type SpeedSelectProps = PropsRuntime<'conversation.input.right'> & Partial<SpeedSelectInjected> & Partial<PropsLocale<'subscription-models'>>;
48
+ /**
49
+ * The `loadSpeed` half of the inject face: the plugin's own speed state plus
50
+ * the host's current model selection (the visibility gate). A model-RPC
51
+ * failure throws rather than answering "hidden" — the caller keeps its last
52
+ * known state, so a transient failure never locks the toggle away.
53
+ *
54
+ * `sessionId` is a plain string: slot and command contexts brand it through
55
+ * different dsh-session copies, and only the service boundary needs one.
56
+ *
57
+ * `models` resolves lazily per call: the ui-model-selection service may
58
+ * register after this plugin applies, and a shell without it (no model seat
59
+ * at all) simply keeps the toggle hidden.
60
+ */
61
+ export declare function createSpeedLoader(connection: ConnectionHandle, models: () => ModelDirectoriesLike | undefined, sessionId: string): SpeedSelectInjected['loadSpeed'];
62
+ /** The `setSpeed` half of the inject face: boolean outcome for the component's busy state. */
63
+ export declare function createSpeedSetter(connection: ConnectionHandle, sessionId: string, models?: () => ModelDirectoriesLike | undefined): SpeedSelectInjected['setSpeed'];
64
+ /**
65
+ * The composer Speed control: a trigger reading `速度 · 快速`/`速度 · 标准`
66
+ * that opens a two-row menu (standard/fast with descriptions, check mark on
67
+ * the current tier). The host pushes nothing on a model switch, so the
68
+ * control re-reads on a slow poll with a single-flight guard; a failed read
69
+ * keeps the last known state, so a transient RPC failure can never lock the
70
+ * toggle away (the earlier mount-only load had no recovery path).
71
+ */
72
+ export declare function SpeedSelect({ loadSpeed, setSpeed, t }: SpeedSelectProps): import("react").JSX.Element | null;