opencode-antigravity-auth 1.4.2 → 1.4.3-beta.0

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.
Files changed (41) hide show
  1. package/README.md +56 -54
  2. package/dist/src/plugin/accounts.d.ts +46 -3
  3. package/dist/src/plugin/accounts.d.ts.map +1 -1
  4. package/dist/src/plugin/accounts.js +187 -8
  5. package/dist/src/plugin/accounts.js.map +1 -1
  6. package/dist/src/plugin/config/loader.d.ts.map +1 -1
  7. package/dist/src/plugin/config/loader.js +7 -4
  8. package/dist/src/plugin/config/loader.js.map +1 -1
  9. package/dist/src/plugin/config/schema.d.ts +19 -0
  10. package/dist/src/plugin/config/schema.d.ts.map +1 -1
  11. package/dist/src/plugin/config/schema.js +55 -0
  12. package/dist/src/plugin/config/schema.js.map +1 -1
  13. package/dist/src/plugin/debug.d.ts +26 -0
  14. package/dist/src/plugin/debug.d.ts.map +1 -1
  15. package/dist/src/plugin/debug.js +69 -1
  16. package/dist/src/plugin/debug.js.map +1 -1
  17. package/dist/src/plugin/quota.d.ts +10 -0
  18. package/dist/src/plugin/quota.d.ts.map +1 -1
  19. package/dist/src/plugin/quota.js +88 -7
  20. package/dist/src/plugin/quota.js.map +1 -1
  21. package/dist/src/plugin/recovery.d.ts.map +1 -1
  22. package/dist/src/plugin/recovery.js +2 -0
  23. package/dist/src/plugin/recovery.js.map +1 -1
  24. package/dist/src/plugin/request.d.ts.map +1 -1
  25. package/dist/src/plugin/request.js +30 -15
  26. package/dist/src/plugin/request.js.map +1 -1
  27. package/dist/src/plugin/storage.d.ts +19 -0
  28. package/dist/src/plugin/storage.d.ts.map +1 -1
  29. package/dist/src/plugin/storage.js +44 -4
  30. package/dist/src/plugin/storage.js.map +1 -1
  31. package/dist/src/plugin/transform/model-resolver.d.ts +7 -9
  32. package/dist/src/plugin/transform/model-resolver.d.ts.map +1 -1
  33. package/dist/src/plugin/transform/model-resolver.js +12 -37
  34. package/dist/src/plugin/transform/model-resolver.js.map +1 -1
  35. package/dist/src/plugin/transform/types.d.ts +1 -1
  36. package/dist/src/plugin/transform/types.d.ts.map +1 -1
  37. package/dist/src/plugin.d.ts +7 -0
  38. package/dist/src/plugin.d.ts.map +1 -1
  39. package/dist/src/plugin.js +262 -50
  40. package/dist/src/plugin.js.map +1 -1
  41. package/package.json +2 -2
package/README.md CHANGED
@@ -77,7 +77,7 @@ Install the opencode-antigravity-auth plugin and add the Antigravity model defin
77
77
  4. **Use it:**
78
78
 
79
79
  ```bash
80
- opencode run "Hello" --model=google/antigravity-claude-sonnet-4-5-thinking --variant=max
80
+ opencode run "Hello" --model=google/claude-sonnet-4-5-thinking --variant=max
81
81
  ```
82
82
 
83
83
  </details>
@@ -100,7 +100,7 @@ Install the opencode-antigravity-auth plugin and add the Antigravity model defin
100
100
  ### Verification
101
101
 
102
102
  ```bash
103
- opencode run "Hello" --model=google/antigravity-claude-sonnet-4-5-thinking --variant=max
103
+ opencode run "Hello" --model=google/claude-sonnet-4-5-thinking --variant=max
104
104
  ```
105
105
 
106
106
  </details>
@@ -111,28 +111,23 @@ opencode run "Hello" --model=google/antigravity-claude-sonnet-4-5-thinking --var
111
111
 
112
112
  ### Model Reference
113
113
 
114
- **Antigravity quota** (Claude + Gemini 3):
114
+ All models use Antigravity quota by default with automatic fallback to Gemini CLI when exhausted.
115
115
 
116
116
  | Model | Variants | Notes |
117
117
  |-------|----------|-------|
118
- | `antigravity-gemini-3-pro` | low, high | Gemini 3 Pro with thinking |
119
- | `antigravity-gemini-3-flash` | minimal, low, medium, high | Gemini 3 Flash with thinking |
120
- | `antigravity-claude-sonnet-4-5` | — | Claude Sonnet 4.5 |
121
- | `antigravity-claude-sonnet-4-5-thinking` | low, max | Claude Sonnet with extended thinking |
122
- | `antigravity-claude-opus-4-5-thinking` | low, max | Claude Opus with extended thinking |
118
+ | `gemini-3-pro` | low, high | Gemini 3 Pro with thinking |
119
+ | `gemini-3-flash` | minimal, low, medium, high | Gemini 3 Flash with thinking |
120
+ | `gemini-2.5-pro` | — | Gemini 2.5 Pro |
121
+ | `gemini-2.5-flash` | | Gemini 2.5 Flash |
122
+ | `claude-sonnet-4-5` | | Claude Sonnet 4.5 |
123
+ | `claude-sonnet-4-5-thinking` | low, max | Claude Sonnet with extended thinking |
124
+ | `claude-opus-4-5-thinking` | low, max | Claude Opus with extended thinking |
123
125
 
124
- **Gemini CLI quota** (separate from Antigravity):
125
-
126
- | Model | Notes |
127
- |-------|-------|
128
- | `gemini-2.5-flash` | Gemini 2.5 Flash |
129
- | `gemini-2.5-pro` | Gemini 2.5 Pro |
130
- | `gemini-3-flash-preview` | Gemini 3 Flash (preview) |
131
- | `gemini-3-pro-preview` | Gemini 3 Pro (preview) |
126
+ > **Quota Behavior:** The plugin tries Antigravity quota first across ALL accounts. Only when Antigravity is exhausted on all accounts does it fall back to Gemini CLI quota. Model names are automatically transformed for the target API (e.g., `gemini-3-flash` → `gemini-3-flash-preview` for CLI).
132
127
 
133
128
  **Using variants:**
134
129
  ```bash
135
- opencode run "Hello" --model=google/antigravity-claude-sonnet-4-5-thinking --variant=max
130
+ opencode run "Hello" --model=google/claude-sonnet-4-5-thinking --variant=max
136
131
  ```
137
132
 
138
133
  For details on variant configuration and thinking levels, see [docs/MODEL-VARIANTS.md](docs/MODEL-VARIANTS.md).
@@ -149,8 +144,8 @@ Add this to your `~/.config/opencode/opencode.json`:
149
144
  "provider": {
150
145
  "google": {
151
146
  "models": {
152
- "antigravity-gemini-3-pro": {
153
- "name": "Gemini 3 Pro (Antigravity)",
147
+ "gemini-3-pro": {
148
+ "name": "Gemini 3 Pro",
154
149
  "limit": { "context": 1048576, "output": 65535 },
155
150
  "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] },
156
151
  "variants": {
@@ -158,8 +153,8 @@ Add this to your `~/.config/opencode/opencode.json`:
158
153
  "high": { "thinkingLevel": "high" }
159
154
  }
160
155
  },
161
- "antigravity-gemini-3-flash": {
162
- "name": "Gemini 3 Flash (Antigravity)",
156
+ "gemini-3-flash": {
157
+ "name": "Gemini 3 Flash",
163
158
  "limit": { "context": 1048576, "output": 65536 },
164
159
  "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] },
165
160
  "variants": {
@@ -169,13 +164,23 @@ Add this to your `~/.config/opencode/opencode.json`:
169
164
  "high": { "thinkingLevel": "high" }
170
165
  }
171
166
  },
172
- "antigravity-claude-sonnet-4-5": {
173
- "name": "Claude Sonnet 4.5 (Antigravity)",
167
+ "gemini-2.5-pro": {
168
+ "name": "Gemini 2.5 Pro",
169
+ "limit": { "context": 1048576, "output": 65536 },
170
+ "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
171
+ },
172
+ "gemini-2.5-flash": {
173
+ "name": "Gemini 2.5 Flash",
174
+ "limit": { "context": 1048576, "output": 65536 },
175
+ "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
176
+ },
177
+ "claude-sonnet-4-5": {
178
+ "name": "Claude Sonnet 4.5",
174
179
  "limit": { "context": 200000, "output": 64000 },
175
180
  "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
176
181
  },
177
- "antigravity-claude-sonnet-4-5-thinking": {
178
- "name": "Claude Sonnet 4.5 Thinking (Antigravity)",
182
+ "claude-sonnet-4-5-thinking": {
183
+ "name": "Claude Sonnet 4.5 Thinking",
179
184
  "limit": { "context": 200000, "output": 64000 },
180
185
  "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] },
181
186
  "variants": {
@@ -183,34 +188,14 @@ Add this to your `~/.config/opencode/opencode.json`:
183
188
  "max": { "thinkingConfig": { "thinkingBudget": 32768 } }
184
189
  }
185
190
  },
186
- "antigravity-claude-opus-4-5-thinking": {
187
- "name": "Claude Opus 4.5 Thinking (Antigravity)",
191
+ "claude-opus-4-5-thinking": {
192
+ "name": "Claude Opus 4.5 Thinking",
188
193
  "limit": { "context": 200000, "output": 64000 },
189
194
  "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] },
190
195
  "variants": {
191
196
  "low": { "thinkingConfig": { "thinkingBudget": 8192 } },
192
197
  "max": { "thinkingConfig": { "thinkingBudget": 32768 } }
193
198
  }
194
- },
195
- "gemini-2.5-flash": {
196
- "name": "Gemini 2.5 Flash (Gemini CLI)",
197
- "limit": { "context": 1048576, "output": 65536 },
198
- "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
199
- },
200
- "gemini-2.5-pro": {
201
- "name": "Gemini 2.5 Pro (Gemini CLI)",
202
- "limit": { "context": 1048576, "output": 65536 },
203
- "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
204
- },
205
- "gemini-3-flash-preview": {
206
- "name": "Gemini 3 Flash Preview (Gemini CLI)",
207
- "limit": { "context": 1048576, "output": 65536 },
208
- "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
209
- },
210
- "gemini-3-pro-preview": {
211
- "name": "Gemini 3 Pro Preview (Gemini CLI)",
212
- "limit": { "context": 1048576, "output": 65535 },
213
- "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
214
199
  }
215
200
  }
216
201
  }
@@ -218,6 +203,8 @@ Add this to your `~/.config/opencode/opencode.json`:
218
203
  }
219
204
  ```
220
205
 
206
+ > **Backward Compatibility:** Legacy model names with `antigravity-` prefix (e.g., `antigravity-gemini-3-flash`) still work. The plugin automatically handles model name transformation for both Antigravity and Gemini CLI APIs.
207
+
221
208
  </details>
222
209
 
223
210
  ---
@@ -255,6 +242,10 @@ OpenCode uses `~/.config/opencode/` on **all platforms** including Windows.
255
242
 
256
243
  > **Windows users**: `~` resolves to your user home directory (e.g., `C:\Users\YourName`). Do NOT use `%APPDATA%`.
257
244
 
245
+ > **Custom path**: Set `OPENCODE_CONFIG_DIR` environment variable to use a custom location.
246
+
247
+ > **Windows migration**: If upgrading from plugin v1.3.x or earlier, the plugin will automatically find your existing config in `%APPDATA%\opencode\` and use it. New installations use `~/.config/opencode/`.
248
+
258
249
  ---
259
250
 
260
251
  ### Multi-Account Auth Issues
@@ -399,8 +390,8 @@ If you encounter errors during a session:
399
390
  {
400
391
  "google_auth": false,
401
392
  "agents": {
402
- "frontend-ui-ux-engineer": { "model": "google/antigravity-gemini-3-pro" },
403
- "document-writer": { "model": "google/antigravity-gemini-3-flash" }
393
+ "frontend-ui-ux-engineer": { "model": "google/gemini-3-pro" },
394
+ "document-writer": { "model": "google/gemini-3-flash" }
404
395
  }
405
396
  }
406
397
  ```
@@ -537,9 +528,9 @@ Disable built-in auth and override agent models in `oh-my-opencode.json`:
537
528
  {
538
529
  "google_auth": false,
539
530
  "agents": {
540
- "frontend-ui-ux-engineer": { "model": "google/antigravity-gemini-3-pro" },
541
- "document-writer": { "model": "google/antigravity-gemini-3-flash" },
542
- "multimodal-looker": { "model": "google/antigravity-gemini-3-flash" }
531
+ "frontend-ui-ux-engineer": { "model": "google/gemini-3-pro" },
532
+ "document-writer": { "model": "google/gemini-3-flash" },
533
+ "multimodal-looker": { "model": "google/gemini-3-flash" }
543
534
  }
544
535
  }
545
536
  ```
@@ -581,6 +572,16 @@ Most users don't need to configure anything — defaults work well.
581
572
  | **5+ accounts** | `"account_selection_strategy": "round-robin"` |
582
573
  | **Parallel agents** | Add `"pid_offset_enabled": true` |
583
574
 
575
+ ### Quota Protection
576
+
577
+ | Option | Default | What it does |
578
+ |--------|---------|--------------|
579
+ | `soft_quota_threshold_percent` | `90` | Skip account when quota usage exceeds this percentage. Prevents Google from penalizing accounts that fully exhaust quota. Set to `100` to disable. |
580
+ | `quota_refresh_interval_minutes` | `15` | Background quota refresh interval. After successful API requests, refreshes quota cache if older than this interval. Set to `0` to disable. |
581
+ | `soft_quota_cache_ttl_minutes` | `"auto"` | How long quota cache is considered fresh. `"auto"` = max(2 × refresh interval, 10 minutes). Set a number (1-120) for fixed TTL. |
582
+
583
+ > **How it works**: Quota cache is refreshed automatically after API requests (when older than `quota_refresh_interval_minutes`) and manually via "Check quotas" in `opencode auth login`. The threshold check uses `soft_quota_cache_ttl_minutes` to determine cache freshness - if cache is older, the account is considered "unknown" and allowed (fail-open). When ALL accounts exceed the threshold, the plugin waits for the earliest quota reset time (like rate limit behavior). If wait time exceeds `max_rate_limit_wait_seconds`, it errors immediately.
584
+
584
585
  ### Rate Limit Scheduling
585
586
 
586
587
  Control how the plugin handles rate limits:
@@ -608,8 +609,9 @@ For all options, see [docs/CONFIGURATION.md](docs/CONFIGURATION.md).
608
609
 
609
610
  **Environment variables:**
610
611
  ```bash
611
- OPENCODE_ANTIGRAVITY_DEBUG=1 opencode # Enable debug logging
612
- OPENCODE_ANTIGRAVITY_DEBUG=2 opencode # Verbose logging
612
+ OPENCODE_CONFIG_DIR=/path/to/config opencode # Custom config directory
613
+ OPENCODE_ANTIGRAVITY_DEBUG=1 opencode # Enable debug logging
614
+ OPENCODE_ANTIGRAVITY_DEBUG=2 opencode # Verbose logging
613
615
  ```
614
616
 
615
617
  ---
@@ -1,7 +1,8 @@
1
- import { type AccountStorageV3, type RateLimitStateV3, type ModelFamily, type HeaderStyle, type CooldownReason } from "./storage";
1
+ import { type AccountStorageV3, type AccountMetadataV3, type RateLimitStateV3, type ModelFamily, type HeaderStyle, type CooldownReason } from "./storage";
2
2
  import type { OAuthAuthDetails, RefreshParts } from "./types";
3
3
  import type { AccountSelectionStrategy } from "./config/schema";
4
4
  import { type Fingerprint, type FingerprintVersion } from "./fingerprint";
5
+ import type { QuotaGroup, QuotaGroupSummary } from "./quota";
5
6
  export type { ModelFamily, HeaderStyle, CooldownReason } from "./storage";
6
7
  export type { AccountSelectionStrategy } from "./config/schema";
7
8
  export type RateLimitReason = "QUOTA_EXHAUSTED" | "RATE_LIMIT_EXCEEDED" | "MODEL_CAPACITY_EXHAUSTED" | "SERVER_ERROR" | "UNKNOWN";
@@ -34,7 +35,24 @@ export interface ManagedAccount {
34
35
  fingerprint?: import("./fingerprint").Fingerprint;
35
36
  /** History of previous fingerprints for this account */
36
37
  fingerprintHistory?: FingerprintVersion[];
38
+ /** Cached quota data from last checkAccountsQuota() call */
39
+ cachedQuota?: Partial<Record<QuotaGroup, QuotaGroupSummary>>;
40
+ cachedQuotaUpdatedAt?: number;
37
41
  }
42
+ /**
43
+ * Resolve the quota group for soft quota checks.
44
+ *
45
+ * When a model string is available, we can precisely determine the quota group.
46
+ * When model is null/undefined, we fall back based on family:
47
+ * - Claude → "claude" quota group
48
+ * - Gemini → "gemini-pro" (conservative fallback; may misclassify flash models)
49
+ *
50
+ * @param family - The model family ("claude" | "gemini")
51
+ * @param model - Optional model string for precise resolution
52
+ * @returns The QuotaGroup to use for soft quota checks
53
+ */
54
+ export declare function resolveQuotaGroup(family: ModelFamily, model?: string | null): QuotaGroup;
55
+ export declare function computeSoftQuotaCacheTtlMs(ttlConfig: "auto" | number, refreshIntervalMinutes: number): number;
38
56
  /**
39
57
  * In-memory multi-account manager with sticky account selection.
40
58
  *
@@ -68,8 +86,8 @@ export declare class AccountManager {
68
86
  */
69
87
  shouldShowAccountToast(accountIndex: number, debounceMs?: number): boolean;
70
88
  markToastShown(accountIndex: number): void;
71
- getCurrentOrNextForFamily(family: ModelFamily, model?: string | null, strategy?: AccountSelectionStrategy, headerStyle?: HeaderStyle, pidOffsetEnabled?: boolean): ManagedAccount | null;
72
- getNextForFamily(family: ModelFamily, model?: string | null, headerStyle?: HeaderStyle): ManagedAccount | null;
89
+ getCurrentOrNextForFamily(family: ModelFamily, model?: string | null, strategy?: AccountSelectionStrategy, headerStyle?: HeaderStyle, pidOffsetEnabled?: boolean, softQuotaThresholdPercent?: number, softQuotaCacheTtlMs?: number): ManagedAccount | null;
90
+ getNextForFamily(family: ModelFamily, model?: string | null, headerStyle?: HeaderStyle, softQuotaThresholdPercent?: number, softQuotaCacheTtlMs?: number): ManagedAccount | null;
73
91
  markRateLimited(account: ManagedAccount, retryAfterMs: number, family: ModelFamily, headerStyle?: HeaderStyle, model?: string | null): void;
74
92
  /**
75
93
  * Mark an account as used after a successful API request.
@@ -90,6 +108,19 @@ export declare class AccountManager {
90
108
  getFreshAccountsForQuota(quotaKey: string, family: ModelFamily, model?: string | null): ManagedAccount[];
91
109
  isRateLimitedForHeaderStyle(account: ManagedAccount, family: ModelFamily, headerStyle: HeaderStyle, model?: string | null): boolean;
92
110
  getAvailableHeaderStyle(account: ManagedAccount, family: ModelFamily, model?: string | null): HeaderStyle | null;
111
+ /**
112
+ * Check if any OTHER account has antigravity quota available for the given family/model.
113
+ *
114
+ * Used to determine whether to switch accounts vs fall back to gemini-cli:
115
+ * - If true: Switch to another account (preserve antigravity priority)
116
+ * - If false: All accounts exhausted antigravity, safe to fall back to gemini-cli
117
+ *
118
+ * @param currentAccountIndex - Index of the current account (will be excluded from check)
119
+ * @param family - Model family ("gemini" or "claude")
120
+ * @param model - Optional model name for model-specific rate limits
121
+ * @returns true if any other enabled, non-cooling-down account has antigravity available
122
+ */
123
+ hasOtherAccountWithAntigravityAvailable(currentAccountIndex: number, family: ModelFamily, model?: string | null): boolean;
93
124
  removeAccount(account: ManagedAccount): boolean;
94
125
  updateFromAuth(account: ManagedAccount, auth: OAuthAuthDetails): void;
95
126
  toAuthDetails(account: ManagedAccount): OAuthAuthDetails;
@@ -118,5 +149,17 @@ export declare class AccountManager {
118
149
  * @returns Array of fingerprint versions, or empty array if not found
119
150
  */
120
151
  getAccountFingerprintHistory(accountIndex: number): FingerprintVersion[];
152
+ updateQuotaCache(accountIndex: number, quotaGroups: Partial<Record<QuotaGroup, QuotaGroupSummary>>): void;
153
+ isAccountOverSoftQuota(account: ManagedAccount, family: ModelFamily, thresholdPercent: number, cacheTtlMs: number, model?: string | null): boolean;
154
+ getAccountsForQuotaCheck(): AccountMetadataV3[];
155
+ getOldestQuotaCacheAge(): number | null;
156
+ areAllAccountsOverSoftQuota(family: ModelFamily, thresholdPercent: number, cacheTtlMs: number, model?: string | null): boolean;
157
+ /**
158
+ * Get minimum wait time until any account's soft quota resets.
159
+ * Returns 0 if any account is available (not over threshold).
160
+ * Returns the minimum resetTime across all over-threshold accounts.
161
+ * Returns null if no resetTime data is available.
162
+ */
163
+ getMinWaitTimeForSoftQuota(family: ModelFamily, thresholdPercent: number, cacheTtlMs: number, model?: string | null): number | null;
121
164
  }
122
165
  //# sourceMappingURL=accounts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"accounts.d.ts","sourceRoot":"","sources":["../../../src/plugin/accounts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"accounts.d.ts","sourceRoot":"","sources":["../../../src/plugin/accounts.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,8 @@ import { formatRefreshParts, parseRefreshParts } from "./auth";
2
2
  import { loadAccounts, saveAccounts } from "./storage";
3
3
  import { getHealthTracker, getTokenTracker, selectHybridAccount } from "./rotation";
4
4
  import { generateFingerprint, MAX_FINGERPRINT_HISTORY } from "./fingerprint";
5
+ import { getModelFamily } from "./transform/model-resolver";
6
+ import { debugLogToFile } from "./debug";
5
7
  import { ANTIGRAVITY_VERSION } from "../constants";
6
8
  /**
7
9
  * Update fingerprint userAgent to current version if outdated.
@@ -153,6 +155,54 @@ function clearExpiredRateLimits(account) {
153
155
  }
154
156
  }
155
157
  }
158
+ /**
159
+ * Resolve the quota group for soft quota checks.
160
+ *
161
+ * When a model string is available, we can precisely determine the quota group.
162
+ * When model is null/undefined, we fall back based on family:
163
+ * - Claude → "claude" quota group
164
+ * - Gemini → "gemini-pro" (conservative fallback; may misclassify flash models)
165
+ *
166
+ * @param family - The model family ("claude" | "gemini")
167
+ * @param model - Optional model string for precise resolution
168
+ * @returns The QuotaGroup to use for soft quota checks
169
+ */
170
+ export function resolveQuotaGroup(family, model) {
171
+ if (model) {
172
+ return getModelFamily(model);
173
+ }
174
+ return family === "claude" ? "claude" : "gemini-pro";
175
+ }
176
+ function isOverSoftQuotaThreshold(account, family, thresholdPercent, cacheTtlMs, model) {
177
+ if (thresholdPercent >= 100)
178
+ return false;
179
+ if (!account.cachedQuota)
180
+ return false;
181
+ if (account.cachedQuotaUpdatedAt == null)
182
+ return false;
183
+ const age = nowMs() - account.cachedQuotaUpdatedAt;
184
+ if (age > cacheTtlMs)
185
+ return false;
186
+ const quotaGroup = resolveQuotaGroup(family, model);
187
+ const groupData = account.cachedQuota[quotaGroup];
188
+ if (groupData?.remainingFraction == null)
189
+ return false;
190
+ const remainingFraction = Math.max(0, Math.min(1, groupData.remainingFraction));
191
+ const usedPercent = (1 - remainingFraction) * 100;
192
+ const isOverThreshold = usedPercent >= thresholdPercent;
193
+ if (isOverThreshold) {
194
+ const accountLabel = account.email || `Account ${account.index + 1}`;
195
+ debugLogToFile(`[SoftQuota] Skipping ${accountLabel}: ${quotaGroup} usage ${usedPercent.toFixed(1)}% >= threshold ${thresholdPercent}%` +
196
+ (groupData.resetTime ? ` (resets: ${groupData.resetTime})` : ''));
197
+ }
198
+ return isOverThreshold;
199
+ }
200
+ export function computeSoftQuotaCacheTtlMs(ttlConfig, refreshIntervalMinutes) {
201
+ if (ttlConfig === "auto") {
202
+ return Math.max(2 * refreshIntervalMinutes, 10) * 60 * 1000;
203
+ }
204
+ return ttlConfig * 60 * 1000;
205
+ }
156
206
  /**
157
207
  * In-memory multi-account manager with sticky account selection.
158
208
  *
@@ -222,6 +272,8 @@ export class AccountManager {
222
272
  fingerprint: acc.fingerprint
223
273
  ? updateFingerprintVersion(acc.fingerprint)
224
274
  : generateFingerprint(),
275
+ cachedQuota: acc.cachedQuota,
276
+ cachedQuotaUpdatedAt: acc.cachedQuotaUpdatedAt,
225
277
  };
226
278
  })
227
279
  .filter((a) => a !== null);
@@ -320,10 +372,10 @@ export class AccountManager {
320
372
  this.lastToastAccountIndex = accountIndex;
321
373
  this.lastToastTime = nowMs();
322
374
  }
323
- getCurrentOrNextForFamily(family, model, strategy = 'sticky', headerStyle = 'antigravity', pidOffsetEnabled = false) {
375
+ getCurrentOrNextForFamily(family, model, strategy = 'sticky', headerStyle = 'antigravity', pidOffsetEnabled = false, softQuotaThresholdPercent = 100, softQuotaCacheTtlMs = 10 * 60 * 1000) {
324
376
  const quotaKey = getQuotaKey(family, headerStyle, model);
325
377
  if (strategy === 'round-robin') {
326
- const next = this.getNextForFamily(family, model, headerStyle);
378
+ const next = this.getNextForFamily(family, model, headerStyle, softQuotaThresholdPercent, softQuotaCacheTtlMs);
327
379
  if (next) {
328
380
  this.markTouchedForQuota(next, quotaKey);
329
381
  this.currentAccountIndexByFamily[family] = next.index;
@@ -341,7 +393,8 @@ export class AccountManager {
341
393
  index: acc.index,
342
394
  lastUsed: acc.lastUsed,
343
395
  healthScore: healthTracker.getScore(acc.index),
344
- isRateLimited: isRateLimitedForFamily(acc, family, model),
396
+ isRateLimited: isRateLimitedForFamily(acc, family, model) ||
397
+ isOverSoftQuotaThreshold(acc, family, softQuotaThresholdPercent, softQuotaCacheTtlMs, model),
345
398
  isCoolingDown: this.isAccountCoolingDown(acc),
346
399
  };
347
400
  });
@@ -364,29 +417,35 @@ export class AccountManager {
364
417
  if (pidOffsetEnabled && !this.sessionOffsetApplied[family] && this.accounts.length > 1) {
365
418
  const pidOffset = process.pid % this.accounts.length;
366
419
  const baseIndex = this.currentAccountIndexByFamily[family] ?? 0;
367
- this.currentAccountIndexByFamily[family] = (baseIndex + pidOffset) % this.accounts.length;
420
+ const newIndex = (baseIndex + pidOffset) % this.accounts.length;
421
+ debugLogToFile(`[Account] Applying PID offset: pid=${process.pid} offset=${pidOffset} family=${family} index=${baseIndex}->${newIndex}`);
422
+ this.currentAccountIndexByFamily[family] = newIndex;
368
423
  this.sessionOffsetApplied[family] = true;
369
424
  }
370
425
  const current = this.getCurrentAccountForFamily(family);
371
426
  if (current) {
372
427
  clearExpiredRateLimits(current);
373
428
  const isLimitedForRequestedStyle = isRateLimitedForHeaderStyle(current, family, headerStyle, model);
374
- if (!isLimitedForRequestedStyle && !this.isAccountCoolingDown(current)) {
429
+ const isOverThreshold = isOverSoftQuotaThreshold(current, family, softQuotaThresholdPercent, softQuotaCacheTtlMs, model);
430
+ if (!isLimitedForRequestedStyle && !isOverThreshold && !this.isAccountCoolingDown(current)) {
375
431
  this.markTouchedForQuota(current, quotaKey);
376
432
  return current;
377
433
  }
378
434
  }
379
- const next = this.getNextForFamily(family, model, headerStyle);
435
+ const next = this.getNextForFamily(family, model, headerStyle, softQuotaThresholdPercent, softQuotaCacheTtlMs);
380
436
  if (next) {
381
437
  this.markTouchedForQuota(next, quotaKey);
382
438
  this.currentAccountIndexByFamily[family] = next.index;
383
439
  }
384
440
  return next;
385
441
  }
386
- getNextForFamily(family, model, headerStyle = "antigravity") {
442
+ getNextForFamily(family, model, headerStyle = "antigravity", softQuotaThresholdPercent = 100, softQuotaCacheTtlMs = 10 * 60 * 1000) {
387
443
  const available = this.accounts.filter((a) => {
388
444
  clearExpiredRateLimits(a);
389
- return a.enabled !== false && !isRateLimitedForHeaderStyle(a, family, headerStyle, model) && !this.isAccountCoolingDown(a);
445
+ return a.enabled !== false &&
446
+ !isRateLimitedForHeaderStyle(a, family, headerStyle, model) &&
447
+ !isOverSoftQuotaThreshold(a, family, softQuotaThresholdPercent, softQuotaCacheTtlMs, model) &&
448
+ !this.isAccountCoolingDown(a);
390
449
  });
391
450
  if (available.length === 0) {
392
451
  return null;
@@ -509,6 +568,43 @@ export class AccountManager {
509
568
  }
510
569
  return null;
511
570
  }
571
+ /**
572
+ * Check if any OTHER account has antigravity quota available for the given family/model.
573
+ *
574
+ * Used to determine whether to switch accounts vs fall back to gemini-cli:
575
+ * - If true: Switch to another account (preserve antigravity priority)
576
+ * - If false: All accounts exhausted antigravity, safe to fall back to gemini-cli
577
+ *
578
+ * @param currentAccountIndex - Index of the current account (will be excluded from check)
579
+ * @param family - Model family ("gemini" or "claude")
580
+ * @param model - Optional model name for model-specific rate limits
581
+ * @returns true if any other enabled, non-cooling-down account has antigravity available
582
+ */
583
+ hasOtherAccountWithAntigravityAvailable(currentAccountIndex, family, model) {
584
+ // Claude has no gemini-cli fallback - always return false
585
+ // (This method is only relevant for Gemini's dual quota pools)
586
+ if (family === "claude") {
587
+ return false;
588
+ }
589
+ return this.accounts.some(acc => {
590
+ // Skip current account
591
+ if (acc.index === currentAccountIndex) {
592
+ return false;
593
+ }
594
+ // Skip disabled accounts
595
+ if (acc.enabled === false) {
596
+ return false;
597
+ }
598
+ // Skip cooling down accounts
599
+ if (this.isAccountCoolingDown(acc)) {
600
+ return false;
601
+ }
602
+ // Clear expired rate limits before checking
603
+ clearExpiredRateLimits(acc);
604
+ // Check if antigravity is available for this account
605
+ return !isRateLimitedForHeaderStyle(acc, family, "antigravity", model);
606
+ });
607
+ }
512
608
  removeAccount(account) {
513
609
  const idx = this.accounts.indexOf(account);
514
610
  if (idx < 0) {
@@ -614,6 +710,8 @@ export class AccountManager {
614
710
  cooldownReason: a.cooldownReason,
615
711
  fingerprint: a.fingerprint,
616
712
  fingerprintHistory: a.fingerprintHistory?.length ? a.fingerprintHistory : undefined,
713
+ cachedQuota: a.cachedQuota && Object.keys(a.cachedQuota).length > 0 ? a.cachedQuota : undefined,
714
+ cachedQuotaUpdatedAt: a.cachedQuotaUpdatedAt,
617
715
  })),
618
716
  activeIndex: claudeIndex,
619
717
  activeIndexByFamily: {
@@ -735,5 +833,86 @@ export class AccountManager {
735
833
  }
736
834
  return [...account.fingerprintHistory];
737
835
  }
836
+ updateQuotaCache(accountIndex, quotaGroups) {
837
+ const account = this.accounts[accountIndex];
838
+ if (account) {
839
+ account.cachedQuota = quotaGroups;
840
+ account.cachedQuotaUpdatedAt = nowMs();
841
+ }
842
+ }
843
+ isAccountOverSoftQuota(account, family, thresholdPercent, cacheTtlMs, model) {
844
+ return isOverSoftQuotaThreshold(account, family, thresholdPercent, cacheTtlMs, model);
845
+ }
846
+ getAccountsForQuotaCheck() {
847
+ return this.accounts.map((a) => ({
848
+ email: a.email,
849
+ refreshToken: a.parts.refreshToken,
850
+ projectId: a.parts.projectId,
851
+ managedProjectId: a.parts.managedProjectId,
852
+ addedAt: a.addedAt,
853
+ lastUsed: a.lastUsed,
854
+ enabled: a.enabled,
855
+ }));
856
+ }
857
+ getOldestQuotaCacheAge() {
858
+ let oldest = null;
859
+ for (const acc of this.accounts) {
860
+ if (acc.enabled === false)
861
+ continue;
862
+ if (acc.cachedQuotaUpdatedAt == null)
863
+ return null;
864
+ const age = nowMs() - acc.cachedQuotaUpdatedAt;
865
+ if (oldest === null || age > oldest)
866
+ oldest = age;
867
+ }
868
+ return oldest;
869
+ }
870
+ areAllAccountsOverSoftQuota(family, thresholdPercent, cacheTtlMs, model) {
871
+ if (thresholdPercent >= 100)
872
+ return false;
873
+ const enabled = this.accounts.filter(a => a.enabled !== false);
874
+ if (enabled.length === 0)
875
+ return false;
876
+ return enabled.every(a => isOverSoftQuotaThreshold(a, family, thresholdPercent, cacheTtlMs, model));
877
+ }
878
+ /**
879
+ * Get minimum wait time until any account's soft quota resets.
880
+ * Returns 0 if any account is available (not over threshold).
881
+ * Returns the minimum resetTime across all over-threshold accounts.
882
+ * Returns null if no resetTime data is available.
883
+ */
884
+ getMinWaitTimeForSoftQuota(family, thresholdPercent, cacheTtlMs, model) {
885
+ if (thresholdPercent >= 100)
886
+ return 0;
887
+ const enabled = this.accounts.filter(a => a.enabled !== false);
888
+ if (enabled.length === 0)
889
+ return null;
890
+ // If any account is available (not over threshold), no wait needed
891
+ const available = enabled.filter(a => !isOverSoftQuotaThreshold(a, family, thresholdPercent, cacheTtlMs, model));
892
+ if (available.length > 0)
893
+ return 0;
894
+ // All accounts are over threshold - find earliest reset time
895
+ // For gemini family, we MUST have the model to distinguish pro vs flash quotas.
896
+ // Fail-open (return null = no wait info) if model is missing to avoid blocking on wrong quota.
897
+ if (!model && family !== "claude")
898
+ return null;
899
+ const quotaGroup = resolveQuotaGroup(family, model);
900
+ const now = nowMs();
901
+ const waitTimes = [];
902
+ for (const acc of enabled) {
903
+ const groupData = acc.cachedQuota?.[quotaGroup];
904
+ if (groupData?.resetTime) {
905
+ const resetTimestamp = Date.parse(groupData.resetTime);
906
+ if (Number.isFinite(resetTimestamp)) {
907
+ waitTimes.push(Math.max(0, resetTimestamp - now));
908
+ }
909
+ }
910
+ }
911
+ if (waitTimes.length === 0)
912
+ return null;
913
+ const minWait = Math.min(...waitTimes);
914
+ // Treat 0 as stale cache (resetTime in the past) → fail-open to avoid spin loop
915
+ return minWait === 0 ? null : minWait;
916
+ }
738
917
  }
739
918
  //# sourceMappingURL=accounts.js.map