dsh-plugin-subscriptions 0.5.1 → 0.5.3
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/README.md +42 -1
- package/README.zh.md +42 -1
- package/lib/auth/device-flow.d.ts +0 -9
- package/lib/auth/device-flow.js +2 -1
- package/lib/auth/rpc.d.ts +44 -13
- package/lib/auth/rpc.js +127 -9
- package/lib/auth/store.d.ts +75 -17
- package/lib/auth/store.js +148 -27
- package/lib/client/SubscriptionsSection.d.ts +26 -3
- package/lib/client/SubscriptionsSection.js +263 -67
- package/lib/client/index.js +11 -0
- package/lib/client/locales.d.ts +82 -10
- package/lib/client/locales.js +82 -10
- package/lib/client.js +837 -223
- package/lib/client.js.map +1 -1
- package/lib/http.d.ts +114 -0
- package/lib/http.js +402 -0
- package/lib/index.d.ts +21 -0
- package/lib/index.js +1938 -208
- package/lib/providers/accounts.d.ts +102 -0
- package/lib/providers/accounts.js +123 -0
- package/lib/providers/antigravity.d.ts +90 -0
- package/lib/providers/antigravity.js +392 -0
- package/lib/providers/claude.d.ts +22 -4
- package/lib/providers/claude.js +97 -16
- package/lib/providers/codex.d.ts +24 -3
- package/lib/providers/codex.js +121 -21
- package/lib/providers/common.d.ts +17 -0
- package/lib/providers/common.js +67 -3
- package/lib/providers/copilot.d.ts +23 -4
- package/lib/providers/copilot.js +99 -19
- package/lib/providers/grok.d.ts +24 -4
- package/lib/providers/grok.js +106 -19
- 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 +148 -0
- package/lib/providers/pool-usage.d.ts +57 -0
- package/lib/providers/pool-usage.js +130 -0
- package/lib/providers/pool.d.ts +107 -0
- package/lib/providers/pool.js +371 -0
- package/lib/tools/image-generate.d.ts +3 -3
- package/lib/tools/image-generate.js +4 -2
- package/lib/tools/video-generate.d.ts +2 -2
- package/lib/tools/video-generate.js +4 -2
- package/lib/tools/x-search.d.ts +2 -2
- package/lib/tools/x-search.js +4 -2
- package/lib/translate/antigravity.d.ts +110 -0
- package/lib/translate/antigravity.js +303 -0
- package/package.json +14 -9
package/lib/providers/codex.d.ts
CHANGED
|
@@ -7,9 +7,10 @@ import { LlmAdapter } from '@deepseek-ai/dsh-llm';
|
|
|
7
7
|
import type { GenerateOptions, LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
8
8
|
import type { FlowSpec } from '../auth/oauth-flow.js';
|
|
9
9
|
import type { CodexSession } from '../auth/store.js';
|
|
10
|
+
import type { PoolAdapter } from './pool.js';
|
|
10
11
|
import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
|
|
11
12
|
import type { ResponsesRequestInput } from '../translate/responses.js';
|
|
12
|
-
import {
|
|
13
|
+
import { AccountTokenManager } from './accounts.js';
|
|
13
14
|
import type { CatalogPersistence, DiscoveredModel, FetchFn, ModelEntry, ProviderUsage } from './common.js';
|
|
14
15
|
export declare const CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
15
16
|
export declare const CODEX_AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
|
|
@@ -90,14 +91,17 @@ export declare const CODEX_CLIENT_VERSION = "0.147.0";
|
|
|
90
91
|
* Fetch the live codex model catalog with the session's auth headers.
|
|
91
92
|
* @param session - the stored session (used as-is; never refreshed here).
|
|
92
93
|
* @param fetchFn - fetch implementation (injectable for tests).
|
|
94
|
+
* @param signal - caller cancellation (pool-assembly timeout).
|
|
93
95
|
* @returns discovered models: hidden entries dropped, sorted by priority.
|
|
94
96
|
*/
|
|
95
|
-
export declare function fetchCodexModels(session: CodexSession, fetchFn?: FetchFn): Promise<DiscoveredModel[]>;
|
|
97
|
+
export declare function fetchCodexModels(session: CodexSession, fetchFn?: FetchFn, signal?: AbortSignal): Promise<DiscoveredModel[]>;
|
|
96
98
|
/** Constructor dependencies for {@link CodexAdapter}. */
|
|
97
99
|
export interface CodexAdapterOptions {
|
|
98
100
|
models: readonly ModelEntry[];
|
|
99
101
|
streamIdleTimeoutMs: number;
|
|
100
|
-
tokens:
|
|
102
|
+
tokens: AccountTokenManager<CodexSession>;
|
|
103
|
+
/** Late-bound pool facade (wired after adapter construction); pools list under their first member's provider. */
|
|
104
|
+
pool?: () => PoolAdapter | undefined;
|
|
101
105
|
/** Whether to fetch the live catalog when logged in (false when config `models` overrides). */
|
|
102
106
|
discovery: boolean;
|
|
103
107
|
/** Warning sink for discovery failures that fall back to the static catalog. */
|
|
@@ -108,6 +112,8 @@ export interface CodexAdapterOptions {
|
|
|
108
112
|
resolveAttachments?: () => AttachmentStore | undefined;
|
|
109
113
|
/** Durable catalog store seeding capability metadata across restarts. */
|
|
110
114
|
catalogStore?: CatalogPersistence;
|
|
115
|
+
/** Per-account catalog bound for the picker union (defaults to {@link DISCOVERY_TIMEOUT_MS}). */
|
|
116
|
+
discoveryTimeoutMs?: number;
|
|
111
117
|
/**
|
|
112
118
|
* Per-request speed lookup (the composer Speed toggle's host half). Returns
|
|
113
119
|
* whether this session's current choice sends the model on the fast tier;
|
|
@@ -126,12 +132,22 @@ export declare function codexRequestBody(options: GenerateOptions, resolved: Res
|
|
|
126
132
|
export declare class CodexAdapter extends LlmAdapter {
|
|
127
133
|
private readonly options;
|
|
128
134
|
private readonly catalog;
|
|
135
|
+
/** In-memory catalogs for non-default accounts (the persisted cache is the default's). */
|
|
136
|
+
private readonly accountCatalogs;
|
|
137
|
+
/** Account whose snapshot currently lives in {@link catalog}; cleared on default change. */
|
|
138
|
+
private catalogOwner;
|
|
129
139
|
constructor(options: CodexAdapterOptions);
|
|
130
140
|
/** Discovery fetcher: resolves the session through the refresh-aware path. */
|
|
131
141
|
private fetchCatalog;
|
|
142
|
+
/** Drop cached catalogs after login/logout so the next list does not reuse a stale plan. */
|
|
143
|
+
clearAccountCatalog(account?: string): void;
|
|
144
|
+
/** Persisted cache for the default account; a throwaway cache for any other. */
|
|
145
|
+
private catalogFor;
|
|
132
146
|
providerInfo(provider: string): LlmProviderInfo;
|
|
133
147
|
private staticModels;
|
|
134
148
|
listModels(provider: string): Promise<readonly LlmModelInfo[]>;
|
|
149
|
+
/** The provider's own catalog: union of every account, or one account when named. */
|
|
150
|
+
listOwnModels(provider: string, account?: string, signal?: AbortSignal): Promise<readonly LlmModelInfo[]>;
|
|
135
151
|
/**
|
|
136
152
|
* The discovered entry for one model. Resolved through the cache's
|
|
137
153
|
* stale-while-revalidate path so capability metadata stays stable across a
|
|
@@ -145,6 +161,11 @@ export declare class CodexAdapter extends LlmAdapter {
|
|
|
145
161
|
/** Ids of every discovered model with a fast tier (the Speed toggle's visibility list). */
|
|
146
162
|
fastCapableModels(): Promise<string[]>;
|
|
147
163
|
resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo>;
|
|
164
|
+
/** Capability resolution of the provider's own models (the pool resolves members here). */
|
|
165
|
+
resolveOwnModel(provider: string, model: string): Promise<LlmResolvedModelInfo>;
|
|
148
166
|
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
167
|
+
/** Pool seam: stream through one specific account instead of the default. */
|
|
168
|
+
streamAccount(options: GenerateOptions, account: string): AsyncIterable<StreamChunk>;
|
|
169
|
+
private streamCore;
|
|
149
170
|
private request;
|
|
150
171
|
}
|
package/lib/providers/codex.js
CHANGED
|
@@ -8,7 +8,9 @@ import { attributionHeaders, EMPTY_RESPONSE_CODE, errorChain, LlmAdapter, LlmErr
|
|
|
8
8
|
import { decodeJwtPayload } from '../auth/jwt.js';
|
|
9
9
|
import { resolveImages } from '../translate/resolved.js';
|
|
10
10
|
import { streamResponses, toResponsesInput, toResponsesTools } from '../translate/responses.js';
|
|
11
|
-
import { httpLlmError, idleWatchdog, mapFetchFailure, ModelCatalogCache, discoverOrRetryAuth, isMissingOrInvalidCredential, oauthEndpointError, OAuthEndpointError,
|
|
11
|
+
import { httpLlmError, idleWatchdog, mapFetchFailure, ModelCatalogCache, discoverAcrossAccounts, discoverOrRetryAuth, isDiscoveryAborted, isMissingOrInvalidCredential, oauthEndpointError, OAuthEndpointError, } from './common.js';
|
|
12
|
+
import { AccountTokenManager, DISCOVERY_TIMEOUT_MS, unionAccountCatalogs } from './accounts.js';
|
|
13
|
+
import { proxiedFetch } from '../http.js';
|
|
12
14
|
export const CODEX_CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';
|
|
13
15
|
export const CODEX_AUTHORIZE_URL = 'https://auth.openai.com/oauth/authorize';
|
|
14
16
|
export const CODEX_TOKEN_URL = 'https://auth.openai.com/oauth/token';
|
|
@@ -152,7 +154,7 @@ function codexSession(tokens, fallback) {
|
|
|
152
154
|
* @returns the session to store.
|
|
153
155
|
*/
|
|
154
156
|
export async function exchangeCodexCode(code, verifier, redirectUri) {
|
|
155
|
-
const response = await
|
|
157
|
+
const response = await proxiedFetch(CODEX_TOKEN_URL, {
|
|
156
158
|
method: 'POST',
|
|
157
159
|
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
158
160
|
body: new URLSearchParams({
|
|
@@ -173,7 +175,7 @@ export async function exchangeCodexCode(code, verifier, redirectUri) {
|
|
|
173
175
|
* @returns the fresh session to store.
|
|
174
176
|
*/
|
|
175
177
|
export async function refreshCodex(session) {
|
|
176
|
-
const response = await
|
|
178
|
+
const response = await proxiedFetch(CODEX_TOKEN_URL, {
|
|
177
179
|
method: 'POST',
|
|
178
180
|
headers: { 'content-type': 'application/json' },
|
|
179
181
|
body: JSON.stringify({
|
|
@@ -253,7 +255,7 @@ function codexUsageWindow(value, fallbackKind) {
|
|
|
253
255
|
* @param signal - caller cancellation from the RPC transport.
|
|
254
256
|
* @returns the mapped usage snapshot.
|
|
255
257
|
*/
|
|
256
|
-
export async function fetchCodexUsage(session, fetchFn =
|
|
258
|
+
export async function fetchCodexUsage(session, fetchFn = proxiedFetch, signal) {
|
|
257
259
|
const response = await fetchFn(CODEX_USAGE_URL, {
|
|
258
260
|
headers: {
|
|
259
261
|
'authorization': `Bearer ${session.accessToken}`,
|
|
@@ -307,9 +309,10 @@ function supportsFastTier(entry) {
|
|
|
307
309
|
* Fetch the live codex model catalog with the session's auth headers.
|
|
308
310
|
* @param session - the stored session (used as-is; never refreshed here).
|
|
309
311
|
* @param fetchFn - fetch implementation (injectable for tests).
|
|
312
|
+
* @param signal - caller cancellation (pool-assembly timeout).
|
|
310
313
|
* @returns discovered models: hidden entries dropped, sorted by priority.
|
|
311
314
|
*/
|
|
312
|
-
export async function fetchCodexModels(session, fetchFn =
|
|
315
|
+
export async function fetchCodexModels(session, fetchFn = proxiedFetch, signal) {
|
|
313
316
|
const url = `${CODEX_MODELS_URL}?client_version=${CODEX_CLIENT_VERSION}`;
|
|
314
317
|
const response = await fetchFn(url, {
|
|
315
318
|
headers: {
|
|
@@ -319,6 +322,7 @@ export async function fetchCodexModels(session, fetchFn = fetch) {
|
|
|
319
322
|
'accept': 'application/json',
|
|
320
323
|
...attributionHeaders(),
|
|
321
324
|
},
|
|
325
|
+
...signal === undefined ? {} : { signal },
|
|
322
326
|
});
|
|
323
327
|
if (!response.ok)
|
|
324
328
|
throw await oauthEndpointError(response, 'codex models');
|
|
@@ -450,14 +454,47 @@ export function codexRequestBody(options, resolved, fast) {
|
|
|
450
454
|
export class CodexAdapter extends LlmAdapter {
|
|
451
455
|
options;
|
|
452
456
|
catalog;
|
|
457
|
+
/** In-memory catalogs for non-default accounts (the persisted cache is the default's). */
|
|
458
|
+
accountCatalogs = new Map();
|
|
459
|
+
/** Account whose snapshot currently lives in {@link catalog}; cleared on default change. */
|
|
460
|
+
catalogOwner;
|
|
453
461
|
constructor(options) {
|
|
454
462
|
super();
|
|
455
463
|
this.options = options;
|
|
456
464
|
this.catalog = new ModelCatalogCache(options.catalogStore);
|
|
457
465
|
}
|
|
458
466
|
/** Discovery fetcher: resolves the session through the refresh-aware path. */
|
|
459
|
-
async fetchCatalog() {
|
|
460
|
-
return fetchCodexModels(await this.options.tokens.session(), this.options.fetchFn);
|
|
467
|
+
async fetchCatalog(account, signal) {
|
|
468
|
+
return fetchCodexModels(await this.options.tokens.session(account), this.options.fetchFn, signal);
|
|
469
|
+
}
|
|
470
|
+
/** Drop cached catalogs after login/logout so the next list does not reuse a stale plan. */
|
|
471
|
+
clearAccountCatalog(account) {
|
|
472
|
+
if (account === undefined)
|
|
473
|
+
this.accountCatalogs.clear();
|
|
474
|
+
else
|
|
475
|
+
this.accountCatalogs.delete(account);
|
|
476
|
+
if (account === undefined || this.catalogOwner === account || this.catalogOwner === undefined) {
|
|
477
|
+
this.catalogOwner = undefined;
|
|
478
|
+
this.catalog.invalidate();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/** Persisted cache for the default account; a throwaway cache for any other. */
|
|
482
|
+
async catalogFor(account) {
|
|
483
|
+
const defaultKey = await this.options.tokens.defaultAccount();
|
|
484
|
+
const key = account ?? defaultKey;
|
|
485
|
+
if (key === undefined || key === defaultKey) {
|
|
486
|
+
if (this.catalogOwner !== undefined && this.catalogOwner !== defaultKey) {
|
|
487
|
+
this.catalog.invalidate();
|
|
488
|
+
}
|
|
489
|
+
this.catalogOwner = defaultKey;
|
|
490
|
+
return this.catalog;
|
|
491
|
+
}
|
|
492
|
+
let cache = this.accountCatalogs.get(key);
|
|
493
|
+
if (cache === undefined) {
|
|
494
|
+
cache = new ModelCatalogCache();
|
|
495
|
+
this.accountCatalogs.set(key, cache);
|
|
496
|
+
}
|
|
497
|
+
return cache;
|
|
461
498
|
}
|
|
462
499
|
providerInfo(provider) {
|
|
463
500
|
return { id: provider, name: 'ChatGPT (Codex)' };
|
|
@@ -471,26 +508,48 @@ export class CodexAdapter extends LlmAdapter {
|
|
|
471
508
|
}));
|
|
472
509
|
}
|
|
473
510
|
async listModels(provider) {
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
if (
|
|
511
|
+
const own = await this.listOwnModels(provider);
|
|
512
|
+
const pool = this.options.pool?.();
|
|
513
|
+
if (pool === undefined)
|
|
514
|
+
return own;
|
|
515
|
+
const extra = await pool.modelsForProvider(provider);
|
|
516
|
+
const seen = new Set(own.map(model => model.id));
|
|
517
|
+
// Account pools reuse the catalog row; only configured tiers are extra.
|
|
518
|
+
return [...own, ...extra.filter(model => !seen.has(model.id))];
|
|
519
|
+
}
|
|
520
|
+
/** The provider's own catalog: union of every account, or one account when named. */
|
|
521
|
+
async listOwnModels(provider, account, signal) {
|
|
522
|
+
if (account === undefined) {
|
|
523
|
+
const accounts = (await this.options.tokens.list()).map(entry => entry.key);
|
|
524
|
+
if (accounts.length === 0)
|
|
525
|
+
return [];
|
|
526
|
+
return unionAccountCatalogs(accounts, (key, accountSignal) => this.listOwnModels(provider, key, accountSignal), { timeoutMs: this.options.discoveryTimeoutMs ?? DISCOVERY_TIMEOUT_MS, ...signal === undefined ? {} : { signal } });
|
|
527
|
+
}
|
|
528
|
+
if (!await this.options.tokens.hasSession(account)) {
|
|
477
529
|
return [];
|
|
530
|
+
}
|
|
478
531
|
if (!this.options.discovery)
|
|
479
532
|
return this.staticModels(provider);
|
|
533
|
+
const catalog = await this.catalogFor(account);
|
|
480
534
|
try {
|
|
481
535
|
// The fetcher runs only on a cache miss, and resolves the session
|
|
482
536
|
// through the refresh-aware path so an expired access token renews here
|
|
483
537
|
// instead of failing discovery into the static fallback.
|
|
484
|
-
const discovered = await discoverOrRetryAuth(force => this.options.tokens.session(force),
|
|
538
|
+
const discovered = await discoverOrRetryAuth(force => this.options.tokens.session(account, force), catalog, () => catalog.get(() => this.fetchCatalog(account, signal)));
|
|
485
539
|
return discovered.map(model => ({
|
|
486
540
|
provider,
|
|
487
541
|
id: model.id,
|
|
488
542
|
name: model.name,
|
|
489
543
|
...model.description === undefined ? {} : { description: model.description },
|
|
490
544
|
inputModalities: CODEX_MODALITIES,
|
|
545
|
+
...model.priority === undefined ? {} : { priority: model.priority },
|
|
491
546
|
}));
|
|
492
547
|
}
|
|
493
548
|
catch (error) {
|
|
549
|
+
// A cancelled discovery must not fall back to the static catalog — the
|
|
550
|
+
// caller (pool assembly) treats abort as "this account sits out".
|
|
551
|
+
if (isDiscoveryAborted(error, signal))
|
|
552
|
+
throw error;
|
|
494
553
|
// A permanent refresh failure deletes the stored session: the provider
|
|
495
554
|
// is logged out, so hide it instead of showing a stale static catalog.
|
|
496
555
|
if (isMissingOrInvalidCredential(error))
|
|
@@ -509,8 +568,12 @@ export class CodexAdapter extends LlmAdapter {
|
|
|
509
568
|
async discovered(model) {
|
|
510
569
|
if (!this.options.discovery)
|
|
511
570
|
return undefined;
|
|
512
|
-
const
|
|
513
|
-
return
|
|
571
|
+
const accounts = (await this.options.tokens.list()).map(entry => entry.key);
|
|
572
|
+
return discoverAcrossAccounts(accounts, async (account) => {
|
|
573
|
+
const catalog = await this.catalogFor(account);
|
|
574
|
+
const models = await catalog.resolve(() => this.fetchCatalog(account));
|
|
575
|
+
return models?.find(entry => entry.id === model);
|
|
576
|
+
});
|
|
514
577
|
}
|
|
515
578
|
/** Whether the discovered catalog advertises a fast tier for this model. */
|
|
516
579
|
async supportsFastTier(model) {
|
|
@@ -521,14 +584,39 @@ export class CodexAdapter extends LlmAdapter {
|
|
|
521
584
|
if (!this.options.discovery)
|
|
522
585
|
return [];
|
|
523
586
|
// Not logged in → no fast models, so the Speed toggle hides after logout
|
|
524
|
-
// (mirrors the listModels guard above).
|
|
525
|
-
|
|
526
|
-
|
|
587
|
+
// (mirrors the listModels guard above). Union every account: a fast-capable
|
|
588
|
+
// model only the non-default lists (e.g. gpt-5.6-sol) must still show Speed.
|
|
589
|
+
const accounts = (await this.options.tokens.list()).map(entry => entry.key);
|
|
590
|
+
if (accounts.length === 0)
|
|
527
591
|
return [];
|
|
528
|
-
const
|
|
529
|
-
|
|
592
|
+
const seen = new Set();
|
|
593
|
+
const ids = [];
|
|
594
|
+
for (const account of accounts) {
|
|
595
|
+
try {
|
|
596
|
+
const catalog = await this.catalogFor(account);
|
|
597
|
+
const models = await catalog.resolve(() => this.fetchCatalog(account));
|
|
598
|
+
for (const model of models ?? []) {
|
|
599
|
+
if (model.fastTier !== true || seen.has(model.id))
|
|
600
|
+
continue;
|
|
601
|
+
seen.add(model.id);
|
|
602
|
+
ids.push(model.id);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
catch {
|
|
606
|
+
// sit out
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return ids;
|
|
530
610
|
}
|
|
531
611
|
async resolveModel(provider, model) {
|
|
612
|
+
const pool = this.options.pool?.();
|
|
613
|
+
if (pool !== undefined && await pool.owns(provider, model)) {
|
|
614
|
+
return pool.resolveModel(provider, model);
|
|
615
|
+
}
|
|
616
|
+
return this.resolveOwnModel(provider, model);
|
|
617
|
+
}
|
|
618
|
+
/** Capability resolution of the provider's own models (the pool resolves members here). */
|
|
619
|
+
async resolveOwnModel(provider, model) {
|
|
532
620
|
// Discovered metadata (when discovery is on) wins over the static entry;
|
|
533
621
|
// the static entry wins over the built-in defaults.
|
|
534
622
|
const discovered = await this.discovered(model);
|
|
@@ -545,13 +633,25 @@ export class CodexAdapter extends LlmAdapter {
|
|
|
545
633
|
};
|
|
546
634
|
}
|
|
547
635
|
async *stream(options) {
|
|
636
|
+
const pool = this.options.pool?.();
|
|
637
|
+
if (pool !== undefined && await pool.owns(options.provider, options.model)) {
|
|
638
|
+
yield* pool.stream(options);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
yield* this.streamCore(options);
|
|
642
|
+
}
|
|
643
|
+
/** Pool seam: stream through one specific account instead of the default. */
|
|
644
|
+
streamAccount(options, account) {
|
|
645
|
+
return this.streamCore(options, account);
|
|
646
|
+
}
|
|
647
|
+
async *streamCore(options, account) {
|
|
548
648
|
const watchdog = idleWatchdog(options.signal, this.options.streamIdleTimeoutMs);
|
|
549
649
|
try {
|
|
550
|
-
let session = await this.options.tokens.session();
|
|
650
|
+
let session = await this.options.tokens.session(account);
|
|
551
651
|
let response = await this.request(options, session, watchdog.signal);
|
|
552
652
|
if (response.status === 401) {
|
|
553
653
|
// One forced refresh + retry on an unexpired-but-rejected token.
|
|
554
|
-
session = await this.options.tokens.session(true);
|
|
654
|
+
session = await this.options.tokens.session(account, true);
|
|
555
655
|
response = await this.request(options, session, watchdog.signal);
|
|
556
656
|
}
|
|
557
657
|
if (!response.ok)
|
|
@@ -573,7 +673,7 @@ export class CodexAdapter extends LlmAdapter {
|
|
|
573
673
|
const fast = this.options.speedFor !== undefined
|
|
574
674
|
&& await this.options.speedFor(options.sessionId, options.model);
|
|
575
675
|
const body = codexRequestBody(options, toResponsesInput(messages, options.system), fast);
|
|
576
|
-
return
|
|
676
|
+
return proxiedFetch(CODEX_API_URL, {
|
|
577
677
|
method: 'POST',
|
|
578
678
|
headers: {
|
|
579
679
|
'authorization': `Bearer ${session.accessToken}`,
|
|
@@ -141,6 +141,13 @@ export declare class TokenManager<S extends TimedSession> {
|
|
|
141
141
|
}
|
|
142
142
|
/** Fetch signature adapters accept for discovery calls (injectable for tests). */
|
|
143
143
|
export type FetchFn = typeof fetch;
|
|
144
|
+
/** Bound on one account catalog fetch or usage poll — a hang must not block the picker. */
|
|
145
|
+
export declare const DISCOVERY_TIMEOUT_MS = 10000;
|
|
146
|
+
/**
|
|
147
|
+
* Run `work` with an aborting signal. Resolves undefined when the timeout
|
|
148
|
+
* fires (the fetch is aborted); other failures propagate.
|
|
149
|
+
*/
|
|
150
|
+
export declare function withTimeout<T>(work: (signal: AbortSignal) => Promise<T>, timeoutMs: number): Promise<T | undefined>;
|
|
144
151
|
/** One rate-limit window reported by a provider's usage endpoint. */
|
|
145
152
|
export interface UsageWindow {
|
|
146
153
|
/** Window kind: `session` for the short rolling window, `weekly` for the 7-day one. */
|
|
@@ -196,6 +203,12 @@ export interface DiscoveredModel {
|
|
|
196
203
|
*/
|
|
197
204
|
copilotResponses?: boolean;
|
|
198
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* First account catalog that lists `model` (callers pass default-first).
|
|
208
|
+
* One failing lookup sits that account out so a sibling's metadata still
|
|
209
|
+
* resolves — the same isolation as the picker catalog union.
|
|
210
|
+
*/
|
|
211
|
+
export declare function discoverAcrossAccounts(accounts: readonly string[], lookup: (account: string) => Promise<DiscoveredModel | undefined>): Promise<DiscoveredModel | undefined>;
|
|
199
212
|
/** How long a discovered catalog is trusted before re-fetching. */
|
|
200
213
|
export declare const DISCOVERY_TTL_MS: number;
|
|
201
214
|
/** A durable snapshot of one provider's discovered catalog. */
|
|
@@ -234,6 +247,8 @@ export declare class ModelCatalogCache {
|
|
|
234
247
|
private seeded;
|
|
235
248
|
/** Set by {@link invalidate} so an in-flight disk read cannot resurrect dropped state. */
|
|
236
249
|
private seedDisabled;
|
|
250
|
+
/** Bumped by {@link invalidate} so a loser in-flight fetch cannot write back. */
|
|
251
|
+
private generation;
|
|
237
252
|
constructor(persistence?: CatalogPersistence | undefined, ttlMs?: number);
|
|
238
253
|
/**
|
|
239
254
|
* The cached catalog when fresh, without fetching.
|
|
@@ -272,6 +287,8 @@ export declare class ModelCatalogCache {
|
|
|
272
287
|
}
|
|
273
288
|
/** Whether discovery failed because the stored login is gone. */
|
|
274
289
|
export declare function isMissingOrInvalidCredential(error: unknown): boolean;
|
|
290
|
+
/** Whether discovery stopped because the caller cancelled or the timeout fired. */
|
|
291
|
+
export declare function isDiscoveryAborted(error: unknown, signal?: AbortSignal): boolean;
|
|
275
292
|
/**
|
|
276
293
|
* Run a catalog fetch, retrying once after a forced token refresh when the
|
|
277
294
|
* first attempt is a 401/AUTH. Only {@link ModelCatalogCache.invalidate}s
|
package/lib/providers/common.js
CHANGED
|
@@ -263,6 +263,47 @@ export class TokenManager {
|
|
|
263
263
|
return next;
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
|
+
/** Bound on one account catalog fetch or usage poll — a hang must not block the picker. */
|
|
267
|
+
export const DISCOVERY_TIMEOUT_MS = 10_000;
|
|
268
|
+
/**
|
|
269
|
+
* Run `work` with an aborting signal. Resolves undefined when the timeout
|
|
270
|
+
* fires (the fetch is aborted); other failures propagate.
|
|
271
|
+
*/
|
|
272
|
+
export function withTimeout(work, timeoutMs) {
|
|
273
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
274
|
+
const aborted = new Promise(resolve => {
|
|
275
|
+
if (signal.aborted)
|
|
276
|
+
resolve(undefined);
|
|
277
|
+
else
|
|
278
|
+
signal.addEventListener('abort', () => resolve(undefined), { once: true });
|
|
279
|
+
});
|
|
280
|
+
return Promise.race([
|
|
281
|
+
work(signal).then(value => (signal.aborted ? undefined : value), (error) => {
|
|
282
|
+
if (signal.aborted)
|
|
283
|
+
return undefined;
|
|
284
|
+
throw error;
|
|
285
|
+
}),
|
|
286
|
+
aborted,
|
|
287
|
+
]);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* First account catalog that lists `model` (callers pass default-first).
|
|
291
|
+
* One failing lookup sits that account out so a sibling's metadata still
|
|
292
|
+
* resolves — the same isolation as the picker catalog union.
|
|
293
|
+
*/
|
|
294
|
+
export async function discoverAcrossAccounts(accounts, lookup) {
|
|
295
|
+
for (const account of accounts) {
|
|
296
|
+
try {
|
|
297
|
+
const found = await lookup(account);
|
|
298
|
+
if (found !== undefined)
|
|
299
|
+
return found;
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// sit out
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
266
307
|
/** How long a discovered catalog is trusted before re-fetching. */
|
|
267
308
|
export const DISCOVERY_TTL_MS = 5 * 60_000;
|
|
268
309
|
/**
|
|
@@ -286,6 +327,8 @@ export class ModelCatalogCache {
|
|
|
286
327
|
seeded;
|
|
287
328
|
/** Set by {@link invalidate} so an in-flight disk read cannot resurrect dropped state. */
|
|
288
329
|
seedDisabled = false;
|
|
330
|
+
/** Bumped by {@link invalidate} so a loser in-flight fetch cannot write back. */
|
|
331
|
+
generation = 0;
|
|
289
332
|
constructor(persistence, ttlMs = DISCOVERY_TTL_MS) {
|
|
290
333
|
this.persistence = persistence;
|
|
291
334
|
this.ttlMs = ttlMs;
|
|
@@ -320,16 +363,25 @@ export class ModelCatalogCache {
|
|
|
320
363
|
}
|
|
321
364
|
/** Run (or join) the single in-flight fetch, updating memory and disk on success. */
|
|
322
365
|
refresh(fetcher) {
|
|
323
|
-
this.inflight
|
|
366
|
+
if (this.inflight !== undefined)
|
|
367
|
+
return this.inflight;
|
|
368
|
+
const gen = this.generation;
|
|
369
|
+
const pending = fetcher()
|
|
324
370
|
.then((models) => {
|
|
371
|
+
if (this.generation !== gen)
|
|
372
|
+
return models;
|
|
325
373
|
const snapshot = { at: Date.now(), models };
|
|
326
374
|
this.entry = snapshot;
|
|
327
375
|
// Write-through is fire-and-forget: a failed save only costs durability.
|
|
328
376
|
void this.persistence?.save(snapshot).catch(() => undefined);
|
|
329
377
|
return models;
|
|
330
378
|
})
|
|
331
|
-
.finally(() => {
|
|
332
|
-
|
|
379
|
+
.finally(() => {
|
|
380
|
+
if (this.generation === gen)
|
|
381
|
+
this.inflight = undefined;
|
|
382
|
+
});
|
|
383
|
+
this.inflight = pending;
|
|
384
|
+
return pending;
|
|
333
385
|
}
|
|
334
386
|
/**
|
|
335
387
|
* Return the cached catalog when fresh, otherwise fetch and cache it.
|
|
@@ -370,7 +422,9 @@ export class ModelCatalogCache {
|
|
|
370
422
|
}
|
|
371
423
|
/** Drop the cached catalog (e.g. after a 401 proved the credential changed). */
|
|
372
424
|
invalidate() {
|
|
425
|
+
this.generation += 1;
|
|
373
426
|
this.entry = undefined;
|
|
427
|
+
this.inflight = undefined;
|
|
374
428
|
this.seedDisabled = true;
|
|
375
429
|
void this.persistence?.clear().catch(() => undefined);
|
|
376
430
|
}
|
|
@@ -380,6 +434,16 @@ export function isMissingOrInvalidCredential(error) {
|
|
|
380
434
|
return error instanceof LlmError
|
|
381
435
|
&& (error.code === 'MISSING_CREDENTIAL' || error.code === 'INVALID_CREDENTIAL');
|
|
382
436
|
}
|
|
437
|
+
/** Whether discovery stopped because the caller cancelled or the timeout fired. */
|
|
438
|
+
export function isDiscoveryAborted(error, signal) {
|
|
439
|
+
if (signal?.aborted === true)
|
|
440
|
+
return true;
|
|
441
|
+
// Only treat abort-shaped errors as cancellation when this call had a signal;
|
|
442
|
+
// a refresh TimeoutError must not fail the whole picker union.
|
|
443
|
+
return signal !== undefined
|
|
444
|
+
&& error instanceof Error
|
|
445
|
+
&& (error.name === 'AbortError' || error.name === 'TimeoutError');
|
|
446
|
+
}
|
|
383
447
|
/** Whether discovery failed because the access token was rejected. */
|
|
384
448
|
function isDiscoveryAuthFailure(error) {
|
|
385
449
|
return (error instanceof OAuthEndpointError && error.status === 401)
|
|
@@ -17,9 +17,10 @@ import { LlmAdapter } from '@deepseek-ai/dsh-llm';
|
|
|
17
17
|
import type { GenerateOptions, LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
18
18
|
import type { DeviceFlowSpec } from '../auth/device-flow.js';
|
|
19
19
|
import type { CopilotSession } from '../auth/store.js';
|
|
20
|
+
import type { PoolAdapter } from './pool.js';
|
|
20
21
|
import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
|
|
21
22
|
import type { ReasoningReplayItem, ResponsesRequestInput, ResponsesStreamEvent } from '../translate/responses.js';
|
|
22
|
-
import {
|
|
23
|
+
import { AccountTokenManager } from './accounts.js';
|
|
23
24
|
import type { CatalogPersistence, DiscoveredModel, FetchFn, ModelEntry } from './common.js';
|
|
24
25
|
/**
|
|
25
26
|
* Client id of the VS Code Copilot Chat GitHub App (pi-mono and
|
|
@@ -118,9 +119,10 @@ export declare function isCopilotPermanentRefreshError(error: unknown): boolean;
|
|
|
118
119
|
* reasoning efforts (the endpoint discloses no default, so none is claimed).
|
|
119
120
|
* @param session - the stored session (used as-is; never refreshed here).
|
|
120
121
|
* @param fetchFn - fetch implementation (injectable for tests).
|
|
122
|
+
* @param signal - caller cancellation (pool-assembly timeout).
|
|
121
123
|
* @returns discovered chat models in endpoint order.
|
|
122
124
|
*/
|
|
123
|
-
export declare function fetchCopilotModels(session: CopilotSession, fetchFn?: FetchFn): Promise<DiscoveredModel[]>;
|
|
125
|
+
export declare function fetchCopilotModels(session: CopilotSession, fetchFn?: FetchFn, signal?: AbortSignal): Promise<DiscoveredModel[]>;
|
|
124
126
|
/** Which upstream protocol one Copilot model speaks. */
|
|
125
127
|
export type CopilotWire = 'chat-completions' | 'responses';
|
|
126
128
|
/**
|
|
@@ -215,12 +217,14 @@ export declare class CopilotResponsesItemNormalizer {
|
|
|
215
217
|
export interface CopilotAdapterOptions {
|
|
216
218
|
models: readonly ModelEntry[];
|
|
217
219
|
streamIdleTimeoutMs: number;
|
|
218
|
-
tokens:
|
|
220
|
+
tokens: AccountTokenManager<CopilotSession>;
|
|
221
|
+
/** Late-bound pool facade (wired after adapter construction); pools list under their first member's provider. */
|
|
222
|
+
pool?: () => PoolAdapter | undefined;
|
|
219
223
|
/** Whether to fetch the live catalog when logged in (false when config `models` overrides). */
|
|
220
224
|
discovery: boolean;
|
|
221
225
|
/** Warning sink for discovery failures that fall back to the static catalog. */
|
|
222
226
|
onWarn?: (message: string) => void;
|
|
223
|
-
/** Fetch implementation for discovery (defaults to
|
|
227
|
+
/** Fetch implementation for discovery (defaults to the proxy-aware fetch). */
|
|
224
228
|
fetchFn?: FetchFn;
|
|
225
229
|
/** Resolve the attachment service per request; absent means image requests fail loudly. */
|
|
226
230
|
resolveAttachments?: () => AttachmentStore | undefined;
|
|
@@ -231,6 +235,10 @@ export interface CopilotAdapterOptions {
|
|
|
231
235
|
export declare class CopilotAdapter extends LlmAdapter {
|
|
232
236
|
private readonly options;
|
|
233
237
|
private readonly catalog;
|
|
238
|
+
/** In-memory catalogs for non-default accounts (the persisted cache is the default's). */
|
|
239
|
+
private readonly accountCatalogs;
|
|
240
|
+
/** Account whose snapshot currently lives in {@link catalog}; cleared on default change. */
|
|
241
|
+
private catalogOwner;
|
|
234
242
|
/**
|
|
235
243
|
* [2026-08-23]-[a reasoning model continuing a tool chain must get its
|
|
236
244
|
* reasoning back or it restarts from scratch every tool round trip; the
|
|
@@ -250,9 +258,15 @@ export declare class CopilotAdapter extends LlmAdapter {
|
|
|
250
258
|
constructor(options: CopilotAdapterOptions);
|
|
251
259
|
/** Discovery fetcher: resolves the session through the refresh-aware path. */
|
|
252
260
|
private fetchCatalog;
|
|
261
|
+
/** Drop cached catalogs after login/logout so the next list does not reuse a stale plan. */
|
|
262
|
+
clearAccountCatalog(account?: string): void;
|
|
263
|
+
/** Persisted cache for the default account; a throwaway cache for any other. */
|
|
264
|
+
private catalogFor;
|
|
253
265
|
providerInfo(provider: string): LlmProviderInfo;
|
|
254
266
|
private staticModels;
|
|
255
267
|
listModels(provider: string): Promise<readonly LlmModelInfo[]>;
|
|
268
|
+
/** The provider's own catalog: union of every account, or one account when named. */
|
|
269
|
+
listOwnModels(provider: string, account?: string, signal?: AbortSignal): Promise<readonly LlmModelInfo[]>;
|
|
256
270
|
/**
|
|
257
271
|
* The discovered entry for one model. Resolved through the cache's
|
|
258
272
|
* stale-while-revalidate path: capability metadata must stay stable across
|
|
@@ -309,7 +323,12 @@ export declare class CopilotAdapter extends LlmAdapter {
|
|
|
309
323
|
*/
|
|
310
324
|
clearReplayState(): void;
|
|
311
325
|
resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo>;
|
|
326
|
+
/** Capability resolution of the provider's own models (the pool resolves members here). */
|
|
327
|
+
resolveOwnModel(provider: string, model: string): Promise<LlmResolvedModelInfo>;
|
|
312
328
|
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
329
|
+
/** Pool seam: stream through one specific account instead of the default. */
|
|
330
|
+
streamAccount(options: GenerateOptions, account: string): AsyncIterable<StreamChunk>;
|
|
331
|
+
private streamCore;
|
|
313
332
|
private request;
|
|
314
333
|
}
|
|
315
334
|
export {};
|