dsh-plugin-subscription-oauth 0.3.0 → 0.5.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/lib/auth/ccswitch.d.ts +36 -0
- package/lib/auth/ccswitch.js +75 -0
- package/lib/auth/rpc.d.ts +8 -1
- package/lib/auth/rpc.js +29 -17
- package/lib/client/UsageChip.d.ts +188 -0
- package/lib/client/UsageChip.js +347 -0
- package/lib/client/index.d.ts +28 -1
- package/lib/client/index.js +46 -1
- package/lib/client/locales.d.ts +4 -0
- package/lib/client/locales.js +4 -0
- package/lib/client/styles.d.ts +15 -1
- package/lib/client/styles.js +128 -1
- package/lib/client/subscriptions-rpc.d.ts +53 -1
- package/lib/client/subscriptions-rpc.js +56 -0
- package/lib/client.js +652 -5
- package/lib/client.js.map +1 -1
- package/lib/protocol.d.ts +50 -1
- package/lib/protocol.js +1 -0
- package/package.json +1 -1
package/lib/client/styles.js
CHANGED
|
@@ -243,7 +243,13 @@ ${scope} .dsh-subscription-disclosure:hover:not(:disabled) {
|
|
|
243
243
|
${scope} .dsh-subscription-meter {
|
|
244
244
|
height: 6px;
|
|
245
245
|
border-radius: 999px;
|
|
246
|
-
|
|
246
|
+
/* The track must stay visible when nothing is spent — a 0 % window otherwise reads as
|
|
247
|
+
"no meter at all", which is the opposite of "nothing used". The layer token falls
|
|
248
|
+
back to the card's own colour on a host that does not define it, so the neutral is
|
|
249
|
+
explicit and translucent, and the inset ring keeps the empty track visible on a
|
|
250
|
+
light theme as well as a dark one. */
|
|
251
|
+
background: #8a8f9826;
|
|
252
|
+
box-shadow: inset 0 0 0 1px #8a8f9833;
|
|
247
253
|
overflow: hidden;
|
|
248
254
|
}
|
|
249
255
|
${scope} .dsh-subscription-meter > span {
|
|
@@ -255,3 +261,124 @@ ${scope} .dsh-subscription-meter > span[data-exhausted="true"] {
|
|
|
255
261
|
background: var(--dsw-alias-state-error-primary, #b42318);
|
|
256
262
|
}
|
|
257
263
|
`;
|
|
264
|
+
/**
|
|
265
|
+
* The composer usage chip's stylesheet.
|
|
266
|
+
*
|
|
267
|
+
* Separate from the page's, because the chip renders in the composer's own bar rather
|
|
268
|
+
* than inside either page container — the page stylesheet is scoped to those
|
|
269
|
+
* containers on purpose, so nothing here may leak into the shell.
|
|
270
|
+
*
|
|
271
|
+
* The chip inherits the bar's type and only draws a meter, so it sits beside the shell's
|
|
272
|
+
* own stats pills without looking like a second design. The trigger is a bare control:
|
|
273
|
+
* the host gives plugin markup no control classes, and a default `<button>` would
|
|
274
|
+
* otherwise read as a raised chip. Every token carries a fallback for hosts that do not
|
|
275
|
+
* define it.
|
|
276
|
+
*/
|
|
277
|
+
export const usageChipCss = `
|
|
278
|
+
.dsh-subscription-usage-chip {
|
|
279
|
+
position: relative;
|
|
280
|
+
display: inline-flex;
|
|
281
|
+
align-items: center;
|
|
282
|
+
/* The bar is a centred flex row whose children are this slot's entries followed by the
|
|
283
|
+
shell's context meter, and a list slot renders its entries as bare children (no
|
|
284
|
+
wrapper). So the chip is itself a flex item: a higher order places it after the
|
|
285
|
+
context meter, and no auto margin is set, so it sits immediately beside it rather
|
|
286
|
+
than being pushed to the edge of the bar. */
|
|
287
|
+
order: 1;
|
|
288
|
+
min-width: 0;
|
|
289
|
+
font-family: inherit;
|
|
290
|
+
font-size: 12px;
|
|
291
|
+
line-height: 1;
|
|
292
|
+
}
|
|
293
|
+
.dsh-subscription-usage-chip-trigger {
|
|
294
|
+
display: inline-flex;
|
|
295
|
+
align-items: center;
|
|
296
|
+
gap: 6px;
|
|
297
|
+
margin: 0;
|
|
298
|
+
padding: 2px 6px;
|
|
299
|
+
border: 0;
|
|
300
|
+
border-radius: var(--dsw-radius-sm, 6px);
|
|
301
|
+
background: transparent;
|
|
302
|
+
color: var(--dsw-alias-label-secondary, #8a8f98);
|
|
303
|
+
font: inherit;
|
|
304
|
+
cursor: pointer;
|
|
305
|
+
}
|
|
306
|
+
.dsh-subscription-usage-chip-trigger:hover {
|
|
307
|
+
background: var(--dsw-alias-interactive-bg-hover, #ffffff14);
|
|
308
|
+
}
|
|
309
|
+
.dsh-subscription-usage-chip-trigger:focus-visible {
|
|
310
|
+
outline: 2px solid var(--dsw-alias-border-focus, #4d6bfe);
|
|
311
|
+
outline-offset: 1px;
|
|
312
|
+
}
|
|
313
|
+
.dsh-subscription-usage-chip-label {
|
|
314
|
+
overflow: hidden;
|
|
315
|
+
max-width: 6ch;
|
|
316
|
+
text-overflow: ellipsis;
|
|
317
|
+
white-space: nowrap;
|
|
318
|
+
}
|
|
319
|
+
.dsh-subscription-usage-chip-cell {
|
|
320
|
+
display: inline-flex;
|
|
321
|
+
align-items: baseline;
|
|
322
|
+
gap: 2px;
|
|
323
|
+
}
|
|
324
|
+
.dsh-subscription-usage-chip-window {
|
|
325
|
+
opacity: 0.75;
|
|
326
|
+
}
|
|
327
|
+
.dsh-subscription-usage-chip-value {
|
|
328
|
+
color: var(--dsw-alias-label-primary, #e8e8e8);
|
|
329
|
+
font-variant-numeric: tabular-nums;
|
|
330
|
+
}
|
|
331
|
+
.dsh-subscription-usage-chip-panel {
|
|
332
|
+
position: absolute;
|
|
333
|
+
bottom: calc(100% + 6px);
|
|
334
|
+
right: 0;
|
|
335
|
+
z-index: 20;
|
|
336
|
+
display: flex;
|
|
337
|
+
flex-direction: column;
|
|
338
|
+
gap: 8px;
|
|
339
|
+
min-width: 168px;
|
|
340
|
+
padding: 10px 12px;
|
|
341
|
+
border: 1px solid var(--dsw-alias-border-secondary, #ffffff1f);
|
|
342
|
+
border-radius: var(--dsw-radius-md, 10px);
|
|
343
|
+
background: var(--dsw-alias-bg-layer-2, #26262a);
|
|
344
|
+
box-shadow: var(--dsw-elevation-prominent, 0 12px 32px #0007);
|
|
345
|
+
color: var(--dsw-alias-label-primary, #e8e8e8);
|
|
346
|
+
white-space: nowrap;
|
|
347
|
+
}
|
|
348
|
+
.dsh-subscription-usage-chip-full {
|
|
349
|
+
font-size: 11px;
|
|
350
|
+
color: var(--dsw-alias-label-secondary, #8a8f98);
|
|
351
|
+
}
|
|
352
|
+
.dsh-subscription-usage-chip-meter {
|
|
353
|
+
display: flex;
|
|
354
|
+
flex-direction: column;
|
|
355
|
+
gap: 4px;
|
|
356
|
+
}
|
|
357
|
+
.dsh-subscription-usage-chip-meterHead {
|
|
358
|
+
display: flex;
|
|
359
|
+
align-items: baseline;
|
|
360
|
+
justify-content: space-between;
|
|
361
|
+
gap: 12px;
|
|
362
|
+
font-size: 12px;
|
|
363
|
+
}
|
|
364
|
+
.dsh-subscription-usage-chip-detail {
|
|
365
|
+
color: var(--dsw-alias-label-secondary, #8a8f98);
|
|
366
|
+
font-variant-numeric: tabular-nums;
|
|
367
|
+
}
|
|
368
|
+
.dsh-subscription-usage-chip-track {
|
|
369
|
+
display: block;
|
|
370
|
+
width: 100%;
|
|
371
|
+
height: 5px;
|
|
372
|
+
border-radius: 999px;
|
|
373
|
+
/* As on the settings meter: an unspent window has to show its track, so the neutral and
|
|
374
|
+
its ring are explicit rather than a layer token that may match the panel. */
|
|
375
|
+
background: #8a8f9826;
|
|
376
|
+
box-shadow: inset 0 0 0 1px #8a8f9833;
|
|
377
|
+
overflow: hidden;
|
|
378
|
+
}
|
|
379
|
+
.dsh-subscription-usage-chip-fill {
|
|
380
|
+
display: block;
|
|
381
|
+
height: 100%;
|
|
382
|
+
background: var(--dsw-alias-state-success-primary, #067647);
|
|
383
|
+
}
|
|
384
|
+
`;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @module dsh-plugin-subscription-oauth/client/subscriptions-rpc
|
|
9
9
|
*/
|
|
10
10
|
import type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client';
|
|
11
|
-
import type { RpcEndpoint, RpcMap } from '../protocol.js';
|
|
11
|
+
import type { CcSwitchBalanceResult, RpcEndpoint, RpcMap } from '../protocol.js';
|
|
12
12
|
/** A failed call: either the transport rejected or the endpoint answered `ok: false`. */
|
|
13
13
|
export declare class SubscriptionsRpcError extends Error {
|
|
14
14
|
readonly code: string;
|
|
@@ -16,9 +16,61 @@ export declare class SubscriptionsRpcError extends Error {
|
|
|
16
16
|
}
|
|
17
17
|
/** Call one endpoint, unwrapping the transport result. */
|
|
18
18
|
export type SubscriptionsCall = <K extends RpcEndpoint>(endpoint: K, payload: RpcMap[K]['payload']) => Promise<RpcMap[K]['value']>;
|
|
19
|
+
/** A caller for one manually added provider's cc-switch balance. */
|
|
20
|
+
export interface CcSwitchBalanceCall {
|
|
21
|
+
(credentialRef: string): Promise<CcSwitchBalanceResult>;
|
|
22
|
+
}
|
|
19
23
|
/**
|
|
20
24
|
* Bind a caller to one connection's RPC transport.
|
|
21
25
|
* @param rpc - the browser connection's RPC caller.
|
|
22
26
|
* @returns a typed caller for this plugin's endpoints.
|
|
23
27
|
*/
|
|
24
28
|
export declare function createSubscriptionsCall(rpc: ConnectionHandle['rpc']): SubscriptionsCall;
|
|
29
|
+
/**
|
|
30
|
+
* The DeepSeek account's wallet balances, as the host's account controller reports them.
|
|
31
|
+
*
|
|
32
|
+
* The controller answers `null` when the account is signed out, `{ status: 'failed' }`
|
|
33
|
+
* when the read did not complete, and `{ status: 'ready', value: <normal wallets>,
|
|
34
|
+
* bonusWallets: <bonus wallets> }` on success.
|
|
35
|
+
*/
|
|
36
|
+
export interface HostAccountBalance {
|
|
37
|
+
/** Normal (topped-up) wallets, one per currency. */
|
|
38
|
+
value: readonly {
|
|
39
|
+
currency: string;
|
|
40
|
+
balance: string;
|
|
41
|
+
}[];
|
|
42
|
+
/** Bonus / promotional wallets, one per currency. */
|
|
43
|
+
bonusWallets?: readonly {
|
|
44
|
+
currency: string;
|
|
45
|
+
balance: string;
|
|
46
|
+
}[];
|
|
47
|
+
}
|
|
48
|
+
/** A narrow caller for the host's own account controller. */
|
|
49
|
+
export interface AccountBalanceCall {
|
|
50
|
+
(): Promise<HostAccountBalance | {
|
|
51
|
+
status: 'failed';
|
|
52
|
+
} | null>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Read the DeepSeek account's balance through the host's own account controller.
|
|
56
|
+
*
|
|
57
|
+
* The host owns the DeepSeek OAuth grant and the platform endpoint, so this asks it
|
|
58
|
+
* rather than re-deriving either. It returns `null` when the account is signed out or
|
|
59
|
+
* the read fails, so a chip can simply not render in that case.
|
|
60
|
+
*
|
|
61
|
+
* @param rpc - the browser connection's RPC caller.
|
|
62
|
+
* @returns the balance, or null when there is nothing to show.
|
|
63
|
+
*/
|
|
64
|
+
export declare function createAccountBalanceCall(rpc: ConnectionHandle['rpc']): AccountBalanceCall;
|
|
65
|
+
/**
|
|
66
|
+
* Read a manually added provider's balance through the plugin's cc-switch endpoint.
|
|
67
|
+
*
|
|
68
|
+
* The caller names the credential reference the provider's profile uses (its
|
|
69
|
+
* `apiKeyEnv`), and the host's credential service resolves it to the API key. The result
|
|
70
|
+
* is `undefined` when there is no key or the provider did not answer — never a wrong
|
|
71
|
+
* figure.
|
|
72
|
+
*
|
|
73
|
+
* @param rpc - the browser connection's RPC caller.
|
|
74
|
+
* @returns a narrow caller taking the credential reference.
|
|
75
|
+
*/
|
|
76
|
+
export declare function createCcSwitchBalanceCall(rpc: ConnectionHandle['rpc']): CcSwitchBalanceCall;
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
import { RPC_PREFIX } from '../protocol.js';
|
|
11
11
|
/** The shared channel the node half's routes are mounted on. */
|
|
12
12
|
const CHANNEL = '/api';
|
|
13
|
+
/** The host's own DeepSeek account controller, from which the chip reads a balance. */
|
|
14
|
+
const ACCOUNT_CHANNEL = '/api';
|
|
15
|
+
const ACCOUNT_NAMESPACE = 'account';
|
|
13
16
|
/** A failed call: either the transport rejected or the endpoint answered `ok: false`. */
|
|
14
17
|
export class SubscriptionsRpcError extends Error {
|
|
15
18
|
code;
|
|
@@ -42,3 +45,56 @@ export function createSubscriptionsCall(rpc) {
|
|
|
42
45
|
return result.value;
|
|
43
46
|
};
|
|
44
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Read the DeepSeek account's balance through the host's own account controller.
|
|
50
|
+
*
|
|
51
|
+
* The host owns the DeepSeek OAuth grant and the platform endpoint, so this asks it
|
|
52
|
+
* rather than re-deriving either. It returns `null` when the account is signed out or
|
|
53
|
+
* the read fails, so a chip can simply not render in that case.
|
|
54
|
+
*
|
|
55
|
+
* @param rpc - the browser connection's RPC caller.
|
|
56
|
+
* @returns the balance, or null when there is nothing to show.
|
|
57
|
+
*/
|
|
58
|
+
export function createAccountBalanceCall(rpc) {
|
|
59
|
+
return async function call() {
|
|
60
|
+
let result;
|
|
61
|
+
try {
|
|
62
|
+
result = await rpc.call(ACCOUNT_CHANNEL, 'account/getBalance', {
|
|
63
|
+
version: '1',
|
|
64
|
+
locale: Intl.DateTimeFormat().resolvedOptions().locale ?? 'en-US',
|
|
65
|
+
timezoneOffsetSeconds: -new Date().getTimezoneOffset(),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
throw new SubscriptionsRpcError(messageOf(error), 'RPC_TRANSPORT');
|
|
70
|
+
}
|
|
71
|
+
if (!result.ok)
|
|
72
|
+
throw new SubscriptionsRpcError(result.error.message, result.error.code);
|
|
73
|
+
return result.value;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Read a manually added provider's balance through the plugin's cc-switch endpoint.
|
|
78
|
+
*
|
|
79
|
+
* The caller names the credential reference the provider's profile uses (its
|
|
80
|
+
* `apiKeyEnv`), and the host's credential service resolves it to the API key. The result
|
|
81
|
+
* is `undefined` when there is no key or the provider did not answer — never a wrong
|
|
82
|
+
* figure.
|
|
83
|
+
*
|
|
84
|
+
* @param rpc - the browser connection's RPC caller.
|
|
85
|
+
* @returns a narrow caller taking the credential reference.
|
|
86
|
+
*/
|
|
87
|
+
export function createCcSwitchBalanceCall(rpc) {
|
|
88
|
+
return async function call(credentialRef) {
|
|
89
|
+
let result;
|
|
90
|
+
try {
|
|
91
|
+
result = await rpc.call(CHANNEL, `${RPC_PREFIX}ccswitch-balance`, { credentialRef });
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
throw new SubscriptionsRpcError(messageOf(error), 'RPC_TRANSPORT');
|
|
95
|
+
}
|
|
96
|
+
if (!result.ok)
|
|
97
|
+
throw new SubscriptionsRpcError(result.error.message, result.error.code);
|
|
98
|
+
return result.value;
|
|
99
|
+
};
|
|
100
|
+
}
|