dsh-plugin-subscription-oauth 0.8.2 → 0.8.4

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. */
@@ -439,6 +467,11 @@ export class AntigravityAdapter extends LlmAdapter {
439
467
  continue;
440
468
  tried.add(accountKey);
441
469
  token = await this.options.tokens.resolve(accountKey);
470
+ // Pacing: a connect attempt right after a refused one is what turns a transient
471
+ // Google safety check into a sustained block, so the streamer waits out a short
472
+ // beat before each retry. opencodex keeps this discipline with a request pacer;
473
+ // this is the same idea narrowed to one provider.
474
+ await paceAntigravityAttempt(watchdog.signal);
442
475
  let response = await this.request(options, model, token.account, token.accessToken, watchdog.signal);
443
476
  if (response.status === 401 || response.status === 403) {
444
477
  // The stored token was accepted locally but refused on the wire; force one
@@ -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
  *
@@ -130,6 +159,10 @@ export async function* streamProviderResponses(options, input) {
130
159
  : accounts;
131
160
  for (const accountKey of tryAccounts) {
132
161
  let token = await tokens.resolve(accountKey);
162
+ // Pace each dispatch: a request right after a refused one is what turns a
163
+ // transient safety check into a sustained block, so the streamer waits out a
164
+ // short beat before each attempt (the same discipline opencodex keeps).
165
+ await paceDispatch(label, watchdog.signal);
133
166
  // Track which accounts a spent-allowance already moved past, so a later model
134
167
  // in the chain does not re-try an account known to be exhausted.
135
168
  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.2",
3
+ "version": "0.8.4",
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",