dsh-subscriptions 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.
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/cordis.patch.yml +12 -0
- package/lib/auth/claude-code-creds.d.ts +23 -0
- package/lib/auth/claude-code-creds.js +172 -0
- package/lib/auth/device-flow.d.ts +55 -0
- package/lib/auth/device-flow.js +177 -0
- package/lib/auth/jwt.d.ts +10 -0
- package/lib/auth/jwt.js +25 -0
- package/lib/auth/oauth-flow.d.ts +91 -0
- package/lib/auth/oauth-flow.js +227 -0
- package/lib/auth/pkce.d.ts +31 -0
- package/lib/auth/pkce.js +35 -0
- package/lib/auth/rpc.d.ts +209 -0
- package/lib/auth/rpc.js +386 -0
- package/lib/auth/store.d.ts +196 -0
- package/lib/auth/store.js +468 -0
- package/lib/client/CursorCard.d.ts +9 -0
- package/lib/client/CursorCard.js +113 -0
- package/lib/client/ExternalUsageCards.d.ts +9 -0
- package/lib/client/ExternalUsageCards.js +72 -0
- package/lib/client/ImageGallery.d.ts +54 -0
- package/lib/client/ImageGallery.js +112 -0
- package/lib/client/ImageGenerateToolview.d.ts +48 -0
- package/lib/client/ImageGenerateToolview.js +133 -0
- package/lib/client/ProviderAccountManager.d.ts +13 -0
- package/lib/client/ProviderAccountManager.js +147 -0
- package/lib/client/ProviderModelEditor.d.ts +39 -0
- package/lib/client/ProviderModelEditor.js +107 -0
- package/lib/client/SpeedSelect.d.ts +71 -0
- package/lib/client/SpeedSelect.js +177 -0
- package/lib/client/SubscriptionUsageBadge.d.ts +116 -0
- package/lib/client/SubscriptionUsageBadge.js +543 -0
- package/lib/client/SubscriptionsSection.d.ts +140 -0
- package/lib/client/SubscriptionsSection.js +544 -0
- package/lib/client/UsageBadgeDisplaySetting.d.ts +7 -0
- package/lib/client/UsageBadgeDisplaySetting.js +36 -0
- package/lib/client/VideoGenerateToolview.d.ts +42 -0
- package/lib/client/VideoGenerateToolview.js +175 -0
- package/lib/client/account-preferences.d.ts +24 -0
- package/lib/client/account-preferences.js +21 -0
- package/lib/client/fast-command.d.ts +2 -0
- package/lib/client/fast-command.js +4 -0
- package/lib/client/host-contract.d.ts +90 -0
- package/lib/client/host-contract.js +101 -0
- package/lib/client/host-icons.d.ts +24 -0
- package/lib/client/host-icons.js +21 -0
- package/lib/client/index.d.ts +27 -0
- package/lib/client/index.js +136 -0
- package/lib/client/locales.d.ts +297 -0
- package/lib/client/locales.js +295 -0
- package/lib/client/subscription-card-styles.d.ts +3 -0
- package/lib/client/subscription-card-styles.js +53 -0
- package/lib/client/subscriptions-rpc.d.ts +14 -0
- package/lib/client/subscriptions-rpc.js +34 -0
- package/lib/client/usage-badge-preferences.d.ts +11 -0
- package/lib/client/usage-badge-preferences.js +41 -0
- package/lib/client.js +4472 -0
- package/lib/client.js.map +1 -0
- package/lib/compat.d.ts +36 -0
- package/lib/compat.js +20 -0
- package/lib/http.d.ts +6 -0
- package/lib/http.js +15 -0
- package/lib/index.d.ts +80 -0
- package/lib/index.js +14018 -0
- package/lib/model-defaults.d.ts +23 -0
- package/lib/model-defaults.js +237 -0
- package/lib/provider-settings.d.ts +39 -0
- package/lib/provider-settings.js +151 -0
- package/lib/providers/account-preferences.d.ts +41 -0
- package/lib/providers/account-preferences.js +153 -0
- package/lib/providers/accounts.d.ts +112 -0
- package/lib/providers/accounts.js +142 -0
- package/lib/providers/antigravity-oauth-client.d.ts +7 -0
- package/lib/providers/antigravity-oauth-client.js +7 -0
- package/lib/providers/antigravity.d.ts +110 -0
- package/lib/providers/antigravity.js +516 -0
- package/lib/providers/catalog-store.d.ts +48 -0
- package/lib/providers/catalog-store.js +243 -0
- package/lib/providers/claude-cli-version.d.ts +15 -0
- package/lib/providers/claude-cli-version.js +24 -0
- package/lib/providers/claude.d.ts +181 -0
- package/lib/providers/claude.js +680 -0
- package/lib/providers/codex-client-version.d.ts +8 -0
- package/lib/providers/codex-client-version.js +17 -0
- package/lib/providers/codex-search.d.ts +29 -0
- package/lib/providers/codex-search.js +198 -0
- package/lib/providers/codex.d.ts +229 -0
- package/lib/providers/codex.js +821 -0
- package/lib/providers/common.d.ts +389 -0
- package/lib/providers/common.js +656 -0
- package/lib/providers/copilot.d.ts +344 -0
- package/lib/providers/copilot.js +888 -0
- package/lib/providers/cursor-adapter.d.ts +15 -0
- package/lib/providers/cursor-adapter.js +39 -0
- package/lib/providers/cursor-auth.d.ts +43 -0
- package/lib/providers/cursor-auth.js +169 -0
- package/lib/providers/cursor-model-settings.d.ts +15 -0
- package/lib/providers/cursor-model-settings.js +51 -0
- package/lib/providers/cursor-usage.d.ts +9 -0
- package/lib/providers/cursor-usage.js +112 -0
- package/lib/providers/external-usage-controller.d.ts +19 -0
- package/lib/providers/external-usage-controller.js +32 -0
- package/lib/providers/external-usage.d.ts +8 -0
- package/lib/providers/external-usage.js +82 -0
- package/lib/providers/grok.d.ts +205 -0
- package/lib/providers/grok.js +713 -0
- package/lib/providers/image-pool.d.ts +30 -0
- package/lib/providers/image-pool.js +89 -0
- package/lib/providers/npm-cli-version.d.ts +56 -0
- package/lib/providers/npm-cli-version.js +98 -0
- package/lib/providers/pool-family.d.ts +56 -0
- package/lib/providers/pool-family.js +45 -0
- package/lib/providers/pool-health.d.ts +74 -0
- package/lib/providers/pool-health.js +150 -0
- package/lib/providers/pool-usage.d.ts +86 -0
- package/lib/providers/pool-usage.js +223 -0
- package/lib/providers/pool.d.ts +108 -0
- package/lib/providers/pool.js +368 -0
- package/lib/providers/rate-limit.d.ts +192 -0
- package/lib/providers/rate-limit.js +338 -0
- package/lib/tools/image-generate.d.ts +128 -0
- package/lib/tools/image-generate.js +438 -0
- package/lib/tools/registration.d.ts +15 -0
- package/lib/tools/registration.js +24 -0
- package/lib/tools/video-generate.d.ts +89 -0
- package/lib/tools/video-generate.js +257 -0
- package/lib/tools/x-search.d.ts +58 -0
- package/lib/tools/x-search.js +197 -0
- package/lib/translate/anthropic.d.ts +161 -0
- package/lib/translate/anthropic.js +515 -0
- package/lib/translate/antigravity-schema.d.ts +4 -0
- package/lib/translate/antigravity-schema.js +145 -0
- package/lib/translate/antigravity-thinking.d.ts +5 -0
- package/lib/translate/antigravity-thinking.js +44 -0
- package/lib/translate/antigravity.d.ts +114 -0
- package/lib/translate/antigravity.js +366 -0
- package/lib/translate/chat-completions.d.ts +120 -0
- package/lib/translate/chat-completions.js +382 -0
- package/lib/translate/resolved.d.ts +80 -0
- package/lib/translate/resolved.js +118 -0
- package/lib/translate/responses.d.ts +179 -0
- package/lib/translate/responses.js +408 -0
- package/lib/translate/sse.d.ts +21 -0
- package/lib/translate/sse.js +56 -0
- package/package.json +114 -0
- package/vendor/cursor/LICENSE +21 -0
- package/vendor/cursor/index.d.ts +18 -0
- package/vendor/cursor/index.js +3574 -0
- package/vendor/cursor/proto.js +254 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
3
|
+
import { callSubscriptionsAuth } from './subscriptions-rpc.js';
|
|
4
|
+
const border = '1px solid var(--dsw-alias-border-l2, #ddd)';
|
|
5
|
+
const control = {
|
|
6
|
+
font: 'inherit', color: 'inherit', background: 'transparent', border,
|
|
7
|
+
borderRadius: 8, padding: '6px 10px', minWidth: 0,
|
|
8
|
+
};
|
|
9
|
+
const actions = { display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' };
|
|
10
|
+
/**
|
|
11
|
+
* Embedded section of the account manager dialog. It owns the model draft
|
|
12
|
+
* (visibility, effort, context, tools) but not its submission: the dialog
|
|
13
|
+
* collects the draft through the handle and saves it with the account edits
|
|
14
|
+
* in one go, so there is a single Save/Cancel pair.
|
|
15
|
+
*/
|
|
16
|
+
export const ProviderModelEditor = forwardRef(function ProviderModelEditor({ provider, rpc, t, disabled = false, onDirtyChange }, ref) {
|
|
17
|
+
const [catalog, setCatalog] = useState();
|
|
18
|
+
const [draft, setDraft] = useState({});
|
|
19
|
+
const [contexts, setContexts] = useState({});
|
|
20
|
+
const [efforts, setEfforts] = useState({});
|
|
21
|
+
const [query, setQuery] = useState('');
|
|
22
|
+
const [busy, setBusy] = useState(false);
|
|
23
|
+
const [dirty, setDirty] = useState(false);
|
|
24
|
+
const [error, setError] = useState('');
|
|
25
|
+
const generation = useRef(0);
|
|
26
|
+
useEffect(() => () => { generation.current++; }, []);
|
|
27
|
+
useEffect(() => { onDirtyChange?.(dirty); }, [dirty]);
|
|
28
|
+
function reset(data) {
|
|
29
|
+
setCatalog(data);
|
|
30
|
+
setDraft(data.settings);
|
|
31
|
+
setEfforts(Object.fromEntries(data.models.map(model => [model.id, model.configured ?? ''])));
|
|
32
|
+
setContexts(Object.fromEntries(Object.entries(data.settings.contextWindows ?? {}).map(([id, value]) => [id, String(value)])));
|
|
33
|
+
setDirty(false);
|
|
34
|
+
}
|
|
35
|
+
async function load(force = false) {
|
|
36
|
+
const request = ++generation.current;
|
|
37
|
+
setBusy(true);
|
|
38
|
+
setError('');
|
|
39
|
+
try {
|
|
40
|
+
const data = await callSubscriptionsAuth(rpc, provider === 'cursor-subscription' ? 'cursorSettings' : 'providerSettings', { provider, force });
|
|
41
|
+
if (generation.current === request)
|
|
42
|
+
reset(data);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (generation.current === request)
|
|
46
|
+
setError(String(error instanceof Error ? error.message : error));
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
if (generation.current === request)
|
|
50
|
+
setBusy(false);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function edit(next) { setDraft(next); setDirty(true); }
|
|
54
|
+
useEffect(() => { void load(); }, [provider, rpc]);
|
|
55
|
+
useImperativeHandle(ref, () => ({
|
|
56
|
+
collect() {
|
|
57
|
+
if (!catalog || !dirty)
|
|
58
|
+
return { efforts: [] };
|
|
59
|
+
const windows = Object.create(null);
|
|
60
|
+
for (const [model, text] of Object.entries(contexts)) {
|
|
61
|
+
if (!text.trim())
|
|
62
|
+
continue;
|
|
63
|
+
const value = Number(text);
|
|
64
|
+
if (!/^\d+$/.test(text.trim()) || !Number.isSafeInteger(value) || value <= 0) {
|
|
65
|
+
setError(t('modelsContextInvalid', { model }));
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
windows[model] = value;
|
|
69
|
+
}
|
|
70
|
+
setError('');
|
|
71
|
+
const { accounts: _accounts, ...settings } = { ...draft, ...(provider === 'codex' ? { contextWindows: windows } : {}) };
|
|
72
|
+
return {
|
|
73
|
+
settings,
|
|
74
|
+
efforts: catalog.models.flatMap(model => {
|
|
75
|
+
const effort = efforts[model.id] ?? '';
|
|
76
|
+
return effort === (model.configured ?? '') || !model.efforts?.length ? [] : [{ model: model.id, effort }];
|
|
77
|
+
}),
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
}), [catalog, dirty, draft, contexts, efforts, provider]);
|
|
81
|
+
const allModels = catalog?.models ?? [];
|
|
82
|
+
const known = new Set(allModels.map(model => model.id));
|
|
83
|
+
const missing = (draft.visibleModels ?? []).filter(id => !known.has(id)).map(id => ({ id, name: id }));
|
|
84
|
+
const models = [...allModels, ...missing].filter(model => `${model.name} ${model.id}`.toLowerCase().includes(query.trim().toLowerCase()));
|
|
85
|
+
const selected = new Set(draft.visibleModels ?? allModels.map(model => model.id));
|
|
86
|
+
return _jsxs("div", { style: { borderTop: border, marginTop: 12, paddingTop: 12 }, children: [_jsx("h3", { style: { margin: 0, fontSize: 15 }, children: t('modelsEdit') }), _jsxs("div", { style: { display: 'grid', gap: 12, marginTop: 12 }, children: [_jsx("p", { style: { margin: 0 }, children: t(provider === 'cursor-subscription' ? 'cursorManageHint' : 'modelsHint') }), error && _jsx("p", { role: "alert", style: { margin: 0, color: 'var(--dsw-alias-state-error-primary, #b42318)' }, children: error }), _jsxs("div", { style: actions, children: [_jsx("button", { type: "button", style: control, disabled: busy || disabled || dirty, onClick: () => { void load(true); }, children: t('usageRefresh') }), busy && _jsx("span", { role: "status", children: t('modelDefaultsLoading') })] }), catalog && _jsxs("fieldset", { disabled: busy || disabled, style: { border: 0, padding: 0, margin: 0, minWidth: 0, display: 'grid', gap: 12 }, children: [_jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: draft.visibleModels === undefined, onChange: event => {
|
|
87
|
+
const next = { ...draft };
|
|
88
|
+
if (event.target.checked)
|
|
89
|
+
delete next.visibleModels;
|
|
90
|
+
else
|
|
91
|
+
next.visibleModels = allModels.map(model => model.id);
|
|
92
|
+
edit(next);
|
|
93
|
+
} }), " ", t('modelsAutomatic')] }), _jsxs("div", { style: actions, children: [_jsx("input", { style: { ...control, flex: '1 1 180px' }, value: query, onChange: event => setQuery(event.target.value), placeholder: t('modelDefaultsFilterPlaceholder'), "aria-label": t('modelDefaultsFilterPlaceholder') }), _jsx("button", { type: "button", style: control, onClick: () => edit({ ...draft, visibleModels: allModels.map(model => model.id) }), children: t('modelsSelectAll') }), _jsx("button", { type: "button", style: control, onClick: () => edit({ ...draft, visibleModels: [] }), children: t('modelsSelectNone') })] }), _jsxs("div", { style: { maxHeight: 360, overflowY: 'auto', display: 'grid', gap: 8 }, children: [models.map(model => _jsxs("div", { style: { borderBottom: border, padding: '8px 2px', display: 'grid', gap: 8 }, children: [_jsxs("label", { style: { overflowWrap: 'anywhere' }, children: [_jsx("input", { type: "checkbox", checked: selected.has(model.id), onChange: event => {
|
|
94
|
+
const next = new Set(selected);
|
|
95
|
+
if (event.target.checked)
|
|
96
|
+
next.add(model.id);
|
|
97
|
+
else
|
|
98
|
+
next.delete(model.id);
|
|
99
|
+
edit({ ...draft, visibleModels: [...next] });
|
|
100
|
+
} }), " ", model.name, !known.has(model.id) && ` (${t('modelsUnavailable')})`] }), _jsxs("div", { style: actions, children: [(model.efforts?.length ?? 0) > 0 && _jsxs("label", { style: actions, children: [t('modelDefaultsTitle'), _jsxs("select", { style: control, "aria-label": `${model.name} ${t('modelDefaultsTitle')}`, value: efforts[model.id] ?? '', onChange: event => {
|
|
101
|
+
setEfforts({ ...efforts, [model.id]: event.target.value });
|
|
102
|
+
setDirty(true);
|
|
103
|
+
}, children: [_jsx("option", { value: "", children: t('modelDefaultsFollowProvider') }), model.configured && !model.efforts?.some(effort => effort.id === model.configured) &&
|
|
104
|
+
_jsxs("option", { value: model.configured, disabled: true, children: [model.configured, " (", t('modelsUnavailable'), ")"] }), model.efforts?.map(effort => _jsx("option", { value: effort.id, children: effort.name }, effort.id))] })] }), provider === 'codex' && model.maxContextWindow !== undefined && _jsxs("div", { style: actions, children: [_jsxs("label", { style: actions, children: [t('modelsContext'), _jsx("input", { style: { ...control, width: 140 }, inputMode: "numeric", value: contexts[model.id] ?? '', "aria-label": `${model.name} ${t('modelsContext')}`, placeholder: String(model.defaultContextWindow), onChange: event => { setContexts({ ...contexts, [model.id]: event.target.value }); setDirty(true); } })] }), _jsx("small", { children: t('modelsContextBounds', { default: model.defaultContextWindow, max: model.maxContextWindow }) })] })] })] }, model.id)), models.length === 0 && _jsx("span", { children: t('modelDefaultsFilterEmpty', { query }) })] }), provider === 'codex' && _jsx("small", { children: t('modelsContextHint') }), catalog.tools.length > 0 && _jsxs("div", { style: { display: 'grid', gap: 8 }, children: [_jsx("strong", { children: t('modelsTools') }), _jsx("small", { children: t('modelsToolsHint') }), catalog.tools.map(tool => _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: draft.tools?.[tool] !== false, onChange: event => edit({
|
|
105
|
+
...draft, tools: { ...draft.tools, [tool]: event.target.checked },
|
|
106
|
+
}) }), " ", t(tool === 'image_generate' ? 'modelsImage' : tool === 'video_generate' ? 'modelsVideo' : tool === 'web_search' ? 'modelsWebSearch' : 'modelsSearch')] }, tool))] })] })] })] });
|
|
107
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
tier: SpeedTier;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Minimal structural face of ui-model-selection's `ctx.modelDirectories`
|
|
17
|
+
* service (the sanctioned cross-plugin channel: cordis services, not value
|
|
18
|
+
* imports — same mirroring discipline as ToolCallOwnerProps). Both dsh lines
|
|
19
|
+
* ship it with this shape; it replaces rc.2's `connection.api.sessions.models`
|
|
20
|
+
* read, which the 0.1.2-alpha removed along with the whole `.api` face.
|
|
21
|
+
*/
|
|
22
|
+
export interface ModelDirectoriesLike {
|
|
23
|
+
/** Resolve one session's shared model directory (throws for unknown sessions). */
|
|
24
|
+
directoryFor(sessionId: string): {
|
|
25
|
+
/** Load the directory; `current` is the session's effective model selection. */
|
|
26
|
+
load(): Promise<{
|
|
27
|
+
current: {
|
|
28
|
+
provider: string;
|
|
29
|
+
model: string;
|
|
30
|
+
} | null;
|
|
31
|
+
}>;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Injected dependencies of {@link SpeedSelect} (slot `inject`, session-bound). */
|
|
35
|
+
export interface SpeedSelectInjected {
|
|
36
|
+
/** Load the session's speed state; `visible` false keeps the control hidden. */
|
|
37
|
+
loadSpeed: () => Promise<SpeedSelectState>;
|
|
38
|
+
/** Set the session's speed tier; resolves false when the write failed. */
|
|
39
|
+
setSpeed: (tier: SpeedTier) => Promise<boolean>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Props delivered by the slot outlet: the framework session kit and InputZone
|
|
43
|
+
* owner share (unused — everything arrives session-bound through the inject
|
|
44
|
+
* face), the injected callbacks, and the locale seat.
|
|
45
|
+
*/
|
|
46
|
+
export type SpeedSelectProps = PropsRuntime<'conversation.input.right'> & Partial<SpeedSelectInjected> & Partial<PropsLocale<'settings.subscriptions'>>;
|
|
47
|
+
/**
|
|
48
|
+
* The `loadSpeed` half of the inject face: the plugin's own speed state plus
|
|
49
|
+
* the host's current model selection (the visibility gate). A model-RPC
|
|
50
|
+
* failure throws rather than answering "hidden" — the caller keeps its last
|
|
51
|
+
* known state, so a transient failure never locks the toggle away.
|
|
52
|
+
*
|
|
53
|
+
* `sessionId` is a plain string: slot and command contexts brand it through
|
|
54
|
+
* different dsh-session copies, and only the service boundary needs one.
|
|
55
|
+
*
|
|
56
|
+
* `models` resolves lazily per call: the ui-model-selection service may
|
|
57
|
+
* register after this plugin applies, and a shell without it (no model seat
|
|
58
|
+
* at all) simply keeps the toggle hidden.
|
|
59
|
+
*/
|
|
60
|
+
export declare function createSpeedLoader(connection: ConnectionHandle, models: () => ModelDirectoriesLike | undefined, sessionId: string): SpeedSelectInjected['loadSpeed'];
|
|
61
|
+
/** The `setSpeed` half of the inject face: boolean outcome for the component's busy state. */
|
|
62
|
+
export declare function createSpeedSetter(connection: ConnectionHandle, sessionId: string): SpeedSelectInjected['setSpeed'];
|
|
63
|
+
/**
|
|
64
|
+
* The composer Speed control: a trigger reading `速度 · 快速`/`速度 · 标准`
|
|
65
|
+
* that opens a two-row menu (standard/fast with descriptions, check mark on
|
|
66
|
+
* the current tier). The host pushes nothing on a model switch, so the
|
|
67
|
+
* control re-reads on a slow poll with a single-flight guard; a failed read
|
|
68
|
+
* keeps the last known state, so a transient RPC failure can never lock the
|
|
69
|
+
* toggle away (the earlier mount-only load had no recovery path).
|
|
70
|
+
*/
|
|
71
|
+
export declare function SpeedSelect({ loadSpeed, setSpeed, t }: SpeedSelectProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Codex Speed toggle: one small control in the composer's right tool row
|
|
4
|
+
* (`conversation.input.right`), switching the session between standard routing
|
|
5
|
+
* and the fast (priority) service tier — the Codex desktop app's Speed menu.
|
|
6
|
+
* The choice is per session and lives in the node half (in-memory); this
|
|
7
|
+
* component holds only viewing state. The control renders nothing until the
|
|
8
|
+
* first load proves the session's current model is a codex model whose catalog
|
|
9
|
+
* advertises the fast tier.
|
|
10
|
+
*
|
|
11
|
+
* Every color resolves through a `--dsw-alias-*` design token and every
|
|
12
|
+
* user-visible string goes through the locale `t` of the
|
|
13
|
+
* 'settings.subscriptions' namespace, same as the settings section.
|
|
14
|
+
*/
|
|
15
|
+
import { useEffect, useRef, useState } from 'react';
|
|
16
|
+
import { callSubscriptionsAuth } from './SubscriptionsSection.js';
|
|
17
|
+
import { en } from './locales.js';
|
|
18
|
+
/**
|
|
19
|
+
* The `loadSpeed` half of the inject face: the plugin's own speed state plus
|
|
20
|
+
* the host's current model selection (the visibility gate). A model-RPC
|
|
21
|
+
* failure throws rather than answering "hidden" — the caller keeps its last
|
|
22
|
+
* known state, so a transient failure never locks the toggle away.
|
|
23
|
+
*
|
|
24
|
+
* `sessionId` is a plain string: slot and command contexts brand it through
|
|
25
|
+
* different dsh-session copies, and only the service boundary needs one.
|
|
26
|
+
*
|
|
27
|
+
* `models` resolves lazily per call: the ui-model-selection service may
|
|
28
|
+
* register after this plugin applies, and a shell without it (no model seat
|
|
29
|
+
* at all) simply keeps the toggle hidden.
|
|
30
|
+
*/
|
|
31
|
+
export function createSpeedLoader(connection, models, sessionId) {
|
|
32
|
+
return async () => {
|
|
33
|
+
const state = await callSubscriptionsAuth(connection.rpc, 'speed', { sessionId });
|
|
34
|
+
const directories = models();
|
|
35
|
+
if (directories === undefined)
|
|
36
|
+
return { visible: false, tier: state.tier };
|
|
37
|
+
const { current } = await directories.directoryFor(sessionId).load();
|
|
38
|
+
const visible = current !== null && current.provider === 'codex'
|
|
39
|
+
&& state.fastModels.includes(current.model);
|
|
40
|
+
return { visible, tier: state.tier };
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** The `setSpeed` half of the inject face: boolean outcome for the component's busy state. */
|
|
44
|
+
export function createSpeedSetter(connection, sessionId) {
|
|
45
|
+
return tier => callSubscriptionsAuth(connection.rpc, 'setSpeed', { sessionId, tier })
|
|
46
|
+
.then(() => true, () => false);
|
|
47
|
+
}
|
|
48
|
+
/** English-dictionary fallback for a missing inject `t` (standalone renders). */
|
|
49
|
+
function fallbackTranslate(key) {
|
|
50
|
+
return en[key];
|
|
51
|
+
}
|
|
52
|
+
const TIERS = ['standard', 'fast'];
|
|
53
|
+
/**
|
|
54
|
+
* The composer Speed control: a trigger reading `速度 · 快速`/`速度 · 标准`
|
|
55
|
+
* that opens a two-row menu (standard/fast with descriptions, check mark on
|
|
56
|
+
* the current tier). Mount and every open reload the host state so a model
|
|
57
|
+
* switch made since the last open self-corrects.
|
|
58
|
+
*/
|
|
59
|
+
/** How often the control re-reads the host state (model switches arrive only by asking). */
|
|
60
|
+
const POLL_INTERVAL_MS = 3000;
|
|
61
|
+
/**
|
|
62
|
+
* The composer Speed control: a trigger reading `速度 · 快速`/`速度 · 标准`
|
|
63
|
+
* that opens a two-row menu (standard/fast with descriptions, check mark on
|
|
64
|
+
* the current tier). The host pushes nothing on a model switch, so the
|
|
65
|
+
* control re-reads on a slow poll with a single-flight guard; a failed read
|
|
66
|
+
* keeps the last known state, so a transient RPC failure can never lock the
|
|
67
|
+
* toggle away (the earlier mount-only load had no recovery path).
|
|
68
|
+
*/
|
|
69
|
+
export function SpeedSelect({ loadSpeed, setSpeed, t }) {
|
|
70
|
+
const translate = t ?? fallbackTranslate;
|
|
71
|
+
const [state, setState] = useState(null);
|
|
72
|
+
const [open, setOpen] = useState(false);
|
|
73
|
+
const [busy, setBusy] = useState(false);
|
|
74
|
+
const rootRef = useRef(null);
|
|
75
|
+
// The inject face may be re-evaluated (new callback identities) on re-render;
|
|
76
|
+
// the poll effect mounts once and reads through this ref, so identity churn
|
|
77
|
+
// neither resets the interval nor multiplies in-flight loads.
|
|
78
|
+
const loadRef = useRef(loadSpeed);
|
|
79
|
+
loadRef.current = loadSpeed;
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (loadRef.current === undefined)
|
|
82
|
+
return;
|
|
83
|
+
let cancelled = false;
|
|
84
|
+
let inflight = false;
|
|
85
|
+
const reload = () => {
|
|
86
|
+
const load = loadRef.current;
|
|
87
|
+
if (load === undefined || inflight)
|
|
88
|
+
return;
|
|
89
|
+
inflight = true;
|
|
90
|
+
void load().then((loaded) => { if (!cancelled)
|
|
91
|
+
setState(loaded); }, () => { }).finally(() => { inflight = false; });
|
|
92
|
+
};
|
|
93
|
+
reload();
|
|
94
|
+
const timer = setInterval(reload, POLL_INTERVAL_MS);
|
|
95
|
+
return () => {
|
|
96
|
+
cancelled = true;
|
|
97
|
+
clearInterval(timer);
|
|
98
|
+
};
|
|
99
|
+
}, []);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (!open)
|
|
102
|
+
return;
|
|
103
|
+
const closeOutside = (event) => {
|
|
104
|
+
if (!rootRef.current?.contains(event.target))
|
|
105
|
+
setOpen(false);
|
|
106
|
+
};
|
|
107
|
+
document.addEventListener('mousedown', closeOutside);
|
|
108
|
+
return () => { document.removeEventListener('mousedown', closeOutside); };
|
|
109
|
+
}, [open]);
|
|
110
|
+
if (loadSpeed === undefined || setSpeed === undefined || state === null || !state.visible) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const choose = (tier) => {
|
|
114
|
+
if (busy)
|
|
115
|
+
return;
|
|
116
|
+
if (tier === state.tier) {
|
|
117
|
+
setOpen(false);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
setBusy(true);
|
|
121
|
+
void setSpeed(tier).then((ok) => {
|
|
122
|
+
setBusy(false);
|
|
123
|
+
if (ok) {
|
|
124
|
+
setState({ visible: true, tier });
|
|
125
|
+
setOpen(false);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
const show = () => {
|
|
130
|
+
setOpen(true);
|
|
131
|
+
const load = loadRef.current;
|
|
132
|
+
if (load === undefined)
|
|
133
|
+
return;
|
|
134
|
+
void load().then(setState, () => { });
|
|
135
|
+
};
|
|
136
|
+
const tierName = (tier) => translate(tier === 'fast' ? 'speedFast' : 'speedStandard');
|
|
137
|
+
const tierDescription = (tier) => translate(tier === 'fast' ? 'speedFastDescription' : 'speedStandardDescription');
|
|
138
|
+
const triggerLabel = `${translate('speed')} · ${tierName(state.tier)}`;
|
|
139
|
+
return (_jsxs("div", { ref: rootRef, style: styles.root, onKeyDown: (event) => {
|
|
140
|
+
if (event.key === 'Escape' && open) {
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
setOpen(false);
|
|
143
|
+
}
|
|
144
|
+
}, children: [open && (_jsx("div", { style: styles.menu, role: "menu", "aria-label": translate('speed'), children: TIERS.map(tier => (_jsxs("button", { type: "button", role: "menuitemradio", "aria-checked": tier === state.tier, style: styles.item, disabled: busy, onClick: () => { choose(tier); }, children: [_jsx("span", { style: styles.itemCheck, children: tier === state.tier ? '✓' : '' }), _jsxs("span", { style: styles.itemText, children: [_jsx("span", { style: styles.itemName, children: tierName(tier) }), _jsx("span", { style: styles.itemDescription, children: tierDescription(tier) })] })] }, tier))) })), _jsx("button", { type: "button", style: styles.trigger, "aria-haspopup": "menu", "aria-expanded": open, title: triggerLabel, disabled: busy, onClick: () => {
|
|
145
|
+
if (open)
|
|
146
|
+
setOpen(false);
|
|
147
|
+
else
|
|
148
|
+
show();
|
|
149
|
+
}, children: triggerLabel })] }));
|
|
150
|
+
}
|
|
151
|
+
const styles = {
|
|
152
|
+
root: { position: 'relative', display: 'inline-flex' },
|
|
153
|
+
trigger: {
|
|
154
|
+
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
155
|
+
background: 'transparent', color: 'var(--dsw-alias-label-secondary)',
|
|
156
|
+
font: 'inherit', fontSize: 12, lineHeight: '18px',
|
|
157
|
+
padding: '2px 8px', cursor: 'pointer', whiteSpace: 'nowrap',
|
|
158
|
+
},
|
|
159
|
+
menu: {
|
|
160
|
+
position: 'absolute', bottom: '100%', right: 0, marginBottom: 4,
|
|
161
|
+
minWidth: 180, padding: 4, zIndex: 20,
|
|
162
|
+
background: 'var(--dsw-alias-bg-layer-1)', border: '1px solid var(--dsw-alias-border-l2)',
|
|
163
|
+
borderRadius: 8, display: 'flex', flexDirection: 'column', gap: 2,
|
|
164
|
+
},
|
|
165
|
+
item: {
|
|
166
|
+
display: 'flex', alignItems: 'flex-start', gap: 6, width: '100%',
|
|
167
|
+
border: 'none', borderRadius: 6, background: 'transparent',
|
|
168
|
+
padding: '6px 8px', cursor: 'pointer', font: 'inherit', textAlign: 'left',
|
|
169
|
+
},
|
|
170
|
+
itemCheck: {
|
|
171
|
+
width: 14, flexShrink: 0, fontSize: 12, lineHeight: '18px',
|
|
172
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
173
|
+
},
|
|
174
|
+
itemText: { display: 'flex', flexDirection: 'column' },
|
|
175
|
+
itemName: { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-primary)' },
|
|
176
|
+
itemDescription: { fontSize: 11, lineHeight: '16px', color: 'var(--dsw-alias-label-tertiary)' },
|
|
177
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
|
+
import type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client';
|
|
4
|
+
import type { AccountStatus, ProviderUsage, SubscriptionProvider, UsageWindow } from './SubscriptionsSection.js';
|
|
5
|
+
import type { ModelDirectoriesLike } from './SpeedSelect.js';
|
|
6
|
+
import type { SubscriptionsKey } from './locales.js';
|
|
7
|
+
/** DSH renamed the data icon in 0.1.7; retain older supported hosts too. */
|
|
8
|
+
export declare function usageBadgeIcon(icons: {
|
|
9
|
+
IconDataOutlineRegular?: ComponentType;
|
|
10
|
+
IconDataOutline16?: ComponentType;
|
|
11
|
+
}): ComponentType;
|
|
12
|
+
/** Injected dependencies (slot `inject`, session-bound). */
|
|
13
|
+
export interface SubscriptionUsageBadgeInjected {
|
|
14
|
+
/** Connection RPC caller to reach the `subscriptions-auth` endpoints. */
|
|
15
|
+
rpc: ConnectionHandle['rpc'];
|
|
16
|
+
/** Resolve the session's effective provider and model together. */
|
|
17
|
+
currentModel: () => Promise<{
|
|
18
|
+
provider: string;
|
|
19
|
+
model: string;
|
|
20
|
+
} | undefined>;
|
|
21
|
+
}
|
|
22
|
+
/** Props delivered by the slot outlet + inject + the locale seat. */
|
|
23
|
+
export type SubscriptionUsageBadgeProps = PropsRuntime<'conversation.composer.dock'> & Partial<SubscriptionUsageBadgeInjected> & Partial<PropsLocale<'settings.subscriptions'>>;
|
|
24
|
+
/** One logged-in account's usage windows, as listed in the expanded dialog. */
|
|
25
|
+
export interface AccountUsageDisplay {
|
|
26
|
+
/** Account key (the `usage` endpoint's `account` argument). */
|
|
27
|
+
key: string;
|
|
28
|
+
/** Display handle (email / login), when the provider reports one. */
|
|
29
|
+
account?: string;
|
|
30
|
+
plan?: string;
|
|
31
|
+
/** The account direct routes serve; the collapsed pill reads this one. */
|
|
32
|
+
isDefault: boolean;
|
|
33
|
+
windows: UsageWindow[];
|
|
34
|
+
}
|
|
35
|
+
/** One provider's usage snapshot: every logged-in account that reports windows. */
|
|
36
|
+
export interface ProviderUsageDisplay {
|
|
37
|
+
provider: BadgeProvider;
|
|
38
|
+
name: string;
|
|
39
|
+
/** Default account first, then the rest in the `status` endpoint's order. */
|
|
40
|
+
accounts: AccountUsageDisplay[];
|
|
41
|
+
}
|
|
42
|
+
/** The account the collapsed pill reads: the default one, else the first listed. */
|
|
43
|
+
export declare function pillAccountOf(d: ProviderUsageDisplay): AccountUsageDisplay;
|
|
44
|
+
/** Brand display names (short form for the compact badge). */
|
|
45
|
+
export type BadgeProvider = SubscriptionProvider | 'cursor-subscription' | 'opencode-go' | 'kimi-coding';
|
|
46
|
+
export interface UsageRosterEntry {
|
|
47
|
+
provider: BadgeProvider;
|
|
48
|
+
account: AccountStatus;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Group per-account usage into the badge's provider rows, in roster order
|
|
52
|
+
* (the `status` provider order, default account first) so rows don't jump
|
|
53
|
+
* around as polls settle at different times. An account without windows is
|
|
54
|
+
* left out, and so is a provider whose accounts all lack windows.
|
|
55
|
+
* @param roster - accounts from {@link loadBadgeRoster}.
|
|
56
|
+
* @param windowsOf - each account's windows, keyed `provider:accountKey`.
|
|
57
|
+
* @param plans - the plan each usage call reported, same keys; the roster's plan is the fallback.
|
|
58
|
+
* @returns one display per provider with at least one account that has windows.
|
|
59
|
+
*/
|
|
60
|
+
export declare function groupUsageDisplays(roster: readonly UsageRosterEntry[], windowsOf: ReadonlyMap<string, UsageWindow[]>, plans: ReadonlyMap<string, string>): ProviderUsageDisplay[];
|
|
61
|
+
/** Read each account source independently so a failing endpoint cannot hide the others. */
|
|
62
|
+
export declare function loadBadgeRoster(rpc: ConnectionHandle['rpc']): Promise<{
|
|
63
|
+
roster: UsageRosterEntry[];
|
|
64
|
+
refreshed: Set<BadgeProvider>;
|
|
65
|
+
}>;
|
|
66
|
+
export declare function usageOf(rpc: ConnectionHandle['rpc'], { provider, account }: UsageRosterEntry): Promise<ProviderUsage>;
|
|
67
|
+
/**
|
|
68
|
+
* The `currentModel` half of the inject face: the session's effective
|
|
69
|
+
* model selection through ui-model-selection's `modelDirectories` service,
|
|
70
|
+
* resolved lazily per call (the service may register after this plugin, and
|
|
71
|
+
* a shell without it reports "unknown"; the badge keeps the most recent
|
|
72
|
+
* subscription selected in this view, or stays hidden).
|
|
73
|
+
*/
|
|
74
|
+
export declare function createCurrentModelReader(models: () => ModelDirectoriesLike | undefined, sessionId: string): SubscriptionUsageBadgeInjected['currentModel'];
|
|
75
|
+
/**
|
|
76
|
+
* Compact time-remaining label derived from the window's `resetsAt` timestamp:
|
|
77
|
+
* "6d18h" (days+hours), "1h58m" (hours+minutes), or "42m" (minutes only).
|
|
78
|
+
* Falls back to the scope/kind abbreviation when no reset time is known.
|
|
79
|
+
*/
|
|
80
|
+
export declare function windowLabel(w: UsageWindow): string;
|
|
81
|
+
/** Keep model quotas separate: matching percentages do not imply a shared pool. */
|
|
82
|
+
export declare function prioritizeWindows(windows: readonly UsageWindow[], model?: string): UsageWindow[];
|
|
83
|
+
/** Small previews keep a live model catalog from taking over the dialog. */
|
|
84
|
+
export declare const WINDOW_PREVIEW_LIMIT = 4;
|
|
85
|
+
export declare function previewWindows(windows: readonly UsageWindow[], model?: string, provider?: BadgeProvider): {
|
|
86
|
+
shown: UsageWindow[];
|
|
87
|
+
hidden: UsageWindow[];
|
|
88
|
+
};
|
|
89
|
+
/** Bounded readout; Antigravity quotas belong to individual models, not the account. */
|
|
90
|
+
export declare function compactSegment(d: ProviderUsageDisplay, model?: string, t?: Translate): string;
|
|
91
|
+
type ModelSelection = {
|
|
92
|
+
provider: string;
|
|
93
|
+
model: string;
|
|
94
|
+
};
|
|
95
|
+
/** Remember only the most recent subscription in this view, including its model scope. */
|
|
96
|
+
export declare function retainSubscriptionSelection(previous: ModelSelection | undefined, current: ModelSelection | undefined): ModelSelection | undefined;
|
|
97
|
+
/** At most one provider; no known selection or no usage means no badge. */
|
|
98
|
+
export declare function collapsedDisplays(displays: readonly ProviderUsageDisplay[], current: string | undefined): readonly ProviderUsageDisplay[];
|
|
99
|
+
/** Order for the expanded dialog: the current provider first, the rest in poll order. */
|
|
100
|
+
export declare function expandedDisplays(displays: readonly ProviderUsageDisplay[], current: string | undefined): readonly ProviderUsageDisplay[];
|
|
101
|
+
type Translate = (key: SubscriptionsKey, params?: Record<string, unknown>) => string;
|
|
102
|
+
/**
|
|
103
|
+
* The composer subscription-usage badge: a pill reading e.g.
|
|
104
|
+
* `Codex 6d1h 25%` for the current model's provider, opening a dialog with
|
|
105
|
+
* every provider's accounts and their windows. Returns null when no data is
|
|
106
|
+
* available.
|
|
107
|
+
*/
|
|
108
|
+
export declare function SubscriptionUsageBadge({ rpc, currentModel, t }: SubscriptionUsageBadgeProps): import("react").JSX.Element;
|
|
109
|
+
/** Preview each account independently; all remaining quotas stay accessible. */
|
|
110
|
+
export declare function AccountWindows({ windows, model, provider, translate }: {
|
|
111
|
+
windows: readonly UsageWindow[];
|
|
112
|
+
model: string | undefined;
|
|
113
|
+
provider?: BadgeProvider;
|
|
114
|
+
translate: Translate;
|
|
115
|
+
}): import("react").JSX.Element;
|
|
116
|
+
export {};
|