dsh-plugin-subscription-oauth 0.17.2 → 0.17.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.
@@ -254,6 +254,16 @@ export declare class AntigravityAdapter extends LlmAdapter {
254
254
  /** Translate an auth failure into the message a user can act on. */
255
255
  private refreshHint;
256
256
  /** Build and send one streaming request for one model. */
257
+ /**
258
+ * One dispatch attempt, retrying transient network faults before giving up on the account.
259
+ *
260
+ * A connect timeout or a dropped socket is a transport hiccup, not a refusal — the
261
+ * account is fine and the request never arrived, so it is worth a few backoff retries
262
+ * before the sweep moves on. An HTTP response (even a 429/403) is returned as-is: those
263
+ * drive the account-switch logic in {@link stream}, and must not be swallowed here.
264
+ * Non-network errors propagate untouched.
265
+ */
266
+ private requestWithRetry;
257
267
  private request;
258
268
  /** Resolve image blocks through the attachment service, when it is present. */
259
269
  private imageResolver;
@@ -62,6 +62,40 @@ async function paceAntigravityAttempt(signal) {
62
62
  signal.addEventListener('abort', onAbort, { once: true });
63
63
  });
64
64
  }
65
+ /** How many times a transient network fault is retried before the account is skipped. */
66
+ const NETWORK_RETRY_ATTEMPTS = 3;
67
+ /** Base delay between network retries; it doubles each attempt. */
68
+ const NETWORK_RETRY_BASE_MS = 800;
69
+ /**
70
+ * Whether an error from `fetch` is a transient network fault worth retrying.
71
+ *
72
+ * The Antigravity call is a single long-lived POST: a connect timeout, a reset, or a
73
+ * dropped socket is a transport hiccup, not a refusal — the account is fine, the request
74
+ * just never arrived. A thrown HTTP-shaped error or a non-network TypeError is not.
75
+ */
76
+ function isTransientNetworkFault(error) {
77
+ if (error instanceof LlmError)
78
+ return false;
79
+ const text = errorChain(error).toLowerCase();
80
+ return /fetch failed|connect timeout|connection reset|econnreset|etimedout|econnrefused|socket hang up|network error|timed out/.test(text);
81
+ }
82
+ /**
83
+ * Sleep for a backoff beat, honouring the caller's abort.
84
+ * @param ms - how long to wait.
85
+ * @param signal - the caller's abort signal.
86
+ */
87
+ function backoff(ms, signal) {
88
+ return new Promise((resolve) => {
89
+ if (signal.aborted)
90
+ return resolve();
91
+ const onAbort = () => { clearTimeout(timer); resolve(); };
92
+ const timer = setTimeout(() => {
93
+ signal.removeEventListener('abort', onAbort);
94
+ resolve();
95
+ }, ms);
96
+ signal.addEventListener('abort', onAbort, { once: true });
97
+ });
98
+ }
65
99
  /** Default Cloud Code Assist origin. */
66
100
  export const ANTIGRAVITY_BASE_URL = 'https://daily-cloudcode-pa.googleapis.com';
67
101
  /** Production origin, tried when the daily one is unavailable. */
@@ -539,7 +573,7 @@ export class AntigravityAdapter extends LlmAdapter {
539
573
  // beat before each retry. opencodex keeps this discipline with a request pacer;
540
574
  // this is the same idea narrowed to one provider.
541
575
  await paceAntigravityAttempt(watchdog.signal);
542
- let response = await this.request(options, model, token.account, token.accessToken, watchdog.signal);
576
+ let response = await this.requestWithRetry(options, model, token.account, token.accessToken, watchdog.signal);
543
577
  if (response.status === 401 || response.status === 403) {
544
578
  // The stored token was accepted locally but refused on the wire; force one
545
579
  // refresh. A 403 that survives the refresh is Google's "verify your account"
@@ -547,7 +581,7 @@ export class AntigravityAdapter extends LlmAdapter {
547
581
  // so the call moves to the next account rather than failing. Only a 401 marks
548
582
  // the credential as dead.
549
583
  token = await this.options.tokens.resolve(token.account, true);
550
- response = await this.request(options, model, token.account, token.accessToken, watchdog.signal);
584
+ response = await this.requestWithRetry(options, model, token.account, token.accessToken, watchdog.signal);
551
585
  if (response.status === 401) {
552
586
  this.options.onCredentialRejected?.(token.account);
553
587
  }
@@ -653,6 +687,33 @@ export class AntigravityAdapter extends LlmAdapter {
653
687
  return error;
654
688
  }
655
689
  /** Build and send one streaming request for one model. */
690
+ /**
691
+ * One dispatch attempt, retrying transient network faults before giving up on the account.
692
+ *
693
+ * A connect timeout or a dropped socket is a transport hiccup, not a refusal — the
694
+ * account is fine and the request never arrived, so it is worth a few backoff retries
695
+ * before the sweep moves on. An HTTP response (even a 429/403) is returned as-is: those
696
+ * drive the account-switch logic in {@link stream}, and must not be swallowed here.
697
+ * Non-network errors propagate untouched.
698
+ */
699
+ async requestWithRetry(options, model, account, accessToken, signal) {
700
+ let lastError;
701
+ for (let attempt = 0; attempt < NETWORK_RETRY_ATTEMPTS; attempt++) {
702
+ try {
703
+ return await this.request(options, model, account, accessToken, signal);
704
+ }
705
+ catch (error) {
706
+ if (!isTransientNetworkFault(error) || signal.aborted)
707
+ throw error;
708
+ lastError = error;
709
+ this.options.onWarn?.(`antigravity: transient network fault (${errorChain(error)}); retrying`);
710
+ if (attempt < NETWORK_RETRY_ATTEMPTS - 1) {
711
+ await backoff(NETWORK_RETRY_BASE_MS * 2 ** attempt, signal);
712
+ }
713
+ }
714
+ }
715
+ throw lastError;
716
+ }
656
717
  async request(options, model, account, accessToken, signal) {
657
718
  // Every call is scoped to the account's Cloud AI Companion project.
658
719
  const held = await this.options.tokens.session(account);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-subscription-oauth",
3
- "version": "0.17.2",
3
+ "version": "0.17.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",