@bitkyc08/opencodex 2.52.0 → 2.53.0-preview.20260913
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/gui/dist/assets/index-BBOZWGB6.css +1 -0
- package/gui/dist/assets/index-D7ynYo2K.js +128 -0
- package/gui/dist/index.html +2 -2
- package/native/remote-workspace-helper/Cargo.lock +130 -0
- package/native/remote-workspace-helper/Cargo.toml +24 -0
- package/native/remote-workspace-helper/src/main.rs +49 -0
- package/native/remote-workspace-helper/src/protocol.rs +246 -0
- package/native/remote-workspace-helper/src/sandbox/macos.rs +19 -0
- package/native/remote-workspace-helper/src/sandbox/mod.rs +77 -0
- package/native/remote-workspace-helper/src/sandbox/windows.rs +15 -0
- package/package.json +6 -1
- package/src/adapters/anthropic-image-normalize.ts +30 -2
- package/src/adapters/anthropic.ts +1 -1
- package/src/adapters/base.ts +8 -2
- package/src/adapters/cursor/cursor-errors.ts +12 -0
- package/src/adapters/cursor/thread-continuity.ts +93 -0
- package/src/adapters/cursor.ts +104 -73
- package/src/adapters/devin/cloud-direct/chat.ts +312 -23
- package/src/adapters/devin/cloud-direct/metadata.ts +31 -2
- package/src/adapters/devin/live-models.ts +70 -3
- package/src/adapters/devin.ts +281 -21
- package/src/adapters/google-wire-compiler.ts +14 -6
- package/src/adapters/google.ts +22 -8
- package/src/adapters/kiro/adapter.ts +316 -0
- package/src/adapters/kiro/conversation.ts +136 -0
- package/src/adapters/kiro/payload.ts +432 -0
- package/src/adapters/kiro/reasoning.ts +56 -0
- package/src/adapters/kiro/stream.ts +1153 -0
- package/src/adapters/kiro/usage.ts +223 -0
- package/src/adapters/kiro/wire.ts +76 -0
- package/src/adapters/kiro.ts +8 -2319
- package/src/adapters/mimo-free.ts +1 -1
- package/src/adapters/openai-chat-images.ts +101 -0
- package/src/adapters/openai-chat.ts +201 -181
- package/src/adapters/openai-responses.ts +92 -224
- package/src/adapters/registry.ts +0 -7
- package/src/adapters/run-turn-queue.ts +13 -6
- package/src/bridge.ts +14 -15
- package/src/chat/inbound.ts +29 -4
- package/src/chat/outbound.ts +145 -107
- package/src/claude/desktop-profile.ts +4 -6
- package/src/cli/account-api.ts +14 -0
- package/src/cli/account-extended.ts +1 -1
- package/src/cli/account-history.ts +60 -0
- package/src/cli/account-main.ts +80 -0
- package/src/cli/account.ts +11 -3
- package/src/cli/capabilities.ts +113 -0
- package/src/cli/catalog.ts +109 -0
- package/src/cli/dispatch.ts +9 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/index.ts +2 -2
- package/src/cli/observe.ts +28 -1
- package/src/cli/opencode.ts +42 -8
- package/src/cli/provider-runtime.ts +11 -1
- package/src/cli/provider.ts +22 -2
- package/src/cli/registry.ts +21 -0
- package/src/cli/remote-workspace.ts +154 -0
- package/src/cli/status.ts +39 -7
- package/src/cli/usage-report.ts +14 -2
- package/src/client/hub-client.ts +34 -0
- package/src/client/hub-state.ts +9 -1
- package/src/codex/account-store.ts +78 -0
- package/src/codex/auth-api.ts +81 -54
- package/src/codex/auth-context.ts +45 -16
- package/src/codex/catalog/effort.ts +1 -1
- package/src/codex/catalog/metadata.ts +3 -6
- package/src/codex/catalog/native-models.ts +4 -4
- package/src/codex/catalog/parsing.ts +2 -20
- package/src/codex/catalog/provider-fetch.ts +10 -1
- package/src/codex/catalog/remote.ts +233 -0
- package/src/codex/catalog/sync.ts +403 -35
- package/src/codex/convergence.ts +1 -1
- package/src/codex/history-manifest.ts +36 -0
- package/src/codex/history-provider.ts +32 -5
- package/src/codex/inject.ts +9 -0
- package/src/codex/main-account.ts +113 -0
- package/src/codex/main-device-reauth-api.ts +89 -0
- package/src/codex/main-device-reauth.ts +217 -0
- package/src/codex/native-residue.ts +9 -2
- package/src/codex/quota-auto-refresh.ts +3 -2
- package/src/codex/quota-capacity.ts +98 -0
- package/src/codex/quota-history.ts +160 -0
- package/src/codex/quota-types.ts +8 -0
- package/src/codex/quota.ts +118 -91
- package/src/codex/refresh.ts +2 -1
- package/src/codex/routing.ts +90 -17
- package/src/codex/sync.ts +33 -4
- package/src/combos/request.ts +19 -1
- package/src/config/multi-agent-surface.ts +61 -0
- package/src/config/provider-validation.ts +176 -0
- package/src/config.ts +213 -11
- package/src/generated/compatibility-version.json +436 -168
- package/src/images/loop.ts +119 -36
- package/src/lib/admission.ts +12 -6
- package/src/lib/redact.ts +7 -0
- package/src/lib/translator-budget.ts +4 -3
- package/src/lib/windows-atomic-replace.ts +1 -0
- package/src/lib/windows-elevation.ts +1 -1
- package/src/oauth/chatgpt-device.ts +62 -5
- package/src/oauth/devin/cli-import.ts +130 -0
- package/src/oauth/devin.ts +63 -8
- package/src/oauth/index.ts +29 -14
- package/src/oauth/kiro.ts +18 -6
- package/src/oauth/login-cli.ts +9 -1
- package/src/oauth/meta-muse-device.ts +464 -0
- package/src/oauth/meta-muse.ts +123 -32
- package/src/oauth/pool-kernel.ts +9 -0
- package/src/oauth/pool-settings-capability.ts +2 -2
- package/src/oauth/store.ts +57 -0
- package/src/oauth/types.ts +31 -0
- package/src/providers/derive.ts +13 -3
- package/src/providers/devin-cli-authmode-migration.ts +57 -35
- package/src/providers/devin-provider-merge-migration.ts +240 -0
- package/src/providers/muse-key-quota.ts +117 -0
- package/src/providers/muse-subscription-usage.ts +14 -2
- package/src/providers/openai-sidecar.ts +25 -3
- package/src/providers/opencode-zen-rate-limit.ts +58 -0
- package/src/providers/provider-id-rewrite.ts +20 -5
- package/src/providers/quota-types.ts +12 -0
- package/src/providers/quota.ts +143 -102
- package/src/providers/reasoning-metadata.ts +543 -0
- package/src/providers/registry.ts +80 -49
- package/src/reasoning-effort.ts +26 -2
- package/src/remote/hub-usage.ts +32 -0
- package/src/remote-control/index.ts +192 -41
- package/src/remote-control/workspace-activation.ts +9 -0
- package/src/remote-control/workspace-agent-connection.ts +366 -0
- package/src/remote-control/workspace-claude-runtime.ts +243 -0
- package/src/remote-control/workspace-codex-runtime.ts +531 -0
- package/src/remote-control/workspace-codex-sandbox.ts +115 -0
- package/src/remote-control/workspace-command-runner.ts +748 -0
- package/src/remote-control/workspace-coordinator.ts +231 -0
- package/src/remote-control/workspace-device.ts +585 -0
- package/src/remote-control/workspace-executable.ts +43 -0
- package/src/remote-control/workspace-executor.ts +397 -0
- package/src/remote-control/workspace-hub.ts +519 -0
- package/src/remote-control/workspace-pi-runtime.ts +382 -0
- package/src/remote-control/workspace-process.ts +129 -0
- package/src/remote-control/workspace-rpc.ts +304 -0
- package/src/remote-control/workspace-runtime.ts +60 -0
- package/src/remote-control/workspace-secret-store.ts +39 -0
- package/src/remote-control/workspace-sessions.ts +799 -0
- package/src/remote-control/workspace-tool-bridge.ts +192 -0
- package/src/responses/code-mode-helper-compat.ts +22 -3
- package/src/responses/hosted-tool-policy.ts +0 -1
- package/src/responses/muse-tool-name-alias.ts +379 -0
- package/src/responses/plaintext-v2-agent-messages.ts +902 -0
- package/src/router.ts +7 -0
- package/src/routing/compatibility/behavior.ts +0 -1
- package/src/server/audio-client.ts +64 -0
- package/src/server/audio-dictation.ts +91 -0
- package/src/server/audio-live.ts +185 -0
- package/src/server/audio-transcriptions.ts +183 -0
- package/src/server/audio-upstream.ts +153 -0
- package/src/server/auth-cors.ts +61 -2
- package/src/server/chat-completions.ts +1 -1
- package/src/server/chat-native-sse.ts +92 -48
- package/src/server/chat-native.ts +37 -15
- package/src/server/hub-usage.ts +57 -0
- package/src/server/images.ts +4 -0
- package/src/server/index.ts +722 -57
- package/src/server/lifecycle.ts +5 -6
- package/src/server/live-call-bindings.ts +60 -0
- package/src/server/live.ts +12 -1
- package/src/server/management/agent-settings-routes.ts +25 -4
- package/src/server/management/api-access.ts +37 -0
- package/src/server/management/api-key-usage.ts +7 -2
- package/src/server/management/config-routes.ts +1 -18
- package/src/server/management/context.ts +15 -0
- package/src/server/management/logs-usage-routes.ts +2 -0
- package/src/server/management/oauth-account-routes.ts +39 -12
- package/src/server/management/provider-routes.ts +125 -2
- package/src/server/management/remote-workspace-routes.ts +140 -0
- package/src/server/management/route-registry.ts +15 -0
- package/src/server/management/usage-aggregate-cache.ts +14 -15
- package/src/server/management/usage-summary-cache.ts +2 -0
- package/src/server/management-api.ts +23 -0
- package/src/server/ports.ts +17 -0
- package/src/server/relay-eager.ts +4 -1
- package/src/server/relay.ts +70 -10
- package/src/server/request-decompress.ts +6 -3
- package/src/server/responses/agent-task-recovery.ts +25 -32
- package/src/server/responses/codex-auth-error.ts +11 -0
- package/src/server/responses/codex-ws-exchange.ts +52 -3
- package/src/server/responses/codex-ws-wire.ts +55 -0
- package/src/server/responses/compact.ts +9 -1
- package/src/server/responses/core.ts +337 -73
- package/src/server/responses/encrypted-payload.ts +45 -2
- package/src/server/responses/ws-upstream.ts +4 -1
- package/src/server/responses-self-named-namespace-scrub.ts +1 -3
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/search.ts +3 -0
- package/src/server/sse-payload-rewrite.ts +136 -51
- package/src/server/ws-bridge.ts +35 -1
- package/src/service/cli.ts +372 -0
- package/src/service/diagnostics.ts +340 -0
- package/src/service/guards.ts +303 -0
- package/src/service/health.ts +222 -0
- package/src/service/launchd.ts +853 -0
- package/src/service/orchestration.ts +617 -0
- package/src/service/repair.ts +334 -0
- package/src/service/state.ts +363 -0
- package/src/service/systemd.ts +229 -0
- package/src/service/windows-ops.ts +690 -0
- package/src/service/windows-scheduler.ts +769 -0
- package/src/service/windows-taskxml.ts +613 -0
- package/src/service.ts +22 -5550
- package/src/storage/cleanup/db.ts +258 -0
- package/src/storage/cleanup/execute.ts +358 -0
- package/src/storage/cleanup/paths.ts +189 -0
- package/src/storage/cleanup/pending.ts +140 -0
- package/src/storage/cleanup/preview.ts +292 -0
- package/src/storage/cleanup/reconcile.ts +347 -0
- package/src/storage/cleanup/restore.ts +932 -0
- package/src/storage/cleanup/satellite.ts +474 -0
- package/src/storage/cleanup/staging.ts +129 -0
- package/src/storage/cleanup/types.ts +98 -0
- package/src/storage/cleanup.ts +49 -3127
- package/src/types/accounts.ts +2 -0
- package/src/types/config.ts +13 -12
- package/src/types/provider.ts +37 -0
- package/src/types/request.ts +2 -0
- package/src/types/tools.ts +17 -5
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +127 -0
- package/src/usage/log.ts +58 -1
- package/src/vision/eligibility.ts +13 -2
- package/src/web-search/loop.ts +56 -3
- package/gui/dist/assets/index-CWXut3rG.js +0 -115
- package/gui/dist/assets/index-EdoPnm9_.css +0 -1
- package/src/adapters/devin-cli/acp.ts +0 -204
- package/src/adapters/devin-cli/adapter.ts +0 -345
- package/src/adapters/devin-cli/binary.ts +0 -69
- package/src/adapters/devin-cli/models.ts +0 -57
- package/src/oauth/devin-cli.ts +0 -149
- package/src/server/responses-reasoning-summary-rewrite.ts +0 -178
package/src/providers/quota.ts
CHANGED
|
@@ -3,9 +3,8 @@ import {
|
|
|
3
3
|
effectiveCodexAuthAccountId,
|
|
4
4
|
fetchMainAccountInfoSnapshot,
|
|
5
5
|
listCodexAuthAccountsSnapshot,
|
|
6
|
-
withSparkVisibility,
|
|
7
6
|
} from "../codex/auth-api";
|
|
8
|
-
import type
|
|
7
|
+
import { withoutRetiredCodexQuota, type StoredAccountQuota } from "../codex/quota";
|
|
9
8
|
import { isMainAccountIdentityGenerationLive } from "../codex/main-account-cache";
|
|
10
9
|
import { MAIN_CODEX_ACCOUNT_ID } from "../codex/main-account";
|
|
11
10
|
import { codexPlanKey } from "../codex/plan";
|
|
@@ -15,8 +14,11 @@ import { getValidAccessToken, getValidAccessTokenForAccount } from "../oauth";
|
|
|
15
14
|
import { getAccountCredential, getAccountSet, getCredential } from "../oauth/store";
|
|
16
15
|
import { antigravityUserAgent } from "../adapters/client-fingerprint";
|
|
17
16
|
import { isCanonicalOllamaCloudUrl } from "../adapters/ollama-native-url";
|
|
18
|
-
import {
|
|
17
|
+
import { DestinationDnsResolutionError } from "../lib/destination-policy";
|
|
18
|
+
import { PinnedHttpError } from "../lib/pinned-http";
|
|
19
|
+
import { ProviderOutboundPolicyError, providerOutboundPost, providerRedirectError, type ProviderOutboundDependencies } from "../lib/provider-outbound";
|
|
19
20
|
import { apiKeyPoolEntryId } from "./api-keys";
|
|
21
|
+
import { fetchMuseKeyQuotaSnapshot } from "./muse-key-quota";
|
|
20
22
|
import { XAI_GROK_CLIENT_VERSION, XAI_GROK_COMPATIBILITY } from "./xai-transport";
|
|
21
23
|
import { getProviderRegistryEntry, providerCodexAccountMode, registryEntryForProviderDestination } from "./registry";
|
|
22
24
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
@@ -51,6 +53,7 @@ import {
|
|
|
51
53
|
} from "./codex-capacity";
|
|
52
54
|
import type {
|
|
53
55
|
AccountQuotaMode,
|
|
56
|
+
QuotaFailureCode,
|
|
54
57
|
ProviderQuota,
|
|
55
58
|
ProviderQuotaCreditsUsd,
|
|
56
59
|
ProviderQuotaWindow,
|
|
@@ -220,12 +223,10 @@ function providerQuotaFromCodexQuota(
|
|
|
220
223
|
quota: StoredAccountQuota | Omit<StoredAccountQuota, "updatedAt"> | null | undefined,
|
|
221
224
|
): CodexCapacityQuota | null {
|
|
222
225
|
if (!quota) return null;
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
quota = withSparkVisibility(quota ?? null) ?? quota;
|
|
228
|
-
return {
|
|
226
|
+
// Direct snapshots bypass account DTOs; sanitize here as well as at ingestion.
|
|
227
|
+
quota = withoutRetiredCodexQuota(quota);
|
|
228
|
+
if (!quota) return null;
|
|
229
|
+
const projected: CodexCapacityQuota = {
|
|
229
230
|
...(quota.shortPercent !== undefined ? { fiveHourPercent: quota.shortPercent } : {}),
|
|
230
231
|
...(quota.shortResetAt !== undefined ? { fiveHourResetAt: quota.shortResetAt } : {}),
|
|
231
232
|
...(quota.weeklyPercent !== undefined ? { weeklyPercent: quota.weeklyPercent } : {}),
|
|
@@ -235,6 +236,7 @@ function providerQuotaFromCodexQuota(
|
|
|
235
236
|
...(quota.customWindows !== undefined ? { customWindows: quota.customWindows } : {}),
|
|
236
237
|
updatedAt: "updatedAt" in quota ? quota.updatedAt : Date.now(),
|
|
237
238
|
};
|
|
239
|
+
return hasQuotaRows(projected) ? projected : null;
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
/** Hash only presentation-relevant state; account ids and email addresses never enter the key. */
|
|
@@ -1602,6 +1604,36 @@ async function fetchKiroQuota(provider: string): Promise<ProviderQuotaReport | n
|
|
|
1602
1604
|
return report(provider, "kiro:usage-limits", snapshot.quota);
|
|
1603
1605
|
}
|
|
1604
1606
|
|
|
1607
|
+
/**
|
|
1608
|
+
* Provider-level row probed from the key endpoint, for an account that CAN be probed.
|
|
1609
|
+
*
|
|
1610
|
+
* Written through the same account cache the passive path reads, so the measurement
|
|
1611
|
+
* survives a restart and the per-account rows at oauth-account-routes.ts:313 pick it up
|
|
1612
|
+
* with no mode change. Deliberately does not flip providerOAuthAccountQuotaMode: that
|
|
1613
|
+
* mode selects readPassiveProviderAccountQuotas, and the probed per-account path it would
|
|
1614
|
+
* switch to is gated on supportsPerAccountQuota, which has no meta-muse reader, so the
|
|
1615
|
+
* GUI account list would go from showing observations to showing nothing.
|
|
1616
|
+
*/
|
|
1617
|
+
async function fetchMuseKeyQuota(provider: string): Promise<ProviderQuotaReport | null> {
|
|
1618
|
+
const probedAccountId = getAccountSet(provider)?.activeAccountId;
|
|
1619
|
+
if (!probedAccountId) return null;
|
|
1620
|
+
const oauthAccessToken = getAccountCredential(provider, probedAccountId)?.muse?.oauthAccessToken;
|
|
1621
|
+
// An imported or pasted credential has no account token and never will: it is
|
|
1622
|
+
// capability, not provider id, that decides whether a probe is possible.
|
|
1623
|
+
if (!oauthAccessToken) return null;
|
|
1624
|
+
const probedAccountKey = accountCacheKey(provider, probedAccountId);
|
|
1625
|
+
const writerGeneration = captureConfigGeneration();
|
|
1626
|
+
const quota = await fetchMuseKeyQuotaSnapshot(probedAccountId, oauthAccessToken);
|
|
1627
|
+
if (!quota) return null;
|
|
1628
|
+
if (mayCommitAccountQuotaKey(probedAccountKey, writerGeneration)) {
|
|
1629
|
+
// Hydrate before writing, for the same reason recordPassiveAccountQuota does:
|
|
1630
|
+
// persistAccountQuotaCache serializes the whole in-memory map.
|
|
1631
|
+
hydrateAccountQuotaCache();
|
|
1632
|
+
accountQuotaCache.set(probedAccountKey, { ts: Date.now(), quota });
|
|
1633
|
+
persistAccountQuotaCache();
|
|
1634
|
+
}
|
|
1635
|
+
return report(provider, `${provider}:key-endpoint`, quota);
|
|
1636
|
+
}
|
|
1605
1637
|
/**
|
|
1606
1638
|
* Provider-level row for a passive provider: the ACTIVE account's last observed
|
|
1607
1639
|
* subscription windows, the same shape `fetchAnthropicQuota` and `fetchKiroQuota`
|
|
@@ -1642,6 +1674,8 @@ type AccountQuotaCacheEntry = {
|
|
|
1642
1674
|
quota: ProviderQuota | null;
|
|
1643
1675
|
/** Last probe failed (429 / network / expired login); still may hold last-good quota. */
|
|
1644
1676
|
unavailable?: true;
|
|
1677
|
+
quotaFailure?: QuotaFailureCode;
|
|
1678
|
+
quotaFailureIsCurrent?: () => boolean;
|
|
1645
1679
|
/** Private new-reader identity; never persisted or serialized. */
|
|
1646
1680
|
identity?: string;
|
|
1647
1681
|
isCurrent?: () => boolean;
|
|
@@ -1753,6 +1787,8 @@ export interface ProviderAccountQuota {
|
|
|
1753
1787
|
quota: ProviderQuota | null;
|
|
1754
1788
|
/** Set when the probe could not reach upstream (expired login, 429, network). */
|
|
1755
1789
|
unavailable?: true;
|
|
1790
|
+
quotaFailure?: QuotaFailureCode;
|
|
1791
|
+
quotaFailureIsCurrent?: () => boolean;
|
|
1756
1792
|
isCurrent?: () => boolean;
|
|
1757
1793
|
}
|
|
1758
1794
|
|
|
@@ -2030,6 +2066,10 @@ function explicitQuotaIdentity(provider: string, accountId: string, configured?:
|
|
|
2030
2066
|
const credential = getAccountCredential(provider, accountId);
|
|
2031
2067
|
const target = explicitQuotaConfig(provider, configured);
|
|
2032
2068
|
if (!credential || !target) return undefined;
|
|
2069
|
+
return quotaCredentialIdentity(provider, accountId, credential, target);
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
function quotaCredentialIdentity(provider: string, accountId: string, credential: NonNullable<ReturnType<typeof getAccountCredential>>, target: OcxProviderConfig): string {
|
|
2033
2073
|
return createHash("sha256").update(JSON.stringify([
|
|
2034
2074
|
provider, accountId, credential.access, credential.refresh, credential.expires,
|
|
2035
2075
|
credential.accountId, credential.projectId, credential.source,
|
|
@@ -2121,6 +2161,12 @@ async function fetchExplicitCurrentQuota(provider: string, config: OcxProviderCo
|
|
|
2121
2161
|
return read.result;
|
|
2122
2162
|
}
|
|
2123
2163
|
|
|
2164
|
+
function antigravityQuotaDiagnosticIdentity(accountId: string, credential = getAccountCredential("google-antigravity", accountId)): string | undefined {
|
|
2165
|
+
return credential ? quotaCredentialIdentity("google-antigravity", accountId, credential, {
|
|
2166
|
+
adapter: "google", baseUrl: ANTIGRAVITY_ACCOUNT_QUOTA_BASE, authMode: "oauth",
|
|
2167
|
+
}) : undefined;
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2124
2170
|
async function fetchAccountQuota(
|
|
2125
2171
|
provider: string,
|
|
2126
2172
|
accountId: string,
|
|
@@ -2134,13 +2180,23 @@ async function fetchAccountQuota(
|
|
|
2134
2180
|
const writerGeneration = captureConfigGeneration();
|
|
2135
2181
|
const cached = accountQuotaCache.get(key);
|
|
2136
2182
|
if (!forceRefresh && cached && Date.now() - cached.ts < ACCOUNT_QUOTA_TTL_MS) {
|
|
2183
|
+
if (provider === "google-antigravity" && cached.quotaFailure && cached.quotaFailureIsCurrent?.() !== true) return { ...cached, quotaFailure: undefined };
|
|
2137
2184
|
return provider === "anthropic" ? { ...cached, quota: normalizeAnthropicQuota(cached.quota, Date.now()) } : cached;
|
|
2138
2185
|
}
|
|
2139
2186
|
const joinable = accountQuotaInflight.get(key);
|
|
2140
2187
|
if (joinable) return joinable;
|
|
2141
2188
|
|
|
2189
|
+
const epoch = explicitAccountEpoch;
|
|
2142
2190
|
const probe = (async (): Promise<AccountQuotaCacheEntry> => {
|
|
2191
|
+
let diagnosticIdentity: string | undefined;
|
|
2192
|
+
let quotaFailure: QuotaFailureCode | undefined;
|
|
2193
|
+
const quotaFailureIsCurrent = () => {
|
|
2194
|
+
try { return epoch === explicitAccountEpoch && diagnosticIdentity !== undefined && diagnosticIdentity === antigravityQuotaDiagnosticIdentity(accountId); }
|
|
2195
|
+
catch { return false; }
|
|
2196
|
+
};
|
|
2197
|
+
const diagnosticFields = () => quotaFailure && quotaFailureIsCurrent() ? { quotaFailure, quotaFailureIsCurrent } : {};
|
|
2143
2198
|
try {
|
|
2199
|
+
if (provider === "google-antigravity") diagnosticIdentity = antigravityQuotaDiagnosticIdentity(accountId);
|
|
2144
2200
|
let quota: ProviderQuota | null;
|
|
2145
2201
|
let kiroSnapshot: KiroUsageSnapshot | null = null;
|
|
2146
2202
|
if (provider === "kiro") {
|
|
@@ -2158,9 +2214,12 @@ async function fetchAccountQuota(
|
|
|
2158
2214
|
// Per-account Gem/Cla windows (#1082). The project id is part of the stored
|
|
2159
2215
|
// credential; without it the probe cannot be made, and that is "unavailable",
|
|
2160
2216
|
// never 0%.
|
|
2161
|
-
const
|
|
2162
|
-
|
|
2163
|
-
|
|
2217
|
+
const credential = getAccountCredential(provider, accountId);
|
|
2218
|
+
diagnosticIdentity = credential?.access === token ? antigravityQuotaDiagnosticIdentity(accountId, credential) : undefined;
|
|
2219
|
+
if (!diagnosticIdentity || !credential?.projectId) throw new Error("antigravity account unavailable");
|
|
2220
|
+
const result = await probeAntigravityUsageQuota(token, credential.projectId);
|
|
2221
|
+
quota = result.kind === "available" ? result.quota : null;
|
|
2222
|
+
if (result.kind === "unavailable") quotaFailure = result.failure;
|
|
2164
2223
|
} else if (provider === "anthropic") {
|
|
2165
2224
|
quota = await fetchAnthropicUsageQuota(token);
|
|
2166
2225
|
} else {
|
|
@@ -2176,6 +2235,7 @@ async function fetchAccountQuota(
|
|
|
2176
2235
|
quota: provider === "anthropic"
|
|
2177
2236
|
? normalizeAnthropicQuota(accountQuotaCache.get(key)?.quota, Date.now()) : cached?.quota ?? null,
|
|
2178
2237
|
unavailable: true,
|
|
2238
|
+
...diagnosticFields(),
|
|
2179
2239
|
};
|
|
2180
2240
|
if (mayCommitAccountQuotaKey(key, writerGeneration)) {
|
|
2181
2241
|
accountQuotaCache.set(key, entry);
|
|
@@ -2196,11 +2256,13 @@ async function fetchAccountQuota(
|
|
|
2196
2256
|
}
|
|
2197
2257
|
return entry;
|
|
2198
2258
|
} catch {
|
|
2259
|
+
if (provider === "google-antigravity") quotaFailure = "account_unavailable";
|
|
2199
2260
|
const entry: AccountQuotaCacheEntry = {
|
|
2200
2261
|
ts: Date.now(),
|
|
2201
2262
|
quota: provider === "anthropic"
|
|
2202
2263
|
? normalizeAnthropicQuota(accountQuotaCache.get(key)?.quota, Date.now()) : cached?.quota ?? null,
|
|
2203
2264
|
unavailable: true,
|
|
2265
|
+
...diagnosticFields(),
|
|
2204
2266
|
};
|
|
2205
2267
|
if (mayCommitAccountQuotaKey(key, writerGeneration)) {
|
|
2206
2268
|
accountQuotaCache.set(key, entry);
|
|
@@ -2233,7 +2295,9 @@ export async function fetchProviderAccountQuotas(
|
|
|
2233
2295
|
accountId: account.id,
|
|
2234
2296
|
quota: provider === "anthropic" ? normalizeAnthropicQuota(entry.quota, Date.now()) : entry.quota,
|
|
2235
2297
|
...(entry.unavailable ? { unavailable: true as const } : {}),
|
|
2298
|
+
...(entry.unavailable && entry.quotaFailure && entry.quotaFailureIsCurrent?.() === true ? { quotaFailure: entry.quotaFailure } : {}),
|
|
2236
2299
|
};
|
|
2300
|
+
if (entry.quotaFailureIsCurrent) Object.defineProperty(result, "quotaFailureIsCurrent", { value: entry.quotaFailureIsCurrent });
|
|
2237
2301
|
if (!explicitAccountReader(provider)) return result;
|
|
2238
2302
|
const identity = entry.identity;
|
|
2239
2303
|
Object.defineProperty(result, "isCurrent", { value: () => {
|
|
@@ -2864,104 +2928,78 @@ export function setAntigravityAccountQuotaTransportForTests(dependencies: Provid
|
|
|
2864
2928
|
* the destination keeps the `provider\0accountId` cache identity exact across config changes.
|
|
2865
2929
|
* A redirect or non-2xx yields null (unavailable), never a partial row.
|
|
2866
2930
|
*/
|
|
2867
|
-
|
|
2868
|
-
|
|
2931
|
+
type AntigravityQuotaProbeResult =
|
|
2932
|
+
| { kind: "available"; quota: ProviderQuota; source: "google-antigravity:retrieveUserQuotaSummary" | "google-antigravity:fetchAvailableModels" }
|
|
2933
|
+
| { kind: "unavailable"; failure: QuotaFailureCode; legacy: { kind: "null" } | { kind: "throw"; error: unknown } };
|
|
2934
|
+
|
|
2935
|
+
function quotaTransportFailure(error: unknown): QuotaFailureCode {
|
|
2936
|
+
if (error instanceof ProviderOutboundPolicyError) return "destination_blocked";
|
|
2937
|
+
if (error instanceof DestinationDnsResolutionError) return "dns_failed";
|
|
2938
|
+
if (error instanceof PinnedHttpError) return error.code === "output_byte_limit" ? "response_unusable" : "timeout";
|
|
2939
|
+
if (error instanceof DOMException && error.name === "TimeoutError") return "timeout";
|
|
2940
|
+
return "transport_error";
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
function quotaHttpFailure(status: number): QuotaFailureCode {
|
|
2944
|
+
if (status >= 300 && status < 400) return "redirect_blocked";
|
|
2945
|
+
if (status === 401 || status === 403) return "access_denied";
|
|
2946
|
+
if (status === 429) return "rate_limited";
|
|
2947
|
+
return "upstream_error";
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
function unavailableAntigravityQuota(failure: QuotaFailureCode): AntigravityQuotaProbeResult {
|
|
2951
|
+
return { kind: "unavailable", failure, legacy: { kind: "null" } };
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
/** Final attempt determines the safe diagnosis; a successful fallback clears the first failure. */
|
|
2955
|
+
async function probeAntigravityUsageQuota(accessToken: string, projectId: string): Promise<AntigravityQuotaProbeResult> {
|
|
2956
|
+
const fetchQuota = (url: string) => providerOutboundPost("google-antigravity", { baseUrl: ANTIGRAVITY_ACCOUNT_QUOTA_BASE }, url, {
|
|
2957
|
+
headers: {
|
|
2958
|
+
Accept: "application/json", "Content-Type": "application/json",
|
|
2959
|
+
"User-Agent": antigravityUserAgent(), Authorization: `Bearer ${accessToken}`,
|
|
2960
|
+
},
|
|
2961
|
+
body: JSON.stringify({ project: projectId }), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
2962
|
+
}, antigravityOutboundDependencies);
|
|
2869
2963
|
try {
|
|
2870
|
-
const
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
},
|
|
2877
|
-
body: JSON.stringify({ project: projectId }),
|
|
2878
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
2879
|
-
}, antigravityOutboundDependencies);
|
|
2880
|
-
if (await providerRedirectError(summaryResponse, summaryUrl)) return null;
|
|
2881
|
-
if (summaryResponse.status === 401 || summaryResponse.status === 403) return null;
|
|
2882
|
-
if (summaryResponse.ok) {
|
|
2883
|
-
const quota = parseAntigravityQuotaSummary(asRecord(await readQuotaJson(summaryResponse)));
|
|
2884
|
-
if (quota) return quota;
|
|
2964
|
+
const response = await fetchQuota(ANTIGRAVITY_QUOTA_SUMMARY_URL);
|
|
2965
|
+
if (await providerRedirectError(response, ANTIGRAVITY_QUOTA_SUMMARY_URL)) return unavailableAntigravityQuota("redirect_blocked");
|
|
2966
|
+
if (response.status === 401 || response.status === 403) return unavailableAntigravityQuota("access_denied");
|
|
2967
|
+
if (response.ok) {
|
|
2968
|
+
const quota = parseAntigravityQuotaSummary(asRecord(await readQuotaJson(response)));
|
|
2969
|
+
if (quota) return { kind: "available", quota, source: "google-antigravity:retrieveUserQuotaSummary" };
|
|
2885
2970
|
}
|
|
2886
2971
|
} catch {
|
|
2887
|
-
//
|
|
2972
|
+
// Existing behavior: summary transport/parse failure may recover through the models probe.
|
|
2973
|
+
}
|
|
2974
|
+
try {
|
|
2975
|
+
const response = await fetchQuota(ANTIGRAVITY_QUOTA_MODELS_URL);
|
|
2976
|
+
if (await providerRedirectError(response, ANTIGRAVITY_QUOTA_MODELS_URL)) return unavailableAntigravityQuota("redirect_blocked");
|
|
2977
|
+
if (!response.ok) return unavailableAntigravityQuota(quotaHttpFailure(response.status));
|
|
2978
|
+
const customWindows = antigravityWindowsFromModels(asRecord(await readQuotaJson(response)));
|
|
2979
|
+
if (!customWindows.length) return unavailableAntigravityQuota("response_unusable");
|
|
2980
|
+
return { kind: "available", quota: { customWindows, updatedAt: Date.now() }, source: "google-antigravity:fetchAvailableModels" };
|
|
2981
|
+
} catch (error) {
|
|
2982
|
+
// The public compatibility wrapper still rejects this exact fallback error; it never enters a DTO.
|
|
2983
|
+
return { kind: "unavailable", failure: quotaTransportFailure(error), legacy: { kind: "throw", error } };
|
|
2888
2984
|
}
|
|
2985
|
+
}
|
|
2889
2986
|
|
|
2890
|
-
|
|
2891
|
-
const
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
"User-Agent": antigravityUserAgent(),
|
|
2896
|
-
Authorization: `Bearer ${accessToken}`,
|
|
2897
|
-
},
|
|
2898
|
-
body: JSON.stringify({ project: projectId }),
|
|
2899
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
2900
|
-
}, antigravityOutboundDependencies);
|
|
2901
|
-
if (await providerRedirectError(response, url)) return null;
|
|
2902
|
-
if (!response.ok) return null;
|
|
2903
|
-
const customWindows = antigravityWindowsFromModels(asRecord(await readQuotaJson(response)));
|
|
2904
|
-
if (customWindows.length === 0) return null;
|
|
2905
|
-
return { customWindows, updatedAt: Date.now() };
|
|
2987
|
+
export async function fetchAntigravityUsageQuota(accessToken: string, projectId: string): Promise<ProviderQuota | null> {
|
|
2988
|
+
const result = await probeAntigravityUsageQuota(accessToken, projectId);
|
|
2989
|
+
if (result.kind === "available") return result.quota;
|
|
2990
|
+
if (result.legacy.kind === "throw") throw result.legacy.error;
|
|
2991
|
+
return null;
|
|
2906
2992
|
}
|
|
2907
2993
|
|
|
2908
2994
|
async function fetchAntigravityQuota(provider: string): Promise<ProviderQuotaReport | null> {
|
|
2909
2995
|
const credential = getCredential("google-antigravity");
|
|
2910
2996
|
if (!credential?.projectId) return null;
|
|
2911
2997
|
let accessToken: string;
|
|
2912
|
-
try {
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
// Both probes are pinned to Google's own host through the provider-outbound
|
|
2919
|
-
// transport, mirroring `fetchAntigravityUsageQuota` above: a configured `baseUrl` is a
|
|
2920
|
-
// routing choice for requests, not a second source of Google's accounting, and these
|
|
2921
|
-
// requests carry the account bearer.
|
|
2922
|
-
const summaryUrl = ANTIGRAVITY_QUOTA_SUMMARY_URL;
|
|
2923
|
-
try {
|
|
2924
|
-
const summaryResponse = await providerOutboundPost("google-antigravity", { baseUrl: ANTIGRAVITY_ACCOUNT_QUOTA_BASE }, summaryUrl, {
|
|
2925
|
-
headers: {
|
|
2926
|
-
Accept: "application/json",
|
|
2927
|
-
"Content-Type": "application/json",
|
|
2928
|
-
"User-Agent": antigravityUserAgent(),
|
|
2929
|
-
Authorization: `Bearer ${accessToken}`,
|
|
2930
|
-
},
|
|
2931
|
-
body: JSON.stringify({ project: credential.projectId }),
|
|
2932
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
2933
|
-
}, antigravityOutboundDependencies);
|
|
2934
|
-
if (await providerRedirectError(summaryResponse, summaryUrl)) return null;
|
|
2935
|
-
if (summaryResponse.status === 401 || summaryResponse.status === 403) return null;
|
|
2936
|
-
if (summaryResponse.ok) {
|
|
2937
|
-
const quota = parseAntigravityQuotaSummary(asRecord(await readQuotaJson(summaryResponse)));
|
|
2938
|
-
if (quota) {
|
|
2939
|
-
return report(provider, "google-antigravity:retrieveUserQuotaSummary", quota);
|
|
2940
|
-
}
|
|
2941
|
-
}
|
|
2942
|
-
} catch {
|
|
2943
|
-
// Fallback on network/fetch error
|
|
2944
|
-
}
|
|
2945
|
-
|
|
2946
|
-
const url = ANTIGRAVITY_QUOTA_MODELS_URL;
|
|
2947
|
-
const response = await providerOutboundPost("google-antigravity", { baseUrl: ANTIGRAVITY_ACCOUNT_QUOTA_BASE }, url, {
|
|
2948
|
-
headers: {
|
|
2949
|
-
Accept: "application/json",
|
|
2950
|
-
"Content-Type": "application/json",
|
|
2951
|
-
"User-Agent": antigravityUserAgent(),
|
|
2952
|
-
Authorization: `Bearer ${accessToken}`,
|
|
2953
|
-
},
|
|
2954
|
-
body: JSON.stringify({ project: credential.projectId }),
|
|
2955
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
2956
|
-
}, antigravityOutboundDependencies);
|
|
2957
|
-
if (await providerRedirectError(response, url)) return null;
|
|
2958
|
-
if (!response.ok) return null;
|
|
2959
|
-
const customWindows = antigravityWindowsFromModels(asRecord(await readQuotaJson(response)));
|
|
2960
|
-
if (customWindows.length === 0) return null;
|
|
2961
|
-
return report(provider, "google-antigravity:fetchAvailableModels", {
|
|
2962
|
-
customWindows,
|
|
2963
|
-
updatedAt: Date.now(),
|
|
2964
|
-
});
|
|
2998
|
+
try { accessToken = await getValidAccessToken("google-antigravity"); } catch { return null; }
|
|
2999
|
+
const result = await probeAntigravityUsageQuota(accessToken, credential.projectId);
|
|
3000
|
+
if (result.kind === "available") return report(provider, result.source, result.quota);
|
|
3001
|
+
if (result.legacy.kind === "throw") throw result.legacy.error;
|
|
3002
|
+
return null;
|
|
2965
3003
|
}
|
|
2966
3004
|
|
|
2967
3005
|
type KeyQuotaReader = (name: string, provider: OcxProviderConfig) => Promise<ProviderQuotaProbeResult>;
|
|
@@ -3032,9 +3070,12 @@ async function maybeFetchProviderQuota(
|
|
|
3032
3070
|
if (provider.authMode === "oauth" && name === "anthropic") return fetchAnthropicQuota(name);
|
|
3033
3071
|
if (provider.authMode === "oauth" && name === "google-antigravity") return await fetchAntigravityQuota(name);
|
|
3034
3072
|
if (provider.authMode === "oauth" && name === "kiro") return fetchKiroQuota(name);
|
|
3035
|
-
//
|
|
3036
|
-
//
|
|
3037
|
-
|
|
3073
|
+
// meta-muse: a device-logged-in account can be probed at the key endpoint; an
|
|
3074
|
+
// imported or pasted one cannot, and falls back to its last in-band observation.
|
|
3075
|
+
// The probe is tried first and its failure is never fatal to the row.
|
|
3076
|
+
if (provider.authMode === "oauth" && hasPassiveAccountQuota(name)) {
|
|
3077
|
+
return (await fetchMuseKeyQuota(name)) ?? await fetchPassiveProviderQuota(name);
|
|
3078
|
+
}
|
|
3038
3079
|
const reader = keyQuotaReaderForProvider(name, provider);
|
|
3039
3080
|
// Keep destination/auth fields bound to the same request as the reader's captured
|
|
3040
3081
|
// bearer, even if the live provider object changes while the quota probe awaits.
|