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.
Files changed (150) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +129 -0
  3. package/cordis.patch.yml +12 -0
  4. package/lib/auth/claude-code-creds.d.ts +23 -0
  5. package/lib/auth/claude-code-creds.js +172 -0
  6. package/lib/auth/device-flow.d.ts +55 -0
  7. package/lib/auth/device-flow.js +177 -0
  8. package/lib/auth/jwt.d.ts +10 -0
  9. package/lib/auth/jwt.js +25 -0
  10. package/lib/auth/oauth-flow.d.ts +91 -0
  11. package/lib/auth/oauth-flow.js +227 -0
  12. package/lib/auth/pkce.d.ts +31 -0
  13. package/lib/auth/pkce.js +35 -0
  14. package/lib/auth/rpc.d.ts +209 -0
  15. package/lib/auth/rpc.js +386 -0
  16. package/lib/auth/store.d.ts +196 -0
  17. package/lib/auth/store.js +468 -0
  18. package/lib/client/CursorCard.d.ts +9 -0
  19. package/lib/client/CursorCard.js +113 -0
  20. package/lib/client/ExternalUsageCards.d.ts +9 -0
  21. package/lib/client/ExternalUsageCards.js +72 -0
  22. package/lib/client/ImageGallery.d.ts +54 -0
  23. package/lib/client/ImageGallery.js +112 -0
  24. package/lib/client/ImageGenerateToolview.d.ts +48 -0
  25. package/lib/client/ImageGenerateToolview.js +133 -0
  26. package/lib/client/ProviderAccountManager.d.ts +13 -0
  27. package/lib/client/ProviderAccountManager.js +147 -0
  28. package/lib/client/ProviderModelEditor.d.ts +39 -0
  29. package/lib/client/ProviderModelEditor.js +107 -0
  30. package/lib/client/SpeedSelect.d.ts +71 -0
  31. package/lib/client/SpeedSelect.js +177 -0
  32. package/lib/client/SubscriptionUsageBadge.d.ts +116 -0
  33. package/lib/client/SubscriptionUsageBadge.js +543 -0
  34. package/lib/client/SubscriptionsSection.d.ts +140 -0
  35. package/lib/client/SubscriptionsSection.js +544 -0
  36. package/lib/client/UsageBadgeDisplaySetting.d.ts +7 -0
  37. package/lib/client/UsageBadgeDisplaySetting.js +36 -0
  38. package/lib/client/VideoGenerateToolview.d.ts +42 -0
  39. package/lib/client/VideoGenerateToolview.js +175 -0
  40. package/lib/client/account-preferences.d.ts +24 -0
  41. package/lib/client/account-preferences.js +21 -0
  42. package/lib/client/fast-command.d.ts +2 -0
  43. package/lib/client/fast-command.js +4 -0
  44. package/lib/client/host-contract.d.ts +90 -0
  45. package/lib/client/host-contract.js +101 -0
  46. package/lib/client/host-icons.d.ts +24 -0
  47. package/lib/client/host-icons.js +21 -0
  48. package/lib/client/index.d.ts +27 -0
  49. package/lib/client/index.js +136 -0
  50. package/lib/client/locales.d.ts +297 -0
  51. package/lib/client/locales.js +295 -0
  52. package/lib/client/subscription-card-styles.d.ts +3 -0
  53. package/lib/client/subscription-card-styles.js +53 -0
  54. package/lib/client/subscriptions-rpc.d.ts +14 -0
  55. package/lib/client/subscriptions-rpc.js +34 -0
  56. package/lib/client/usage-badge-preferences.d.ts +11 -0
  57. package/lib/client/usage-badge-preferences.js +41 -0
  58. package/lib/client.js +4472 -0
  59. package/lib/client.js.map +1 -0
  60. package/lib/compat.d.ts +36 -0
  61. package/lib/compat.js +20 -0
  62. package/lib/http.d.ts +6 -0
  63. package/lib/http.js +15 -0
  64. package/lib/index.d.ts +80 -0
  65. package/lib/index.js +14018 -0
  66. package/lib/model-defaults.d.ts +23 -0
  67. package/lib/model-defaults.js +237 -0
  68. package/lib/provider-settings.d.ts +39 -0
  69. package/lib/provider-settings.js +151 -0
  70. package/lib/providers/account-preferences.d.ts +41 -0
  71. package/lib/providers/account-preferences.js +153 -0
  72. package/lib/providers/accounts.d.ts +112 -0
  73. package/lib/providers/accounts.js +142 -0
  74. package/lib/providers/antigravity-oauth-client.d.ts +7 -0
  75. package/lib/providers/antigravity-oauth-client.js +7 -0
  76. package/lib/providers/antigravity.d.ts +110 -0
  77. package/lib/providers/antigravity.js +516 -0
  78. package/lib/providers/catalog-store.d.ts +48 -0
  79. package/lib/providers/catalog-store.js +243 -0
  80. package/lib/providers/claude-cli-version.d.ts +15 -0
  81. package/lib/providers/claude-cli-version.js +24 -0
  82. package/lib/providers/claude.d.ts +181 -0
  83. package/lib/providers/claude.js +680 -0
  84. package/lib/providers/codex-client-version.d.ts +8 -0
  85. package/lib/providers/codex-client-version.js +17 -0
  86. package/lib/providers/codex-search.d.ts +29 -0
  87. package/lib/providers/codex-search.js +198 -0
  88. package/lib/providers/codex.d.ts +229 -0
  89. package/lib/providers/codex.js +821 -0
  90. package/lib/providers/common.d.ts +389 -0
  91. package/lib/providers/common.js +656 -0
  92. package/lib/providers/copilot.d.ts +344 -0
  93. package/lib/providers/copilot.js +888 -0
  94. package/lib/providers/cursor-adapter.d.ts +15 -0
  95. package/lib/providers/cursor-adapter.js +39 -0
  96. package/lib/providers/cursor-auth.d.ts +43 -0
  97. package/lib/providers/cursor-auth.js +169 -0
  98. package/lib/providers/cursor-model-settings.d.ts +15 -0
  99. package/lib/providers/cursor-model-settings.js +51 -0
  100. package/lib/providers/cursor-usage.d.ts +9 -0
  101. package/lib/providers/cursor-usage.js +112 -0
  102. package/lib/providers/external-usage-controller.d.ts +19 -0
  103. package/lib/providers/external-usage-controller.js +32 -0
  104. package/lib/providers/external-usage.d.ts +8 -0
  105. package/lib/providers/external-usage.js +82 -0
  106. package/lib/providers/grok.d.ts +205 -0
  107. package/lib/providers/grok.js +713 -0
  108. package/lib/providers/image-pool.d.ts +30 -0
  109. package/lib/providers/image-pool.js +89 -0
  110. package/lib/providers/npm-cli-version.d.ts +56 -0
  111. package/lib/providers/npm-cli-version.js +98 -0
  112. package/lib/providers/pool-family.d.ts +56 -0
  113. package/lib/providers/pool-family.js +45 -0
  114. package/lib/providers/pool-health.d.ts +74 -0
  115. package/lib/providers/pool-health.js +150 -0
  116. package/lib/providers/pool-usage.d.ts +86 -0
  117. package/lib/providers/pool-usage.js +223 -0
  118. package/lib/providers/pool.d.ts +108 -0
  119. package/lib/providers/pool.js +368 -0
  120. package/lib/providers/rate-limit.d.ts +192 -0
  121. package/lib/providers/rate-limit.js +338 -0
  122. package/lib/tools/image-generate.d.ts +128 -0
  123. package/lib/tools/image-generate.js +438 -0
  124. package/lib/tools/registration.d.ts +15 -0
  125. package/lib/tools/registration.js +24 -0
  126. package/lib/tools/video-generate.d.ts +89 -0
  127. package/lib/tools/video-generate.js +257 -0
  128. package/lib/tools/x-search.d.ts +58 -0
  129. package/lib/tools/x-search.js +197 -0
  130. package/lib/translate/anthropic.d.ts +161 -0
  131. package/lib/translate/anthropic.js +515 -0
  132. package/lib/translate/antigravity-schema.d.ts +4 -0
  133. package/lib/translate/antigravity-schema.js +145 -0
  134. package/lib/translate/antigravity-thinking.d.ts +5 -0
  135. package/lib/translate/antigravity-thinking.js +44 -0
  136. package/lib/translate/antigravity.d.ts +114 -0
  137. package/lib/translate/antigravity.js +366 -0
  138. package/lib/translate/chat-completions.d.ts +120 -0
  139. package/lib/translate/chat-completions.js +382 -0
  140. package/lib/translate/resolved.d.ts +80 -0
  141. package/lib/translate/resolved.js +118 -0
  142. package/lib/translate/responses.d.ts +179 -0
  143. package/lib/translate/responses.js +408 -0
  144. package/lib/translate/sse.d.ts +21 -0
  145. package/lib/translate/sse.js +56 -0
  146. package/package.json +114 -0
  147. package/vendor/cursor/LICENSE +21 -0
  148. package/vendor/cursor/index.d.ts +18 -0
  149. package/vendor/cursor/index.js +3574 -0
  150. package/vendor/cursor/proto.js +254 -0
@@ -0,0 +1,175 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Keyed toolview for the `video_generate` tool: renders the generated video
4
+ * inline in the conversation. The row shows the call's prompt while running
5
+ * and after settling; a settled success loads the MP4 bytes through the node
6
+ * half's `/subscriptions-auth` `video` endpoint (by bare file name), builds a
7
+ * Blob URL, and plays it in a native `<video controls>` element. The file
8
+ * name comes from the result's presentation meta when the dispatch was
9
+ * top-level, and is recovered from the "Saved video to …" text line for
10
+ * nested (Code Mode) dispatches, where the harness computes no meta. A result
11
+ * with neither renders its text; an error result renders the first error line.
12
+ *
13
+ * The 'tool.call.toolview' SlotMap entry is declared by
14
+ * ImageGenerateToolview.tsx in this same package (one declaration per
15
+ * augmentation), so this file only mirrors the owner-props shape.
16
+ */
17
+ import { useEffect, useState } from 'react';
18
+ import * as primitives from '@deepseek-ai/dsh-client-ui-primitives';
19
+ import { hostIcon } from './host-icons.js';
20
+ import { callSubscriptionsAuth } from './subscriptions-rpc.js';
21
+ import { en } from './locales.js';
22
+ /** DSH 0.1.7 renamed IconSparkle16; resolving it by name keeps older hosts working too. */
23
+ const SparkleIcon = hostIcon(primitives, 'Sparkle');
24
+ /** Title prompt truncation budget (characters). */
25
+ const PROMPT_MAX_LENGTH = 60;
26
+ /**
27
+ * Build the video loader over the `/subscriptions-auth` `video` endpoint.
28
+ * @param rpc - Connection RPC caller.
29
+ * @returns loader resolving a bare file name to the decoded bytes.
30
+ */
31
+ export function createVideoLoader(rpc) {
32
+ return name => callSubscriptionsAuth(rpc, 'video', { name });
33
+ }
34
+ /**
35
+ * English-dictionary fallback for a missing locale seat (standalone renders);
36
+ * the framework always supplies the namespace-bound one.
37
+ */
38
+ function fallbackTranslate(key, params) {
39
+ let text = en[key];
40
+ for (const [name, value] of Object.entries(params ?? {})) {
41
+ text = text.replaceAll(`{${name}}`, String(value));
42
+ }
43
+ return text;
44
+ }
45
+ /** Extract the prompt from the call's raw args JSON; falls back to the first string value, then the raw line. */
46
+ function derivePrompt(argsRaw) {
47
+ let parsed;
48
+ try {
49
+ parsed = JSON.parse(argsRaw);
50
+ }
51
+ catch {
52
+ // Non-JSON args (mid-stream truncation): fall back to the raw string below.
53
+ parsed = undefined;
54
+ }
55
+ let prompt;
56
+ if (typeof parsed === 'object' && parsed !== null) {
57
+ const args = parsed;
58
+ if (typeof args.prompt === 'string' && args.prompt !== '')
59
+ prompt = args.prompt;
60
+ else {
61
+ for (const value of Object.values(args)) {
62
+ if (typeof value === 'string' && value !== '') {
63
+ prompt = value;
64
+ break;
65
+ }
66
+ }
67
+ }
68
+ }
69
+ const line = (prompt ?? argsRaw).split('\n', 1)[0] ?? '';
70
+ return line.length > PROMPT_MAX_LENGTH ? `${line.slice(0, PROMPT_MAX_LENGTH)}…` : line;
71
+ }
72
+ /** Flatten a settled result's text blocks (the fallback body and the error line). */
73
+ function resultText(block) {
74
+ if (!('kind' in block))
75
+ return '';
76
+ const parts = [];
77
+ for (const part of block.content) {
78
+ if (part.type === 'text')
79
+ parts.push(part.text);
80
+ }
81
+ if (parts.length === 0 && block.error !== undefined)
82
+ parts.push(`${block.error.name}: ${block.error.code}`);
83
+ return parts.join('\n');
84
+ }
85
+ /**
86
+ * The generated video's bare file name: presentation meta first (top-level
87
+ * dispatches), then the render text's "Saved video to …" line (nested
88
+ * dispatches compute no meta).
89
+ */
90
+ function resolveFileName(block) {
91
+ if (!('kind' in block))
92
+ return undefined;
93
+ const meta = block.meta;
94
+ if (typeof meta === 'object' && meta !== null) {
95
+ const fileName = meta.fileName;
96
+ if (typeof fileName === 'string' && fileName.length > 0)
97
+ return fileName;
98
+ }
99
+ const match = /^Saved video to (.+\.mp4)/m.exec(resultText(block));
100
+ if (match === null)
101
+ return undefined;
102
+ const path = match[1];
103
+ return path.slice(path.lastIndexOf('/') + 1);
104
+ }
105
+ /** Decode a base64 payload into bytes (browser-side; no Buffer). */
106
+ function base64Bytes(dataBase64) {
107
+ const binary = atob(dataBase64);
108
+ const bytes = new Uint8Array(binary.length);
109
+ for (let index = 0; index < binary.length; index++)
110
+ bytes[index] = binary.charCodeAt(index);
111
+ return bytes;
112
+ }
113
+ const styles = {
114
+ container: { display: 'flex', flexDirection: 'column', gap: 6, padding: '4px 0' },
115
+ row: { display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 },
116
+ icon: { display: 'inline-flex', flexShrink: 0, color: 'var(--dsw-alias-label-tertiary)' },
117
+ title: {
118
+ fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)',
119
+ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
120
+ },
121
+ subtle: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },
122
+ output: {
123
+ margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-secondary)',
124
+ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere',
125
+ },
126
+ error: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' },
127
+ video: {
128
+ display: 'block', maxWidth: 480, width: '100%', borderRadius: 8,
129
+ backgroundColor: 'var(--dsw-alias-bg-skeleton)',
130
+ },
131
+ };
132
+ /**
133
+ * The `video_generate` keyed toolview component.
134
+ * @param props - owner share, inject face, and locale seat (spread flat).
135
+ * @returns the call row plus, once settled, the player / text / error body.
136
+ */
137
+ export function VideoGenerateToolview(props) {
138
+ const { block, loadVideo } = props;
139
+ const t = props.t ?? fallbackTranslate;
140
+ const settled = block !== undefined && 'kind' in block;
141
+ const isError = settled && block.isError;
142
+ const fileName = block !== undefined && settled && !isError ? resolveFileName(block) : undefined;
143
+ const [load, setLoad] = useState({ phase: 'loading' });
144
+ useEffect(() => {
145
+ if (fileName === undefined || loadVideo === undefined)
146
+ return;
147
+ let cancelled = false;
148
+ let objectUrl;
149
+ setLoad({ phase: 'loading' });
150
+ loadVideo(fileName).then((video) => {
151
+ if (cancelled)
152
+ return;
153
+ // Uint8Array#slice detaches a plain ArrayBuffer view for BlobPart.
154
+ objectUrl = URL.createObjectURL(new Blob([base64Bytes(video.dataBase64).slice()], { type: video.mediaType }));
155
+ setLoad({ phase: 'ready', url: objectUrl });
156
+ }, (error) => {
157
+ if (cancelled)
158
+ return;
159
+ setLoad({ phase: 'failed', message: error instanceof Error ? error.message : String(error) });
160
+ });
161
+ return () => {
162
+ cancelled = true;
163
+ if (objectUrl !== undefined)
164
+ URL.revokeObjectURL(objectUrl);
165
+ };
166
+ }, [fileName, loadVideo]);
167
+ if (block === undefined)
168
+ return null;
169
+ const argsRaw = (settled ? block.call?.argsRaw : block.phase === 'start' ? block.argsRaw : undefined) ?? '';
170
+ const title = `video_generate: ${derivePrompt(argsRaw)}`;
171
+ const text = settled ? resultText(block) : '';
172
+ return (_jsxs("div", { style: styles.container, children: [_jsxs("div", { style: styles.row, children: [_jsx("span", { style: styles.icon, children: _jsx(SparkleIcon, { size: 14 }) }), _jsx("span", { style: styles.title, children: title })] }), !settled && _jsx("p", { style: styles.subtle, children: t('generatingVideo') }), settled && isError && text !== '' && (_jsx("p", { style: styles.error, children: text.split('\n', 1)[0] })), settled && !isError && fileName !== undefined && load.phase === 'loading' && (_jsx("p", { style: styles.subtle, children: t('videoLoading') })), settled && !isError && fileName !== undefined && load.phase === 'failed' && (_jsx("p", { style: styles.error, children: t('videoLoadFailed', { message: load.message }) })), settled && !isError && fileName !== undefined && load.phase === 'ready' && (
173
+ // eslint-disable-next-line jsx-a11y/media-has-caption -- generated clip; no caption track exists.
174
+ _jsx("video", { style: styles.video, src: load.url, controls: true, preload: "metadata" })), settled && !isError && fileName === undefined && text !== '' && (_jsx("p", { style: styles.output, children: text }))] }));
175
+ }
@@ -0,0 +1,24 @@
1
+ import type { AccountPreferences, ProviderPreferences } from '../provider-settings.js';
2
+ export interface AccountCatalogRow {
3
+ key: string;
4
+ label: string;
5
+ models: {
6
+ id: string;
7
+ name: string;
8
+ }[];
9
+ unavailable?: boolean;
10
+ }
11
+ /** Missing allowlist follows discovery; explicit [] must remain empty. */
12
+ export declare function accountPoolSelection(account: AccountPreferences, models: readonly {
13
+ id: string;
14
+ }[]): Set<string>;
15
+ /** Retain unavailable saved IDs so a temporary catalog failure cannot erase them. */
16
+ export declare function accountModelRows(account: AccountCatalogRow, preferences: AccountPreferences): {
17
+ unavailable: boolean;
18
+ id: string;
19
+ name: string;
20
+ }[];
21
+ /** The model editor owns everything except the separately managed accounts. */
22
+ export declare function mergeLatestAccounts(draft: ProviderPreferences, latest: ProviderPreferences): ProviderPreferences;
23
+ /** Only touched accounts replace their latest preferences; other settings survive. */
24
+ export declare function mergeAccountChanges(latest: ProviderPreferences, changes: Record<string, AccountPreferences>): ProviderPreferences;
@@ -0,0 +1,21 @@
1
+ /** Missing allowlist follows discovery; explicit [] must remain empty. */
2
+ export function accountPoolSelection(account, models) {
3
+ return new Set(account.poolModels ?? models.map(model => model.id));
4
+ }
5
+ /** Retain unavailable saved IDs so a temporary catalog failure cannot erase them. */
6
+ export function accountModelRows(account, preferences) {
7
+ const known = new Set(account.models.map(model => model.id));
8
+ return [
9
+ ...account.models.map(model => ({ ...model, unavailable: false })),
10
+ ...(preferences.poolModels ?? []).filter(id => !known.has(id)).map(id => ({ id, name: id, unavailable: true })),
11
+ ];
12
+ }
13
+ /** The model editor owns everything except the separately managed accounts. */
14
+ export function mergeLatestAccounts(draft, latest) {
15
+ const { accounts: _stale, ...settings } = draft;
16
+ return { ...settings, ...(latest.accounts === undefined ? {} : { accounts: latest.accounts }) };
17
+ }
18
+ /** Only touched accounts replace their latest preferences; other settings survive. */
19
+ export function mergeAccountChanges(latest, changes) {
20
+ return { ...latest, accounts: { ...latest.accounts, ...changes } };
21
+ }
@@ -0,0 +1,2 @@
1
+ /** Build the locale resolver used by the /fast command contribution. */
2
+ export declare function fastCommandDescription(t: () => string): () => string;
@@ -0,0 +1,4 @@
1
+ /** Build the locale resolver used by the /fast command contribution. */
2
+ export function fastCommandDescription(t) {
3
+ return () => t();
4
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Everything the client half assumes about the DSH host that the compiler
3
+ * cannot check.
4
+ *
5
+ * The client bundle reads host modules at runtime, not at build time. A
6
+ * renamed icon export compiles against the old type declarations and then
7
+ * renders `undefined`. A slot outlet the host stops rendering never mounts our
8
+ * entry, so the page looks normal and the feature is just gone. A removed DOM
9
+ * marker only breaks layout. None of these fail `tsc`, and a unit test with
10
+ * a hand-made host cannot see them either.
11
+ *
12
+ * So each such assumption is listed here once, next to the host package that
13
+ * owns it. `scripts/check-host-contract.mjs` downloads every supported DSH
14
+ * version and checks that each entry appears in that version's published
15
+ * code. The host E2E reads the same diagnostics entries to catch slot
16
+ * crashes. The spec test/host-contract.spec.ts scans src/client and fails
17
+ * when code starts relying on a literal host name that is missing here.
18
+ *
19
+ * This module has no imports, so plain Node scripts can import it directly.
20
+ */
21
+ /** Prefix of the console warning the client logs when a host lacks a listed name. */
22
+ export declare const HOST_CONTRACT_MISS = "dsh-subscriptions: host contract miss";
23
+ export declare const HOST_CONTRACT: {
24
+ /**
25
+ * Host modules the client bundle loads by specifier. The web shell serves
26
+ * only the modules in its static module table (`staticModules` in
27
+ * dsh-web-frontend), so each specifier the built lib/client.js loads must
28
+ * be a key of that table.
29
+ */
30
+ readonly moduleTable: "@deepseek-ai/dsh-web-frontend";
31
+ /** Named runtime exports the client reads from a host module. */
32
+ readonly exports: {
33
+ readonly '@deepseek-ai/dsh-client-ui-primitives': readonly ["useAnchoredPosition", "useDismissOnOutsidePointer"];
34
+ };
35
+ /**
36
+ * 16px glyphs read through hostIcon(). The host must export
37
+ * `Icon<Name>Regular` (DSH 0.1.7 and later) or `Icon<Name>16` (earlier).
38
+ */
39
+ readonly icons: {
40
+ readonly package: "@deepseek-ai/dsh-client-ui-primitives";
41
+ readonly names: readonly ["Sparkle", "DataOutline"];
42
+ };
43
+ /** Slots the client registers into, each with the package that renders its outlet. */
44
+ readonly slots: {
45
+ readonly 'settings.section': "@deepseek-ai/dsh-client-ui-settings-general";
46
+ readonly 'tool.call.toolview': "@deepseek-ai/dsh-client-ui-tool";
47
+ readonly 'conversation.input.right': "@deepseek-ai/dsh-client-ui-conversation";
48
+ readonly 'conversation.composer.dock': "@deepseek-ai/dsh-client-ui-conversation";
49
+ };
50
+ /**
51
+ * Context services the client injects or reads, each with the package that
52
+ * provides it (`ctx.provide("<name>")` or a cordis Service named `<name>`).
53
+ * A service nobody provides leaves the plugin's apply() waiting forever,
54
+ * with nothing in the console.
55
+ */
56
+ readonly services: {
57
+ readonly commandUi: "@deepseek-ai/dsh-client-ui-commands";
58
+ readonly connection: "@deepseek-ai/dsh-client-connection";
59
+ readonly locale: "@deepseek-ai/dsh-client-locale";
60
+ readonly modelDirectories: "@deepseek-ai/dsh-client-ui-model-selection";
61
+ readonly slots: "@deepseek-ai/dsh-client-ui-renderer";
62
+ };
63
+ /** DOM attributes the client looks up in host-rendered markup. */
64
+ readonly markers: {
65
+ /** The shipped StatsPills row; the usage badge renders into it as a third pill. */
66
+ readonly composerStats: {
67
+ readonly attribute: "data-composer-stats";
68
+ readonly package: "@deepseek-ai/dsh-client-ui-chat";
69
+ };
70
+ };
71
+ /**
72
+ * What the slot renderer emits when an entry throws. It catches the error,
73
+ * logs `crashLog`, and renders an element carrying `errorAttribute` in the
74
+ * entry's place, so a crash never reaches the page's error handlers. The
75
+ * host E2E watches for both.
76
+ */
77
+ readonly diagnostics: {
78
+ readonly package: "@deepseek-ai/dsh-client-ui-renderer";
79
+ readonly crashLog: "slot entry crashed in";
80
+ readonly errorAttribute: "data-slot-error";
81
+ readonly slotAttribute: "data-slot";
82
+ };
83
+ /** Design tokens the client styles with; the host theme must define each one. */
84
+ readonly tokens: {
85
+ readonly package: "@deepseek-ai/dsh-client-ui-theme";
86
+ readonly names: readonly ["--dsw-alias-bg-layer-1", "--dsw-alias-bg-skeleton", "--dsw-alias-border-l2", "--dsw-alias-border-l2-darkmode-thin", "--dsw-alias-interactive-bg-hover", "--dsw-alias-interactive-bg-hover-solid", "--dsw-alias-label-dimmed", "--dsw-alias-label-primary", "--dsw-alias-label-secondary", "--dsw-alias-label-tertiary", "--dsw-alias-state-error-primary", "--dsw-alias-state-success-primary", "--dsw-alias-state-warn-label", "--dsw-elevation-prominent", "--dsw-specific-menu"];
87
+ };
88
+ };
89
+ /** A glyph name listed in {@link HOST_CONTRACT}; hostIcon() accepts no other. */
90
+ export type HostIconName = (typeof HOST_CONTRACT.icons.names)[number];
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Everything the client half assumes about the DSH host that the compiler
3
+ * cannot check.
4
+ *
5
+ * The client bundle reads host modules at runtime, not at build time. A
6
+ * renamed icon export compiles against the old type declarations and then
7
+ * renders `undefined`. A slot outlet the host stops rendering never mounts our
8
+ * entry, so the page looks normal and the feature is just gone. A removed DOM
9
+ * marker only breaks layout. None of these fail `tsc`, and a unit test with
10
+ * a hand-made host cannot see them either.
11
+ *
12
+ * So each such assumption is listed here once, next to the host package that
13
+ * owns it. `scripts/check-host-contract.mjs` downloads every supported DSH
14
+ * version and checks that each entry appears in that version's published
15
+ * code. The host E2E reads the same diagnostics entries to catch slot
16
+ * crashes. The spec test/host-contract.spec.ts scans src/client and fails
17
+ * when code starts relying on a literal host name that is missing here.
18
+ *
19
+ * This module has no imports, so plain Node scripts can import it directly.
20
+ */
21
+ /** Prefix of the console warning the client logs when a host lacks a listed name. */
22
+ export const HOST_CONTRACT_MISS = 'dsh-subscriptions: host contract miss';
23
+ export const HOST_CONTRACT = {
24
+ /**
25
+ * Host modules the client bundle loads by specifier. The web shell serves
26
+ * only the modules in its static module table (`staticModules` in
27
+ * dsh-web-frontend), so each specifier the built lib/client.js loads must
28
+ * be a key of that table.
29
+ */
30
+ moduleTable: '@deepseek-ai/dsh-web-frontend',
31
+ /** Named runtime exports the client reads from a host module. */
32
+ exports: {
33
+ '@deepseek-ai/dsh-client-ui-primitives': ['useAnchoredPosition', 'useDismissOnOutsidePointer'],
34
+ },
35
+ /**
36
+ * 16px glyphs read through hostIcon(). The host must export
37
+ * `Icon<Name>Regular` (DSH 0.1.7 and later) or `Icon<Name>16` (earlier).
38
+ */
39
+ icons: {
40
+ package: '@deepseek-ai/dsh-client-ui-primitives',
41
+ names: ['Sparkle', 'DataOutline'],
42
+ },
43
+ /** Slots the client registers into, each with the package that renders its outlet. */
44
+ slots: {
45
+ 'settings.section': '@deepseek-ai/dsh-client-ui-settings-general',
46
+ 'tool.call.toolview': '@deepseek-ai/dsh-client-ui-tool',
47
+ 'conversation.input.right': '@deepseek-ai/dsh-client-ui-conversation',
48
+ 'conversation.composer.dock': '@deepseek-ai/dsh-client-ui-conversation',
49
+ },
50
+ /**
51
+ * Context services the client injects or reads, each with the package that
52
+ * provides it (`ctx.provide("<name>")` or a cordis Service named `<name>`).
53
+ * A service nobody provides leaves the plugin's apply() waiting forever,
54
+ * with nothing in the console.
55
+ */
56
+ services: {
57
+ commandUi: '@deepseek-ai/dsh-client-ui-commands',
58
+ connection: '@deepseek-ai/dsh-client-connection',
59
+ locale: '@deepseek-ai/dsh-client-locale',
60
+ modelDirectories: '@deepseek-ai/dsh-client-ui-model-selection',
61
+ slots: '@deepseek-ai/dsh-client-ui-renderer',
62
+ },
63
+ /** DOM attributes the client looks up in host-rendered markup. */
64
+ markers: {
65
+ /** The shipped StatsPills row; the usage badge renders into it as a third pill. */
66
+ composerStats: { attribute: 'data-composer-stats', package: '@deepseek-ai/dsh-client-ui-chat' },
67
+ },
68
+ /**
69
+ * What the slot renderer emits when an entry throws. It catches the error,
70
+ * logs `crashLog`, and renders an element carrying `errorAttribute` in the
71
+ * entry's place, so a crash never reaches the page's error handlers. The
72
+ * host E2E watches for both.
73
+ */
74
+ diagnostics: {
75
+ package: '@deepseek-ai/dsh-client-ui-renderer',
76
+ crashLog: 'slot entry crashed in',
77
+ errorAttribute: 'data-slot-error',
78
+ slotAttribute: 'data-slot',
79
+ },
80
+ /** Design tokens the client styles with; the host theme must define each one. */
81
+ tokens: {
82
+ package: '@deepseek-ai/dsh-client-ui-theme',
83
+ names: [
84
+ '--dsw-alias-bg-layer-1',
85
+ '--dsw-alias-bg-skeleton',
86
+ '--dsw-alias-border-l2',
87
+ '--dsw-alias-border-l2-darkmode-thin',
88
+ '--dsw-alias-interactive-bg-hover',
89
+ '--dsw-alias-interactive-bg-hover-solid',
90
+ '--dsw-alias-label-dimmed',
91
+ '--dsw-alias-label-primary',
92
+ '--dsw-alias-label-secondary',
93
+ '--dsw-alias-label-tertiary',
94
+ '--dsw-alias-state-error-primary',
95
+ '--dsw-alias-state-success-primary',
96
+ '--dsw-alias-state-warn-label',
97
+ '--dsw-elevation-prominent',
98
+ '--dsw-specific-menu',
99
+ ],
100
+ },
101
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Glyphs from the host's icon set that survive its renames. DSH 0.1.7
3
+ * renamed the 16px glyphs from `Icon<Name>16` to `Icon<Name>Regular`. The
4
+ * client reads host modules at runtime, so a named import of a glyph the
5
+ * host no longer exports is `undefined`, and rendering it crashes the whole
6
+ * slot (React error #130).
7
+ *
8
+ * A glyph name must be listed in HOST_CONTRACT.icons, which
9
+ * scripts/check-host-contract.mjs checks against each supported DSH version.
10
+ */
11
+ import type { ComponentType } from 'react';
12
+ import type { IconProps } from '@deepseek-ai/dsh-client-ui-primitives';
13
+ import type { HostIconName } from './host-contract.js';
14
+ /**
15
+ * Resolve one 16px glyph under either host naming.
16
+ *
17
+ * A glyph the host lacks renders nothing, and the lookup logs a
18
+ * {@link HOST_CONTRACT_MISS} warning. The host E2E fails on that warning, so a
19
+ * missing icon is reported instead of shipping as a blank space.
20
+ * @param icons - the host primitives module (`import * as`), read by name.
21
+ * @param name - the glyph name without prefix or size, e.g. `Sparkle`.
22
+ * @returns the host's component, or one rendering nothing.
23
+ */
24
+ export declare function hostIcon(icons: object, name: HostIconName): ComponentType<IconProps>;
@@ -0,0 +1,21 @@
1
+ import { HOST_CONTRACT, HOST_CONTRACT_MISS } from './host-contract.js';
2
+ /** A decorative glyph the host lacks renders nothing rather than crashing. */
3
+ const NoIcon = () => null;
4
+ /**
5
+ * Resolve one 16px glyph under either host naming.
6
+ *
7
+ * A glyph the host lacks renders nothing, and the lookup logs a
8
+ * {@link HOST_CONTRACT_MISS} warning. The host E2E fails on that warning, so a
9
+ * missing icon is reported instead of shipping as a blank space.
10
+ * @param icons - the host primitives module (`import * as`), read by name.
11
+ * @param name - the glyph name without prefix or size, e.g. `Sparkle`.
12
+ * @returns the host's component, or one rendering nothing.
13
+ */
14
+ export function hostIcon(icons, name) {
15
+ const exports = icons;
16
+ const icon = exports[`Icon${name}Regular`] ?? exports[`Icon${name}16`];
17
+ if (icon !== undefined)
18
+ return icon;
19
+ console.warn(`${HOST_CONTRACT_MISS}: ${HOST_CONTRACT.icons.package} exports neither Icon${name}Regular nor Icon${name}16`);
20
+ return NoIcon;
21
+ }
@@ -0,0 +1,27 @@
1
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
2
+ import type { SubscriptionsKey } from './locales.js';
3
+ export type { SubscriptionsSectionInjected, SubscriptionsSectionProps } from './SubscriptionsSection.js';
4
+ export type { ImageGenerateToolviewInjected, ImageGenerateToolviewProps } from './ImageGenerateToolview.js';
5
+ export type { VideoGenerateToolviewInjected, VideoGenerateToolviewProps } from './VideoGenerateToolview.js';
6
+ export type { SpeedSelectInjected, SpeedSelectProps, SpeedState, SpeedTier } from './SpeedSelect.js';
7
+ export type { SubscriptionUsageBadgeInjected, SubscriptionUsageBadgeProps } from './SubscriptionUsageBadge.js';
8
+ export type { SubscriptionsKey } from './locales.js';
9
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
10
+ interface LocaleNamespaceMap {
11
+ /** The Subscriptions settings page copy. */
12
+ 'settings.subscriptions': SubscriptionsKey;
13
+ }
14
+ }
15
+ /**
16
+ * Required services (cordis fiber inject): `slots` carries the registration
17
+ * seat, `connection` the `/subscriptions-auth` RPC caller, and `locale` the copy
18
+ * dictionaries.
19
+ */
20
+ export declare const inject: string[];
21
+ /**
22
+ * Register the Subscriptions section once the `settings.section` declaration
23
+ * is on the ledger (the shell's apply order relative to this one is NOT
24
+ * constrained; registration depends on the slot through `slots.inject()`).
25
+ * @param ctx - client root context.
26
+ */
27
+ export declare function apply(ctx: ClientContext): void;
@@ -0,0 +1,136 @@
1
+ // `.js` extension: this package's tsconfig lacks the reference repo's
2
+ // allowImportingTsExtensions/rewriteRelativeImportExtensions pair; under
3
+ // nodenext the .js specifier resolves to the .tsx source (see README note).
4
+ import { SubscriptionsSection } from './SubscriptionsSection.js';
5
+ import { ImageGenerateToolview, createImageLoader } from './ImageGenerateToolview.js';
6
+ import { VideoGenerateToolview, createVideoLoader } from './VideoGenerateToolview.js';
7
+ import { SpeedSelect, createSpeedLoader, createSpeedSetter } from './SpeedSelect.js';
8
+ import { SubscriptionUsageBadge, createCurrentModelReader } from './SubscriptionUsageBadge.js';
9
+ import { en, zh } from './locales.js';
10
+ import { fastCommandDescription } from './fast-command.js';
11
+ /** Dictionary namespace owned by this plugin. */
12
+ const NS = 'settings.subscriptions';
13
+ /**
14
+ * Required services (cordis fiber inject): `slots` carries the registration
15
+ * seat, `connection` the `/subscriptions-auth` RPC caller, and `locale` the copy
16
+ * dictionaries.
17
+ */
18
+ export const inject = ['slots', 'connection', 'locale'];
19
+ /**
20
+ * Register the Subscriptions section once the `settings.section` declaration
21
+ * is on the ledger (the shell's apply order relative to this one is NOT
22
+ * constrained; registration depends on the slot through `slots.inject()`).
23
+ * @param ctx - client root context.
24
+ */
25
+ export function apply(ctx) {
26
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-subscriptions: copy dictionaries');
27
+ // Settings-shell nudge: the panel (nav title + header row + section body)
28
+ // sits flush against the panel's top edge; push it down a little to leave
29
+ // breathing room. Scoped by the settings panel's own dialog role + nav child
30
+ // so other aria-modal dialogs (e.g. the attachment lightbox) are untouched.
31
+ ctx.effect(() => {
32
+ const style = document.createElement('style');
33
+ style.setAttribute('data-plugin', 'dsh-subscriptions');
34
+ style.textContent = 'div[role="dialog"][aria-modal="true"]:has(> nav) { padding-top: 14px; }';
35
+ document.head.appendChild(style);
36
+ return () => style.remove();
37
+ }, 'dsh-subscriptions: settings panel breathing room');
38
+ // The shell's Context merge types `connection` as the host handle; in the
39
+ // browser shell the same key holds the full client ConnectionHandle.
40
+ const connection = ctx.get('connection');
41
+ const t = ctx.locale.bind(NS);
42
+ const injected = () => ({ rpc: connection.rpc, t });
43
+ ctx.slots.inject('settings.section', () => ctx.slots.register({
44
+ name: 'settings.section',
45
+ id: 'subscriptions',
46
+ order: 90,
47
+ // A thunk re-evaluated per read, so the nav label follows the active locale.
48
+ label: () => t('nav'),
49
+ inject: injected,
50
+ }, SubscriptionsSection));
51
+ // The image_generate keyed toolview owns how image calls render inline; its
52
+ // gallery bytes ride the same channel through the injected loader. The
53
+ // framework synthesizes the toolview's own `t` seat from `locale: NS`.
54
+ const toolviewInjected = () => ({ load: createImageLoader(connection.rpc) });
55
+ ctx.slots.inject('tool.call.toolview', () => ctx.slots.register({
56
+ name: 'tool.call.toolview',
57
+ key: 'image_generate',
58
+ locale: NS,
59
+ inject: toolviewInjected,
60
+ }, ImageGenerateToolview));
61
+ // The video_generate keyed toolview plays the saved MP4 inline; its bytes
62
+ // ride the same channel's `video` endpoint through the injected loader.
63
+ const videoToolviewInjected = () => ({ loadVideo: createVideoLoader(connection.rpc) });
64
+ ctx.slots.inject('tool.call.toolview', () => ctx.slots.register({
65
+ name: 'tool.call.toolview',
66
+ key: 'video_generate',
67
+ locale: NS,
68
+ inject: videoToolviewInjected,
69
+ }, VideoGenerateToolview));
70
+ // The composer Speed toggle (codex fast tier) sits in the right tool row,
71
+ // just left of the model selector; the framework synthesizes its `t` seat
72
+ // from `locale: NS`, and the inject face binds each session's RPC calls.
73
+ // The current-model read rides ui-model-selection's `modelDirectories`
74
+ // service, resolved lazily so registration order never matters.
75
+ const models = () => ctx.get('modelDirectories');
76
+ ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
77
+ name: 'conversation.input.right',
78
+ id: 'codex-speed',
79
+ order: 0,
80
+ locale: NS,
81
+ inject: (sessionId) => ({
82
+ loadSpeed: createSpeedLoader(connection, models, sessionId),
83
+ setSpeed: createSpeedSetter(connection, sessionId),
84
+ }),
85
+ }, SpeedSelect));
86
+ // The subscription usage badge renders a stats pill in the composer's dock
87
+ // (conversation.composer.dock) — collapsed, the current model's provider
88
+ // (e.g. "Codex 6d1h 25%"); clicking opens every provider's windows. A fresh
89
+ // id means it appears beside the shipped StatsPills, never replacing them;
90
+ // the current-model read shares the Speed toggle's `modelDirectories` path.
91
+ ctx.slots.inject('conversation.composer.dock', () => ctx.slots.register({
92
+ name: 'conversation.composer.dock',
93
+ id: 'subscription-usage',
94
+ order: 10,
95
+ locale: NS,
96
+ inject: (sessionId) => ({
97
+ rpc: connection.rpc,
98
+ currentModel: createCurrentModelReader(models, sessionId),
99
+ }),
100
+ }, SubscriptionUsageBadge));
101
+ // The /fast slash command offers the same Standard/Fast choice as a popup.
102
+ // `available` is synchronous and sees only the session id, so the command
103
+ // stays listed everywhere; `options` throws the friendly gate when the
104
+ // session's current model is not a fast-capable codex model (the same
105
+ // in-popup error posture the /model contribution uses for its guards).
106
+ ctx.inject(['commandUi'], (scope) => {
107
+ const command = scope.get('commandUi');
108
+ scope.effect(() => command.register({
109
+ name: 'fast',
110
+ // dsh 0.1.5-alpha made `description` a locale resolver evaluated per
111
+ // candidate pass (commit 5d9603b76, "feat(web): localize slash command
112
+ // descriptions"); earlier lines read the value as a plain string. The
113
+ // bare string threw `contribution.description is not a function` inside
114
+ // the registry's candidate pass on 0.1.5, aborting the whole `/` source
115
+ // and hiding every host command — /plan, /model, /goal, ... — not just
116
+ // /fast. Older lines render a function child as empty copy, no crash.
117
+ description: fastCommandDescription(() => t('commandFast')),
118
+ available: () => true,
119
+ ui: {
120
+ kind: 'popupSelect',
121
+ options: async (session) => {
122
+ const state = await createSpeedLoader(connection, models, session.sessionId)();
123
+ if (!state.visible)
124
+ throw new Error(t('commandFastUnavailable'));
125
+ return [
126
+ { id: 'standard', label: t('speedStandard'), detail: t('speedStandardDescription') },
127
+ { id: 'fast', label: t('speedFast'), detail: t('speedFastDescription') },
128
+ ].map(option => ({ ...option, active: option.id === state.tier }));
129
+ },
130
+ onSelect: async (option, session) => {
131
+ await createSpeedSetter(connection, session.sessionId)(option.id);
132
+ },
133
+ },
134
+ }), 'dsh-subscriptions: /fast contribution');
135
+ });
136
+ }