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,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic OAuth authorization-code flow engine: one temporary loopback HTTP
|
|
3
|
+
* server per login attempt receives the provider's redirect, validates
|
|
4
|
+
* `state`, and yields the authorization `code`. A pasted callback URL or bare
|
|
5
|
+
* code can substitute for the browser redirect (`manual`), and the user can
|
|
6
|
+
* abort (`cancel`). At most one attempt per provider runs at a time.
|
|
7
|
+
*/
|
|
8
|
+
import { type PkcePair } from './pkce.js';
|
|
9
|
+
/** Default attempt lifetime: three minutes for the user to complete login. */
|
|
10
|
+
export declare const DEFAULT_FLOW_TIMEOUT_MS = 180000;
|
|
11
|
+
/** Where the temporary callback server listens; port 0 asks the OS for an ephemeral port. */
|
|
12
|
+
export interface ListenSpec {
|
|
13
|
+
host: string;
|
|
14
|
+
/** Tried in order; the first free port wins (covers the codex 1455→1457 fallback). */
|
|
15
|
+
ports: readonly number[];
|
|
16
|
+
}
|
|
17
|
+
/** Inputs an authorize-URL builder may need for one attempt. */
|
|
18
|
+
export interface AuthorizeInput {
|
|
19
|
+
/** Loopback redirect URI pointing at the temporary server. */
|
|
20
|
+
redirectUri: string;
|
|
21
|
+
state: string;
|
|
22
|
+
pkce: PkcePair;
|
|
23
|
+
/** Random hex nonce; only providers that require one use it. */
|
|
24
|
+
nonce: string;
|
|
25
|
+
}
|
|
26
|
+
/** Static per-provider flow facts. */
|
|
27
|
+
export interface FlowSpec {
|
|
28
|
+
/** Path the provider redirects to on the loopback server. */
|
|
29
|
+
callbackPath: string;
|
|
30
|
+
listen: ListenSpec;
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Build the provider authorize URL for one attempt.
|
|
34
|
+
* @param input - redirect URI, state, PKCE pair, and nonce minted for this attempt.
|
|
35
|
+
* @returns the URL the user's browser should open.
|
|
36
|
+
*/
|
|
37
|
+
buildAuthorizeUrl(input: AuthorizeInput): string;
|
|
38
|
+
}
|
|
39
|
+
/** One in-flight login attempt. */
|
|
40
|
+
export interface OAuthAttempt {
|
|
41
|
+
/** URL to open in the user's browser. */
|
|
42
|
+
readonly authorizeUrl: string;
|
|
43
|
+
/** Redirect URI registered for this attempt (echoed at the token exchange). */
|
|
44
|
+
readonly redirectUri: string;
|
|
45
|
+
/** PKCE pair minted for this attempt. */
|
|
46
|
+
readonly pkce: PkcePair;
|
|
47
|
+
/** State parameter minted for this attempt (some providers echo it at exchange). */
|
|
48
|
+
readonly state: string;
|
|
49
|
+
/**
|
|
50
|
+
* Wait for the authorization code from the browser callback or `manual`.
|
|
51
|
+
* @returns the authorization code; rejects on timeout, provider error, or cancel.
|
|
52
|
+
*/
|
|
53
|
+
waitCode(): Promise<string>;
|
|
54
|
+
/**
|
|
55
|
+
* Feed a pasted full callback URL (code + state extracted and validated) or
|
|
56
|
+
* a bare authorization code (state cannot be checked) into this attempt.
|
|
57
|
+
* @param input - the pasted text.
|
|
58
|
+
* @throws when the input carries no code or a mismatched state.
|
|
59
|
+
*/
|
|
60
|
+
manual(input: string): void;
|
|
61
|
+
/** Abort the attempt and close its callback server. */
|
|
62
|
+
cancel(): void;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Own the set of in-flight login attempts, keyed by provider. One attempt per
|
|
66
|
+
* provider at a time; an attempt removes itself when it settles.
|
|
67
|
+
*/
|
|
68
|
+
export declare class OAuthFlowManager {
|
|
69
|
+
private attempts;
|
|
70
|
+
/**
|
|
71
|
+
* Whether a login attempt is running for one provider.
|
|
72
|
+
* @param provider - the provider route.
|
|
73
|
+
* @returns true while an attempt is waiting for its code.
|
|
74
|
+
*/
|
|
75
|
+
isBusy(provider: string): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* The pending attempt for one provider, when any.
|
|
78
|
+
* @param provider - the provider route.
|
|
79
|
+
* @returns the in-flight attempt, or `undefined`.
|
|
80
|
+
*/
|
|
81
|
+
pending(provider: string): OAuthAttempt | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* Start a login attempt: mint PKCE/state, open the loopback callback
|
|
84
|
+
* server, and build the authorize URL.
|
|
85
|
+
* @param provider - the provider route (one attempt at a time).
|
|
86
|
+
* @param spec - static flow facts for this provider.
|
|
87
|
+
* @returns the live attempt; its `waitCode()` settles the login.
|
|
88
|
+
* @throws when an attempt is already running or no callback port is free.
|
|
89
|
+
*/
|
|
90
|
+
start(provider: string, spec: FlowSpec): Promise<OAuthAttempt>;
|
|
91
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic OAuth authorization-code flow engine: one temporary loopback HTTP
|
|
3
|
+
* server per login attempt receives the provider's redirect, validates
|
|
4
|
+
* `state`, and yields the authorization `code`. A pasted callback URL or bare
|
|
5
|
+
* code can substitute for the browser redirect (`manual`), and the user can
|
|
6
|
+
* abort (`cancel`). At most one attempt per provider runs at a time.
|
|
7
|
+
*/
|
|
8
|
+
import { createServer } from 'node:http';
|
|
9
|
+
import { createPkce, randomHex, randomToken } from './pkce.js';
|
|
10
|
+
/** Default attempt lifetime: three minutes for the user to complete login. */
|
|
11
|
+
export const DEFAULT_FLOW_TIMEOUT_MS = 180_000;
|
|
12
|
+
const SUCCESS_PAGE = '<!doctype html><html><head><meta charset="utf-8"><title>Login successful</title></head>'
|
|
13
|
+
+ '<body style="font-family:sans-serif"><h1>Login successful</h1>'
|
|
14
|
+
+ '<p>You can close this tab and return to DeepSeek Harness.</p></body></html>';
|
|
15
|
+
function failurePage(detail) {
|
|
16
|
+
return '<!doctype html><html><head><meta charset="utf-8"><title>Login failed</title></head>'
|
|
17
|
+
+ `<body style="font-family:sans-serif"><h1>Login failed</h1><p>${detail.replace(/[<>&]/g, '')}</p></body></html>`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Loopback addresses one listen host covers. `localhost` resolves to ::1 or
|
|
21
|
+
* 127.0.0.1 depending on the client, and Node binds exactly one of them per
|
|
22
|
+
* listen call — a browser picking the other family gets connection-refused
|
|
23
|
+
* and the login times out, so both families must serve the callback.
|
|
24
|
+
*/
|
|
25
|
+
function listenHosts(host) {
|
|
26
|
+
return host === 'localhost' ? ['127.0.0.1', '::1'] : [host];
|
|
27
|
+
}
|
|
28
|
+
/** True when the address family does not exist on this machine (safe to skip), unlike a taken port. */
|
|
29
|
+
function familyUnavailable(error) {
|
|
30
|
+
const code = error.code;
|
|
31
|
+
return code === 'EADDRNOTAVAIL' || code === 'EPROTONOSUPPORT';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Listen on the first port of the spec free on every loopback family;
|
|
35
|
+
* rejects when every port fails. Ephemeral ports (0) are retried so each
|
|
36
|
+
* family can be re-bound onto the first family's assigned port.
|
|
37
|
+
*/
|
|
38
|
+
async function listen(handler, spec) {
|
|
39
|
+
const hosts = listenHosts(spec.host);
|
|
40
|
+
const candidates = spec.ports.flatMap(port => (port === 0 ? [0, 0, 0] : [port]));
|
|
41
|
+
let lastError;
|
|
42
|
+
for (const candidate of candidates) {
|
|
43
|
+
const servers = [];
|
|
44
|
+
let port = candidate;
|
|
45
|
+
let unusable = false;
|
|
46
|
+
for (const host of hosts) {
|
|
47
|
+
const server = createServer(handler);
|
|
48
|
+
try {
|
|
49
|
+
await new Promise((resolve, reject) => {
|
|
50
|
+
const onError = (error) => reject(error);
|
|
51
|
+
server.once('error', onError);
|
|
52
|
+
server.listen(port, host, () => {
|
|
53
|
+
server.removeListener('error', onError);
|
|
54
|
+
resolve();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
const address = server.address();
|
|
58
|
+
if (address === null)
|
|
59
|
+
throw new Error(`callback server on ${host}:${port} has no address`);
|
|
60
|
+
if (port === 0)
|
|
61
|
+
port = address.port;
|
|
62
|
+
servers.push(server);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
server.close();
|
|
66
|
+
if (familyUnavailable(error))
|
|
67
|
+
continue;
|
|
68
|
+
lastError = error;
|
|
69
|
+
unusable = true;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (unusable || servers.length === 0) {
|
|
74
|
+
for (const server of servers)
|
|
75
|
+
server.close();
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
return { servers, port };
|
|
79
|
+
}
|
|
80
|
+
throw lastError instanceof Error
|
|
81
|
+
? lastError
|
|
82
|
+
: new Error(`callback server could not listen on ${spec.host} (ports ${spec.ports.join(', ')})`);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Own the set of in-flight login attempts, keyed by provider. One attempt per
|
|
86
|
+
* provider at a time; an attempt removes itself when it settles.
|
|
87
|
+
*/
|
|
88
|
+
export class OAuthFlowManager {
|
|
89
|
+
attempts = new Map();
|
|
90
|
+
/**
|
|
91
|
+
* Whether a login attempt is running for one provider.
|
|
92
|
+
* @param provider - the provider route.
|
|
93
|
+
* @returns true while an attempt is waiting for its code.
|
|
94
|
+
*/
|
|
95
|
+
isBusy(provider) {
|
|
96
|
+
return this.attempts.has(provider);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The pending attempt for one provider, when any.
|
|
100
|
+
* @param provider - the provider route.
|
|
101
|
+
* @returns the in-flight attempt, or `undefined`.
|
|
102
|
+
*/
|
|
103
|
+
pending(provider) {
|
|
104
|
+
return this.attempts.get(provider);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Start a login attempt: mint PKCE/state, open the loopback callback
|
|
108
|
+
* server, and build the authorize URL.
|
|
109
|
+
* @param provider - the provider route (one attempt at a time).
|
|
110
|
+
* @param spec - static flow facts for this provider.
|
|
111
|
+
* @returns the live attempt; its `waitCode()` settles the login.
|
|
112
|
+
* @throws when an attempt is already running or no callback port is free.
|
|
113
|
+
*/
|
|
114
|
+
async start(provider, spec) {
|
|
115
|
+
if (this.attempts.has(provider)) {
|
|
116
|
+
throw new Error(`a ${provider} login attempt is already in progress`);
|
|
117
|
+
}
|
|
118
|
+
const input = {
|
|
119
|
+
redirectUri: '',
|
|
120
|
+
state: randomToken(32),
|
|
121
|
+
pkce: createPkce(),
|
|
122
|
+
nonce: randomHex(8),
|
|
123
|
+
};
|
|
124
|
+
const timeoutMs = spec.timeoutMs ?? DEFAULT_FLOW_TIMEOUT_MS;
|
|
125
|
+
let resolveCode;
|
|
126
|
+
let rejectCode;
|
|
127
|
+
const codePromise = new Promise((resolve, reject) => {
|
|
128
|
+
resolveCode = resolve;
|
|
129
|
+
rejectCode = reject;
|
|
130
|
+
});
|
|
131
|
+
let settled = false;
|
|
132
|
+
let timer;
|
|
133
|
+
let servers = [];
|
|
134
|
+
const handler = (request, response) => {
|
|
135
|
+
const url = new URL(request.url ?? '/', 'http://localhost');
|
|
136
|
+
if (url.pathname !== spec.callbackPath) {
|
|
137
|
+
response.writeHead(404, { 'content-type': 'text/plain' });
|
|
138
|
+
response.end('not found');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const errorDescription = url.searchParams.get('error_description') ?? url.searchParams.get('error');
|
|
142
|
+
if (errorDescription !== null) {
|
|
143
|
+
response.writeHead(200, { 'content-type': 'text/html' });
|
|
144
|
+
response.end(failurePage(errorDescription));
|
|
145
|
+
settle(new Error(`authorization failed: ${errorDescription}`));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (url.searchParams.get('state') !== input.state) {
|
|
149
|
+
// A stray or replayed redirect must not kill the real attempt.
|
|
150
|
+
response.writeHead(400, { 'content-type': 'text/plain' });
|
|
151
|
+
response.end('state mismatch');
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const code = url.searchParams.get('code');
|
|
155
|
+
if (code === null || code.length === 0) {
|
|
156
|
+
response.writeHead(400, { 'content-type': 'text/plain' });
|
|
157
|
+
response.end('missing authorization code');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
response.writeHead(200, { 'content-type': 'text/html' });
|
|
161
|
+
response.end(SUCCESS_PAGE);
|
|
162
|
+
settle(undefined, code);
|
|
163
|
+
};
|
|
164
|
+
const settle = (error, code) => {
|
|
165
|
+
if (settled)
|
|
166
|
+
return;
|
|
167
|
+
settled = true;
|
|
168
|
+
if (timer !== undefined)
|
|
169
|
+
clearTimeout(timer);
|
|
170
|
+
for (const server of servers) {
|
|
171
|
+
server.close();
|
|
172
|
+
server.closeAllConnections();
|
|
173
|
+
}
|
|
174
|
+
this.attempts.delete(provider);
|
|
175
|
+
if (error !== undefined)
|
|
176
|
+
rejectCode(error);
|
|
177
|
+
else if (code !== undefined)
|
|
178
|
+
resolveCode(code);
|
|
179
|
+
};
|
|
180
|
+
const bound = await listen(handler, spec.listen);
|
|
181
|
+
servers = bound.servers;
|
|
182
|
+
input.redirectUri = `http://${spec.listen.host}:${bound.port}${spec.callbackPath}`;
|
|
183
|
+
timer = setTimeout(() => {
|
|
184
|
+
settle(new Error(`login timed out after ${Math.round(timeoutMs / 1000)}s`));
|
|
185
|
+
}, timeoutMs);
|
|
186
|
+
timer.unref();
|
|
187
|
+
const attempt = {
|
|
188
|
+
authorizeUrl: spec.buildAuthorizeUrl(input),
|
|
189
|
+
redirectUri: input.redirectUri,
|
|
190
|
+
pkce: input.pkce,
|
|
191
|
+
state: input.state,
|
|
192
|
+
waitCode: () => codePromise,
|
|
193
|
+
manual(rawInput) {
|
|
194
|
+
if (settled)
|
|
195
|
+
throw new Error(`the ${provider} login attempt already finished`);
|
|
196
|
+
const trimmed = rawInput.trim();
|
|
197
|
+
let code;
|
|
198
|
+
let pastedState;
|
|
199
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
200
|
+
const url = new URL(trimmed);
|
|
201
|
+
code = url.searchParams.get('code') ?? undefined;
|
|
202
|
+
pastedState = url.searchParams.get('state') ?? undefined;
|
|
203
|
+
}
|
|
204
|
+
else if (trimmed.includes('code=')) {
|
|
205
|
+
const params = new URLSearchParams(trimmed);
|
|
206
|
+
code = params.get('code') ?? undefined;
|
|
207
|
+
pastedState = params.get('state') ?? undefined;
|
|
208
|
+
}
|
|
209
|
+
else if (trimmed.length > 0 && !/\s/.test(trimmed)) {
|
|
210
|
+
code = trimmed;
|
|
211
|
+
}
|
|
212
|
+
if (code === undefined || code.length === 0) {
|
|
213
|
+
throw new Error('no authorization code found in the pasted input');
|
|
214
|
+
}
|
|
215
|
+
if (pastedState !== undefined && pastedState !== input.state) {
|
|
216
|
+
throw new Error('state mismatch: the pasted URL belongs to a different login attempt');
|
|
217
|
+
}
|
|
218
|
+
settle(undefined, code);
|
|
219
|
+
},
|
|
220
|
+
cancel() {
|
|
221
|
+
settle(new Error('login cancelled'));
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
this.attempts.set(provider, attempt);
|
|
225
|
+
return attempt;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** PKCE (RFC 7636) and random-token helpers for the OAuth login flows. */
|
|
2
|
+
/** One PKCE verifier/challenge pair; the verifier stays local until the token exchange. */
|
|
3
|
+
export interface PkcePair {
|
|
4
|
+
/** High-entropy secret sent as `code_verifier` at the token endpoint. */
|
|
5
|
+
verifier: string;
|
|
6
|
+
/** S256 digest of the verifier, sent as `code_challenge` at the authorize endpoint. */
|
|
7
|
+
challenge: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Base64url-encode without padding.
|
|
11
|
+
* @param buffer - raw bytes.
|
|
12
|
+
* @returns the RFC 4648 §5 encoding.
|
|
13
|
+
*/
|
|
14
|
+
export declare function base64url(buffer: Buffer): string;
|
|
15
|
+
/**
|
|
16
|
+
* Mint a fresh PKCE pair (32-byte verifier, S256 challenge).
|
|
17
|
+
* @returns the pair for one authorization attempt.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createPkce(): PkcePair;
|
|
20
|
+
/**
|
|
21
|
+
* Mint a URL-safe random token (default 16 bytes) for OAuth `state`.
|
|
22
|
+
* @param bytes - entropy length.
|
|
23
|
+
* @returns base64url-encoded random bytes.
|
|
24
|
+
*/
|
|
25
|
+
export declare function randomToken(bytes?: number): string;
|
|
26
|
+
/**
|
|
27
|
+
* Mint lowercase-hex random bytes (for Grok's `nonce` parameter).
|
|
28
|
+
* @param bytes - entropy length; the hex string is twice as long.
|
|
29
|
+
* @returns hex-encoded random bytes.
|
|
30
|
+
*/
|
|
31
|
+
export declare function randomHex(bytes?: number): string;
|
package/lib/auth/pkce.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** PKCE (RFC 7636) and random-token helpers for the OAuth login flows. */
|
|
2
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
3
|
+
/**
|
|
4
|
+
* Base64url-encode without padding.
|
|
5
|
+
* @param buffer - raw bytes.
|
|
6
|
+
* @returns the RFC 4648 §5 encoding.
|
|
7
|
+
*/
|
|
8
|
+
export function base64url(buffer) {
|
|
9
|
+
return buffer.toString('base64url');
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Mint a fresh PKCE pair (32-byte verifier, S256 challenge).
|
|
13
|
+
* @returns the pair for one authorization attempt.
|
|
14
|
+
*/
|
|
15
|
+
export function createPkce() {
|
|
16
|
+
const verifier = base64url(randomBytes(32));
|
|
17
|
+
const challenge = base64url(createHash('sha256').update(verifier).digest());
|
|
18
|
+
return { verifier, challenge };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Mint a URL-safe random token (default 16 bytes) for OAuth `state`.
|
|
22
|
+
* @param bytes - entropy length.
|
|
23
|
+
* @returns base64url-encoded random bytes.
|
|
24
|
+
*/
|
|
25
|
+
export function randomToken(bytes = 32) {
|
|
26
|
+
return base64url(randomBytes(bytes));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Mint lowercase-hex random bytes (for Grok's `nonce` parameter).
|
|
30
|
+
* @param bytes - entropy length; the hex string is twice as long.
|
|
31
|
+
* @returns hex-encoded random bytes.
|
|
32
|
+
*/
|
|
33
|
+
export function randomHex(bytes = 8) {
|
|
34
|
+
return randomBytes(bytes).toString('hex');
|
|
35
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `subscriptions-auth` host RPC endpoints the web Settings page drives.
|
|
3
|
+
* They are mounted as exact POST Fetch routes on the shared `/api` channel
|
|
4
|
+
* (`/api/subscriptions-auth.<endpoint>`) and registered only when a host
|
|
5
|
+
* `connection` service exists (the web profile); headless compositions load
|
|
6
|
+
* the plugin without it. All business outcomes are returned as RpcResult
|
|
7
|
+
* values; handlers never throw.
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
10
|
+
import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
|
|
11
|
+
import { type ProviderId } from './store.js';
|
|
12
|
+
import type { ProviderUsage } from '../providers/common.js';
|
|
13
|
+
import type { CliVersion } from '../providers/npm-cli-version.js';
|
|
14
|
+
import type { ExternalUsageSource, ExternalUsageStatus } from '../providers/external-usage-controller.js';
|
|
15
|
+
import type { CursorAuthStatus } from '../providers/cursor-auth.js';
|
|
16
|
+
/**
|
|
17
|
+
* Endpoint-name prefix under the shared `/api` channel: endpoint `status`
|
|
18
|
+
* is served at `/api/subscriptions-auth.status`. The browser half calls
|
|
19
|
+
* `rpc.call('/api', 'subscriptions-auth.status', payload)`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const SUBSCRIPTIONS_AUTH_PREFIX = "subscriptions-auth.";
|
|
22
|
+
/**
|
|
23
|
+
* Every endpoint {@link dispatch} answers; each gets one exact Fetch route.
|
|
24
|
+
* Kept in one place so the route table and the switch cannot drift apart.
|
|
25
|
+
*/
|
|
26
|
+
export declare const SUBSCRIPTIONS_AUTH_ENDPOINTS: readonly ["providerSettings", "setProviderSettings", "status", "login", "manual", "cancel", "logout", "setDefault", "usage", "externalStatus", "externalUsage", "cursorStatus", "cursorLogin", "cursorCancel", "cursorLogout", "cursorUsage", "cursorModels", "cursorSettings", "cursorSetSettings", "image", "video", "speed", "setSpeed", "modelDefaults", "setModelDefault"];
|
|
27
|
+
/** Decoded image bytes returned by the `image` endpoint. */
|
|
28
|
+
export interface ImageBytesResult {
|
|
29
|
+
mediaType: string;
|
|
30
|
+
dataBase64: string;
|
|
31
|
+
}
|
|
32
|
+
/** Decoded video bytes returned by the `video` endpoint. */
|
|
33
|
+
export interface VideoBytesResult {
|
|
34
|
+
mediaType: string;
|
|
35
|
+
dataBase64: string;
|
|
36
|
+
}
|
|
37
|
+
/** One session's speed choice: standard routing or the fast (priority) tier. */
|
|
38
|
+
export type SpeedTier = 'standard' | 'fast';
|
|
39
|
+
/** `speed` endpoint value: the session's choice plus the visibility list. */
|
|
40
|
+
export interface SpeedState {
|
|
41
|
+
/** The session's current speed tier (default `standard`). */
|
|
42
|
+
tier: SpeedTier;
|
|
43
|
+
/** Codex model ids whose catalog advertises a fast tier. */
|
|
44
|
+
fastModels: string[];
|
|
45
|
+
}
|
|
46
|
+
/** Speed state the RPC handler delegates to (in-memory, per session). */
|
|
47
|
+
export interface SpeedController {
|
|
48
|
+
/** Current speed state: the session's tier and the fast-capable codex models. */
|
|
49
|
+
speed(sessionId: string): Promise<SpeedState>;
|
|
50
|
+
/** Set one session's speed tier. */
|
|
51
|
+
setSpeed(sessionId: string, tier: SpeedTier): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
/** One logged-in account, as rendered by the Settings page. */
|
|
54
|
+
export interface AccountStatus {
|
|
55
|
+
/** Stable account key (store identity). */
|
|
56
|
+
key: string;
|
|
57
|
+
/** Display identity (email / login), when known. */
|
|
58
|
+
account?: string;
|
|
59
|
+
/** Epoch milliseconds at which the stored access token expires. */
|
|
60
|
+
expiresAt?: number;
|
|
61
|
+
/** Plan name the session carries (codex planType / claude subscriptionType), when known. */
|
|
62
|
+
plan?: string;
|
|
63
|
+
/** Whether direct (non-pool) routes serve this account. */
|
|
64
|
+
isDefault: boolean;
|
|
65
|
+
}
|
|
66
|
+
/** Login state of one provider, as rendered by the Settings page. */
|
|
67
|
+
export interface ProviderStatus {
|
|
68
|
+
/** Whether a login attempt is currently waiting for its code. */
|
|
69
|
+
busy: boolean;
|
|
70
|
+
/** Logged-in accounts, default first. */
|
|
71
|
+
accounts: AccountStatus[];
|
|
72
|
+
/** The last login error, shown until the next success. */
|
|
73
|
+
detail?: string;
|
|
74
|
+
/** The CLI version this route presents (Codex, Claude), and where it came from. */
|
|
75
|
+
clientVersion?: CliVersion;
|
|
76
|
+
}
|
|
77
|
+
/** How a Claude login should acquire credentials (other providers ignore it). */
|
|
78
|
+
export type LoginMethod = 'oauth' | 'keychain';
|
|
79
|
+
/** One model's default-effort picker state, as rendered by the Settings page. */
|
|
80
|
+
export interface ModelDefaultView {
|
|
81
|
+
/** Wire model id. */
|
|
82
|
+
id: string;
|
|
83
|
+
/** Human-readable display name. */
|
|
84
|
+
name: string;
|
|
85
|
+
/** Advertised effort levels, in catalog order (empty when the model has no reasoning). */
|
|
86
|
+
efforts: {
|
|
87
|
+
id: string;
|
|
88
|
+
name: string;
|
|
89
|
+
}[];
|
|
90
|
+
/** The user-configured default effort, when set. */
|
|
91
|
+
configured?: string;
|
|
92
|
+
}
|
|
93
|
+
/** One provider's default-effort picker state. */
|
|
94
|
+
export interface ModelDefaultsCatalog {
|
|
95
|
+
/** The subscription provider route. */
|
|
96
|
+
provider: ProviderId;
|
|
97
|
+
/** Models the picker can configure, in catalog order. */
|
|
98
|
+
models: ModelDefaultView[];
|
|
99
|
+
}
|
|
100
|
+
/** Per-account catalog returned by providerSettings, including providers without usage APIs. */
|
|
101
|
+
export interface ProviderAccountCatalog {
|
|
102
|
+
key: string;
|
|
103
|
+
/** Original identity; the editable alias lives in settings.accounts[key].alias. */
|
|
104
|
+
label: string;
|
|
105
|
+
models: {
|
|
106
|
+
id: string;
|
|
107
|
+
name: string;
|
|
108
|
+
}[];
|
|
109
|
+
unavailable?: boolean;
|
|
110
|
+
}
|
|
111
|
+
/** Provider and account preference operations behind providerSettings/setProviderSettings. */
|
|
112
|
+
export interface ProviderSettingsController {
|
|
113
|
+
get(provider: ProviderId, force: boolean): Promise<unknown>;
|
|
114
|
+
set(provider: ProviderId, settings: unknown): Promise<void>;
|
|
115
|
+
}
|
|
116
|
+
export interface ModelDefaultsController {
|
|
117
|
+
/** Per-provider picker state for the Settings page. */
|
|
118
|
+
catalog(force?: boolean): Promise<ModelDefaultsCatalog[]>;
|
|
119
|
+
/** Set one model's configured default effort; undefined clears the override. */
|
|
120
|
+
set(provider: ProviderId, model: string, effort: string | undefined): Promise<void>;
|
|
121
|
+
}
|
|
122
|
+
/** Provider-agnostic auth operations the RPC handler delegates to. */
|
|
123
|
+
export interface AuthController {
|
|
124
|
+
/** Current status of one provider. */
|
|
125
|
+
status(provider: ProviderId): Promise<ProviderStatus>;
|
|
126
|
+
/**
|
|
127
|
+
* Start a background login attempt.
|
|
128
|
+
* @param provider - the provider route.
|
|
129
|
+
* @param method - Claude only: force the OAuth browser flow or the Claude
|
|
130
|
+
* Code credential import; omitted keeps the auto behavior (import when
|
|
131
|
+
* available, else OAuth).
|
|
132
|
+
* @returns the authorize URL for the user's browser; device-flow providers
|
|
133
|
+
* (copilot) also return the `userCode` the user types at that URL.
|
|
134
|
+
* @throws when an attempt is already running for this provider.
|
|
135
|
+
*/
|
|
136
|
+
login(provider: ProviderId, method?: LoginMethod): Promise<{
|
|
137
|
+
authorizeUrl: string;
|
|
138
|
+
userCode?: string;
|
|
139
|
+
}>;
|
|
140
|
+
/**
|
|
141
|
+
* Feed a pasted callback URL or bare code into the pending attempt.
|
|
142
|
+
* @throws when no attempt is pending or the input is unusable.
|
|
143
|
+
*/
|
|
144
|
+
manual(provider: ProviderId, input: string): Promise<void>;
|
|
145
|
+
/** Abort the pending attempt; a no-op when none is pending. */
|
|
146
|
+
cancel(provider: ProviderId): Promise<void>;
|
|
147
|
+
/** Delete one account's stored session. */
|
|
148
|
+
logout(provider: ProviderId, account: string): Promise<void>;
|
|
149
|
+
/** Pin the account direct (non-pool) routes serve. */
|
|
150
|
+
setDefault(provider: ProviderId, account: string): Promise<void>;
|
|
151
|
+
/**
|
|
152
|
+
* Current subscription usage of one account.
|
|
153
|
+
* @param signal - caller cancellation from the RPC transport.
|
|
154
|
+
* @param force - bypass a fresh cached snapshot for an honest re-check
|
|
155
|
+
* (the manual Refresh button); a live failure cooldown still applies.
|
|
156
|
+
* @returns `{ supported: false }` when the provider has no usage endpoint.
|
|
157
|
+
* @throws when logged out or the usage lookup fails.
|
|
158
|
+
*/
|
|
159
|
+
usage(provider: ProviderId, account: string, signal: AbortSignal, force?: boolean): Promise<ProviderUsage>;
|
|
160
|
+
/**
|
|
161
|
+
* Read one image attachment's bytes for inline display.
|
|
162
|
+
* @param ref - the full durable reference (`readImage` verifies against it).
|
|
163
|
+
* @param signal - caller cancellation from the RPC transport.
|
|
164
|
+
* @returns the media type and base64-encoded bytes.
|
|
165
|
+
* @throws when no attachment service is mounted or the read fails.
|
|
166
|
+
*/
|
|
167
|
+
readImage(ref: ImageAttachmentRef, signal: AbortSignal): Promise<ImageBytesResult>;
|
|
168
|
+
/**
|
|
169
|
+
* Read one generated video's bytes for inline playback.
|
|
170
|
+
* @param name - bare MP4 file name inside the plugin's videos directory
|
|
171
|
+
* (validated against {@link VIDEO_NAME_PATTERN}; never a path).
|
|
172
|
+
* @param signal - caller cancellation from the RPC transport.
|
|
173
|
+
* @returns the media type and base64-encoded bytes.
|
|
174
|
+
* @throws when the file does not exist or cannot be read.
|
|
175
|
+
*/
|
|
176
|
+
readVideo(name: string, signal: AbortSignal): Promise<VideoBytesResult>;
|
|
177
|
+
}
|
|
178
|
+
/** Payload carried no usable provider id — an RPC client bug, not a server failure. */
|
|
179
|
+
export declare class BadRequest extends Error {
|
|
180
|
+
}
|
|
181
|
+
/** Credential-backed quota readers for model providers already in DSH. */
|
|
182
|
+
export interface ExternalUsageRpcController {
|
|
183
|
+
status(): Promise<Record<ExternalUsageSource, ExternalUsageStatus>>;
|
|
184
|
+
usage(source: ExternalUsageSource, signal: AbortSignal): Promise<ProviderUsage>;
|
|
185
|
+
}
|
|
186
|
+
/** Cursor account actions; OAuth tokens remain in the host credential service. */
|
|
187
|
+
export interface CursorRpcController {
|
|
188
|
+
status(): Promise<CursorAuthStatus>;
|
|
189
|
+
login(): Promise<{
|
|
190
|
+
authorizeUrl: string;
|
|
191
|
+
}>;
|
|
192
|
+
cancel(): Promise<void>;
|
|
193
|
+
logout(): Promise<void>;
|
|
194
|
+
usage(signal: AbortSignal): Promise<ProviderUsage>;
|
|
195
|
+
models(force: boolean, signal: AbortSignal): Promise<{
|
|
196
|
+
id: string;
|
|
197
|
+
name: string;
|
|
198
|
+
}[]>;
|
|
199
|
+
settings(force: boolean, signal: AbortSignal): Promise<unknown>;
|
|
200
|
+
setSettings(input: unknown): Promise<void>;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Register the `/subscriptions-auth` RPC channel when a host connection exists.
|
|
204
|
+
* @param ctx - the plugin context (headless profiles have no `connection`).
|
|
205
|
+
* @param controller - the auth operations backing the endpoints.
|
|
206
|
+
* @param speed - the per-session speed-tier state backing the Speed toggle.
|
|
207
|
+
* @param modelDefaults - optional per-model default-effort state backing `modelDefaults`/`setModelDefault`.
|
|
208
|
+
*/
|
|
209
|
+
export declare function registerAuthRpc(ctx: Context, controller: AuthController, speed: SpeedController, modelDefaults?: ModelDefaultsController | undefined, providerSettings?: ProviderSettingsController | undefined, externalUsage?: ExternalUsageRpcController | undefined, cursor?: CursorRpcController | undefined): void;
|