dsh-plugin-subscription-oauth 0.8.3 → 0.8.5
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.
|
@@ -32,6 +32,9 @@ export declare const ANTIGRAVITY_AUTHORIZE_URL = "https://accounts.google.com/o/
|
|
|
32
32
|
export declare const ANTIGRAVITY_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
33
33
|
/** Google's profile endpoint, used for the account label. */
|
|
34
34
|
export declare const ANTIGRAVITY_USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo";
|
|
35
|
+
/** Minimum beat between Antigravity dispatch attempts, so a refused account is not re-hit
|
|
36
|
+
* at once and a sweep across the pool does not become a burst. */
|
|
37
|
+
export declare const ANTIGRAVITY_PACING_MS = 2000;
|
|
35
38
|
/** Default Cloud Code Assist origin. */
|
|
36
39
|
export declare const ANTIGRAVITY_BASE_URL = "https://daily-cloudcode-pa.googleapis.com";
|
|
37
40
|
/** Production origin, tried when the daily one is unavailable. */
|
|
@@ -34,6 +34,34 @@ export const ANTIGRAVITY_AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/v
|
|
|
34
34
|
export const ANTIGRAVITY_TOKEN_URL = 'https://oauth2.googleapis.com/token';
|
|
35
35
|
/** Google's profile endpoint, used for the account label. */
|
|
36
36
|
export const ANTIGRAVITY_USERINFO_URL = 'https://www.googleapis.com/oauth2/v2/userinfo';
|
|
37
|
+
/** Minimum beat between Antigravity dispatch attempts, so a refused account is not re-hit
|
|
38
|
+
* at once and a sweep across the pool does not become a burst. */
|
|
39
|
+
export const ANTIGRAVITY_PACING_MS = 2_000;
|
|
40
|
+
/** The last moment an Antigravity dispatch started; the next waits out the beat. */
|
|
41
|
+
let lastAntigravityDispatchAt = 0;
|
|
42
|
+
/**
|
|
43
|
+
* Wait out the Antigravity pacing beat before a dispatch attempt.
|
|
44
|
+
*
|
|
45
|
+
* A request right after a refused one is what turns a transient Google safety check into
|
|
46
|
+
* a sustained block; a short, provider-scoped pause between attempts is the discipline
|
|
47
|
+
* that keeps the pool reachable. Aborts when the caller's signal fires.
|
|
48
|
+
*
|
|
49
|
+
* @param signal - the caller's abort signal.
|
|
50
|
+
*/
|
|
51
|
+
async function paceAntigravityAttempt(signal) {
|
|
52
|
+
const wait = lastAntigravityDispatchAt + ANTIGRAVITY_PACING_MS - Date.now();
|
|
53
|
+
lastAntigravityDispatchAt = Date.now();
|
|
54
|
+
if (wait <= 0 || signal.aborted)
|
|
55
|
+
return;
|
|
56
|
+
await new Promise((resolve) => {
|
|
57
|
+
const onAbort = () => { clearTimeout(timer); resolve(); };
|
|
58
|
+
const timer = setTimeout(() => {
|
|
59
|
+
signal.removeEventListener('abort', onAbort);
|
|
60
|
+
resolve();
|
|
61
|
+
}, wait);
|
|
62
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
63
|
+
});
|
|
64
|
+
}
|
|
37
65
|
/** Default Cloud Code Assist origin. */
|
|
38
66
|
export const ANTIGRAVITY_BASE_URL = 'https://daily-cloudcode-pa.googleapis.com';
|
|
39
67
|
/** Production origin, tried when the daily one is unavailable. */
|
|
@@ -430,30 +458,22 @@ export class AntigravityAdapter extends LlmAdapter {
|
|
|
430
458
|
const diag = [`pool=${accounts.join(',') || '(none)'}`];
|
|
431
459
|
const tried = new Set();
|
|
432
460
|
let token = await this.options.tokens.resolve();
|
|
433
|
-
//
|
|
434
|
-
// request
|
|
435
|
-
|
|
436
|
-
|
|
461
|
+
// Stay on the default account until it refuses. opencodex keeps account affinity —
|
|
462
|
+
// a request stays on one account and only moves after a 429 — because switching
|
|
463
|
+
// accounts on every request is what reads to Google as two accounts in play at once
|
|
464
|
+
// and trips the verify gate. Reordering here caused exactly that.
|
|
465
|
+
const order = [token.account, ...accounts.filter((a) => a !== token.account)];
|
|
437
466
|
for (const accountKey of order) {
|
|
438
467
|
if (tried.has(accountKey))
|
|
439
468
|
continue;
|
|
440
469
|
tried.add(accountKey);
|
|
441
470
|
token = await this.options.tokens.resolve(accountKey);
|
|
442
|
-
//
|
|
443
|
-
//
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
catch (error) {
|
|
450
|
-
const transport = error instanceof TypeError || error.name === 'AbortError';
|
|
451
|
-
if (transport && order.length > 1 && !watchdog.timedOut) {
|
|
452
|
-
diag.push(`transport on ${token.account}, moving to next`);
|
|
453
|
-
continue;
|
|
454
|
-
}
|
|
455
|
-
throw error;
|
|
456
|
-
}
|
|
471
|
+
// Pacing: a connect attempt right after a refused one is what turns a transient
|
|
472
|
+
// Google safety check into a sustained block, so the streamer waits out a short
|
|
473
|
+
// beat before each retry. opencodex keeps this discipline with a request pacer;
|
|
474
|
+
// this is the same idea narrowed to one provider.
|
|
475
|
+
await paceAntigravityAttempt(watchdog.signal);
|
|
476
|
+
let response = await this.request(options, model, token.account, token.accessToken, watchdog.signal);
|
|
457
477
|
if (response.status === 401 || response.status === 403) {
|
|
458
478
|
// The stored token was accepted locally but refused on the wire; force one
|
|
459
479
|
// refresh. A 403 that survives the refresh is Google's "verify your account"
|
|
@@ -15,6 +15,12 @@ import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
|
15
15
|
import type { SessionBase } from '../auth/store.js';
|
|
16
16
|
import type { TokenManager } from '../auth/tokens.js';
|
|
17
17
|
import type { UsageWindow } from '../protocol.js';
|
|
18
|
+
/**
|
|
19
|
+
* Minimum beat between dispatch attempts on one route, so a refused account is not
|
|
20
|
+
* re-hit at once and a sweep across the pool does not become a burst. opencodex keeps
|
|
21
|
+
* this discipline with a request pacer; this is the same idea narrowed to one plugin.
|
|
22
|
+
*/
|
|
23
|
+
export declare const DISPATCH_PACING_MS = 2000;
|
|
18
24
|
/**
|
|
19
25
|
* Build the cross-account hooks one route needs to draw on its whole account pool.
|
|
20
26
|
*
|
|
@@ -15,6 +15,35 @@ import { EMPTY_RESPONSE_CODE, LlmError } from '@deepseek-ai/dsh-llm';
|
|
|
15
15
|
import { allowanceScheduler } from '../auth/allowance-scheduler.js';
|
|
16
16
|
import { streamResponses } from '../translate/responses.js';
|
|
17
17
|
import { httpLlmError, idleWatchdog, mapFetchFailure } from './common.js';
|
|
18
|
+
/**
|
|
19
|
+
* Minimum beat between dispatch attempts on one route, so a refused account is not
|
|
20
|
+
* re-hit at once and a sweep across the pool does not become a burst. opencodex keeps
|
|
21
|
+
* this discipline with a request pacer; this is the same idea narrowed to one plugin.
|
|
22
|
+
*/
|
|
23
|
+
export const DISPATCH_PACING_MS = 2_000;
|
|
24
|
+
/** Per-route last-dispatch instants, so each route paces its own attempts. */
|
|
25
|
+
const lastDispatchAt = new Map();
|
|
26
|
+
/**
|
|
27
|
+
* Wait out a route's pacing beat before a dispatch attempt.
|
|
28
|
+
*
|
|
29
|
+
* @param route - the route key pacing its own attempts.
|
|
30
|
+
* @param signal - the caller's abort signal.
|
|
31
|
+
*/
|
|
32
|
+
async function paceDispatch(route, signal) {
|
|
33
|
+
const last = lastDispatchAt.get(route) ?? 0;
|
|
34
|
+
const wait = last + DISPATCH_PACING_MS - Date.now();
|
|
35
|
+
lastDispatchAt.set(route, Date.now());
|
|
36
|
+
if (wait <= 0 || signal.aborted)
|
|
37
|
+
return;
|
|
38
|
+
await new Promise((resolve) => {
|
|
39
|
+
const onAbort = () => { clearTimeout(timer); resolve(); };
|
|
40
|
+
const timer = setTimeout(() => {
|
|
41
|
+
signal.removeEventListener('abort', onAbort);
|
|
42
|
+
resolve();
|
|
43
|
+
}, wait);
|
|
44
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
45
|
+
});
|
|
46
|
+
}
|
|
18
47
|
/**
|
|
19
48
|
* Build the cross-account hooks one route needs to draw on its whole account pool.
|
|
20
49
|
*
|
|
@@ -101,28 +130,17 @@ export async function* streamProviderResponses(options, input) {
|
|
|
101
130
|
if (models.length === 0) {
|
|
102
131
|
throw new LlmError(`${label} has no enabled model to dispatch to`, EMPTY_RESPONSE_CODE);
|
|
103
132
|
}
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
// the
|
|
133
|
+
// Stay on the default account until it refuses. opencodex keeps account affinity — a
|
|
134
|
+
// request stays on one account and only moves after a 429 — because switching accounts
|
|
135
|
+
// on every request is what reads to a provider as several accounts in play at once and
|
|
136
|
+
// trips the safety gate. Reordering candidates here caused exactly that.
|
|
108
137
|
let accounts = input.accounts === undefined
|
|
109
138
|
? undefined
|
|
110
139
|
: [...await input.accounts()];
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (accounts !== undefined && accounts.length > 1 && input.isAccountSpent !== undefined) {
|
|
116
|
-
const fresh = [];
|
|
117
|
-
const tired = [];
|
|
118
|
-
for (const account of accounts) {
|
|
119
|
-
if (await input.isAccountSpent(account))
|
|
120
|
-
tired.push(account);
|
|
121
|
-
else
|
|
122
|
-
fresh.push(account);
|
|
123
|
-
}
|
|
124
|
-
if (tired.length > 0 && fresh.length > 0) {
|
|
125
|
-
accounts = [...fresh, ...tired];
|
|
140
|
+
if (accounts !== undefined && accounts.length > 1) {
|
|
141
|
+
const defaultAccount = await tokens.defaultAccount();
|
|
142
|
+
if (defaultAccount !== undefined) {
|
|
143
|
+
accounts = [defaultAccount, ...accounts.filter((a) => a !== defaultAccount)];
|
|
126
144
|
}
|
|
127
145
|
}
|
|
128
146
|
const tryAccounts = accounts === undefined || accounts.length === 0
|
|
@@ -130,6 +148,10 @@ export async function* streamProviderResponses(options, input) {
|
|
|
130
148
|
: accounts;
|
|
131
149
|
for (const accountKey of tryAccounts) {
|
|
132
150
|
let token = await tokens.resolve(accountKey);
|
|
151
|
+
// Pace each dispatch: a request right after a refused one is what turns a
|
|
152
|
+
// transient safety check into a sustained block, so the streamer waits out a
|
|
153
|
+
// short beat before each attempt (the same discipline opencodex keeps).
|
|
154
|
+
await paceDispatch(label, watchdog.signal);
|
|
133
155
|
// Track which accounts a spent-allowance already moved past, so a later model
|
|
134
156
|
// in the chain does not re-try an account known to be exhausted.
|
|
135
157
|
const spent = new Set();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-subscription-oauth",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
4
4
|
"description": "Use subscription accounts as DeepSeek Harness LLM providers — Kimi Code (OAuth device flow), Grok / X Premium (OIDC + PKCE), Google Antigravity (Google OAuth + Cloud Code Assist) and GLM / BigModel (ZCode's browser sign-in) — with per-model visibility, drag-to-reorder Auto fallback and plan quotas from the web Settings page",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "EricFL1998",
|