dsh-plugin-subscription-oauth 0.8.4 → 0.8.6

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.
@@ -163,6 +163,12 @@ export declare class ProviderController<S extends SessionBase> {
163
163
  * @throws {ProviderError} with `REAUTH_REQUIRED` when the account must sign in again.
164
164
  */
165
165
  refresh(account?: string): Promise<ProviderRefreshResult>;
166
+ /**
167
+ * Re-read the endpoint's live model list.
168
+ *
169
+ * @param force - fetch again even when a list was already read this session.
170
+ * @returns a fresh status.
171
+ */
166
172
  /**
167
173
  * Re-read the endpoint's live model list.
168
174
  *
@@ -271,6 +271,12 @@ export class ProviderController {
271
271
  this.lastError = undefined;
272
272
  return { account: token.account, expiresAt: token.expiresAt, status: await this.status() };
273
273
  }
274
+ /**
275
+ * Re-read the endpoint's live model list.
276
+ *
277
+ * @param force - fetch again even when a list was already read this session.
278
+ * @returns a fresh status.
279
+ */
274
280
  /**
275
281
  * Re-read the endpoint's live model list.
276
282
  *
@@ -287,23 +293,33 @@ export class ProviderController {
287
293
  // every startup would be noise.
288
294
  if (!await this.options.store.hasAny())
289
295
  return this.status();
290
- let accessToken;
291
- try {
292
- accessToken = (await this.options.tokens.resolve()).accessToken;
293
- }
294
- catch (error) {
295
- // Discovery is a convenience: an unusable session is reported by the
296
- // card's account list, not as a second failure here.
297
- this.options.onWarn?.(`${spec.id} model discovery skipped: ${messageOf(error)}`);
298
- return this.status();
296
+ // The catalog is the union of every signed-in account's live list, because one
297
+ // model id draws on the whole pool: a model any account can serve must appear, and
298
+ // which account answers at call time is the scheduler's choice. A model only the
299
+ // non-default account has is offered, then may fall through to it on dispatch.
300
+ const discovered = [];
301
+ for (const account of Object.keys(await this.options.store.list())) {
302
+ let accessToken;
303
+ try {
304
+ accessToken = (await this.options.tokens.resolve(account)).accessToken;
305
+ }
306
+ catch (error) {
307
+ this.options.onWarn?.(`${spec.id} model discovery for ${account} skipped: ${messageOf(error)}`);
308
+ continue;
309
+ }
310
+ const ids = await spec.discover(accessToken);
311
+ if (ids !== undefined) {
312
+ for (const id of ids)
313
+ if (!discovered.includes(id))
314
+ discovered.push(id);
315
+ }
299
316
  }
300
- const ids = await spec.discover(accessToken);
301
- if (ids !== undefined) {
302
- this.discovered = ids;
303
- spec.applyDiscovery?.(ids);
317
+ if (discovered.length > 0) {
318
+ this.discovered = discovered;
319
+ spec.applyDiscovery?.(discovered);
304
320
  // A model this route now serves is no longer hand-added: keeping the extra
305
321
  // entry would list it as manual and invite a pointless "remove" later.
306
- await this.forgetDiscovered(ids);
322
+ await this.forgetDiscovered(discovered);
307
323
  this.options.onCatalogChanged?.();
308
324
  }
309
325
  return this.status();
@@ -98,9 +98,14 @@ function agentIds(payload) {
98
98
  push(id);
99
99
  }
100
100
  }
101
- if (isRecord(payload.tieredModelIds) && Array.isArray(payload.tieredModelIds.flash)) {
102
- for (const id of payload.tieredModelIds.flash)
103
- push(id);
101
+ if (isRecord(payload.tieredModelIds)) {
102
+ for (const key of Object.keys(payload.tieredModelIds)) {
103
+ const list = payload.tieredModelIds[key];
104
+ if (Array.isArray(list)) {
105
+ for (const id of list)
106
+ push(id);
107
+ }
108
+ }
104
109
  }
105
110
  // No usable grouping: fall back to every entry, which is still better than nothing
106
111
  // as long as the non-agent surfaces are excluded.
@@ -458,10 +458,11 @@ export class AntigravityAdapter extends LlmAdapter {
458
458
  const diag = [`pool=${accounts.join(',') || '(none)'}`];
459
459
  const tried = new Set();
460
460
  let token = await this.options.tokens.resolve();
461
- // Move an account known to be spent (from a past failure) to the back, so a fresh
462
- // request starts on one that has allowance. A lone account still serves.
463
- const order = [...accounts.filter((a) => a !== token.account && !this.isSpent(a)), token.account,
464
- ...accounts.filter((a) => a !== token.account && this.isSpent(a))];
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)];
465
466
  for (const accountKey of order) {
466
467
  if (tried.has(accountKey))
467
468
  continue;
@@ -521,8 +522,15 @@ export class AntigravityAdapter extends LlmAdapter {
521
522
  yield* streamAntigravity(response.body, () => { watchdog.pulse(); });
522
523
  return;
523
524
  }
524
- const exhausted = new LlmError(`Antigravity API: every account's allowance is spent [${diag.join(' | ')}]`, EMPTY_RESPONSE_CODE);
525
- throw exhausted;
525
+ // Every account was tried and none served. The headline names the most actionable
526
+ // refusal — a 403 "verify your account" is something the user can fix in the browser;
527
+ // a 429 "allowance spent" just means wait for the reset. The diagnostic lists each
528
+ // account and the status it returned.
529
+ const last403 = diag.reduce((acc, line) => (line.startsWith('403 on') ? line : acc), undefined);
530
+ const message = last403 === undefined
531
+ ? `Antigravity API: every account's allowance is spent [${diag.join(' | ')}]`
532
+ : `Antigravity API request failed: ${last403.replace(/^403 on /, 'account ')} refused (HTTP 403: Verify your account to continue) [${diag.join(' | ')}]`;
533
+ throw new LlmError(message, EMPTY_RESPONSE_CODE);
526
534
  }
527
535
  catch (error) {
528
536
  throw mapFetchFailure('Antigravity API', this.refreshHint(error), watchdog, options.signal);
@@ -130,28 +130,17 @@ export async function* streamProviderResponses(options, input) {
130
130
  if (models.length === 0) {
131
131
  throw new LlmError(`${label} has no enabled model to dispatch to`, EMPTY_RESPONSE_CODE);
132
132
  }
133
- // When the provider meters per account and an account's allowance can run out,
134
- // every account is a candidate: a spent one falls through to the next, so one
135
- // model id draws on the whole pool's allowance. Accounts are tried in order;
136
- // the first that serves the request wins.
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.
137
137
  let accounts = input.accounts === undefined
138
138
  ? undefined
139
139
  : [...await input.accounts()];
140
- // Send work straight to an account that has allowance: one already known to be
141
- // spent until a future reset is moved to the back, so a fresh request does not
142
- // fail against it once to find out. This is a preference, not a skip — a lone
143
- // account still serves, and the spent-retry below catches a stale record.
144
- if (accounts !== undefined && accounts.length > 1 && input.isAccountSpent !== undefined) {
145
- const fresh = [];
146
- const tired = [];
147
- for (const account of accounts) {
148
- if (await input.isAccountSpent(account))
149
- tired.push(account);
150
- else
151
- fresh.push(account);
152
- }
153
- if (tired.length > 0 && fresh.length > 0) {
154
- 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)];
155
144
  }
156
145
  }
157
146
  const tryAccounts = accounts === undefined || accounts.length === 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-subscription-oauth",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
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",