@yansigit/opencodex 2.31.1 → 2.31.3
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-DJDp_XER.js → index-Cxt5fZMP.js} +14 -14
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/base.ts +6 -0
- package/src/adapters/command-code-project-context.ts +377 -0
- package/src/adapters/command-code.ts +5 -1
- package/src/adapters/cursor/cursor-errors.ts +12 -0
- package/src/adapters/cursor/live-transport.ts +21 -0
- package/src/adapters/cursor/native-exec-bridge.ts +141 -0
- package/src/adapters/cursor/thread-continuity.ts +71 -0
- package/src/adapters/cursor.ts +90 -28
- package/src/adapters/google-http.ts +12 -2
- package/src/adapters/google-wire-compiler.ts +91 -2
- package/src/adapters/google.ts +83 -15
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +57 -25
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/oauth/anthropic-routing.ts +129 -219
- package/src/oauth/antigravity-routing.ts +165 -0
- package/src/oauth/cursor-routing.ts +252 -0
- package/src/oauth/index.ts +3 -0
- package/src/providers/cursor-pool.ts +3 -3
- package/src/routing/account-pool/affinity.ts +125 -0
- package/src/routing/account-pool/cooldown.ts +145 -0
- package/src/routing/account-pool/index.ts +45 -0
- package/src/routing/account-pool/resolve.ts +356 -0
- package/src/routing/account-pool/types.ts +32 -0
- package/src/routing/compatibility/behavior.ts +3 -0
- package/src/server/management/oauth-account-routes.ts +47 -12
- package/src/server/responses/core.ts +400 -72
- package/src/types/config.ts +8 -0
- package/src/types/provider.ts +7 -0
- package/src/types/request.ts +13 -3
- package/src/usage/log.ts +4 -0
- package/src/web-search/gemini-executor.ts +35 -13
- package/src/web-search/index.ts +85 -1
|
@@ -106,12 +106,26 @@ import {
|
|
|
106
106
|
rotateAnthropicAccountOn429,
|
|
107
107
|
} from "../../oauth/anthropic-routing";
|
|
108
108
|
import {
|
|
109
|
+
antigravity429StickWaitMs,
|
|
110
|
+
antigravitySessionKeyFromParts,
|
|
109
111
|
bindAntigravityProject,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
bindAntigravitySessionAffinity,
|
|
113
|
+
resolveAntigravityAccountForSession,
|
|
114
|
+
rotateAntigravityAccountOn429,
|
|
112
115
|
} from "../../oauth/antigravity-routing";
|
|
113
|
-
import {
|
|
114
|
-
|
|
116
|
+
import {
|
|
117
|
+
CURSOR_POOL_MAX_FAILOVERS_PER_REQUEST,
|
|
118
|
+
bindCursorSessionAffinity,
|
|
119
|
+
cursorSessionKeyFromParts,
|
|
120
|
+
formatCursorProviderForLog,
|
|
121
|
+
isCursorAccountPoolActive,
|
|
122
|
+
recordCursorAccountBillingCooldown,
|
|
123
|
+
resolveCursorAccountForSession,
|
|
124
|
+
rotateCursorAccountOn429,
|
|
125
|
+
rotateCursorAccountOnAuth,
|
|
126
|
+
} from "../../oauth/cursor-routing";
|
|
127
|
+
import { getAccountCredential } from "../../oauth/store";
|
|
128
|
+
import { buildWebSearchTool, mediaBridgeWillRun, planWebSearch, resolveCcaInTurnGrounding, runWithWebSearch, shouldResolveOpenAiWebSearchSidecar } from "../../web-search";
|
|
115
129
|
import { buildImageTool, buildVideoTool, planImageBridge, planVideoBridge, runWithImageBridge, clampImageMaxRounds, IMAGE_GEN_TOOL_NAME, VIDEO_GEN_TOOL_NAME } from "../../images";
|
|
116
130
|
import { describeImagesInPlace, isModelTextOnly, planVisionSidecar, resolveOpenAiVisionModel, shouldResolveOpenAiVisionSidecar, stripImagesInPlace } from "../../vision";
|
|
117
131
|
import { createAdapterEventQueue, preflightAdapterEvents, type AdapterEventQueue } from "../../adapters/run-turn-queue";
|
|
@@ -506,7 +520,7 @@ function bindRouteReasoningReplayScope(args: {
|
|
|
506
520
|
// seed assigned before route binding. A Cursor conversation must be scoped to the exact
|
|
507
521
|
// provider/destination/adapter/model/credential that serves it.
|
|
508
522
|
if (continuationOwner) parsed._cursorIdentityScope = providerContinuationRouteScope(continuationOwner);
|
|
509
|
-
else if (!parsed._cursorIdentityScope?.
|
|
523
|
+
else if (!parsed._cursorIdentityScope?.trim()) {
|
|
510
524
|
// Prevent the adapter's token-only fallback from recreating a provider-private id after the
|
|
511
525
|
// route owner failed closed. The sentinel is per parsed request and contains no credential.
|
|
512
526
|
parsed._cursorIdentityScope = `cursor-unowned:${randomUUID()}`;
|
|
@@ -1185,6 +1199,8 @@ export interface HandleResponsesOptions {
|
|
|
1185
1199
|
admission?: DataPlaneAdmission;
|
|
1186
1200
|
/** Called at most once after the complete client body is read and accepted for dispatch. */
|
|
1187
1201
|
onRequestBodyRead?: () => void;
|
|
1202
|
+
/** Internal combo handoff invoked after the final adapter accepts the parsed request. */
|
|
1203
|
+
onRequestValidated?: () => void;
|
|
1188
1204
|
forceEmptyResponseId?: boolean;
|
|
1189
1205
|
abortSignal?: AbortSignal;
|
|
1190
1206
|
/** One-shot TTFT callback: first non-empty model output observed (WP4). */
|
|
@@ -1820,6 +1836,7 @@ export async function handleComboResponses(
|
|
|
1820
1836
|
let lastFailure: Response | null = null;
|
|
1821
1837
|
while (pick) {
|
|
1822
1838
|
if (options.abortSignal?.aborted) return clientCancelledResponse();
|
|
1839
|
+
const selectedPick = pick;
|
|
1823
1840
|
const childLog: RequestLogContext = {
|
|
1824
1841
|
model: pick.target.model,
|
|
1825
1842
|
provider: pick.target.provider,
|
|
@@ -1841,24 +1858,31 @@ export async function handleComboResponses(
|
|
|
1841
1858
|
});
|
|
1842
1859
|
let resolvedAuth: CodexAuthContext | undefined;
|
|
1843
1860
|
let terminalRecorder: ((status: ResponsesTerminalStatus, httpStatusOverride?: number) => void) | undefined;
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1861
|
+
let started: number | undefined;
|
|
1862
|
+
let attempt: ReturnType<typeof beginRequestAttempt> | undefined;
|
|
1863
|
+
const beginAttempt = (): void => {
|
|
1864
|
+
if (attempt) return;
|
|
1865
|
+
started = Date.now();
|
|
1866
|
+
attempt = beginRequestAttempt(
|
|
1867
|
+
(logCtx.attempts?.length ?? 0) + 1,
|
|
1868
|
+
selectedPick.target.provider,
|
|
1869
|
+
selectedPick.target.model,
|
|
1870
|
+
config.providers[selectedPick.target.provider]!.adapter,
|
|
1871
|
+
);
|
|
1872
|
+
childLog.activeAttempt = attempt;
|
|
1873
|
+
childLog.activeAttemptStartedAt = started;
|
|
1874
|
+
recordAttemptRequestedEffort(childLog);
|
|
1875
|
+
};
|
|
1852
1876
|
let attemptRetained = false;
|
|
1853
1877
|
const retainCancelledAttempt = (): void => {
|
|
1854
|
-
if (attemptRetained) return;
|
|
1878
|
+
if (attemptRetained || !attempt) return;
|
|
1855
1879
|
sealRequestAttemptIdentity(
|
|
1856
1880
|
attempt,
|
|
1857
1881
|
childLog.provider,
|
|
1858
1882
|
childLog.providerAdapter ?? attempt.adapter,
|
|
1859
1883
|
childLog.accountLogLabel,
|
|
1860
1884
|
);
|
|
1861
|
-
finishRequestAttempt(attempt, 499, Date.now() - started, childLog.usage);
|
|
1885
|
+
finishRequestAttempt(attempt, 499, Date.now() - (started ?? Date.now()), childLog.usage);
|
|
1862
1886
|
(logCtx.attempts ??= []).push(attempt);
|
|
1863
1887
|
attemptRetained = true;
|
|
1864
1888
|
};
|
|
@@ -1878,10 +1902,11 @@ export async function handleComboResponses(
|
|
|
1878
1902
|
comboAttempt: true,
|
|
1879
1903
|
comboReplaySnapshot,
|
|
1880
1904
|
deferCodexResetDerivedCooldown,
|
|
1905
|
+
onRequestValidated: beginAttempt,
|
|
1881
1906
|
// Attempt-relative TTFT is recorded HERE (not via childLog.firstOutputMs — a later
|
|
1882
1907
|
// Object.assign(logCtx, childLog) would overwrite the request-relative value).
|
|
1883
1908
|
onFirstOutput: () => {
|
|
1884
|
-
if (attempt.firstOutputMs === undefined) {
|
|
1909
|
+
if (attempt && started !== undefined && attempt.firstOutputMs === undefined) {
|
|
1885
1910
|
attempt.firstOutputMs = Math.max(0, Date.now() - started);
|
|
1886
1911
|
}
|
|
1887
1912
|
options.onFirstOutput?.();
|
|
@@ -1908,6 +1933,7 @@ export async function handleComboResponses(
|
|
|
1908
1933
|
}
|
|
1909
1934
|
|
|
1910
1935
|
if (response.ok) {
|
|
1936
|
+
if (!attempt) return response;
|
|
1911
1937
|
sealRequestAttemptIdentity(
|
|
1912
1938
|
attempt,
|
|
1913
1939
|
childLog.provider,
|
|
@@ -1954,20 +1980,22 @@ export async function handleComboResponses(
|
|
|
1954
1980
|
retainCancelledAttempt();
|
|
1955
1981
|
return clientCancelledResponse();
|
|
1956
1982
|
}
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1983
|
+
if (attempt) {
|
|
1984
|
+
sealRequestAttemptIdentity(
|
|
1985
|
+
attempt,
|
|
1986
|
+
childLog.provider,
|
|
1987
|
+
childLog.providerAdapter ?? attempt.adapter,
|
|
1988
|
+
childLog.accountLogLabel,
|
|
1989
|
+
);
|
|
1990
|
+
finishRequestAttempt(
|
|
1991
|
+
attempt,
|
|
1992
|
+
response.status,
|
|
1993
|
+
Date.now() - (started ?? Date.now()),
|
|
1994
|
+
failure.usage,
|
|
1995
|
+
);
|
|
1996
|
+
(logCtx.attempts ??= []).push(attempt);
|
|
1997
|
+
attemptRetained = true;
|
|
1998
|
+
}
|
|
1971
1999
|
lastFailure = failure.response;
|
|
1972
2000
|
if (comboFailureDecision(failure.response.status, failure.classificationText, {
|
|
1973
2001
|
code: failure.upstreamCode,
|
|
@@ -1976,7 +2004,7 @@ export async function handleComboResponses(
|
|
|
1976
2004
|
return lastFailure;
|
|
1977
2005
|
}
|
|
1978
2006
|
console.warn(
|
|
1979
|
-
`[combo] ${comboId}: ${targetKey(pick.target)} failed with ${response.status} after ${Date.now() - started}ms`,
|
|
2007
|
+
`[combo] ${comboId}: ${targetKey(pick.target)} failed with ${response.status} after ${started === undefined ? 0 : Date.now() - started}ms`,
|
|
1980
2008
|
);
|
|
1981
2009
|
const nextPick = advanceComboAfterFailure(config, pick, {
|
|
1982
2010
|
retryAfter: failure.retryAfter,
|
|
@@ -2589,13 +2617,27 @@ async function handleResponsesInner(
|
|
|
2589
2617
|
let anthropicPoolFailovers = 0;
|
|
2590
2618
|
let antigravityAccountId: string | undefined;
|
|
2591
2619
|
let antigravityFailovers = 0;
|
|
2620
|
+
let cursorPoolAccountId: string | null = null;
|
|
2621
|
+
let cursorPoolFailovers = 0;
|
|
2622
|
+
const oauthSessionKeyParts = {
|
|
2623
|
+
sessionIdHeader: sessionIdHeaderFromRequest(req.headers),
|
|
2624
|
+
threadIdHeader: req.headers.get("thread-id"),
|
|
2625
|
+
promptCacheKey: typeof parsed.options.promptCacheKey === "string" ? parsed.options.promptCacheKey : null,
|
|
2626
|
+
clientThreadId: typeof parsed._clientThreadId === "string" ? parsed._clientThreadId : null,
|
|
2627
|
+
promptCacheKeyIsSharedCohort: options.promptCacheKeyIsSharedCohort === true,
|
|
2628
|
+
};
|
|
2592
2629
|
const anthropicSessionKey = route.providerName === "anthropic" && route.provider.authMode === "oauth"
|
|
2593
|
-
? anthropicSessionKeyFromParts(
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2630
|
+
? anthropicSessionKeyFromParts(oauthSessionKeyParts)
|
|
2631
|
+
: null;
|
|
2632
|
+
const antigravitySessionKey = route.providerName === "google-antigravity"
|
|
2633
|
+
&& route.provider.authMode === "oauth"
|
|
2634
|
+
&& route.provider.googleMode === "cloud-code-assist"
|
|
2635
|
+
? antigravitySessionKeyFromParts(oauthSessionKeyParts)
|
|
2636
|
+
: null;
|
|
2637
|
+
const cursorSessionKey = route.providerName === "cursor"
|
|
2638
|
+
&& route.provider.authMode === "oauth"
|
|
2639
|
+
? cursorSessionKeyFromParts({
|
|
2597
2640
|
clientThreadId: typeof parsed._clientThreadId === "string" ? parsed._clientThreadId : null,
|
|
2598
|
-
promptCacheKeyIsSharedCohort: options.promptCacheKeyIsSharedCohort === true,
|
|
2599
2641
|
})
|
|
2600
2642
|
: null;
|
|
2601
2643
|
if (route.provider.authMode === "oauth") {
|
|
@@ -2620,48 +2662,88 @@ async function handleResponsesInner(
|
|
|
2620
2662
|
promoteAnthropicActiveAccount(selection.accountId);
|
|
2621
2663
|
route.provider = { ...route.provider, apiKey: accessToken };
|
|
2622
2664
|
logCtx.provider = formatAnthropicProviderForLog("anthropic", selection.accountId, config);
|
|
2623
|
-
} else
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2665
|
+
} else if (
|
|
2666
|
+
route.providerName === "google-antigravity"
|
|
2667
|
+
&& route.provider.googleMode === "cloud-code-assist"
|
|
2668
|
+
) {
|
|
2669
|
+
const selection = resolveAntigravityAccountForSession(antigravitySessionKey);
|
|
2670
|
+
if (!selection.accountId) {
|
|
2671
|
+
if (selection.reason === "all-cooled") {
|
|
2672
|
+
return formatErrorResponse(
|
|
2673
|
+
429,
|
|
2674
|
+
"rate_limit_error",
|
|
2675
|
+
"All Google Antigravity OAuth accounts are temporarily unavailable",
|
|
2676
|
+
);
|
|
2633
2677
|
}
|
|
2634
|
-
|
|
2635
|
-
|
|
2678
|
+
return formatErrorResponse(
|
|
2679
|
+
401,
|
|
2680
|
+
"authentication_error",
|
|
2681
|
+
"No eligible Google Antigravity OAuth account available",
|
|
2682
|
+
);
|
|
2683
|
+
}
|
|
2684
|
+
let resolved = await getValidAccessTokenSnapshot(route.providerName);
|
|
2685
|
+
if (selection.accountId !== resolved.accountId) {
|
|
2686
|
+
const accessToken = await getValidAccessTokenForAccount("google-antigravity", selection.accountId);
|
|
2687
|
+
const nextCredential = getAccountCredential("google-antigravity", selection.accountId);
|
|
2636
2688
|
resolved = {
|
|
2637
2689
|
...resolved,
|
|
2638
|
-
accountId:
|
|
2690
|
+
accountId: selection.accountId,
|
|
2639
2691
|
accessToken,
|
|
2640
2692
|
// Always replace; omitting a missing id would keep the previous account's projectId.
|
|
2641
2693
|
projectId: nextCredential?.projectId,
|
|
2642
2694
|
};
|
|
2643
|
-
skippedAntigravityCooldown = true;
|
|
2644
2695
|
}
|
|
2645
2696
|
replayOAuthCredentialSnapshot = {
|
|
2646
2697
|
accountId: resolved.accountId,
|
|
2647
2698
|
generation: resolved.generation,
|
|
2648
2699
|
};
|
|
2649
|
-
if (
|
|
2650
|
-
if (isOAuth401ReplayProvider) sentOAuthSnapshot = resolved;
|
|
2700
|
+
if (selection.reason === "failover") replayOAuthCredentialSnapshot = undefined;
|
|
2651
2701
|
route.provider = { ...route.provider, apiKey: resolved.accessToken };
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2702
|
+
antigravityAccountId = selection.accountId;
|
|
2703
|
+
bindAntigravitySessionAffinity(antigravitySessionKey, selection.accountId);
|
|
2704
|
+
const bound = bindAntigravityProject(route.provider, resolved.projectId);
|
|
2705
|
+
if (!bound.ok) {
|
|
2706
|
+
return formatErrorResponse(bound.status, bound.type, bound.message);
|
|
2707
|
+
}
|
|
2708
|
+
route.provider = bound.provider;
|
|
2709
|
+
} else if (
|
|
2710
|
+
route.providerName === "cursor"
|
|
2711
|
+
&& route.provider.authMode === "oauth"
|
|
2712
|
+
&& isCursorAccountPoolActive(config)
|
|
2713
|
+
) {
|
|
2714
|
+
const selection = resolveCursorAccountForSession(cursorSessionKey, config);
|
|
2715
|
+
if (!selection.accountId) {
|
|
2716
|
+
if (selection.reason === "all-cooled") {
|
|
2717
|
+
return formatErrorResponse(
|
|
2718
|
+
429,
|
|
2719
|
+
"rate_limit_error",
|
|
2720
|
+
"All Cursor OAuth accounts are temporarily rate-limited",
|
|
2721
|
+
);
|
|
2663
2722
|
}
|
|
2723
|
+
return formatErrorResponse(401, "authentication_error", "No eligible Cursor OAuth account available");
|
|
2724
|
+
}
|
|
2725
|
+
let resolved = await getValidAccessTokenSnapshot("cursor");
|
|
2726
|
+
if (selection.accountId !== resolved.accountId) {
|
|
2727
|
+
const accessToken = await getValidAccessTokenForAccount("cursor", selection.accountId);
|
|
2728
|
+
resolved = { ...resolved, accountId: selection.accountId, accessToken };
|
|
2664
2729
|
}
|
|
2730
|
+
cursorPoolAccountId = selection.accountId;
|
|
2731
|
+
parsed._cursorIdentityScope = selection.accountId;
|
|
2732
|
+
bindCursorSessionAffinity(cursorSessionKey, selection.accountId);
|
|
2733
|
+
replayOAuthCredentialSnapshot = {
|
|
2734
|
+
accountId: resolved.accountId,
|
|
2735
|
+
generation: resolved.generation,
|
|
2736
|
+
};
|
|
2737
|
+
route.provider = { ...route.provider, apiKey: resolved.accessToken };
|
|
2738
|
+
logCtx.provider = formatCursorProviderForLog("cursor", selection.accountId);
|
|
2739
|
+
} else {
|
|
2740
|
+
const resolved = await getValidAccessTokenSnapshot(route.providerName);
|
|
2741
|
+
replayOAuthCredentialSnapshot = {
|
|
2742
|
+
accountId: resolved.accountId,
|
|
2743
|
+
generation: resolved.generation,
|
|
2744
|
+
};
|
|
2745
|
+
if (isOAuth401ReplayProvider) sentOAuthSnapshot = resolved;
|
|
2746
|
+
route.provider = { ...route.provider, apiKey: resolved.accessToken };
|
|
2665
2747
|
if (route.providerName === "kiro") {
|
|
2666
2748
|
// `{}` is intentional: this is an account-scoped request with no stored routing metadata.
|
|
2667
2749
|
// Only genuinely accountless adapter calls leave the context undefined and use local/env fallback.
|
|
@@ -2719,6 +2801,16 @@ async function handleResponsesInner(
|
|
|
2719
2801
|
logCtx.conversationId = normalizeLogConversationId(parsed._cursorConversationId);
|
|
2720
2802
|
}
|
|
2721
2803
|
logCtx.providerAdapter = adapter.name;
|
|
2804
|
+
try {
|
|
2805
|
+
adapter.validateRequest?.(parsed);
|
|
2806
|
+
} catch (err) {
|
|
2807
|
+
return formatErrorResponse(
|
|
2808
|
+
400,
|
|
2809
|
+
"invalid_request_error",
|
|
2810
|
+
redactSecretString(err instanceof Error ? err.message : String(err)),
|
|
2811
|
+
);
|
|
2812
|
+
}
|
|
2813
|
+
options.onRequestValidated?.();
|
|
2722
2814
|
// Ordinary requests receive one durable attempt only after their final initial
|
|
2723
2815
|
// adapter is resolved. Combo children own their attempt and retries keep it.
|
|
2724
2816
|
if (!options.comboAttempt && !logCtx.activeAttempt) {
|
|
@@ -3969,13 +4061,23 @@ async function handleResponsesInner(
|
|
|
3969
4061
|
// - non-runTurn: web-search wins over image when both eligible (documented priority)
|
|
3970
4062
|
// - runTurn: image bridge may run (it supports runTurn); web-search is skipped so runTurn
|
|
3971
4063
|
// can proceed for web-search-only turns
|
|
3972
|
-
const wsPlan = !routedCompaction
|
|
3973
|
-
? planWebSearch(config, parsed, false, route.provider, route.modelId, openAiSidecar)
|
|
3974
|
-
: undefined;
|
|
3975
4064
|
const imgPlan = !routedCompaction ? await planImageBridge(config, parsed, route.provider) : undefined;
|
|
3976
4065
|
const vidPlan = !routedCompaction ? await planVideoBridge(config, parsed, route.provider) : undefined;
|
|
3977
|
-
const
|
|
3978
|
-
|
|
4066
|
+
const hasMediaPlan = !!(imgPlan || vidPlan);
|
|
4067
|
+
// A media plan is not enough to suppress Gemini 2.x grounding: both bridges inject tools only
|
|
4068
|
+
// on streaming turns. This is the potential-injection value used to resolve sidecar precedence.
|
|
4069
|
+
const mediaMayInject = mediaBridgeWillRun(hasMediaPlan, false, !!adapter.runTurn, parsed.stream);
|
|
4070
|
+
const wsPlan = !routedCompaction
|
|
4071
|
+
? planWebSearch(config, parsed, false, route.provider, route.modelId, openAiSidecar, mediaMayInject)
|
|
4072
|
+
: undefined;
|
|
4073
|
+
const webSearchWinsMedia = !!wsPlan && !adapter.runTurn;
|
|
4074
|
+
const mediaWillRun = mediaBridgeWillRun(hasMediaPlan, !!wsPlan, !!adapter.runTurn, parsed.stream);
|
|
4075
|
+
const ccaInTurnGrounding = !routedCompaction
|
|
4076
|
+
? resolveCcaInTurnGrounding(config, parsed, false, route.provider, route.modelId, mediaWillRun)
|
|
4077
|
+
: undefined;
|
|
4078
|
+
if (ccaInTurnGrounding) parsed._ccaInTurnGrounding = ccaInTurnGrounding;
|
|
4079
|
+
const canRunWebSearch = webSearchWinsMedia && !ccaInTurnGrounding;
|
|
4080
|
+
if (hasMediaPlan && webSearchWinsMedia) {
|
|
3979
4081
|
// Web search takes priority when both are active — the media bridge cannot run
|
|
3980
4082
|
// alongside runWithWebSearch. Surface a runtime signal so the user knows their
|
|
3981
4083
|
// configured video/image bridge was skipped for this turn, rather than silently
|
|
@@ -3983,7 +4085,7 @@ async function handleResponsesInner(
|
|
|
3983
4085
|
if (vidPlan) console.warn("[videos] video bridge skipped: web search is active for this turn");
|
|
3984
4086
|
if (imgPlan) console.warn("[images] image bridge skipped: web search is active for this turn");
|
|
3985
4087
|
}
|
|
3986
|
-
if (
|
|
4088
|
+
if (mediaWillRun || (imgPlan && !parsed.stream && !webSearchWinsMedia)) {
|
|
3987
4089
|
// The image bridge detects a hosted image_generation tool and requires streaming.
|
|
3988
4090
|
// The video bridge activates from config and injects a tool — it also needs streaming
|
|
3989
4091
|
// (the loop returns SSE). For video-only (no imgPlan) on a non-streaming request, skip
|
|
@@ -4782,8 +4884,29 @@ async function handleResponsesInner(
|
|
|
4782
4884
|
&& antigravityAccountId
|
|
4783
4885
|
&& antigravityFailovers < 3
|
|
4784
4886
|
) {
|
|
4785
|
-
const
|
|
4786
|
-
|
|
4887
|
+
const stickWaitMs = antigravity429StickWaitMs(antigravityAccountId);
|
|
4888
|
+
if (stickWaitMs !== null) {
|
|
4889
|
+
await Bun.sleep(stickWaitMs);
|
|
4890
|
+
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
4891
|
+
invalidateSameTargetRequest();
|
|
4892
|
+
activeAdapter = resolveAdapter(
|
|
4893
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
4894
|
+
config.cacheRetention,
|
|
4895
|
+
);
|
|
4896
|
+
bindRouteReasoningReplayScope({
|
|
4897
|
+
parsed,
|
|
4898
|
+
providerName: route.providerName,
|
|
4899
|
+
provider: route.provider,
|
|
4900
|
+
adapterName: activeAdapter.name,
|
|
4901
|
+
codexAuthContext: authCtx,
|
|
4902
|
+
forwardHeaders: selectedForwardHeaders,
|
|
4903
|
+
});
|
|
4904
|
+
const result = await rebuildAndRefetch("rate-limit-429");
|
|
4905
|
+
if ("failed" in result) return result.failed;
|
|
4906
|
+
upstreamResponse = result;
|
|
4907
|
+
continue;
|
|
4908
|
+
}
|
|
4909
|
+
const nextAccountId = rotateAntigravityAccountOn429(antigravityAccountId, antigravitySessionKey);
|
|
4787
4910
|
if (!nextAccountId) break;
|
|
4788
4911
|
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
4789
4912
|
try {
|
|
@@ -4813,7 +4936,6 @@ async function handleResponsesInner(
|
|
|
4813
4936
|
codexAuthContext: authCtx,
|
|
4814
4937
|
forwardHeaders: selectedForwardHeaders,
|
|
4815
4938
|
});
|
|
4816
|
-
void setActiveAccount("google-antigravity", nextAccountId).catch(() => { /* best-effort promotion */ });
|
|
4817
4939
|
const result = await rebuildAndRefetch("rate-limit-429");
|
|
4818
4940
|
if ("failed" in result) return result.failed;
|
|
4819
4941
|
upstreamResponse = result;
|
|
@@ -4821,6 +4943,105 @@ async function handleResponsesInner(
|
|
|
4821
4943
|
break;
|
|
4822
4944
|
}
|
|
4823
4945
|
}
|
|
4946
|
+
if (
|
|
4947
|
+
upstreamResponse.status === 402
|
|
4948
|
+
&& cursorPoolAccountId
|
|
4949
|
+
&& isCursorAccountPoolActive(config)
|
|
4950
|
+
) {
|
|
4951
|
+
recordCursorAccountBillingCooldown(
|
|
4952
|
+
cursorPoolAccountId,
|
|
4953
|
+
upstreamResponse.headers.get("retry-after"),
|
|
4954
|
+
);
|
|
4955
|
+
}
|
|
4956
|
+
while (
|
|
4957
|
+
(upstreamResponse.status === 401 || upstreamResponse.status === 403)
|
|
4958
|
+
&& route.providerName === "cursor"
|
|
4959
|
+
&& route.provider.authMode === "oauth"
|
|
4960
|
+
&& cursorPoolAccountId
|
|
4961
|
+
&& isCursorAccountPoolActive(config)
|
|
4962
|
+
&& cursorPoolFailovers < CURSOR_POOL_MAX_FAILOVERS_PER_REQUEST
|
|
4963
|
+
) {
|
|
4964
|
+
const nextAccountId = rotateCursorAccountOnAuth(
|
|
4965
|
+
config,
|
|
4966
|
+
cursorPoolAccountId,
|
|
4967
|
+
cursorSessionKey,
|
|
4968
|
+
);
|
|
4969
|
+
if (!nextAccountId) break;
|
|
4970
|
+
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
4971
|
+
try {
|
|
4972
|
+
const accessToken = await getValidAccessTokenForAccount("cursor", nextAccountId);
|
|
4973
|
+
cursorPoolAccountId = nextAccountId;
|
|
4974
|
+
cursorPoolFailovers += 1;
|
|
4975
|
+
parsed._cursorIdentityScope = nextAccountId;
|
|
4976
|
+
route.provider = { ...route.provider, apiKey: accessToken };
|
|
4977
|
+
replayOAuthCredentialSnapshot = undefined;
|
|
4978
|
+
invalidateSameTargetRequest();
|
|
4979
|
+
activeAdapter = resolveAdapter(
|
|
4980
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
4981
|
+
config.cacheRetention,
|
|
4982
|
+
);
|
|
4983
|
+
bindRouteReasoningReplayScope({
|
|
4984
|
+
parsed,
|
|
4985
|
+
providerName: route.providerName,
|
|
4986
|
+
provider: route.provider,
|
|
4987
|
+
adapterName: activeAdapter.name,
|
|
4988
|
+
codexAuthContext: authCtx,
|
|
4989
|
+
forwardHeaders: selectedForwardHeaders,
|
|
4990
|
+
});
|
|
4991
|
+
logCtx.provider = formatCursorProviderForLog("cursor", nextAccountId);
|
|
4992
|
+
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name, logCtx.accountLogLabel);
|
|
4993
|
+
const result = await rebuildAndRefetch("cursor-oauth-auth");
|
|
4994
|
+
if ("failed" in result) return result.failed;
|
|
4995
|
+
upstreamResponse = result;
|
|
4996
|
+
} catch {
|
|
4997
|
+
break;
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
5000
|
+
while (
|
|
5001
|
+
upstreamResponse.status === 429
|
|
5002
|
+
&& route.providerName === "cursor"
|
|
5003
|
+
&& route.provider.authMode === "oauth"
|
|
5004
|
+
&& cursorPoolAccountId
|
|
5005
|
+
&& isCursorAccountPoolActive(config)
|
|
5006
|
+
&& cursorPoolFailovers < CURSOR_POOL_MAX_FAILOVERS_PER_REQUEST
|
|
5007
|
+
) {
|
|
5008
|
+
const nextAccountId = rotateCursorAccountOn429(
|
|
5009
|
+
config,
|
|
5010
|
+
cursorPoolAccountId,
|
|
5011
|
+
upstreamResponse.headers.get("retry-after"),
|
|
5012
|
+
cursorSessionKey,
|
|
5013
|
+
);
|
|
5014
|
+
if (!nextAccountId) break;
|
|
5015
|
+
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
5016
|
+
try {
|
|
5017
|
+
const accessToken = await getValidAccessTokenForAccount("cursor", nextAccountId);
|
|
5018
|
+
cursorPoolAccountId = nextAccountId;
|
|
5019
|
+
cursorPoolFailovers += 1;
|
|
5020
|
+
parsed._cursorIdentityScope = nextAccountId;
|
|
5021
|
+
route.provider = { ...route.provider, apiKey: accessToken };
|
|
5022
|
+
replayOAuthCredentialSnapshot = undefined;
|
|
5023
|
+
invalidateSameTargetRequest();
|
|
5024
|
+
activeAdapter = resolveAdapter(
|
|
5025
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
5026
|
+
config.cacheRetention,
|
|
5027
|
+
);
|
|
5028
|
+
bindRouteReasoningReplayScope({
|
|
5029
|
+
parsed,
|
|
5030
|
+
providerName: route.providerName,
|
|
5031
|
+
provider: route.provider,
|
|
5032
|
+
adapterName: activeAdapter.name,
|
|
5033
|
+
codexAuthContext: authCtx,
|
|
5034
|
+
forwardHeaders: selectedForwardHeaders,
|
|
5035
|
+
});
|
|
5036
|
+
logCtx.provider = formatCursorProviderForLog("cursor", nextAccountId);
|
|
5037
|
+
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name, logCtx.accountLogLabel);
|
|
5038
|
+
const result = await rebuildAndRefetch("cursor-oauth-429");
|
|
5039
|
+
if ("failed" in result) return result.failed;
|
|
5040
|
+
upstreamResponse = result;
|
|
5041
|
+
} catch {
|
|
5042
|
+
break;
|
|
5043
|
+
}
|
|
5044
|
+
}
|
|
4824
5045
|
// Unknown provenance is deliberately fail-soft in pre-flight: after a restart, TTL expiry,
|
|
4825
5046
|
// or LRU eviction, a valid same-backend blob must survive. A decoder's own 4xx identity is
|
|
4826
5047
|
// the missing authoritative signal. Rebuild once through the same sanitation path used by a
|
|
@@ -5155,6 +5376,113 @@ async function handleResponsesInner(
|
|
|
5155
5376
|
}
|
|
5156
5377
|
}
|
|
5157
5378
|
}
|
|
5379
|
+
if (
|
|
5380
|
+
response.status === 402
|
|
5381
|
+
&& cursorPoolAccountId
|
|
5382
|
+
&& isCursorAccountPoolActive(config)
|
|
5383
|
+
) {
|
|
5384
|
+
recordCursorAccountBillingCooldown(
|
|
5385
|
+
cursorPoolAccountId,
|
|
5386
|
+
response.headers.get("retry-after"),
|
|
5387
|
+
);
|
|
5388
|
+
}
|
|
5389
|
+
if (
|
|
5390
|
+
(response.status === 401 || response.status === 403)
|
|
5391
|
+
&& route.providerName === "cursor"
|
|
5392
|
+
&& route.provider.authMode === "oauth"
|
|
5393
|
+
&& cursorPoolAccountId
|
|
5394
|
+
&& isCursorAccountPoolActive(config)
|
|
5395
|
+
&& cursorPoolFailovers < CURSOR_POOL_MAX_FAILOVERS_PER_REQUEST
|
|
5396
|
+
) {
|
|
5397
|
+
const nextAccountId = rotateCursorAccountOnAuth(
|
|
5398
|
+
config,
|
|
5399
|
+
cursorPoolAccountId,
|
|
5400
|
+
cursorSessionKey,
|
|
5401
|
+
);
|
|
5402
|
+
if (nextAccountId) {
|
|
5403
|
+
try { void response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
|
|
5404
|
+
try {
|
|
5405
|
+
const accessToken = await getValidAccessTokenForAccount("cursor", nextAccountId);
|
|
5406
|
+
cursorPoolAccountId = nextAccountId;
|
|
5407
|
+
cursorPoolFailovers += 1;
|
|
5408
|
+
parsed._cursorIdentityScope = nextAccountId;
|
|
5409
|
+
route.provider = { ...route.provider, apiKey: accessToken };
|
|
5410
|
+
replayOAuthCredentialSnapshot = undefined;
|
|
5411
|
+
invalidateSameTargetRequest();
|
|
5412
|
+
activeAdapter = resolveAdapter(
|
|
5413
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
5414
|
+
config.cacheRetention,
|
|
5415
|
+
);
|
|
5416
|
+
bindRouteReasoningReplayScope({
|
|
5417
|
+
parsed: nextParsed,
|
|
5418
|
+
providerName: route.providerName,
|
|
5419
|
+
provider: route.provider,
|
|
5420
|
+
adapterName: activeAdapter.name,
|
|
5421
|
+
});
|
|
5422
|
+
bindRouteReasoningReplayScope({
|
|
5423
|
+
parsed,
|
|
5424
|
+
providerName: route.providerName,
|
|
5425
|
+
provider: route.provider,
|
|
5426
|
+
adapterName: activeAdapter.name,
|
|
5427
|
+
});
|
|
5428
|
+
logCtx.provider = formatCursorProviderForLog("cursor", nextAccountId);
|
|
5429
|
+
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name, logCtx.accountLogLabel);
|
|
5430
|
+
nextContinuationRecoveryKind = "cursor-oauth-auth";
|
|
5431
|
+
continue;
|
|
5432
|
+
} catch {
|
|
5433
|
+
// fall through to emit continuation error below
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
5437
|
+
if (
|
|
5438
|
+
response.status === 429
|
|
5439
|
+
&& route.providerName === "cursor"
|
|
5440
|
+
&& route.provider.authMode === "oauth"
|
|
5441
|
+
&& cursorPoolAccountId
|
|
5442
|
+
&& isCursorAccountPoolActive(config)
|
|
5443
|
+
&& cursorPoolFailovers < CURSOR_POOL_MAX_FAILOVERS_PER_REQUEST
|
|
5444
|
+
) {
|
|
5445
|
+
const nextAccountId = rotateCursorAccountOn429(
|
|
5446
|
+
config,
|
|
5447
|
+
cursorPoolAccountId,
|
|
5448
|
+
response.headers.get("retry-after"),
|
|
5449
|
+
cursorSessionKey,
|
|
5450
|
+
);
|
|
5451
|
+
if (nextAccountId) {
|
|
5452
|
+
try { void response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
|
|
5453
|
+
try {
|
|
5454
|
+
const accessToken = await getValidAccessTokenForAccount("cursor", nextAccountId);
|
|
5455
|
+
cursorPoolAccountId = nextAccountId;
|
|
5456
|
+
cursorPoolFailovers += 1;
|
|
5457
|
+
parsed._cursorIdentityScope = nextAccountId;
|
|
5458
|
+
route.provider = { ...route.provider, apiKey: accessToken };
|
|
5459
|
+
replayOAuthCredentialSnapshot = undefined;
|
|
5460
|
+
invalidateSameTargetRequest();
|
|
5461
|
+
activeAdapter = resolveAdapter(
|
|
5462
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
5463
|
+
config.cacheRetention,
|
|
5464
|
+
);
|
|
5465
|
+
bindRouteReasoningReplayScope({
|
|
5466
|
+
parsed: nextParsed,
|
|
5467
|
+
providerName: route.providerName,
|
|
5468
|
+
provider: route.provider,
|
|
5469
|
+
adapterName: activeAdapter.name,
|
|
5470
|
+
});
|
|
5471
|
+
bindRouteReasoningReplayScope({
|
|
5472
|
+
parsed,
|
|
5473
|
+
providerName: route.providerName,
|
|
5474
|
+
provider: route.provider,
|
|
5475
|
+
adapterName: activeAdapter.name,
|
|
5476
|
+
});
|
|
5477
|
+
logCtx.provider = formatCursorProviderForLog("cursor", nextAccountId);
|
|
5478
|
+
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name, logCtx.accountLogLabel);
|
|
5479
|
+
nextContinuationRecoveryKind = "cursor-oauth-429";
|
|
5480
|
+
continue;
|
|
5481
|
+
} catch {
|
|
5482
|
+
// fall through to emit continuation error below
|
|
5483
|
+
}
|
|
5484
|
+
}
|
|
5485
|
+
}
|
|
5158
5486
|
if (shouldAttemptImageTierRetry({
|
|
5159
5487
|
status: response.status,
|
|
5160
5488
|
adapterName: activeAdapter.name,
|
package/src/types/config.ts
CHANGED
|
@@ -601,6 +601,14 @@ export interface OcxConfig {
|
|
|
601
601
|
/** Successful new-session binds retained on one round-robin selection. Default 1; range 1..100. */
|
|
602
602
|
stickyLimit?: number;
|
|
603
603
|
};
|
|
604
|
+
/**
|
|
605
|
+
* Opt-in Cursor OAuth account pool. Default OFF.
|
|
606
|
+
* Sticky `_clientThreadId` affinity + bounded 429/auth failover when ≥2 OAuth accounts exist.
|
|
607
|
+
* Does not wire weighted-round-robin `CursorCredentialRouter`.
|
|
608
|
+
*/
|
|
609
|
+
cursorAccountPool?: {
|
|
610
|
+
enabled?: boolean;
|
|
611
|
+
};
|
|
604
612
|
/** Virtual `combo/<id>` models spanning concrete provider/model targets (issue #133). */
|
|
605
613
|
combos?: Record<string, OcxComboConfig>;
|
|
606
614
|
/**
|
package/src/types/provider.ts
CHANGED
|
@@ -169,6 +169,13 @@ export interface OcxProviderConfig {
|
|
|
169
169
|
* version here instead of waiting for a code change. Absent uses the adapter's current default.
|
|
170
170
|
*/
|
|
171
171
|
commandCodeVersion?: string;
|
|
172
|
+
/**
|
|
173
|
+
* Command Code OAuth `/alpha/generate` project-context envelope. When `"on"`, the adapter
|
|
174
|
+
* fills `memory`, `taste`, and `skills` from bounded files under `process.cwd()`. Absent or
|
|
175
|
+
* `"off"` keeps the empty envelope (`memory: ""`, `taste: null`, `skills: null`). Does not
|
|
176
|
+
* enable taste learning (`x-taste-learning` stays `"false"`).
|
|
177
|
+
*/
|
|
178
|
+
projectContext?: "off" | "on";
|
|
172
179
|
/**
|
|
173
180
|
* Responses upstream that stores nothing server-side (DeepSeek documents "the API
|
|
174
181
|
* is stateless"). Stateful request parameters are dropped, `store` is pinned false,
|