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.
- package/README.md +56 -54
- package/dist/src/plugin/accounts.d.ts +46 -3
- package/dist/src/plugin/accounts.d.ts.map +1 -1
- package/dist/src/plugin/accounts.js +187 -8
- package/dist/src/plugin/accounts.js.map +1 -1
- package/dist/src/plugin/config/loader.d.ts.map +1 -1
- package/dist/src/plugin/config/loader.js +7 -4
- package/dist/src/plugin/config/loader.js.map +1 -1
- package/dist/src/plugin/config/schema.d.ts +19 -0
- package/dist/src/plugin/config/schema.d.ts.map +1 -1
- package/dist/src/plugin/config/schema.js +55 -0
- package/dist/src/plugin/config/schema.js.map +1 -1
- package/dist/src/plugin/debug.d.ts +26 -0
- package/dist/src/plugin/debug.d.ts.map +1 -1
- package/dist/src/plugin/debug.js +69 -1
- package/dist/src/plugin/debug.js.map +1 -1
- package/dist/src/plugin/quota.d.ts +10 -0
- package/dist/src/plugin/quota.d.ts.map +1 -1
- package/dist/src/plugin/quota.js +88 -7
- package/dist/src/plugin/quota.js.map +1 -1
- package/dist/src/plugin/recovery.d.ts.map +1 -1
- package/dist/src/plugin/recovery.js +2 -0
- package/dist/src/plugin/recovery.js.map +1 -1
- package/dist/src/plugin/request.d.ts.map +1 -1
- package/dist/src/plugin/request.js +30 -15
- package/dist/src/plugin/request.js.map +1 -1
- package/dist/src/plugin/storage.d.ts +19 -0
- package/dist/src/plugin/storage.d.ts.map +1 -1
- package/dist/src/plugin/storage.js +44 -4
- package/dist/src/plugin/storage.js.map +1 -1
- package/dist/src/plugin/transform/model-resolver.d.ts +7 -9
- package/dist/src/plugin/transform/model-resolver.d.ts.map +1 -1
- package/dist/src/plugin/transform/model-resolver.js +12 -37
- package/dist/src/plugin/transform/model-resolver.js.map +1 -1
- package/dist/src/plugin/transform/types.d.ts +1 -1
- package/dist/src/plugin/transform/types.d.ts.map +1 -1
- package/dist/src/plugin.d.ts +7 -0
- package/dist/src/plugin.d.ts.map +1 -1
- package/dist/src/plugin.js +262 -50
- package/dist/src/plugin.js.map +1 -1
- 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/
|
|
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/
|
|
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
|
-
|
|
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
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
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
|
|
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/
|
|
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
|
-
"
|
|
153
|
-
"name": "Gemini 3 Pro
|
|
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
|
-
"
|
|
162
|
-
"name": "Gemini 3 Flash
|
|
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
|
-
"
|
|
173
|
-
"name": "
|
|
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
|
-
"
|
|
178
|
-
"name": "Claude Sonnet 4.5 Thinking
|
|
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
|
-
"
|
|
187
|
-
"name": "Claude Opus 4.5 Thinking
|
|
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/
|
|
403
|
-
"document-writer": { "model": "google/
|
|
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/
|
|
541
|
-
"document-writer": { "model": "google/
|
|
542
|
-
"multimodal-looker": { "model": "google/
|
|
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
|
-
|
|
612
|
-
OPENCODE_ANTIGRAVITY_DEBUG=
|
|
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":"AACA,OAAO,EAA8B,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;
|
|
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
|
-
|
|
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
|
-
|
|
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 &&
|
|
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
|