-
-
Notifications
You must be signed in to change notification settings - Fork 735
fix: reload stale provider state in already-open sessions #539
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1410,7 +1410,7 @@ export const createAntigravityPlugin = (providerId: string) => async ( | |
|
|
||
| // Note: AccountManager now ensures the current auth is always included in accounts | ||
|
|
||
| const accountManager = await AccountManager.loadFromDisk(auth); | ||
| let accountManager = await AccountManager.loadFromDisk(auth); | ||
| activeAccountManager = accountManager; | ||
| if (accountManager.getAccountCount() > 0) { | ||
| accountManager.requestSaveToDisk(); | ||
|
|
@@ -1461,9 +1461,8 @@ export const createAntigravityPlugin = (providerId: string) => async ( | |
| return fetch(input, init); | ||
| } | ||
|
|
||
| if (accountManager.getAccountCount() === 0) { | ||
| throw new Error("No Antigravity accounts configured. Run `opencode auth login`."); | ||
| } | ||
| accountManager = await AccountManager.loadFromDisk(latestAuth); | ||
| activeAccountManager = accountManager; | ||
|
Comment on lines
+1464
to
+1465
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid replacing the On Line 1464 and Line 1486, this hot-swaps the manager instance instead of reconciling it in place. Also applies to: 1477-1494 🤖 Prompt for AI Agents |
||
|
|
||
| const urlString = toUrlString(input); | ||
| const family = getModelFamilyFromUrl(urlString); | ||
|
|
@@ -1474,6 +1473,26 @@ export const createAntigravityPlugin = (providerId: string) => async ( | |
| debugLines.push(line); | ||
| }; | ||
| pushDebug(`request=${urlString}`); | ||
| let reloadedProviderState = false; | ||
| const reloadProviderState = async (reason: string): Promise<boolean> => { | ||
| if (reloadedProviderState) { | ||
| return false; | ||
| } | ||
| const reloadedAuth = await getAuth(); | ||
| if (!isOAuthAuth(reloadedAuth)) { | ||
| return false; | ||
| } | ||
| reloadedProviderState = true; | ||
| accountManager = await AccountManager.loadFromDisk(reloadedAuth); | ||
| activeAccountManager = accountManager; | ||
| rateLimitStateByAccountQuota.clear(); | ||
| emptyResponseAttempts.clear(); | ||
| accountFailureState.clear(); | ||
| rateLimitToastShown = false; | ||
| softQuotaToastShown = false; | ||
|
Comment on lines
+1488
to
+1492
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In practice the reload path is rare and the worst case is that a concurrent request retries against a previously-known rate-limited account once before re-discovering the limit. This is unlikely to be noticeable, but it's worth noting that the clear is not scoped to the triggering request. Prompt To Fix With AIThis is a comment left during a code review.
Path: src/plugin.ts
Line: 1488-1492
Comment:
**Clearing shared state affects concurrent in-flight requests**
`rateLimitStateByAccountQuota`, `emptyResponseAttempts`, `accountFailureState`, `rateLimitToastShown`, and `softQuotaToastShown` are all closure-level variables shared across every concurrent request in the same plugin instance. When one request triggers `reloadProviderState`, these maps are cleared globally, which means any other request that is mid-flight at the same moment loses its accumulated per-attempt tracking (e.g. backoff state, empty-response counters).
In practice the reload path is rare and the worst case is that a concurrent request retries against a previously-known rate-limited account once before re-discovering the limit. This is unlikely to be noticeable, but it's worth noting that the clear is not scoped to the triggering request.
How can I resolve this? If you propose a fix, please make it concise. |
||
| pushDebug(`reload-provider-state reason=${reason} accounts=${accountManager.getAccountCount()}`); | ||
| return true; | ||
| }; | ||
|
|
||
| type FailureContext = { | ||
| response: Response; | ||
|
|
@@ -1554,6 +1573,9 @@ export const createAntigravityPlugin = (providerId: string) => async ( | |
| } = routingDecision; | ||
|
|
||
| if (accountCount === 0) { | ||
| if (await reloadProviderState("no-accounts")) { | ||
| continue; | ||
| } | ||
| throw new Error("No Antigravity accounts available. Run `opencode auth login`."); | ||
| } | ||
|
|
||
|
|
@@ -1646,6 +1668,9 @@ export const createAntigravityPlugin = (providerId: string) => async ( | |
| // 0 means disabled (wait indefinitely) | ||
| const maxWaitMs = (config.max_rate_limit_wait_seconds ?? 300) * 1000; | ||
| if (maxWaitMs > 0 && waitMs > maxWaitMs) { | ||
| if (await reloadProviderState(`all-rate-limited:${family}`)) { | ||
| continue; | ||
| } | ||
| const waitTimeFormatted = formatWaitTime(waitMs); | ||
| await showToast( | ||
| `Rate limited for ${waitTimeFormatted}. Try again later or add another account.`, | ||
|
|
@@ -1827,6 +1852,7 @@ export const createAntigravityPlugin = (providerId: string) => async ( | |
| const warmupUrl = toWarmupStreamUrl(prepared.request); | ||
| const warmupHeaders = new Headers(prepared.init.headers ?? {}); | ||
| warmupHeaders.set("accept", "text/event-stream"); | ||
| warmupHeaders.delete("x-goog-api-key"); | ||
|
|
||
| const warmupInit: RequestInit = { | ||
| ...prepared.init, | ||
|
|
@@ -1979,16 +2005,23 @@ export const createAntigravityPlugin = (providerId: string) => async ( | |
| }, | ||
| ); | ||
|
|
||
| const requestHeaders = new Headers(prepared.init.headers ?? {}); | ||
| requestHeaders.delete("x-goog-api-key"); | ||
| const requestInit: RequestInit = { | ||
| ...prepared.init, | ||
| headers: requestHeaders, | ||
| }; | ||
|
|
||
| const originalUrl = toUrlString(input); | ||
| const resolvedUrl = toUrlString(prepared.request); | ||
| pushDebug(`endpoint=${currentEndpoint}`); | ||
| pushDebug(`resolved=${resolvedUrl}`); | ||
| const debugContext = startAntigravityDebugRequest({ | ||
| originalUrl, | ||
| resolvedUrl, | ||
| method: prepared.init.method, | ||
| headers: prepared.init.headers, | ||
| body: prepared.init.body, | ||
| method: requestInit.method, | ||
| headers: requestInit.headers, | ||
| body: requestInit.body, | ||
| streaming: prepared.streaming, | ||
| projectId: projectContext.effectiveProjectId, | ||
| }); | ||
|
|
@@ -2022,7 +2055,7 @@ export const createAntigravityPlugin = (providerId: string) => async ( | |
| tokenConsumed = getTokenTracker().consume(account.index); | ||
| } | ||
|
|
||
| const response = await fetch(prepared.request, prepared.init); | ||
| const response = await fetch(prepared.request, requestInit); | ||
| pushDebug(`status=${response.status} ${response.statusText}`); | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1003,7 +1003,7 @@ export class AccountManager { | |
| lastUsed: a.lastUsed, | ||
| enabled: a.enabled, | ||
| lastSwitchReason: a.lastSwitchReason, | ||
| rateLimitResetTimes: Object.keys(a.rateLimitResetTimes).length > 0 ? a.rateLimitResetTimes : undefined, | ||
| rateLimitResetTimes: { ...a.rateLimitResetTimes }, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't treat every empty On Line 1006, every save from an account with no in-memory limits now emits 🤖 Prompt for AI Agents |
||
| coolingDownUntil: a.coolingDownUntil, | ||
| cooldownReason: a.cooldownReason, | ||
| fingerprint: a.fingerprint, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AccountManager.loadFromDiskis now called at the top of everyfetchinvocation, unconditionally. For heavy workloads (e.g. many parallel tool-call requests in a single agent turn) this multiplies disk reads proportionally.Since the intent is only to avoid staleness between user interactions, it could be worth debouncing or caching the result for a short window (e.g. 1–5 s) so that a burst of concurrent requests within the same turn shares one disk read. This is not a correctness issue, but it may become noticeable at scale.
Prompt To Fix With AI