@yansigit/opencodex 2.32.0 → 2.33.1
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 +2 -2
- package/gui/dist/assets/index-CIDo4y4k.js +102 -0
- package/gui/dist/assets/index-DrSQdTRd.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +8 -5
- package/src/adapters/anthropic.ts +20 -6
- package/src/adapters/azure.ts +20 -4
- package/src/adapters/base.ts +3 -1
- package/src/adapters/command-code.ts +131 -17
- package/src/adapters/cursor/envelope-echo.ts +162 -0
- package/src/adapters/cursor/live-transport.ts +4 -2
- package/src/adapters/cursor/native-exec-fs.ts +13 -12
- package/src/adapters/cursor/native-exec-network.ts +3 -5
- package/src/adapters/cursor/native-exec-policy.ts +47 -0
- package/src/adapters/cursor/native-exec-shell.ts +13 -25
- package/src/adapters/cursor/native-exec.ts +18 -10
- package/src/adapters/cursor/protobuf-events.ts +186 -9
- package/src/adapters/cursor/protobuf-request.ts +53 -18
- package/src/adapters/cursor/request-builder.ts +11 -3
- package/src/adapters/cursor/tool-definitions.ts +49 -2
- package/src/adapters/cursor/tool-result-normalize.ts +21 -8
- package/src/adapters/cursor/types.ts +11 -3
- package/src/adapters/cursor.ts +123 -0
- package/src/adapters/google-aistudio-parser.ts +49 -0
- package/src/adapters/google-antigravity-replay.ts +2 -2
- package/src/adapters/google-antigravity-wire.ts +7 -0
- package/src/adapters/google-errors.ts +6 -2
- package/src/adapters/google-http.ts +30 -7
- package/src/adapters/google-truncation.ts +5 -0
- package/src/adapters/google-wire-compiler.ts +38 -6
- package/src/adapters/google.ts +242 -28
- package/src/adapters/kiro-tools.ts +20 -9
- package/src/adapters/openai-chat.ts +9 -0
- package/src/adapters/openai-responses.ts +2 -1
- package/src/bridge.ts +112 -9
- package/src/chat/inbound.ts +15 -0
- package/src/claude/context-windows.ts +16 -9
- package/src/cli/doctor.ts +2 -2
- package/src/cli/index.ts +10 -3
- package/src/cli/provider.ts +6 -0
- package/src/cli/status.ts +23 -0
- package/src/codex/auth-api.ts +4 -2
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +12 -12
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/model-metadata.ts +39 -12
- package/src/codex/catalog/parsing.ts +38 -27
- package/src/codex/catalog/provider-fetch.ts +232 -133
- package/src/codex/catalog/sync.ts +1 -1
- package/src/codex/convergence.ts +5 -0
- package/src/codex/shim.ts +56 -3
- package/src/config/provider-validation.ts +37 -0
- package/src/config.ts +78 -2
- package/src/generated/compatibility-version.json +187 -119
- package/src/generated/model-metadata.ts +3 -0
- package/src/images/loop.ts +37 -6
- package/src/lib/azure-identity.ts +154 -0
- package/src/lib/debug.ts +42 -0
- package/src/lib/errors.ts +14 -0
- package/src/lib/provider-outbound.ts +45 -33
- package/src/lib/provider-tls-profile.ts +309 -0
- package/src/lib/proxy-env.ts +49 -0
- package/src/lib/redact.ts +10 -1
- package/src/oauth/aistudio-native-daemon.ts +62 -0
- package/src/oauth/aistudio-session-sync.ts +95 -0
- package/src/oauth/antigravity-routing.ts +282 -236
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/command-code.ts +5 -16
- package/src/oauth/google-aistudio-auth.ts +98 -0
- package/src/oauth/google-antigravity.ts +42 -5
- package/src/oauth/index.ts +15 -3
- package/src/oauth/key-providers.ts +8 -0
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/login-cli.ts +66 -1
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +6 -0
- package/src/providers/antigravity-quota.ts +3 -1
- package/src/providers/api-keys.ts +2 -1
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/derive.ts +5 -1
- package/src/providers/key-failover.ts +5 -1
- package/src/providers/openai-tiers.ts +5 -0
- package/src/providers/provider-id-rewrite.ts +1 -0
- package/src/providers/quota.ts +148 -50
- package/src/providers/registry.ts +27 -4
- package/src/providers/request-pacing.ts +33 -6
- package/src/providers/xai-transport.ts +21 -0
- package/src/responses/google-provider-options.ts +36 -0
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +11 -0
- package/src/responses/provider-opaque-metadata.ts +3 -3
- package/src/responses/schema.ts +37 -0
- package/src/responses/state.ts +94 -4
- package/src/router.ts +11 -2
- package/src/routing/account-pool/cooldown.ts +8 -0
- package/src/routing/account-pool/index.ts +1 -0
- package/src/server/aistudio-ws-hub.ts +295 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +2 -0
- package/src/server/images.ts +19 -35
- package/src/server/index.ts +94 -0
- package/src/server/management/agent-settings-routes.ts +205 -15
- package/src/server/management/combo-routes.ts +6 -0
- package/src/server/management/config-routes.ts +31 -5
- package/src/server/management/logs-usage-routes.ts +11 -5
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/oauth-account-routes.ts +38 -7
- package/src/server/management/provider-routes.ts +113 -15
- package/src/server/management/routing-profile-routes.ts +3 -0
- package/src/server/port-reclaim.ts +19 -1
- package/src/server/request-log-conversation.ts +12 -0
- package/src/server/request-log.ts +23 -1
- package/src/server/responses/agent-task-recovery.ts +1 -1
- package/src/server/responses/compact.ts +30 -1
- package/src/server/responses/core.ts +363 -156
- package/src/server/responses/empty-completion-guard.ts +35 -6
- package/src/server/responses/fetch-helpers.ts +18 -5
- package/src/server/responses/policy-fallback.ts +1 -1
- package/src/server/responses/v2-native-parent-override.ts +59 -0
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-undeclared-tool-guard.ts +90 -8
- package/src/server/ws-bridge.ts +2 -1
- package/src/service.ts +1 -1
- package/src/smoke/fingerprint-cache.ts +133 -0
- package/src/smoke/live-scenarios.ts +33 -0
- package/src/smoke/runner.ts +119 -0
- package/src/types/config.ts +16 -1
- package/src/types/provider.ts +18 -1
- package/src/types/request.ts +30 -0
- package/src/types/tools.ts +51 -0
- package/src/types.ts +6 -0
- package/src/usage/command-code-manifest.ts +116 -0
- package/src/usage/cost.ts +2 -2
- package/src/usage/expected-prices.ts +83 -0
- package/src/usage/log.ts +2 -2
- package/src/usage/summary.ts +34 -12
- package/src/web-search/gemini-executor.ts +6 -4
- package/src/web-search/index.ts +16 -8
- package/src/web-search/loop.ts +42 -6
- package/gui/dist/assets/index-BG43zwVe.js +0 -102
- package/gui/dist/assets/index-CiSI-jrP.css +0 -1
package/src/providers/quota.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { isMainAccountIdentityGenerationLive } from "../codex/main-account-cache
|
|
|
8
8
|
import { MAIN_CODEX_ACCOUNT_ID } from "../codex/main-account";
|
|
9
9
|
import { codexPlanKey } from "../codex/plan";
|
|
10
10
|
import { resolveEnvValue } from "../config";
|
|
11
|
-
import { getValidAccessToken, getValidAccessTokenForAccount } from "../oauth";
|
|
11
|
+
import { getValidAccessToken, getValidAccessTokenForAccount, getValidAccessTokenSnapshot, getValidAccessTokenSnapshotForAccount, type OAuthAccessSnapshot } from "../oauth";
|
|
12
12
|
import { getAccountCredential, getAccountSet, getCredential } from "../oauth/store";
|
|
13
13
|
import { antigravityUserAgent } from "../adapters/client-fingerprint";
|
|
14
14
|
import { apiKeyPoolEntryId } from "./api-keys";
|
|
@@ -16,6 +16,7 @@ import { XAI_GROK_CLIENT_VERSION, XAI_GROK_COMPATIBILITY } from "./xai-transport
|
|
|
16
16
|
import { getProviderRegistryEntry, providerCodexAccountMode, registryEntryForProviderDestination } from "./registry";
|
|
17
17
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
18
18
|
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "./openai-tiers";
|
|
19
|
+
import { globalAiStudioRelayHub } from "../server/aistudio-ws-hub";
|
|
19
20
|
import {
|
|
20
21
|
captureConfigGeneration,
|
|
21
22
|
sweepExpiredOnWrite,
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
isTerminalAntigravityQuotaStatus,
|
|
35
36
|
} from "./antigravity-quota";
|
|
36
37
|
import { antigravityHostCandidates, isAntigravityHttpsHost } from "../adapters/google-antigravity-hosts";
|
|
38
|
+
import { providerTlsFetch } from "../lib/provider-tls-profile";
|
|
37
39
|
|
|
38
40
|
/** Match oauth/index REFRESH_SKEW_MS — use stored access without refresh when still fresh. */
|
|
39
41
|
const ACCOUNT_TOKEN_SKEW_MS = 60_000;
|
|
@@ -1400,7 +1402,12 @@ export interface ProviderAccountQuota {
|
|
|
1400
1402
|
|
|
1401
1403
|
/** Providers whose per-account quota can be probed. Extend as other OAuth APIs are covered. */
|
|
1402
1404
|
export function supportsPerAccountQuota(provider: string): boolean {
|
|
1403
|
-
return
|
|
1405
|
+
return (
|
|
1406
|
+
provider === "anthropic" ||
|
|
1407
|
+
provider === "google-antigravity" ||
|
|
1408
|
+
provider === "command-code" ||
|
|
1409
|
+
provider === "cursor"
|
|
1410
|
+
);
|
|
1404
1411
|
}
|
|
1405
1412
|
|
|
1406
1413
|
function accountCacheKey(provider: string, accountId: string): string {
|
|
@@ -1519,8 +1526,21 @@ async function fetchAccountQuota(
|
|
|
1519
1526
|
|
|
1520
1527
|
const probe = (async (): Promise<AccountQuotaCacheEntry> => {
|
|
1521
1528
|
try {
|
|
1522
|
-
|
|
1523
|
-
|
|
1529
|
+
let quota: ProviderQuota | null = null;
|
|
1530
|
+
if (provider === "anthropic") {
|
|
1531
|
+
const token = await getTokenForAccountQuotaProbe(provider, accountId);
|
|
1532
|
+
quota = await fetchAnthropicUsageQuota(token);
|
|
1533
|
+
} else if (provider === "google-antigravity") {
|
|
1534
|
+
quota = await fetchAntigravityAccountQuota(accountId);
|
|
1535
|
+
} else if (provider === "command-code") {
|
|
1536
|
+
const token = await getTokenForAccountQuotaProbe(provider, accountId);
|
|
1537
|
+
const res = await fetchCommandCodeUsageQuota(token);
|
|
1538
|
+
quota = res === TERMINAL_QUOTA_FAILURE ? null : res;
|
|
1539
|
+
} else if (provider === "cursor") {
|
|
1540
|
+
const token = await getTokenForAccountQuotaProbe(provider, accountId);
|
|
1541
|
+
const res = await fetchCursorUsageQuota(token);
|
|
1542
|
+
quota = res?.quota ?? null;
|
|
1543
|
+
}
|
|
1524
1544
|
if (!quota) {
|
|
1525
1545
|
// Preserve last-good bars and mark unavailable; advance TTL so failures
|
|
1526
1546
|
// negative-cache instead of re-probing on every GUI poll.
|
|
@@ -1735,8 +1755,9 @@ function parseCommandCodeWindow(value: unknown): { percent: number; resetAt?: nu
|
|
|
1735
1755
|
if (!row) return null;
|
|
1736
1756
|
const cap = toFiniteNumber(row.cap);
|
|
1737
1757
|
const used = toFiniteNumber(row.used);
|
|
1738
|
-
|
|
1739
|
-
|
|
1758
|
+
const percent = cap !== undefined && used !== undefined && cap > 0 && used >= 0
|
|
1759
|
+
? normalizePercent((used / cap) * 100)
|
|
1760
|
+
: normalizePercent(row.percent);
|
|
1740
1761
|
if (percent === undefined) return null;
|
|
1741
1762
|
const resetAt = quotaResetAt(row);
|
|
1742
1763
|
return { percent, ...(resetAt !== undefined ? { resetAt } : {}) };
|
|
@@ -1823,11 +1844,7 @@ async function resolveCommandCodeQuotaBearer(config: OcxProviderConfig): Promise
|
|
|
1823
1844
|
* usage view uses (windowLimits.fiveHour / windowLimits.weekly), plus soft
|
|
1824
1845
|
* whoami (team orgId scoping) and subscription-scoped spend for creditsUsd.
|
|
1825
1846
|
*/
|
|
1826
|
-
async function
|
|
1827
|
-
// Never release credentials to a user-edited or lookalike provider host.
|
|
1828
|
-
if (!isCanonicalCommandCodeBaseUrl(config.baseUrl)) return null;
|
|
1829
|
-
const bearer = await resolveCommandCodeQuotaBearer(config);
|
|
1830
|
-
if (!bearer) return null;
|
|
1847
|
+
async function fetchCommandCodeUsageQuota(bearer: string): Promise<ProviderQuota | null | typeof TERMINAL_QUOTA_FAILURE> {
|
|
1831
1848
|
const whoamiBody = await fetchCommandCodeJson(COMMAND_CODE_WHOAMI_URL, bearer);
|
|
1832
1849
|
const whoami = asRecord(whoamiBody?.data) ?? whoamiBody;
|
|
1833
1850
|
const org = asRecord(whoami?.org);
|
|
@@ -1851,7 +1868,7 @@ async function fetchCommandCodeQuota(provider: string, config: OcxProviderConfig
|
|
|
1851
1868
|
const fiveHour = parseCommandCodeWindow(limits?.fiveHour);
|
|
1852
1869
|
const weekly = parseCommandCodeWindow(limits?.weekly);
|
|
1853
1870
|
const creditsUsd = await fetchCommandCodeSpend(bearer, credits, orgQuery);
|
|
1854
|
-
return
|
|
1871
|
+
return {
|
|
1855
1872
|
...(fiveHour ? {
|
|
1856
1873
|
fiveHourPercent: fiveHour.percent,
|
|
1857
1874
|
...(fiveHour.resetAt !== undefined ? { fiveHourResetAt: fiveHour.resetAt } : {}),
|
|
@@ -1862,18 +1879,21 @@ async function fetchCommandCodeQuota(provider: string, config: OcxProviderConfig
|
|
|
1862
1879
|
} : {}),
|
|
1863
1880
|
...(creditsUsd ? { creditsUsd } : {}),
|
|
1864
1881
|
updatedAt: Date.now(),
|
|
1865
|
-
}
|
|
1882
|
+
};
|
|
1866
1883
|
}
|
|
1867
1884
|
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1885
|
+
async function fetchCommandCodeQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
|
|
1886
|
+
// Never release credentials to a user-edited or lookalike provider host.
|
|
1887
|
+
if (!isCanonicalCommandCodeBaseUrl(config.baseUrl)) return null;
|
|
1888
|
+
const bearer = await resolveCommandCodeQuotaBearer(config);
|
|
1889
|
+
if (!bearer) return null;
|
|
1890
|
+
const result = await fetchCommandCodeUsageQuota(bearer);
|
|
1891
|
+
if (!result || result === TERMINAL_QUOTA_FAILURE) return result;
|
|
1892
|
+
return report(provider, "command-code:credits", result);
|
|
1893
|
+
}
|
|
1876
1894
|
|
|
1895
|
+
/** Cursor included usage via api2.cursor.sh (Bearer from OAuth) — unofficial, may change. */
|
|
1896
|
+
async function fetchCursorUsageQuota(accessToken: string): Promise<{ quota: ProviderQuota; source: string } | null> {
|
|
1877
1897
|
const authHeaders = {
|
|
1878
1898
|
Accept: "application/json",
|
|
1879
1899
|
Authorization: `Bearer ${accessToken}`,
|
|
@@ -1933,15 +1953,17 @@ async function fetchCursorQuota(provider: string): Promise<ProviderQuotaReport |
|
|
|
1933
1953
|
}
|
|
1934
1954
|
|
|
1935
1955
|
if (totalPercent !== undefined || customWindows.length > 0) {
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
...(
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1956
|
+
return {
|
|
1957
|
+
source: "cursor:period-usage",
|
|
1958
|
+
quota: {
|
|
1959
|
+
...(totalPercent !== undefined ? {
|
|
1960
|
+
monthlyPercent: totalPercent,
|
|
1961
|
+
...(resetAt !== undefined ? { monthlyResetAt: resetAt } : {}),
|
|
1962
|
+
} : {}),
|
|
1963
|
+
...(customWindows.length > 0 ? { customWindows } : {}),
|
|
1964
|
+
updatedAt: Date.now(),
|
|
1965
|
+
},
|
|
1966
|
+
};
|
|
1945
1967
|
}
|
|
1946
1968
|
}
|
|
1947
1969
|
}
|
|
@@ -1967,12 +1989,14 @@ async function fetchCursorQuota(provider: string): Promise<ProviderQuotaReport |
|
|
|
1967
1989
|
? normalizePercent((used / limit) * 100)
|
|
1968
1990
|
: undefined);
|
|
1969
1991
|
if (percent !== undefined) {
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1992
|
+
return {
|
|
1993
|
+
source: "cursor:usage-summary",
|
|
1994
|
+
quota: {
|
|
1995
|
+
monthlyPercent: percent,
|
|
1996
|
+
monthlyResetAt: normalizeResetAt(body?.billingCycleEnd),
|
|
1997
|
+
updatedAt: Date.now(),
|
|
1998
|
+
},
|
|
1999
|
+
};
|
|
1976
2000
|
}
|
|
1977
2001
|
}
|
|
1978
2002
|
}
|
|
@@ -2021,11 +2045,26 @@ async function fetchCursorQuota(provider: string): Promise<ProviderQuotaReport |
|
|
|
2021
2045
|
return Date.UTC(start.getUTCFullYear(), start.getUTCMonth() + 1, start.getUTCDate());
|
|
2022
2046
|
})()
|
|
2023
2047
|
: undefined;
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2048
|
+
return {
|
|
2049
|
+
source: "cursor:auth-usage",
|
|
2050
|
+
quota: {
|
|
2051
|
+
monthlyPercent: percent,
|
|
2052
|
+
...(monthlyResetAt !== undefined ? { monthlyResetAt } : {}),
|
|
2053
|
+
updatedAt: Date.now(),
|
|
2054
|
+
},
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
async function fetchCursorQuota(provider: string): Promise<ProviderQuotaReport | null> {
|
|
2059
|
+
let accessToken: string;
|
|
2060
|
+
try {
|
|
2061
|
+
accessToken = await getValidAccessToken("cursor");
|
|
2062
|
+
} catch {
|
|
2063
|
+
return null;
|
|
2064
|
+
}
|
|
2065
|
+
const result = await fetchCursorUsageQuota(accessToken);
|
|
2066
|
+
if (!result) return null;
|
|
2067
|
+
const built = report(provider, result.source, result.quota);
|
|
2029
2068
|
return built ? { ...built, reverseEngineered: true } : null;
|
|
2030
2069
|
}
|
|
2031
2070
|
|
|
@@ -2077,23 +2116,63 @@ function antigravityUsedPercent(quotaInfo: Record<string, unknown>): number | un
|
|
|
2077
2116
|
return normalizePercent(100 - remaining);
|
|
2078
2117
|
}
|
|
2079
2118
|
|
|
2119
|
+
async function fetchAntigravityAccountQuota(accountId: string): Promise<ProviderQuota | null> {
|
|
2120
|
+
let snapshot: OAuthAccessSnapshot;
|
|
2121
|
+
try {
|
|
2122
|
+
snapshot = await getValidAccessTokenSnapshotForAccount("google-antigravity", accountId);
|
|
2123
|
+
} catch {
|
|
2124
|
+
return null;
|
|
2125
|
+
}
|
|
2126
|
+
if (!snapshot.projectId) return null;
|
|
2127
|
+
const entry = getProviderRegistryEntry("google-antigravity");
|
|
2128
|
+
const baseUrl = (entry?.baseUrl || "https://daily-cloudcode-pa.googleapis.com").replace(/\/+$/, "");
|
|
2129
|
+
const providerConfig: Pick<OcxProviderConfig, "adapter" | "authMode" | "baseUrl" | "googleMode" | "tlsProfile"> = {
|
|
2130
|
+
adapter: "google",
|
|
2131
|
+
authMode: "oauth",
|
|
2132
|
+
googleMode: "cloud-code-assist",
|
|
2133
|
+
baseUrl,
|
|
2134
|
+
};
|
|
2135
|
+
const fetchImpl = providerTlsFetch("google-antigravity", providerConfig, globalThis.fetch);
|
|
2136
|
+
let liveQuota: ProviderQuota | null;
|
|
2137
|
+
try {
|
|
2138
|
+
liveQuota = await fetchAntigravityLiveQuota({
|
|
2139
|
+
accessToken: snapshot.accessToken,
|
|
2140
|
+
projectId: snapshot.projectId,
|
|
2141
|
+
baseUrl,
|
|
2142
|
+
timeoutMs: REQUEST_TIMEOUT_MS,
|
|
2143
|
+
fetchImpl,
|
|
2144
|
+
});
|
|
2145
|
+
} catch (error) {
|
|
2146
|
+
if (error instanceof AntigravityQuotaRpcError && isTerminalAntigravityQuotaStatus(error.status)) {
|
|
2147
|
+
return null;
|
|
2148
|
+
}
|
|
2149
|
+
liveQuota = null;
|
|
2150
|
+
}
|
|
2151
|
+
if (!liveQuota) return null;
|
|
2152
|
+
return {
|
|
2153
|
+
...liveQuota,
|
|
2154
|
+
updatedAt: Date.now(),
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2080
2158
|
async function fetchAntigravityQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
|
|
2081
|
-
|
|
2082
|
-
if (!credential?.projectId) return null;
|
|
2083
|
-
let accessToken: string;
|
|
2159
|
+
let snapshot: OAuthAccessSnapshot;
|
|
2084
2160
|
try {
|
|
2085
|
-
|
|
2161
|
+
snapshot = await getValidAccessTokenSnapshot("google-antigravity");
|
|
2086
2162
|
} catch {
|
|
2087
2163
|
return null;
|
|
2088
2164
|
}
|
|
2165
|
+
if (!snapshot.projectId) return null;
|
|
2089
2166
|
const baseUrl = (config.baseUrl || "https://daily-cloudcode-pa.googleapis.com").replace(/\/+$/, "");
|
|
2167
|
+
const fetchImpl = providerTlsFetch(provider, config, globalThis.fetch);
|
|
2090
2168
|
let liveQuota: ProviderQuota | null;
|
|
2091
2169
|
try {
|
|
2092
2170
|
liveQuota = await fetchAntigravityLiveQuota({
|
|
2093
|
-
accessToken,
|
|
2094
|
-
projectId:
|
|
2171
|
+
accessToken: snapshot.accessToken,
|
|
2172
|
+
projectId: snapshot.projectId,
|
|
2095
2173
|
baseUrl,
|
|
2096
2174
|
timeoutMs: REQUEST_TIMEOUT_MS,
|
|
2175
|
+
fetchImpl,
|
|
2097
2176
|
});
|
|
2098
2177
|
} catch (error) {
|
|
2099
2178
|
if (error instanceof AntigravityQuotaRpcError && isTerminalAntigravityQuotaStatus(error.status)) {
|
|
@@ -2106,15 +2185,15 @@ async function fetchAntigravityQuota(provider: string, config: OcxProviderConfig
|
|
|
2106
2185
|
for (const [index, host] of antigravityHostCandidates(baseUrl).entries()) {
|
|
2107
2186
|
if (!isAntigravityHttpsHost(host)) continue;
|
|
2108
2187
|
try {
|
|
2109
|
-
const response = await
|
|
2188
|
+
const response = await fetchImpl(`${host}/v1internal:fetchAvailableModels`, {
|
|
2110
2189
|
method: "POST",
|
|
2111
2190
|
headers: {
|
|
2112
2191
|
Accept: "application/json",
|
|
2113
2192
|
"Content-Type": "application/json",
|
|
2114
2193
|
"User-Agent": antigravityUserAgent(),
|
|
2115
|
-
Authorization: `Bearer ${accessToken}`,
|
|
2194
|
+
Authorization: `Bearer ${snapshot.accessToken}`,
|
|
2116
2195
|
},
|
|
2117
|
-
body: JSON.stringify({ project:
|
|
2196
|
+
body: JSON.stringify({ project: snapshot.projectId }),
|
|
2118
2197
|
redirect: "error",
|
|
2119
2198
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
2120
2199
|
});
|
|
@@ -2173,6 +2252,22 @@ async function fetchAntigravityQuota(provider: string, config: OcxProviderConfig
|
|
|
2173
2252
|
});
|
|
2174
2253
|
}
|
|
2175
2254
|
|
|
2255
|
+
async function fetchAiStudioQuota(name: string, provider: OcxProviderConfig): Promise<ProviderQuotaReport | null> {
|
|
2256
|
+
const active = globalAiStudioRelayHub.hasActiveSessions();
|
|
2257
|
+
const sessionCount = globalAiStudioRelayHub.getActiveSessionCount();
|
|
2258
|
+
const now = Date.now();
|
|
2259
|
+
|
|
2260
|
+
return {
|
|
2261
|
+
provider: name,
|
|
2262
|
+
label: "Google AI Studio (Web)",
|
|
2263
|
+
source: active ? `Browser Relay (${sessionCount} active tab${sessionCount > 1 ? "s" : ""})` : "Browser Relay (Disconnected)",
|
|
2264
|
+
updatedAt: now,
|
|
2265
|
+
quota: {
|
|
2266
|
+
updatedAt: now,
|
|
2267
|
+
},
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2176
2271
|
async function maybeFetchProviderQuota(
|
|
2177
2272
|
name: string,
|
|
2178
2273
|
provider: OcxProviderConfig,
|
|
@@ -2248,6 +2343,9 @@ async function maybeFetchProviderQuota(
|
|
|
2248
2343
|
if ((provider.authMode ?? "key") === "key" && name === "neuralwatt") {
|
|
2249
2344
|
return fetchNeuralwattQuota(name, provider);
|
|
2250
2345
|
}
|
|
2346
|
+
if (provider.googleMode === "ai-studio-web" || name === "google-aistudio") {
|
|
2347
|
+
return fetchAiStudioQuota(name, provider);
|
|
2348
|
+
}
|
|
2251
2349
|
return null;
|
|
2252
2350
|
} catch {
|
|
2253
2351
|
return null;
|
|
@@ -161,6 +161,7 @@ export interface ProviderRegistryEntry {
|
|
|
161
161
|
note?: string;
|
|
162
162
|
dashboardUrl?: string;
|
|
163
163
|
defaultModel?: string;
|
|
164
|
+
requestPacing?: OcxProviderConfig["requestPacing"];
|
|
164
165
|
models?: string[];
|
|
165
166
|
liveModels?: boolean;
|
|
166
167
|
/**
|
|
@@ -298,7 +299,7 @@ export interface ProviderRegistryEntry {
|
|
|
298
299
|
jawcodeBundle?: string;
|
|
299
300
|
extraMetadataAliases?: string[];
|
|
300
301
|
metadataModelIdNormalize?: MetadataModelIdNormalize;
|
|
301
|
-
googleMode?: "ai-studio" | "vertex" | "cloud-code-assist";
|
|
302
|
+
googleMode?: "ai-studio" | "vertex" | "cloud-code-assist" | "ai-studio-web";
|
|
302
303
|
project?: string;
|
|
303
304
|
location?: string;
|
|
304
305
|
}
|
|
@@ -1597,7 +1598,28 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1597
1598
|
// 2026-07-10: defaultModel is frozen pending Vertex-specific Tier-2 evidence; Gemini API
|
|
1598
1599
|
// evidence from ai.google.dev does not establish Vertex publisher availability.
|
|
1599
1600
|
{ id: "google-vertex", label: "Google Vertex AI", adapter: "google", baseUrl: "https://aiplatform.googleapis.com", authKind: "key", dashboardUrl: "https://console.cloud.google.com/vertex-ai", defaultModel: "gemini-3-pro", googleMode: "vertex", jawcodeBundle: "google", extraMetadataAliases: ["gemini-vertex"] },
|
|
1600
|
-
{ id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", allowBaseUrlOverride: true, dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, liveModels: true, defaultModel: "gemini-3.7-flash", modelContextWindows: ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, modelInputModalities: ANTIGRAVITY_MODEL_INPUT_MODALITIES, modelReasoningEfforts: ANTIGRAVITY_MODEL_EFFORTS, googleMode: "cloud-code-assist", jawcodeBundle: "google", extraMetadataAliases: ["antigravity", "gemini-antigravity"] },
|
|
1601
|
+
{ id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", allowBaseUrlOverride: true, dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, liveModels: true, defaultModel: "gemini-3.7-flash", requestPacing: { enabled: true, requestsPerMinute: 30, minIntervalMs: 2_000, jitterMs: 500 }, modelContextWindows: ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, modelInputModalities: ANTIGRAVITY_MODEL_INPUT_MODALITIES, modelReasoningEfforts: ANTIGRAVITY_MODEL_EFFORTS, googleMode: "cloud-code-assist", jawcodeBundle: "google", extraMetadataAliases: ["antigravity", "gemini-antigravity"] },
|
|
1602
|
+
{
|
|
1603
|
+
id: "google-aistudio",
|
|
1604
|
+
label: "Google AI Studio (Web)",
|
|
1605
|
+
adapter: "google",
|
|
1606
|
+
baseUrl: "https://alkalimakersuite-pa.clients6.google.com",
|
|
1607
|
+
authKind: "local",
|
|
1608
|
+
keyOptional: true,
|
|
1609
|
+
featured: true,
|
|
1610
|
+
dashboardPreset: true,
|
|
1611
|
+
dashboardUrl: "https://aistudio.google.com",
|
|
1612
|
+
defaultModel: "gemini-3.7-flash",
|
|
1613
|
+
models: ["gemini-3.7-flash", "gemini-3.1-pro-preview", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-3.5-flash"],
|
|
1614
|
+
liveModels: false,
|
|
1615
|
+
// Conservative pacing for a browser-backed subscription session: avoid bursts while
|
|
1616
|
+
// keeping interactive coding-agent requests usable. Jitter reduces synchronized retries.
|
|
1617
|
+
requestPacing: { enabled: true, requestsPerMinute: 8, minIntervalMs: 7_500, jitterMs: 1_500 },
|
|
1618
|
+
googleMode: "ai-studio-web",
|
|
1619
|
+
jawcodeBundle: "google",
|
|
1620
|
+
extraMetadataAliases: ["aistudio", "gemini-aistudio"],
|
|
1621
|
+
note: "Relays prompts through your active Google AI Studio / Google AI Pro browser session at /aistudio/bridge (default proxy port 10100).",
|
|
1622
|
+
},
|
|
1601
1623
|
{ id: "azure-openai", label: "Azure OpenAI", adapter: "azure-openai", baseUrl: "https://{resource}.openai.azure.com/openai", authKind: "key", featured: true, dashboardUrl: "https://portal.azure.com" },
|
|
1602
1624
|
{ id: "ollama", label: "Ollama (local)", adapter: "openai-chat", baseUrl: "http://localhost:11434/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
|
|
1603
1625
|
{ id: "vllm", label: "vLLM (local)", adapter: "openai-chat", baseUrl: "http://localhost:8000/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
|
|
@@ -1867,6 +1889,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1867
1889
|
liveModels: true,
|
|
1868
1890
|
preserveCustomDestination: true,
|
|
1869
1891
|
defaultModel: "deepseek/deepseek-v4-flash",
|
|
1892
|
+
promptCacheKey: true,
|
|
1870
1893
|
// The default is also the cold-start seed: live discovery failure must not empty the catalog
|
|
1871
1894
|
// for a freshly configured provider with no stale cache (issue #308 pattern).
|
|
1872
1895
|
models: ["deepseek/deepseek-v4-flash"],
|
|
@@ -2911,8 +2934,8 @@ export function providerCodexAccountMode(id: string, provider?: OcxProviderConfi
|
|
|
2911
2934
|
*/
|
|
2912
2935
|
export function effectiveGoogleMode(
|
|
2913
2936
|
providerId: string,
|
|
2914
|
-
prov: { adapter?: string; googleMode?: "ai-studio" | "vertex" | "cloud-code-assist" },
|
|
2915
|
-
): "ai-studio" | "vertex" | "cloud-code-assist" | null {
|
|
2937
|
+
prov: { adapter?: string; googleMode?: "ai-studio" | "vertex" | "cloud-code-assist" | "ai-studio-web" },
|
|
2938
|
+
): "ai-studio" | "vertex" | "cloud-code-assist" | "ai-studio-web" | null {
|
|
2916
2939
|
if (prov.adapter !== "google") return null;
|
|
2917
2940
|
return prov.googleMode ?? getProviderRegistryEntry(providerId)?.googleMode ?? "ai-studio";
|
|
2918
2941
|
}
|
|
@@ -33,6 +33,8 @@ interface Waiter {
|
|
|
33
33
|
modelId?: string;
|
|
34
34
|
providerIntervalMs: number;
|
|
35
35
|
modelIntervalMs: number;
|
|
36
|
+
providerJitterMs: number;
|
|
37
|
+
modelJitterMs: number;
|
|
36
38
|
queuedAt: number;
|
|
37
39
|
signal?: AbortSignal;
|
|
38
40
|
resolve: () => void;
|
|
@@ -54,6 +56,7 @@ export interface RequestPacingRuntime {
|
|
|
54
56
|
setTimer: (callback: () => void, delayMs: number) => unknown;
|
|
55
57
|
clearTimer: (handle: unknown) => void;
|
|
56
58
|
enqueueMicrotask: (callback: () => void) => void;
|
|
59
|
+
random: () => number;
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
export interface ProviderRequestPacingStatus {
|
|
@@ -71,6 +74,7 @@ const defaultRuntime: RequestPacingRuntime = {
|
|
|
71
74
|
setTimer: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
72
75
|
clearTimer: handle => clearTimeout(handle as ReturnType<typeof setTimeout>),
|
|
73
76
|
enqueueMicrotask: queueMicrotask,
|
|
77
|
+
random: Math.random,
|
|
74
78
|
};
|
|
75
79
|
let runtime = defaultRuntime;
|
|
76
80
|
let lastReconciledGeneration = 0;
|
|
@@ -90,6 +94,19 @@ function normalizedInterval(rule: RequestPacingRule | undefined): number {
|
|
|
90
94
|
return Math.max(rpmInterval, fixedInterval);
|
|
91
95
|
}
|
|
92
96
|
|
|
97
|
+
function normalizedJitter(rule: RequestPacingRule | undefined): number {
|
|
98
|
+
return rule?.jitterMs !== undefined && Number.isInteger(rule.jitterMs)
|
|
99
|
+
? Math.min(60_000, Math.max(0, rule.jitterMs))
|
|
100
|
+
: 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function jitterDelay(jitterMs: number): number {
|
|
104
|
+
if (jitterMs <= 0) return 0;
|
|
105
|
+
const sample = runtime.random();
|
|
106
|
+
if (!Number.isFinite(sample)) return 0;
|
|
107
|
+
return Math.min(jitterMs, Math.floor(Math.max(0, sample) * jitterMs));
|
|
108
|
+
}
|
|
109
|
+
|
|
93
110
|
export function requestPacingIntervalMs(provider: OcxProviderConfig, modelId?: string): number {
|
|
94
111
|
const policy = provider.requestPacing;
|
|
95
112
|
if (!policy?.enabled) return 0;
|
|
@@ -100,12 +117,17 @@ export function requestPacingIntervalMs(provider: OcxProviderConfig, modelId?: s
|
|
|
100
117
|
function requestPacingIntervals(provider: OcxProviderConfig, modelId?: string): {
|
|
101
118
|
providerIntervalMs: number;
|
|
102
119
|
modelIntervalMs: number;
|
|
120
|
+
providerJitterMs: number;
|
|
121
|
+
modelJitterMs: number;
|
|
103
122
|
} {
|
|
104
123
|
const policy = provider.requestPacing;
|
|
105
|
-
if (!policy?.enabled) return { providerIntervalMs: 0, modelIntervalMs: 0 };
|
|
124
|
+
if (!policy?.enabled) return { providerIntervalMs: 0, modelIntervalMs: 0, providerJitterMs: 0, modelJitterMs: 0 };
|
|
125
|
+
const model = modelId ? policy.models?.[modelId] : undefined;
|
|
106
126
|
return {
|
|
107
127
|
providerIntervalMs: normalizedInterval(policy),
|
|
108
|
-
modelIntervalMs:
|
|
128
|
+
modelIntervalMs: normalizedInterval(model),
|
|
129
|
+
providerJitterMs: normalizedJitter(policy),
|
|
130
|
+
modelJitterMs: normalizedJitter(model),
|
|
109
131
|
};
|
|
110
132
|
}
|
|
111
133
|
|
|
@@ -186,9 +208,9 @@ function runQueue(providerName: string, state: ProviderPacer): void {
|
|
|
186
208
|
const startedAt = runtime.now();
|
|
187
209
|
state.lastStartedAt = startedAt;
|
|
188
210
|
state.lastModelId = waiter.modelId;
|
|
189
|
-
state.providerNextStartAt = startedAt + waiter.providerIntervalMs;
|
|
190
|
-
if (waiter.modelId && waiter.modelIntervalMs > 0) {
|
|
191
|
-
state.modelNextStartAt.set(waiter.modelId, startedAt + waiter.modelIntervalMs);
|
|
211
|
+
state.providerNextStartAt = startedAt + waiter.providerIntervalMs + jitterDelay(waiter.providerJitterMs);
|
|
212
|
+
if (waiter.modelId && (waiter.modelIntervalMs > 0 || waiter.modelJitterMs > 0)) {
|
|
213
|
+
state.modelNextStartAt.set(waiter.modelId, startedAt + waiter.modelIntervalMs + jitterDelay(waiter.modelJitterMs));
|
|
192
214
|
}
|
|
193
215
|
waiter.resolve();
|
|
194
216
|
runtime.enqueueMicrotask(() => runQueue(providerName, state));
|
|
@@ -201,7 +223,12 @@ export async function waitForProviderRequestSlot(
|
|
|
201
223
|
signal?: AbortSignal,
|
|
202
224
|
): Promise<void> {
|
|
203
225
|
const intervals = requestPacingIntervals(provider, modelId);
|
|
204
|
-
if (Math.max(
|
|
226
|
+
if (Math.max(
|
|
227
|
+
intervals.providerIntervalMs,
|
|
228
|
+
intervals.modelIntervalMs,
|
|
229
|
+
intervals.providerJitterMs,
|
|
230
|
+
intervals.modelJitterMs,
|
|
231
|
+
) <= 0) return;
|
|
205
232
|
if (signal?.aborted) throw abortReason(signal);
|
|
206
233
|
|
|
207
234
|
const state = pacers.get(providerName) ?? {
|
|
@@ -4,6 +4,27 @@ import { resolveGithubCopilotTransport } from "./github-copilot-transport";
|
|
|
4
4
|
|
|
5
5
|
export const XAI_GROK_CLI_BASE_URL = "https://cli-chat-proxy.grok.com/v1";
|
|
6
6
|
|
|
7
|
+
/** The two hosts that serve xAI's Responses API: the public API and the Grok CLI proxy. */
|
|
8
|
+
const XAI_RESPONSES_HOSTS = new Set(["api.x.ai", "cli-chat-proxy.grok.com"]);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* True when this provider's Responses traffic terminates at xAI itself.
|
|
12
|
+
*
|
|
13
|
+
* Probed 2026-08-22 one field per request: the two hosts accept and refuse exactly the same
|
|
14
|
+
* web_search fields, so they are one dialect rather than two. Matching is exact-host over
|
|
15
|
+
* https, which keeps lookalikes (`api.x.ai.evil.test`) and nonstandard ports out.
|
|
16
|
+
*/
|
|
17
|
+
export function isXaiResponsesDestination(provider: Pick<OcxProviderConfig, "baseUrl">): boolean {
|
|
18
|
+
try {
|
|
19
|
+
const url = new URL(provider.baseUrl);
|
|
20
|
+
return url.protocol === "https:"
|
|
21
|
+
&& XAI_RESPONSES_HOSTS.has(url.hostname.toLowerCase())
|
|
22
|
+
&& (url.port === "" || url.port === "443");
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
7
28
|
export const XAI_GROK_COMPATIBILITY = {
|
|
8
29
|
version: "0.2.93",
|
|
9
30
|
userAgent: "opencodex-grok/0.2.93",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { effectiveGoogleMode } from "../providers/registry";
|
|
2
|
+
import type { OcxParsedRequest, OcxProviderConfig } from "../types";
|
|
3
|
+
|
|
4
|
+
const AI_STUDIO_CACHED_CONTENT = /^cachedContents\/[^/?#\s]+$/;
|
|
5
|
+
const VERTEX_CACHED_CONTENT = /^projects\/[^/?#\s]+\/locations\/[^/?#\s]+\/cachedContents\/[^/?#\s]+$/;
|
|
6
|
+
|
|
7
|
+
export function googleProviderOptionsRouteError(
|
|
8
|
+
parsed: Pick<OcxParsedRequest, "options">,
|
|
9
|
+
route: {
|
|
10
|
+
providerName: string;
|
|
11
|
+
provider: OcxProviderConfig;
|
|
12
|
+
adapterName: string;
|
|
13
|
+
},
|
|
14
|
+
): string | undefined {
|
|
15
|
+
if (!parsed.options.providerOptions?.google) return undefined;
|
|
16
|
+
const mode = effectiveGoogleMode(route.providerName, route.provider);
|
|
17
|
+
if (mode === "cloud-code-assist") {
|
|
18
|
+
return "provider_options.google is not supported on Google Cloud Code Assist routes";
|
|
19
|
+
}
|
|
20
|
+
if (route.adapterName !== "google" || (mode !== "ai-studio" && mode !== "vertex")) {
|
|
21
|
+
return "provider_options.google is supported only on Google AI Studio or Vertex routes";
|
|
22
|
+
}
|
|
23
|
+
if (mode === "ai-studio") {
|
|
24
|
+
const cachedContent = parsed.options.providerOptions.google.cachedContent;
|
|
25
|
+
if (cachedContent && VERTEX_CACHED_CONTENT.test(cachedContent)) {
|
|
26
|
+
return "provider_options.google.cached_content must use cachedContents/{id} on Google AI Studio routes";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (mode === "vertex") {
|
|
30
|
+
const cachedContent = parsed.options.providerOptions.google.cachedContent;
|
|
31
|
+
if (cachedContent && AI_STUDIO_CACHED_CONTENT.test(cachedContent)) {
|
|
32
|
+
return "provider_options.google.cached_content must use projects/{project}/locations/{location}/cachedContents/{id} on Vertex routes";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|