@bitkyc08/opencodex 2.82.0 → 2.83.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/{App-Ds_qL7o8.js → App-Ba8uG696.js} +4 -4
- package/gui/dist/assets/{Tray-BqHF5Trd.js → Tray-IImbNPRy.js} +1 -1
- package/gui/dist/assets/{index-D3FBCNiP.js → index-BHRZdJuv.js} +12 -12
- package/gui/dist/assets/{quota-summary-DTBC8n7-.js → quota-summary-DiJO6sYY.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/cursor/catalog.ts +2 -2
- package/src/adapters/cursor/effort-map.ts +1 -1
- package/src/adapters/devin/live-models.ts +2 -2
- package/src/adapters/google-truncation.ts +22 -0
- package/src/adapters/google.ts +7 -3
- package/src/adapters/ollama-native.ts +18 -14
- package/src/adapters/openai-responses/passthrough.ts +2 -0
- package/src/adapters/openai-responses/plaintext-reasoning-notice.ts +85 -0
- package/src/adapters/openai-responses/reasoning.ts +11 -1
- package/src/bridge/errors.ts +7 -1
- package/src/claude/context-windows.ts +3 -3
- package/src/claude/desktop-picker.ts +1 -1
- package/src/cli/account-auth.ts +20 -12
- package/src/cli/resolve.ts +1 -1
- package/src/clients/config-export/model-metadata.ts +16 -0
- package/src/clients/config-export/zcode-store.ts +46 -11
- package/src/clients/config-export/zcode.ts +3 -5
- package/src/clients/config-export.ts +1 -1
- package/src/codex/auth-api/pool-mode-gate.ts +4 -5
- package/src/codex/auth-context.ts +8 -3
- package/src/codex/catalog/native-models.ts +1 -1
- package/src/codex/catalog/provider-models.ts +25 -0
- package/src/codex/desktop-app/darwin.ts +1 -1
- package/src/codex/desktop-app/handoff.ts +1 -1
- package/src/codex/desktop-app/linux.ts +1 -1
- package/src/codex/desktop-app/lock.ts +1 -1
- package/src/codex/desktop-app/types.ts +1 -1
- package/src/codex/desktop-app/windows.ts +1 -1
- package/src/codex/desktop-app-restart.ts +1 -1
- package/src/codex/main-account.ts +6 -7
- package/src/generated/compatibility-version.json +91 -75
- package/src/grok/inject.ts +245 -221
- package/src/grok/reset-coupon-ledger.ts +223 -57
- package/src/grok/reset-coupons.ts +13 -6
- package/src/integrations/cursor-detect.ts +1 -1
- package/src/lib/errors.ts +25 -0
- package/src/lib/redact.ts +2 -2
- package/src/oauth/index.ts +1 -1
- package/src/oauth/meta-muse-device.ts +1 -1
- package/src/oauth/types.ts +1 -1
- package/src/providers/anthropic-fast.ts +1 -1
- package/src/providers/anthropic-reset-grant-ledger.ts +1 -1
- package/src/providers/devin-cli-authmode-migration.ts +1 -1
- package/src/providers/devin-provider-merge-migration.ts +1 -1
- package/src/providers/github-copilot-auto.ts +169 -0
- package/src/providers/model-presets.ts +1 -1
- package/src/providers/muse-subscription-usage.ts +1 -1
- package/src/providers/registry/entries-core.ts +6 -6
- package/src/providers/registry/entries-extended.ts +4 -0
- package/src/providers/registry/model-seeds.ts +3 -3
- package/src/providers/xai-fast-model.ts +1 -1
- package/src/server/inbound-body-admission.ts +24 -7
- package/src/server/index/link-listener.ts +30 -17
- package/src/server/index/serve-options.ts +8 -2
- package/src/server/index.ts +1 -1
- package/src/server/lifecycle.ts +31 -4
- package/src/server/management/grok-coupon-routes.ts +277 -52
- package/src/server/management/link-routes.ts +42 -20
- package/src/server/management/route-registry.ts +2 -2
- package/src/server/relay-eager.ts +2 -0
- package/src/server/relay.ts +18 -7
- package/src/server/request-log-failure-attribution.ts +3 -0
- package/src/server/request-log-terminal-status.ts +186 -0
- package/src/server/request-log.ts +15 -118
- package/src/server/responses/adapter-continuation.ts +9 -1
- package/src/server/responses/adapter-dispatch.ts +65 -6
- package/src/server/responses/compact.ts +3 -2
- package/src/server/responses/core-opaque-recovery.ts +5 -5
- package/src/server/responses/core.ts +6 -6
- package/src/server/responses/empty-completion-guard.ts +18 -2
- package/src/server/responses/jev-model-invoke.ts +1 -1
- package/src/server/responses/passthrough-dispatch.ts +115 -60
- package/src/server/responses/passthrough-execution.ts +3 -3
- package/src/server/responses/request-transport.ts +125 -5
- package/src/server/responses/run-turn-execution.ts +13 -5
- package/src/server/responses/sidecar-execution.ts +5 -0
- package/src/server/responses/spend-storage-error.ts +33 -0
- package/src/types/request.ts +5 -0
- package/src/usage/expected-prices.ts +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spendLedgerStorageErrorResponse } from "./spend-storage-error";
|
|
1
2
|
import { createPhysicalSendReporter } from "../../lib/request-execution-budget";
|
|
2
3
|
import { createInferenceSendBudget } from "../inference/context";
|
|
3
4
|
import { unboundPoolSpendRefusalResponse } from "../workflow-refusal";
|
|
@@ -1099,11 +1100,11 @@ export async function handleResponsesCompact(
|
|
|
1099
1100
|
let outcomeCtx = authCtx;
|
|
1100
1101
|
const localDispatchRefusal = (error: unknown): Response | undefined => {
|
|
1101
1102
|
const cause = unwrapUpstreamRetryEvidenceError(error);
|
|
1102
|
-
const response = cause instanceof SendBudgetExhaustedError
|
|
1103
|
+
const response = spendLedgerStorageErrorResponse(error, logCtx) ?? (cause instanceof SendBudgetExhaustedError
|
|
1103
1104
|
? unboundPoolSpendRefusalResponse(logCtx) ?? formatErrorResponse(429, "request_send_budget_exhausted", cause.message)
|
|
1104
1105
|
: mapCodexAuthContextErrorToResponse(cause, {
|
|
1105
1106
|
now: Date.now(), accountSelector: route.codexAccountNamespace,
|
|
1106
|
-
});
|
|
1107
|
+
}));
|
|
1107
1108
|
if (response) {
|
|
1108
1109
|
releaseUpstreamHostAdmission(compactHostAdmissionLease);
|
|
1109
1110
|
compactHostAdmissionLease = null;
|
|
@@ -465,13 +465,13 @@ export function resetStreamedOpaqueBlobLogContext(logCtx: RequestLogContext): vo
|
|
|
465
465
|
export type OpaqueBlobRecoveryGuard = { attempted: boolean };
|
|
466
466
|
|
|
467
467
|
|
|
468
|
-
export type OpaqueBlobRecoveryResult =
|
|
468
|
+
export type OpaqueBlobRecoveryResult<Failure = Response> =
|
|
469
469
|
| { kind: "skipped" }
|
|
470
470
|
| { kind: "recovered"; response: Response }
|
|
471
|
-
| { kind: "failed"; response:
|
|
471
|
+
| { kind: "failed"; response: Failure };
|
|
472
472
|
|
|
473
473
|
|
|
474
|
-
export async function attemptOpaqueBlobRecovery(
|
|
474
|
+
export async function attemptOpaqueBlobRecovery<Failure = Response>(
|
|
475
475
|
args: {
|
|
476
476
|
response: Response;
|
|
477
477
|
outboundBody?: string;
|
|
@@ -480,8 +480,8 @@ export async function attemptOpaqueBlobRecovery(
|
|
|
480
480
|
guard: OpaqueBlobRecoveryGuard;
|
|
481
481
|
signal: AbortSignal;
|
|
482
482
|
},
|
|
483
|
-
rebuild: (kind: AttemptRecoveryKind) => Promise<Response | { failed:
|
|
484
|
-
): Promise<OpaqueBlobRecoveryResult
|
|
483
|
+
rebuild: (kind: AttemptRecoveryKind) => Promise<Response | { failed: Failure }>,
|
|
484
|
+
): Promise<OpaqueBlobRecoveryResult<Failure>> {
|
|
485
485
|
const errorBody = await opaqueBlobRejectionBodyForRecovery(
|
|
486
486
|
args.response,
|
|
487
487
|
args.outboundBody,
|
|
@@ -28,7 +28,6 @@ import { deliverAdapterResponse } from "./adapter-delivery";
|
|
|
28
28
|
import { releaseUpstreamHostAdmission } from "../../codex/upstream-host-health";
|
|
29
29
|
import { releaseCodexAuthContextProbeLease } from "../../codex/auth-context";
|
|
30
30
|
import { runWithCompactionRecovery } from "./compaction-recovery";
|
|
31
|
-
|
|
32
31
|
/**
|
|
33
32
|
* Route one `/v1/responses` request through the adapter pipeline: recovery loop, passthrough
|
|
34
33
|
* wire, image/web-search bridges, and the terminal-guard continuation.
|
|
@@ -75,7 +74,6 @@ export async function handleResponses(
|
|
|
75
74
|
if (!options.comboInitialSend?.producerOwned) options.comboInitialSend?.permit.release();
|
|
76
75
|
}
|
|
77
76
|
}
|
|
78
|
-
|
|
79
77
|
export async function handleComboResponses(
|
|
80
78
|
req: Request,
|
|
81
79
|
rawBody: unknown,
|
|
@@ -94,7 +92,6 @@ export async function handleComboResponses(
|
|
|
94
92
|
requestDispatchers,
|
|
95
93
|
);
|
|
96
94
|
}
|
|
97
|
-
|
|
98
95
|
/** Compose request phases while retaining the original admission-finally ownership. */
|
|
99
96
|
async function handleResponsesInner(
|
|
100
97
|
req: Request,
|
|
@@ -107,6 +104,7 @@ async function handleResponsesInner(
|
|
|
107
104
|
pendingHostAdmissionLease: null,
|
|
108
105
|
authCtx: { kind: "main", accountId: null },
|
|
109
106
|
};
|
|
107
|
+
let releasePendingSend = () => {};
|
|
110
108
|
try {
|
|
111
109
|
const requestState = await prepareResponsesRequest(requestContext, admissionState, requestDispatchers);
|
|
112
110
|
if (requestState instanceof Response) return requestState;
|
|
@@ -124,7 +122,7 @@ async function handleResponsesInner(
|
|
|
124
122
|
const sendBudgetState = createResponsesSendBudget(requestContext);
|
|
125
123
|
if (sendBudgetState instanceof Response) return sendBudgetState;
|
|
126
124
|
if ("passthrough" in transportState.adapter && transportState.adapter.passthrough && !sidecarState.routedCompaction) {
|
|
127
|
-
|
|
125
|
+
const passthroughResult = await executePassthroughResponse(
|
|
128
126
|
requestContext,
|
|
129
127
|
admissionState,
|
|
130
128
|
requestState,
|
|
@@ -133,6 +131,9 @@ async function handleResponsesInner(
|
|
|
133
131
|
responseEffects,
|
|
134
132
|
sendBudgetState,
|
|
135
133
|
);
|
|
134
|
+
if (passthroughResult instanceof Response) return passthroughResult;
|
|
135
|
+
const unclaimedHop = sendBudgetState.pendingHopPermit;
|
|
136
|
+
releasePendingSend = () => { if (sendBudgetState.pendingHopPermit === unclaimedHop) { unclaimedHop?.release(); sendBudgetState.pendingHopPermit = undefined; } };
|
|
136
137
|
}
|
|
137
138
|
const sidecarPlans = await executeResponsesSidecars(
|
|
138
139
|
requestContext,
|
|
@@ -182,15 +183,14 @@ async function handleResponsesInner(
|
|
|
182
183
|
continuationState,
|
|
183
184
|
);
|
|
184
185
|
} finally {
|
|
186
|
+
releasePendingSend();
|
|
185
187
|
if (admissionState.pendingHostAdmissionLease) {
|
|
186
188
|
releaseUpstreamHostAdmission(admissionState.pendingHostAdmissionLease);
|
|
187
189
|
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
188
190
|
}
|
|
189
191
|
}
|
|
190
192
|
}
|
|
191
|
-
|
|
192
193
|
const requestDispatchers: ResponsesDispatchers = { handleResponses, handleComboResponses };
|
|
193
|
-
|
|
194
194
|
export { adapterNeedsForcedContinuation } from "./core-replay";
|
|
195
195
|
export {
|
|
196
196
|
sidecarOutcomeRecorder, codexLogAccountId, usesCodexForwardPoolAuth, preAuthUpstreamHostCircuitKey,
|
|
@@ -293,11 +293,27 @@ export async function* guardEmptyCompletionEventStream(
|
|
|
293
293
|
return;
|
|
294
294
|
}
|
|
295
295
|
if (event.type === "error") {
|
|
296
|
-
if (
|
|
296
|
+
if (event.replaySafeBeforeOutput === true && retries < maxRetries) {
|
|
297
|
+
// The upstream failed the turn before any text or tool call (#6876: Vertex dropped a
|
|
298
|
+
// malformed function call). Nothing actionable reached the client, so this is the same
|
|
299
|
+
// replay-safe shape as an empty completion: retry the identical turn once.
|
|
300
|
+
usage = mergeUsage(usage, event.usage);
|
|
301
|
+
retries += 1;
|
|
302
|
+
try {
|
|
303
|
+
source = await options.continuation();
|
|
304
|
+
} catch {
|
|
305
|
+
yield emptyCompletionRetryFailedEvent(usage, true);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
terminalSeen = true;
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
if (retries > 0 && event.status !== 499 && event.replaySafeBeforeOutput !== true) {
|
|
297
312
|
// The retry failed upstream. Its body cannot reach the client (the
|
|
298
313
|
// 200 head went out with the first attempt), so state the failure in
|
|
299
314
|
// the stream's own error framing — same move as the router's
|
|
300
|
-
// empty_completion_retry_failed. Client cancels (499) pass through
|
|
315
|
+
// empty_completion_retry_failed. Client cancels (499) pass through, and a
|
|
316
|
+
// replay-safe failure that repeats keeps its own, more specific message.
|
|
301
317
|
yield emptyCompletionRetryFailedEvent(mergeUsage(usage, event.usage), true);
|
|
302
318
|
return;
|
|
303
319
|
}
|
|
@@ -136,7 +136,7 @@ export function createJevModelInvoker(context: JevModelInvokerContext): JevModel
|
|
|
136
136
|
if (new TextEncoder().encode(body).byteLength > JEV_MAX_REQUEST_BYTES) {
|
|
137
137
|
throw new JevModelInvokeError("malformed", "decision request too large");
|
|
138
138
|
}
|
|
139
|
-
const lease = tryAdmitTurn();
|
|
139
|
+
const lease = tryAdmitTurn(undefined, signal);
|
|
140
140
|
if (!lease) throw new JevModelInvokeError("network", "decision turn not admitted");
|
|
141
141
|
const childLog: RequestLogContext = { model, provider: "unknown", inboundProtocol: "responses" };
|
|
142
142
|
let usage: Record<string, number> | undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spendLedgerStorageErrorResponse } from "./spend-storage-error";
|
|
1
2
|
import { createSteeringSettingsNormalizer } from "./native-steering-policy";
|
|
2
3
|
import { nativeResponseControlEligible } from "./native-response-control";
|
|
3
4
|
import { NativeInjectionReplay } from "./native-injection-replay";
|
|
@@ -8,7 +9,7 @@ import type {
|
|
|
8
9
|
PassthroughAdmissionState,
|
|
9
10
|
} from "./core-options";
|
|
10
11
|
import type { PreparedResponsesRequest } from "./request-prepare";
|
|
11
|
-
import type
|
|
12
|
+
import { CopilotAutoAdapterHandoff, type ResponsesTransport } from "./request-transport";
|
|
12
13
|
import type { ResponsesEffects } from "./response-effects";
|
|
13
14
|
import type { ResponsesSendBudget } from "./request-send-budget";
|
|
14
15
|
import { transientSendCapFor } from "./request-send-budget";
|
|
@@ -166,6 +167,9 @@ import { isTransientConsoleGoUploadRejection } from "../../providers/opencode-ze
|
|
|
166
167
|
import { planReasoningEffortDowngrade } from "../../providers/reasoning-metadata";
|
|
167
168
|
import { unboundPoolSpendRefusalResponse } from "../workflow-refusal";
|
|
168
169
|
|
|
170
|
+
/** Transfer an uncommitted Copilot exchange to the existing translated pipeline without new admission. */
|
|
171
|
+
export interface PassthroughAdapterHandoff { kind: "adapter-handoff" }
|
|
172
|
+
|
|
169
173
|
/** Prepares and recovers one native Responses exchange before client commitment. */
|
|
170
174
|
export async function preparePassthroughExchange(
|
|
171
175
|
requestContext: Pick<ResponsesRequestContext, "config" | "logCtx" | "options" | "req">,
|
|
@@ -192,6 +196,12 @@ export async function preparePassthroughExchange(
|
|
|
192
196
|
| "passiveQuotaWriterGeneration"
|
|
193
197
|
| "oauthDispatch"
|
|
194
198
|
| "resolveSelectionAdapter"
|
|
199
|
+
| "resolveCopilotSelection"
|
|
200
|
+
| "copilotRefusalResponse"
|
|
201
|
+
| "oauth401ReplayAttempted"
|
|
202
|
+
| "activeAdapter"
|
|
203
|
+
| "runTurnAdapter"
|
|
204
|
+
| "invalidateSameTargetRequest"
|
|
195
205
|
| "isOAuth401ReplayProvider"
|
|
196
206
|
| "sentOAuthSnapshot"
|
|
197
207
|
| "refreshResolvedOAuthSelection"
|
|
@@ -248,6 +258,8 @@ export async function preparePassthroughExchange(
|
|
|
248
258
|
passiveQuotaWriterGeneration,
|
|
249
259
|
oauthDispatch,
|
|
250
260
|
resolveSelectionAdapter,
|
|
261
|
+
resolveCopilotSelection,
|
|
262
|
+
copilotRefusalResponse,
|
|
251
263
|
isOAuth401ReplayProvider,
|
|
252
264
|
refreshResolvedOAuthSelection,
|
|
253
265
|
applyFailoverSnapshot,
|
|
@@ -732,7 +744,7 @@ export async function preparePassthroughExchange(
|
|
|
732
744
|
// consumer's cancel to a signalled fetch, so we pass the signal and relay through relayWithAbort,
|
|
733
745
|
// whose cancel() aborts the upstream — preventing leaked connections (RC2, passthrough path).
|
|
734
746
|
const upstream = new AbortController();
|
|
735
|
-
linkAbortSignal(upstream, options.abortSignal);
|
|
747
|
+
const cleanupUpstreamAbort = linkAbortSignal(upstream, options.abortSignal ?? req.signal);
|
|
736
748
|
const connectMs = config.connectTimeoutMs ?? 200_000;
|
|
737
749
|
let upstreamResponse: Response;
|
|
738
750
|
/**
|
|
@@ -822,7 +834,7 @@ export async function preparePassthroughExchange(
|
|
|
822
834
|
httpOnly: true,
|
|
823
835
|
providerName: route.providerName,
|
|
824
836
|
modelId: route.modelId,
|
|
825
|
-
dispatchOverride: oauthDispatch(request),
|
|
837
|
+
dispatchOverride: oauthDispatch(request, parsed, "passthrough"),
|
|
826
838
|
beforeDispatch: headers => {
|
|
827
839
|
if (signal.aborted) throw signal.reason;
|
|
828
840
|
if (!transportState.selectionIsCurrent(transportState.requestBindings.get(request))) {
|
|
@@ -896,9 +908,10 @@ export async function preparePassthroughExchange(
|
|
|
896
908
|
describeOutboundBodyRefusal(result),
|
|
897
909
|
);
|
|
898
910
|
};
|
|
899
|
-
const transportFailureResponse = (err: unknown): Response => {
|
|
911
|
+
const transportFailureResponse = (err: unknown): Response | PassthroughAdapterHandoff => {
|
|
912
|
+
cleanupUpstreamAbort();
|
|
900
913
|
upstream.abort();
|
|
901
|
-
if (options.abortSignal?.aborted) {
|
|
914
|
+
if (options.abortSignal?.aborted || req.signal.aborted) {
|
|
902
915
|
releaseUpstreamHostAdmission(nativeHostState.lease);
|
|
903
916
|
nativeHostState.lease = null;
|
|
904
917
|
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
@@ -915,6 +928,17 @@ export async function preparePassthroughExchange(
|
|
|
915
928
|
?? formatErrorResponse(429, "request_send_budget_exhausted", err.message);
|
|
916
929
|
}
|
|
917
930
|
const refusal = unwrapUpstreamRetryEvidenceError(err);
|
|
931
|
+
if (refusal instanceof CopilotAutoAdapterHandoff) {
|
|
932
|
+
bindRefreshedAdapter(refusal.adapter);
|
|
933
|
+
return handoffToChat(refusal.adapter);
|
|
934
|
+
}
|
|
935
|
+
const copilotRefusal = copilotRefusalResponse(refusal);
|
|
936
|
+
if (copilotRefusal) {
|
|
937
|
+
releaseUpstreamHostAdmission(nativeHostState.lease);
|
|
938
|
+
nativeHostState.lease = null;
|
|
939
|
+
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
940
|
+
return copilotRefusal;
|
|
941
|
+
}
|
|
918
942
|
// Pacing may outlive the selected account's admission. No fetch occurred, so do
|
|
919
943
|
// not turn an operator pause into a 502 or charge it to host/account health.
|
|
920
944
|
if (refusal instanceof OAuthAccountPausedError || refusal instanceof OAuthLoginRequiredError || refusal instanceof AnthropicAccountCooldownError) {
|
|
@@ -930,7 +954,7 @@ export async function preparePassthroughExchange(
|
|
|
930
954
|
}
|
|
931
955
|
return formatErrorResponse(403, "permission_error", publicOAuthAuthenticationErrorMessage(refusal));
|
|
932
956
|
}
|
|
933
|
-
const localRefusal = mapCodexAuthContextErrorToResponse(refusal, {
|
|
957
|
+
const localRefusal = spendLedgerStorageErrorResponse(err, logCtx) ?? mapCodexAuthContextErrorToResponse(refusal, {
|
|
934
958
|
now: Date.now(), accountSelector: route.codexAccountNamespace,
|
|
935
959
|
});
|
|
936
960
|
if (localRefusal) {
|
|
@@ -1004,7 +1028,7 @@ export async function preparePassthroughExchange(
|
|
|
1004
1028
|
nativeControl: nativeResponseControlEligible(route.provider, options.nativeControl) && options.inboundTransport === "websocket" && !options.comboAttempt
|
|
1005
1029
|
&& responseEffects.plaintextV2AgentMessageToolNames.size === 0
|
|
1006
1030
|
? options.nativeControl : undefined,
|
|
1007
|
-
dispatchOverride: oauthDispatch(request),
|
|
1031
|
+
dispatchOverride: oauthDispatch(request, parsed, "passthrough"),
|
|
1008
1032
|
providerName: route.providerName,
|
|
1009
1033
|
modelId: route.modelId,
|
|
1010
1034
|
onPhysicalDispatch: receiptMode ? sendBudgetState.noteInitialDispatch : undefined,
|
|
@@ -1034,7 +1058,33 @@ export async function preparePassthroughExchange(
|
|
|
1034
1058
|
const opaqueBlobRecoveryGuard: OpaqueBlobRecoveryGuard = { attempted: false };
|
|
1035
1059
|
// At most one reasoning-effort downgrade per request.
|
|
1036
1060
|
const reasoningEffortDowngradeGuard: { attempted: boolean } = { attempted: false };
|
|
1037
|
-
|
|
1061
|
+
/** Rebind the refreshed wire before transferring this request to the Chat pipeline. */
|
|
1062
|
+
function bindRefreshedAdapter(refreshedAdapter: typeof transportState.adapter) {
|
|
1063
|
+
bindRouteReasoningReplayScope({
|
|
1064
|
+
parsed,
|
|
1065
|
+
providerName: route.providerName,
|
|
1066
|
+
provider: route.provider,
|
|
1067
|
+
adapterName: refreshedAdapter.name,
|
|
1068
|
+
oauthCredentialSnapshot: transportState.replayOAuthCredentialSnapshot,
|
|
1069
|
+
});
|
|
1070
|
+
logCtx.providerAdapter = refreshedAdapter.name;
|
|
1071
|
+
sealRequestAttemptIdentity(
|
|
1072
|
+
logCtx.activeAttempt,
|
|
1073
|
+
logCtx.provider,
|
|
1074
|
+
refreshedAdapter.name,
|
|
1075
|
+
logCtx.accountLogLabel,
|
|
1076
|
+
);
|
|
1077
|
+
recordAttemptCredentialSource(logCtx.activeAttempt, route.providerName, route.provider, refreshedAdapter.name);
|
|
1078
|
+
}
|
|
1079
|
+
/** Transfer adapter ownership without retaining the native abort link or request cache. */
|
|
1080
|
+
function handoffToChat(refreshedAdapter: typeof transportState.adapter) {
|
|
1081
|
+
cleanupUpstreamAbort();
|
|
1082
|
+
upstream.abort();
|
|
1083
|
+
transportState.adapter = transportState.activeAdapter = transportState.runTurnAdapter = refreshedAdapter;
|
|
1084
|
+
transportState.invalidateSameTargetRequest();
|
|
1085
|
+
return { kind: "adapter-handoff" as const };
|
|
1086
|
+
}
|
|
1087
|
+
let oauth401ReplayAttempted = transportState.oauth401ReplayAttempted;
|
|
1038
1088
|
let codex401ReplayKind: "main" | "stored" | null = null;
|
|
1039
1089
|
// Console Go answers a transient 400 "Invalid upload request." for bodies it accepts
|
|
1040
1090
|
// moments later; at most one byte-identical replay is allowed per request.
|
|
@@ -1043,7 +1093,7 @@ export async function preparePassthroughExchange(
|
|
|
1043
1093
|
let rateLimitRetries = 0;
|
|
1044
1094
|
const rebuildAndRefetch = async (
|
|
1045
1095
|
recovery: AttemptRecoveryKind,
|
|
1046
|
-
): Promise<Response | { failed: Response }> => {
|
|
1096
|
+
): Promise<Response | { failed: Response | PassthroughAdapterHandoff }> => {
|
|
1047
1097
|
const retryAdapter = resolveSelectionAdapter(
|
|
1048
1098
|
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
1049
1099
|
config.cacheRetention,
|
|
@@ -1113,7 +1163,7 @@ export async function preparePassthroughExchange(
|
|
|
1113
1163
|
nativeControl: nativeResponseControlEligible(route.provider, options.nativeControl) && options.inboundTransport === "websocket" && !options.comboAttempt
|
|
1114
1164
|
&& responseEffects.plaintextV2AgentMessageToolNames.size === 0
|
|
1115
1165
|
? options.nativeControl : undefined,
|
|
1116
|
-
dispatchOverride: oauthDispatch(request),
|
|
1166
|
+
dispatchOverride: oauthDispatch(request, parsed, "passthrough"),
|
|
1117
1167
|
providerName: route.providerName,
|
|
1118
1168
|
modelId: route.modelId,
|
|
1119
1169
|
onCodexWsQuota: codexWsQuotaObserver(admissionState.authCtx, route.provider, route.modelId),
|
|
@@ -1220,7 +1270,7 @@ export async function preparePassthroughExchange(
|
|
|
1220
1270
|
nativeControl: nativeResponseControlEligible(route.provider, options.nativeControl) && options.inboundTransport === "websocket" && !options.comboAttempt
|
|
1221
1271
|
&& responseEffects.plaintextV2AgentMessageToolNames.size === 0
|
|
1222
1272
|
? options.nativeControl : undefined,
|
|
1223
|
-
dispatchOverride: oauthDispatch(request),
|
|
1273
|
+
dispatchOverride: oauthDispatch(request, parsed, "passthrough"),
|
|
1224
1274
|
providerName: route.providerName,
|
|
1225
1275
|
modelId: route.modelId,
|
|
1226
1276
|
onCodexWsQuota: codexWsQuotaObserver(admissionState.authCtx, route.provider, route.modelId),
|
|
@@ -1282,7 +1332,7 @@ export async function preparePassthroughExchange(
|
|
|
1282
1332
|
// fault and throw away the credential evidence the client needs.
|
|
1283
1333
|
&& !sendBudgetExhausted(transientSendAttempts())
|
|
1284
1334
|
) {
|
|
1285
|
-
oauth401ReplayAttempted = true;
|
|
1335
|
+
oauth401ReplayAttempted = transportState.oauth401ReplayAttempted = true;
|
|
1286
1336
|
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
1287
1337
|
let refreshed: OAuthAccessSnapshot;
|
|
1288
1338
|
try {
|
|
@@ -1305,7 +1355,7 @@ export async function preparePassthroughExchange(
|
|
|
1305
1355
|
if (route.providerName === "kiro") {
|
|
1306
1356
|
parsed._kiroAuthContext = { ...(refreshed.kiro ?? {}) };
|
|
1307
1357
|
}
|
|
1308
|
-
|
|
1358
|
+
let refreshedProvider = resolveProviderTransport(
|
|
1309
1359
|
route.providerName,
|
|
1310
1360
|
{
|
|
1311
1361
|
...route.provider,
|
|
@@ -1318,29 +1368,21 @@ export async function preparePassthroughExchange(
|
|
|
1318
1368
|
: undefined,
|
|
1319
1369
|
);
|
|
1320
1370
|
route.provider = refreshedProvider;
|
|
1371
|
+
try { await resolveCopilotSelection(parsed); }
|
|
1372
|
+
catch (err) { return transportFailureResponse(err); }
|
|
1373
|
+
refreshedProvider = route.provider;
|
|
1321
1374
|
const refreshedAdapter = resolveSelectionAdapter(
|
|
1322
1375
|
resolveWireProtocolOverride(route.providerName, route.modelId, refreshedProvider, inboundWire, route.staticPolicy),
|
|
1323
1376
|
config.cacheRetention,
|
|
1324
1377
|
);
|
|
1378
|
+
bindRefreshedAdapter(refreshedAdapter);
|
|
1325
1379
|
if (!("passthrough" in refreshedAdapter) || !refreshedAdapter.passthrough) {
|
|
1380
|
+
if (route.providerName === "github-copilot" && refreshedAdapter.name === "openai-chat") {
|
|
1381
|
+
return handoffToChat(refreshedAdapter);
|
|
1382
|
+
}
|
|
1326
1383
|
upstream.abort();
|
|
1327
1384
|
return formatErrorResponse(502, "upstream_error", "OAuth refresh changed the provider wire unexpectedly");
|
|
1328
1385
|
}
|
|
1329
|
-
bindRouteReasoningReplayScope({
|
|
1330
|
-
parsed,
|
|
1331
|
-
providerName: route.providerName,
|
|
1332
|
-
provider: refreshedProvider,
|
|
1333
|
-
adapterName: refreshedAdapter.name,
|
|
1334
|
-
oauthCredentialSnapshot: transportState.replayOAuthCredentialSnapshot,
|
|
1335
|
-
});
|
|
1336
|
-
logCtx.providerAdapter = refreshedAdapter.name;
|
|
1337
|
-
sealRequestAttemptIdentity(
|
|
1338
|
-
logCtx.activeAttempt,
|
|
1339
|
-
logCtx.provider,
|
|
1340
|
-
refreshedAdapter.name,
|
|
1341
|
-
logCtx.accountLogLabel,
|
|
1342
|
-
);
|
|
1343
|
-
recordAttemptCredentialSource(logCtx.activeAttempt, route.providerName, route.provider, refreshedAdapter.name);
|
|
1344
1386
|
try {
|
|
1345
1387
|
request = await refreshedAdapter.buildRequest(parsed, {
|
|
1346
1388
|
headers: requestState.selectedForwardHeaders,
|
|
@@ -1373,7 +1415,7 @@ export async function preparePassthroughExchange(
|
|
|
1373
1415
|
nativeControl: nativeResponseControlEligible(route.provider, options.nativeControl) && options.inboundTransport === "websocket" && !options.comboAttempt
|
|
1374
1416
|
&& responseEffects.plaintextV2AgentMessageToolNames.size === 0
|
|
1375
1417
|
? options.nativeControl : undefined,
|
|
1376
|
-
dispatchOverride: oauthDispatch(request),
|
|
1418
|
+
dispatchOverride: oauthDispatch(request, parsed, "passthrough"),
|
|
1377
1419
|
providerName: route.providerName,
|
|
1378
1420
|
modelId: route.modelId,
|
|
1379
1421
|
onCodexWsQuota: codexWsQuotaObserver(admissionState.authCtx, route.provider, route.modelId),
|
|
@@ -1414,38 +1456,50 @@ export async function preparePassthroughExchange(
|
|
|
1414
1456
|
true,
|
|
1415
1457
|
);
|
|
1416
1458
|
if (hop.allowed) {
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
if (nextAccountId) {
|
|
1425
|
-
try { snapshot = await failoverAccountSnapshot(route.providerName, nextAccountId); }
|
|
1426
|
-
catch { /* Keep the original 429 body readable when the next credential is unavailable. */ }
|
|
1427
|
-
}
|
|
1428
|
-
if (snapshot && await applyFailoverSnapshot(snapshot)) {
|
|
1429
|
-
transportState.genericFailovers += 1;
|
|
1430
|
-
route.provider = resolveProviderTransport(
|
|
1431
|
-
route.providerName, route.provider, parsed.options.promptCacheKey, transportState.sentOAuthSnapshot?.apiBaseUrl,
|
|
1459
|
+
let transferred = false;
|
|
1460
|
+
try {
|
|
1461
|
+
const nextAccountId = rotateGenericOAuthAccountOn429(
|
|
1462
|
+
config, route.providerName, transportState.genericFailoverAccountId,
|
|
1463
|
+
upstreamResponse.headers.get("retry-after"),
|
|
1464
|
+
Date.now(),
|
|
1465
|
+
route.modelId,
|
|
1432
1466
|
);
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1467
|
+
let snapshot: OAuthAccessSnapshot | undefined;
|
|
1468
|
+
if (nextAccountId) {
|
|
1469
|
+
try { snapshot = await failoverAccountSnapshot(route.providerName, nextAccountId); }
|
|
1470
|
+
catch { /* Keep the original 429 body readable when the next credential is unavailable. */ }
|
|
1471
|
+
}
|
|
1472
|
+
if (snapshot && await applyFailoverSnapshot(snapshot)) {
|
|
1473
|
+
transportState.genericFailovers += 1;
|
|
1474
|
+
route.provider = resolveProviderTransport(
|
|
1475
|
+
route.providerName, route.provider, parsed.options.promptCacheKey, transportState.sentOAuthSnapshot?.apiBaseUrl,
|
|
1476
|
+
);
|
|
1477
|
+
const rotatedAdapter = resolveSelectionAdapter(
|
|
1478
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
1479
|
+
config.cacheRetention,
|
|
1480
|
+
);
|
|
1481
|
+
bindRefreshedAdapter(rotatedAdapter);
|
|
1482
|
+
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already closed */ }
|
|
1483
|
+
sendBudgetState.pendingHopPermit = hop.permit;
|
|
1484
|
+
if (route.providerName === "github-copilot" && rotatedAdapter.name === "openai-chat") {
|
|
1485
|
+
transferred = true;
|
|
1486
|
+
return handoffToChat(rotatedAdapter);
|
|
1487
|
+
}
|
|
1488
|
+
const result = await rebuildAndRefetch("oauth-account-429");
|
|
1489
|
+
sendBudgetState.pendingHopPermit = undefined;
|
|
1490
|
+
if ("failed" in result) return result.failed;
|
|
1491
|
+
upstreamResponse = result;
|
|
1492
|
+
continue passthroughRecovery;
|
|
1493
|
+
}
|
|
1494
|
+
} catch (err) {
|
|
1437
1495
|
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already closed */ }
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
upstreamResponse = result;
|
|
1445
|
-
continue passthroughRecovery;
|
|
1496
|
+
return transportFailureResponse(err);
|
|
1497
|
+
} finally {
|
|
1498
|
+
if (!transferred) {
|
|
1499
|
+
if (sendBudgetState.pendingHopPermit === hop.permit) sendBudgetState.pendingHopPermit = undefined;
|
|
1500
|
+
hop.permit?.release();
|
|
1501
|
+
}
|
|
1446
1502
|
}
|
|
1447
|
-
// No credential moved, so the reservation costs nothing.
|
|
1448
|
-
hop.permit?.release();
|
|
1449
1503
|
} else {
|
|
1450
1504
|
// The activation quorum ignores cooldowns; prove that the selector has a live alternate
|
|
1451
1505
|
// before describing this as a recovery that only the shared request budget withheld.
|
|
@@ -1508,7 +1562,7 @@ export async function preparePassthroughExchange(
|
|
|
1508
1562
|
nativeControl: nativeResponseControlEligible(route.provider, options.nativeControl) && options.inboundTransport === "websocket" && !options.comboAttempt
|
|
1509
1563
|
&& responseEffects.plaintextV2AgentMessageToolNames.size === 0
|
|
1510
1564
|
? options.nativeControl : undefined,
|
|
1511
|
-
dispatchOverride: oauthDispatch(request),
|
|
1565
|
+
dispatchOverride: oauthDispatch(request, parsed, "passthrough"),
|
|
1512
1566
|
providerName: route.providerName,
|
|
1513
1567
|
modelId: route.modelId,
|
|
1514
1568
|
onCodexWsQuota: codexWsQuotaObserver(admissionState.authCtx, route.provider, route.modelId),
|
|
@@ -1875,6 +1929,7 @@ export async function preparePassthroughExchange(
|
|
|
1875
1929
|
const localUpstream = isLocalUpstream(route.provider.baseUrl);
|
|
1876
1930
|
|
|
1877
1931
|
return {
|
|
1932
|
+
kind: undefined,
|
|
1878
1933
|
codexSafetyBufferingOptions,
|
|
1879
1934
|
imageGenCallAliases,
|
|
1880
1935
|
routedCustomToolNames,
|
|
@@ -1917,4 +1972,4 @@ export async function preparePassthroughExchange(
|
|
|
1917
1972
|
};
|
|
1918
1973
|
}
|
|
1919
1974
|
|
|
1920
|
-
export type PassthroughExchange = Exclude<Awaited<ReturnType<typeof preparePassthroughExchange>>, Response>;
|
|
1975
|
+
export type PassthroughExchange = Exclude<Awaited<ReturnType<typeof preparePassthroughExchange>>, Response | PassthroughAdapterHandoff>;
|
|
@@ -8,7 +8,7 @@ import type { ResponsesTransport } from "./request-transport";
|
|
|
8
8
|
import type { ResponsesSidecarAuth } from "./request-sidecar-auth";
|
|
9
9
|
import type { ResponsesEffects } from "./response-effects";
|
|
10
10
|
import type { ResponsesSendBudget } from "./request-send-budget";
|
|
11
|
-
import { preparePassthroughExchange } from "./passthrough-dispatch";
|
|
11
|
+
import { preparePassthroughExchange, type PassthroughAdapterHandoff } from "./passthrough-dispatch";
|
|
12
12
|
import { deliverPassthroughResponse } from "./passthrough-delivery";
|
|
13
13
|
import { guardDirectPassthroughBodyInactivity } from "../../lib/response-body-inactivity";
|
|
14
14
|
import { resolveStallTimeoutMs } from "../../stall-timeout";
|
|
@@ -24,7 +24,7 @@ export async function executePassthroughResponse(
|
|
|
24
24
|
sidecarState: ResponsesSidecarAuth,
|
|
25
25
|
responseEffects: ResponsesEffects,
|
|
26
26
|
sendBudgetState: ResponsesSendBudget,
|
|
27
|
-
): Promise<Response> {
|
|
27
|
+
): Promise<Response | PassthroughAdapterHandoff> {
|
|
28
28
|
const nativeHostState: PassthroughAdmissionState = { lease: admissionState.pendingHostAdmissionLease };
|
|
29
29
|
admissionState.pendingHostAdmissionLease = null;
|
|
30
30
|
try {
|
|
@@ -37,7 +37,7 @@ export async function executePassthroughResponse(
|
|
|
37
37
|
responseEffects,
|
|
38
38
|
sendBudgetState,
|
|
39
39
|
);
|
|
40
|
-
if (nativeExchange instanceof Response) return nativeExchange;
|
|
40
|
+
if (nativeExchange instanceof Response || nativeExchange.kind === "adapter-handoff") return nativeExchange;
|
|
41
41
|
const response = await deliverPassthroughResponse(
|
|
42
42
|
requestContext,
|
|
43
43
|
admissionState,
|