dsh-subscriptions 0.2.0 → 0.2.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 +220 -180
- package/lib/auth/claude-code-creds.js +31 -9
- package/lib/auth/device-flow.js +16 -9
- package/lib/auth/oauth-flow.js +13 -10
- package/lib/auth/rpc.d.ts +8 -4
- package/lib/auth/rpc.js +89 -25
- package/lib/auth/store.js +80 -31
- package/lib/client/CursorCard.d.ts +1 -1
- package/lib/client/CursorCard.js +50 -16
- package/lib/client/ExternalUsageCards.d.ts +1 -1
- package/lib/client/ExternalUsageCards.js +37 -16
- package/lib/client/ImageGallery.d.ts +2 -2
- package/lib/client/ImageGallery.js +92 -33
- package/lib/client/ImageGenerateToolview.js +50 -14
- package/lib/client/OllamaCard.d.ts +1 -1
- package/lib/client/OllamaCard.js +26 -8
- package/lib/client/OllamaSettingsManager.js +113 -30
- package/lib/client/ProviderAccountManager.d.ts +1 -1
- package/lib/client/ProviderAccountManager.js +164 -49
- package/lib/client/ProviderModelEditor.js +137 -34
- package/lib/client/SpeedSelect.d.ts +62 -14
- package/lib/client/SpeedSelect.js +261 -49
- package/lib/client/SubscriptionUsageBadge.d.ts +4 -10
- package/lib/client/SubscriptionUsageBadge.js +300 -112
- package/lib/client/SubscriptionsSection.d.ts +2 -0
- package/lib/client/SubscriptionsSection.js +265 -85
- package/lib/client/UsageBadgeDisplaySetting.js +35 -12
- package/lib/client/UsageMeter.d.ts +1 -1
- package/lib/client/UsageMeter.js +62 -24
- package/lib/client/VideoGenerateToolview.js +69 -17
- package/lib/client/account-preferences.d.ts +8 -0
- package/lib/client/account-preferences.js +20 -5
- package/lib/client/host-contract.d.ts +11 -2
- package/lib/client/host-contract.js +20 -3
- package/lib/client/index.d.ts +5 -5
- package/lib/client/index.js +34 -12
- package/lib/client/locales.d.ts +22 -0
- package/lib/client/locales.js +73 -51
- package/lib/client/model-filter.d.ts +37 -0
- package/lib/client/model-filter.js +45 -0
- package/lib/client/ollama-draft.js +21 -12
- package/lib/client/reset-action.js +6 -4
- package/lib/client/reset-credits-view.d.ts +1 -1
- package/lib/client/reset-credits-view.js +66 -17
- package/lib/client/subscription-card-styles.js +94 -24
- package/lib/client/usage-color-preferences.js +4 -2
- package/lib/client/usage-pace.js +30 -12
- package/lib/client/usage-window-filter.d.ts +7 -0
- package/lib/client/usage-window-filter.js +12 -0
- package/lib/client.js +480 -136
- package/lib/client.js.map +1 -1
- package/lib/http.js +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.js +167 -76
- package/lib/model-defaults.js +4 -2
- package/lib/provider-settings.d.ts +17 -0
- package/lib/provider-settings.js +79 -16
- package/lib/providers/account-preferences.js +51 -22
- package/lib/providers/accounts.js +15 -9
- package/lib/providers/antigravity-oauth-client.js +2 -2
- package/lib/providers/antigravity.js +141 -68
- package/lib/providers/catalog-store.js +120 -41
- package/lib/providers/claude-cli-version.js +3 -3
- package/lib/providers/claude.d.ts +9 -2
- package/lib/providers/claude.js +153 -90
- package/lib/providers/codex-client-version.js +3 -3
- package/lib/providers/codex-search.js +40 -13
- package/lib/providers/codex.js +196 -112
- package/lib/providers/common.js +71 -39
- package/lib/providers/copilot.js +143 -82
- package/lib/providers/cursor-adapter.js +42 -18
- package/lib/providers/cursor-auth.d.ts +1 -1
- package/lib/providers/cursor-auth.js +69 -24
- package/lib/providers/cursor-model-settings.js +14 -5
- package/lib/providers/cursor-usage.js +27 -13
- package/lib/providers/external-usage-controller.js +14 -5
- package/lib/providers/external-usage.js +59 -23
- package/lib/providers/grok.js +152 -89
- package/lib/providers/image-pool.js +29 -11
- package/lib/providers/minimax-usage.js +31 -11
- package/lib/providers/npm-cli-version.js +10 -3
- package/lib/providers/ollama-settings.js +24 -8
- package/lib/providers/ollama-web.js +29 -11
- package/lib/providers/ollama.js +129 -66
- package/lib/providers/pool-family.js +11 -3
- package/lib/providers/pool-health.js +28 -8
- package/lib/providers/pool-usage.js +16 -9
- package/lib/providers/pool.d.ts +8 -0
- package/lib/providers/pool.js +81 -42
- package/lib/providers/rate-limit.js +8 -2
- package/lib/providers/replay.js +40 -17
- package/lib/providers/reset-redemption.js +18 -6
- package/lib/tools/image-generate.js +123 -65
- package/lib/tools/registration.js +5 -2
- package/lib/tools/video-generate.js +51 -31
- package/lib/tools/x-search.js +50 -22
- package/lib/translate/anthropic.js +85 -37
- package/lib/translate/antigravity-schema.js +71 -28
- package/lib/translate/antigravity-thinking.js +22 -9
- package/lib/translate/antigravity.js +151 -52
- package/lib/translate/chat-completions.js +67 -23
- package/lib/translate/resolved.js +55 -21
- package/lib/translate/responses.js +69 -29
- package/lib/translate/sse.js +4 -1
- package/package.json +27 -4
package/lib/providers/common.js
CHANGED
|
@@ -22,19 +22,22 @@ export function validateModels(models, label) {
|
|
|
22
22
|
if (model.name !== undefined && model.name.length === 0) {
|
|
23
23
|
throw new Error(`${label}: catalog model "${model.id}" has an empty name`);
|
|
24
24
|
}
|
|
25
|
-
if (model.contextWindow !== undefined
|
|
26
|
-
|
|
25
|
+
if (model.contextWindow !== undefined &&
|
|
26
|
+
(!Number.isInteger(model.contextWindow) || model.contextWindow <= 0)) {
|
|
27
27
|
throw new Error(`${label}: catalog model "${model.id}" contextWindow must be a positive integer`);
|
|
28
28
|
}
|
|
29
|
-
if (model.maxTokens !== undefined &&
|
|
29
|
+
if (model.maxTokens !== undefined &&
|
|
30
|
+
(!Number.isInteger(model.maxTokens) || model.maxTokens <= 0)) {
|
|
30
31
|
throw new Error(`${label}: catalog model "${model.id}" maxTokens must be a positive integer`);
|
|
31
32
|
}
|
|
32
|
-
if (model.inputModalities !== undefined
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
if (model.inputModalities !== undefined &&
|
|
34
|
+
(model.inputModalities.length === 0 ||
|
|
35
|
+
model.inputModalities.some((modality) => modality !== 'text' && modality !== 'image'))) {
|
|
35
36
|
throw new Error(`${label}: catalog model "${model.id}" inputModalities must be a non-empty list of "text"/"image"`);
|
|
36
37
|
}
|
|
37
|
-
if (model.wire !== undefined &&
|
|
38
|
+
if (model.wire !== undefined &&
|
|
39
|
+
model.wire !== 'chat-completions' &&
|
|
40
|
+
model.wire !== 'responses') {
|
|
38
41
|
throw new Error(`${label}: catalog model "${model.id}" wire must be "chat-completions" or "responses"`);
|
|
39
42
|
}
|
|
40
43
|
if (seen.has(model.id))
|
|
@@ -42,11 +45,15 @@ export function validateModels(models, label) {
|
|
|
42
45
|
seen.add(model.id);
|
|
43
46
|
return {
|
|
44
47
|
id: model.id,
|
|
45
|
-
...model.name === undefined ? {} : { name: model.name },
|
|
46
|
-
...model.contextWindow === undefined
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
...model.
|
|
48
|
+
...(model.name === undefined ? {} : { name: model.name }),
|
|
49
|
+
...(model.contextWindow === undefined
|
|
50
|
+
? {}
|
|
51
|
+
: { contextWindow: model.contextWindow }),
|
|
52
|
+
...(model.maxTokens === undefined ? {} : { maxTokens: model.maxTokens }),
|
|
53
|
+
...(model.inputModalities === undefined
|
|
54
|
+
? {}
|
|
55
|
+
: { inputModalities: [...model.inputModalities] }),
|
|
56
|
+
...(model.wire === undefined ? {} : { wire: model.wire }),
|
|
50
57
|
};
|
|
51
58
|
});
|
|
52
59
|
}
|
|
@@ -80,6 +87,8 @@ export async function parseProviderJson(input, origin) {
|
|
|
80
87
|
catch (error) {
|
|
81
88
|
if (!(error instanceof SyntaxError))
|
|
82
89
|
throw error;
|
|
90
|
+
// Do not attach the original SyntaxError: Node's message quotes the input, and the host renders cause.
|
|
91
|
+
// eslint-disable-next-line preserve-caught-error -- the replacement omits the provider excerpt on purpose
|
|
83
92
|
throw new SyntaxError(providerDiagnostic(`${origin}: invalid JSON`, true));
|
|
84
93
|
}
|
|
85
94
|
}
|
|
@@ -122,7 +131,9 @@ export async function readBoundedJson(response, label, maxBytes) {
|
|
|
122
131
|
try {
|
|
123
132
|
reader.releaseLock();
|
|
124
133
|
}
|
|
125
|
-
catch {
|
|
134
|
+
catch {
|
|
135
|
+
/* best effort */
|
|
136
|
+
}
|
|
126
137
|
}
|
|
127
138
|
try {
|
|
128
139
|
return JSON.parse(text);
|
|
@@ -187,7 +198,9 @@ export async function httpLlmError(response, label, options = {}) {
|
|
|
187
198
|
// re-hitting the same closed window.
|
|
188
199
|
const rateLimited = response.status === 429;
|
|
189
200
|
const reset = rateLimited
|
|
190
|
-
? options.rateLimitReset?.(response, body, now) ??
|
|
201
|
+
? (options.rateLimitReset?.(response, body, now) ??
|
|
202
|
+
googleQuotaReset(body, now) ??
|
|
203
|
+
retryAfterInstant(response, now))
|
|
191
204
|
: retryAfterInstant(response, now);
|
|
192
205
|
if (reset === undefined && rateLimited) {
|
|
193
206
|
// Header names and values are provider-controlled too; none are displayed.
|
|
@@ -195,7 +208,9 @@ export async function httpLlmError(response, label, options = {}) {
|
|
|
195
208
|
}
|
|
196
209
|
return new LlmError(message, code, {
|
|
197
210
|
status: response.status,
|
|
198
|
-
...reset === undefined
|
|
211
|
+
...(reset === undefined
|
|
212
|
+
? {}
|
|
213
|
+
: { providerRetryAfterMs: waitFromReset(reset, now) }),
|
|
199
214
|
});
|
|
200
215
|
}
|
|
201
216
|
/**
|
|
@@ -284,9 +299,13 @@ export function idleWatchdog(caller, timeoutMs) {
|
|
|
284
299
|
*/
|
|
285
300
|
export function mapFetchFailure(label, error, watchdog, caller) {
|
|
286
301
|
if (watchdog.timedOut())
|
|
287
|
-
return new LlmError(`${label} stream idle timeout`, 'TIMEOUT', {
|
|
302
|
+
return new LlmError(`${label} stream idle timeout`, 'TIMEOUT', {
|
|
303
|
+
cause: error,
|
|
304
|
+
});
|
|
288
305
|
if (caller?.aborted === true)
|
|
289
|
-
return new LlmError(`${label} request aborted by caller`, 'ABORTED', {
|
|
306
|
+
return new LlmError(`${label} request aborted by caller`, 'ABORTED', {
|
|
307
|
+
cause: error,
|
|
308
|
+
});
|
|
290
309
|
if (error instanceof LlmError)
|
|
291
310
|
return error;
|
|
292
311
|
return new LlmError(`${label} request failed`, 'TRANSPORT', { cause: error });
|
|
@@ -387,10 +406,11 @@ export class TokenManager {
|
|
|
387
406
|
async session(forceRefresh = false) {
|
|
388
407
|
const session = await this.options.load();
|
|
389
408
|
if (session === undefined) {
|
|
390
|
-
throw new LlmError(`dsh-subscriptions: not logged in to ${this.options.displayName}; `
|
|
391
|
-
|
|
409
|
+
throw new LlmError(`dsh-subscriptions: not logged in to ${this.options.displayName}; ` +
|
|
410
|
+
'log in via Settings → Subscriptions in the dsh web app', 'MISSING_CREDENTIAL');
|
|
392
411
|
}
|
|
393
|
-
if (!forceRefresh &&
|
|
412
|
+
if (!forceRefresh &&
|
|
413
|
+
session.expiresAt - Date.now() > this.options.preemptMs) {
|
|
394
414
|
return session;
|
|
395
415
|
}
|
|
396
416
|
this.inflight ??= this.doRefresh(session).finally(() => {
|
|
@@ -406,7 +426,9 @@ export class TokenManager {
|
|
|
406
426
|
// log out an account that just signed in. Serve it instead.
|
|
407
427
|
const attempted = this.attempted;
|
|
408
428
|
const stored = await this.options.load();
|
|
409
|
-
if (stored !== undefined &&
|
|
429
|
+
if (stored !== undefined &&
|
|
430
|
+
attempted !== undefined &&
|
|
431
|
+
stored.refreshToken !== attempted.refreshToken) {
|
|
410
432
|
return stored;
|
|
411
433
|
}
|
|
412
434
|
await this.options.remove();
|
|
@@ -426,9 +448,9 @@ export class TokenManager {
|
|
|
426
448
|
// A concurrent caller may have refreshed while this one waited: re-read
|
|
427
449
|
// the store and skip the round trip when the stored session is fresh.
|
|
428
450
|
const current = await this.options.load();
|
|
429
|
-
if (current !== undefined
|
|
430
|
-
|
|
431
|
-
|
|
451
|
+
if (current !== undefined &&
|
|
452
|
+
current.accessToken !== session.accessToken &&
|
|
453
|
+
current.expiresAt - Date.now() > this.options.preemptMs) {
|
|
432
454
|
return current;
|
|
433
455
|
}
|
|
434
456
|
const attempted = current ?? session;
|
|
@@ -446,14 +468,14 @@ export const DISCOVERY_TIMEOUT_MS = 10_000;
|
|
|
446
468
|
*/
|
|
447
469
|
export function withTimeout(work, timeoutMs) {
|
|
448
470
|
const signal = AbortSignal.timeout(timeoutMs);
|
|
449
|
-
const aborted = new Promise(resolve => {
|
|
471
|
+
const aborted = new Promise((resolve) => {
|
|
450
472
|
if (signal.aborted)
|
|
451
473
|
resolve(undefined);
|
|
452
474
|
else
|
|
453
475
|
signal.addEventListener('abort', () => resolve(undefined), { once: true });
|
|
454
476
|
});
|
|
455
477
|
return Promise.race([
|
|
456
|
-
work(signal).then(value => (signal.aborted ? undefined : value), (error) => {
|
|
478
|
+
work(signal).then((value) => (signal.aborted ? undefined : value), (error) => {
|
|
457
479
|
if (signal.aborted)
|
|
458
480
|
return undefined;
|
|
459
481
|
throw error;
|
|
@@ -463,7 +485,9 @@ export function withTimeout(work, timeoutMs) {
|
|
|
463
485
|
}
|
|
464
486
|
/** Display name for a wire reasoning-effort identifier. */
|
|
465
487
|
export function effortDisplayName(effort) {
|
|
466
|
-
return effort === 'xhigh'
|
|
488
|
+
return effort === 'xhigh'
|
|
489
|
+
? 'Extra High'
|
|
490
|
+
: effort.charAt(0).toUpperCase() + effort.slice(1);
|
|
467
491
|
}
|
|
468
492
|
/**
|
|
469
493
|
* Fold a configured per-model default effort into a reasoning block, keeping
|
|
@@ -492,7 +516,9 @@ export function mergeReasoning(configuredDefault, base, options) {
|
|
|
492
516
|
? undefined
|
|
493
517
|
: {
|
|
494
518
|
efforts: [...base.efforts],
|
|
495
|
-
...(base.defaultEffort === undefined
|
|
519
|
+
...(base.defaultEffort === undefined
|
|
520
|
+
? {}
|
|
521
|
+
: { defaultEffort: base.defaultEffort }),
|
|
496
522
|
};
|
|
497
523
|
if (configuredDefault === undefined)
|
|
498
524
|
return detached;
|
|
@@ -502,10 +528,13 @@ export function mergeReasoning(configuredDefault, base, options) {
|
|
|
502
528
|
// catalog does not cover). Inventing a reasoning block here would claim a
|
|
503
529
|
// capability nobody advertised; only a fallback-based provider may.
|
|
504
530
|
return options?.extendable === true
|
|
505
|
-
? {
|
|
531
|
+
? {
|
|
532
|
+
efforts: [{ id: effort, name: effortDisplayName(effort) }],
|
|
533
|
+
defaultEffort: effort,
|
|
534
|
+
}
|
|
506
535
|
: undefined;
|
|
507
536
|
}
|
|
508
|
-
if (base.efforts.some(entry => entry.id === effort)) {
|
|
537
|
+
if (base.efforts.some((entry) => entry.id === effort)) {
|
|
509
538
|
return { efforts: [...base.efforts], defaultEffort: effort };
|
|
510
539
|
}
|
|
511
540
|
if (options?.extendable !== true)
|
|
@@ -584,7 +613,9 @@ export class ModelCatalogCache {
|
|
|
584
613
|
if (this.persistence === undefined)
|
|
585
614
|
return Promise.resolve();
|
|
586
615
|
this.seeded ??= this.persistence.load().then((snapshot) => {
|
|
587
|
-
if (snapshot !== undefined &&
|
|
616
|
+
if (snapshot !== undefined &&
|
|
617
|
+
this.entry === undefined &&
|
|
618
|
+
!this.seedDisabled) {
|
|
588
619
|
this.entry = snapshot;
|
|
589
620
|
}
|
|
590
621
|
}, () => undefined);
|
|
@@ -660,8 +691,8 @@ export class ModelCatalogCache {
|
|
|
660
691
|
}
|
|
661
692
|
/** Whether discovery failed because the stored login is gone. */
|
|
662
693
|
export function isMissingOrInvalidCredential(error) {
|
|
663
|
-
return error instanceof LlmError
|
|
664
|
-
|
|
694
|
+
return (error instanceof LlmError &&
|
|
695
|
+
(error.code === 'MISSING_CREDENTIAL' || error.code === 'INVALID_CREDENTIAL'));
|
|
665
696
|
}
|
|
666
697
|
/** Whether discovery stopped because the caller cancelled or the timeout fired. */
|
|
667
698
|
export function isDiscoveryAborted(error, signal) {
|
|
@@ -669,9 +700,9 @@ export function isDiscoveryAborted(error, signal) {
|
|
|
669
700
|
return true;
|
|
670
701
|
// Only treat abort-shaped errors as cancellation when this call had a signal;
|
|
671
702
|
// a refresh TimeoutError must not fail the whole picker union.
|
|
672
|
-
return signal !== undefined
|
|
673
|
-
|
|
674
|
-
|
|
703
|
+
return (signal !== undefined &&
|
|
704
|
+
error instanceof Error &&
|
|
705
|
+
(error.name === 'AbortError' || error.name === 'TimeoutError'));
|
|
675
706
|
}
|
|
676
707
|
/**
|
|
677
708
|
* Whether discovery failed because the access token was rejected. After
|
|
@@ -680,8 +711,8 @@ export function isDiscoveryAborted(error, signal) {
|
|
|
680
711
|
* the refresh grant still answers, so the store keeps the session.
|
|
681
712
|
*/
|
|
682
713
|
export function isDiscoveryAuthFailure(error) {
|
|
683
|
-
return (error instanceof OAuthEndpointError && error.status === 401)
|
|
684
|
-
|
|
714
|
+
return ((error instanceof OAuthEndpointError && error.status === 401) ||
|
|
715
|
+
(error instanceof LlmError && error.code === 'AUTH'));
|
|
685
716
|
}
|
|
686
717
|
/**
|
|
687
718
|
* Run a catalog fetch, retrying once after a forced token refresh when the
|
|
@@ -701,7 +732,8 @@ export async function discoverOrRetryAuth(session, catalog, run) {
|
|
|
701
732
|
return await run();
|
|
702
733
|
}
|
|
703
734
|
catch (retryError) {
|
|
704
|
-
if (!isMissingOrInvalidCredential(retryError) &&
|
|
735
|
+
if (!isMissingOrInvalidCredential(retryError) &&
|
|
736
|
+
isDiscoveryAuthFailure(retryError)) {
|
|
705
737
|
catalog.invalidate();
|
|
706
738
|
}
|
|
707
739
|
throw retryError;
|