@bitkyc08/opencodex 2.52.0 → 2.53.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/index-BBOZWGB6.css +1 -0
- package/gui/dist/assets/index-BlO4Yl6q.js +128 -0
- package/gui/dist/index.html +2 -2
- package/native/remote-workspace-helper/Cargo.lock +130 -0
- package/native/remote-workspace-helper/Cargo.toml +24 -0
- package/native/remote-workspace-helper/src/main.rs +49 -0
- package/native/remote-workspace-helper/src/protocol.rs +246 -0
- package/native/remote-workspace-helper/src/sandbox/macos.rs +19 -0
- package/native/remote-workspace-helper/src/sandbox/mod.rs +77 -0
- package/native/remote-workspace-helper/src/sandbox/windows.rs +15 -0
- package/package.json +6 -1
- package/src/adapters/anthropic-image-normalize.ts +30 -2
- package/src/adapters/anthropic.ts +1 -1
- package/src/adapters/base.ts +8 -2
- package/src/adapters/cursor/cursor-errors.ts +12 -0
- package/src/adapters/cursor/thread-continuity.ts +93 -0
- package/src/adapters/cursor.ts +104 -73
- package/src/adapters/devin/cloud-direct/chat.ts +312 -23
- package/src/adapters/devin/cloud-direct/metadata.ts +31 -2
- package/src/adapters/devin/live-models.ts +70 -3
- package/src/adapters/devin.ts +281 -21
- package/src/adapters/google-wire-compiler.ts +14 -6
- package/src/adapters/google.ts +22 -8
- package/src/adapters/kiro/adapter.ts +316 -0
- package/src/adapters/kiro/conversation.ts +136 -0
- package/src/adapters/kiro/payload.ts +432 -0
- package/src/adapters/kiro/reasoning.ts +56 -0
- package/src/adapters/kiro/stream.ts +1153 -0
- package/src/adapters/kiro/usage.ts +223 -0
- package/src/adapters/kiro/wire.ts +76 -0
- package/src/adapters/kiro.ts +8 -2319
- package/src/adapters/mimo-free.ts +1 -1
- package/src/adapters/openai-chat-images.ts +101 -0
- package/src/adapters/openai-chat.ts +201 -181
- package/src/adapters/openai-responses.ts +92 -224
- package/src/adapters/registry.ts +0 -7
- package/src/adapters/run-turn-queue.ts +13 -6
- package/src/bridge.ts +14 -15
- package/src/chat/inbound.ts +29 -4
- package/src/chat/outbound.ts +145 -107
- package/src/claude/desktop-profile.ts +4 -6
- package/src/cli/account-api.ts +14 -0
- package/src/cli/account-extended.ts +1 -1
- package/src/cli/account-history.ts +60 -0
- package/src/cli/account-main.ts +80 -0
- package/src/cli/account.ts +11 -3
- package/src/cli/capabilities.ts +113 -0
- package/src/cli/catalog.ts +109 -0
- package/src/cli/dispatch.ts +9 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/index.ts +2 -2
- package/src/cli/observe.ts +28 -1
- package/src/cli/opencode.ts +42 -8
- package/src/cli/provider-runtime.ts +11 -1
- package/src/cli/provider.ts +22 -2
- package/src/cli/registry.ts +21 -0
- package/src/cli/remote-workspace.ts +154 -0
- package/src/cli/status.ts +39 -7
- package/src/cli/usage-report.ts +14 -2
- package/src/client/hub-client.ts +34 -0
- package/src/client/hub-state.ts +9 -1
- package/src/codex/account-store.ts +78 -0
- package/src/codex/auth-api.ts +81 -54
- package/src/codex/auth-context.ts +45 -16
- package/src/codex/catalog/effort.ts +1 -1
- package/src/codex/catalog/metadata.ts +3 -6
- package/src/codex/catalog/native-models.ts +4 -4
- package/src/codex/catalog/parsing.ts +2 -20
- package/src/codex/catalog/provider-fetch.ts +10 -1
- package/src/codex/catalog/remote.ts +233 -0
- package/src/codex/catalog/sync.ts +403 -35
- package/src/codex/convergence.ts +1 -1
- package/src/codex/history-manifest.ts +36 -0
- package/src/codex/history-provider.ts +32 -5
- package/src/codex/inject.ts +9 -0
- package/src/codex/main-account.ts +113 -0
- package/src/codex/main-device-reauth-api.ts +89 -0
- package/src/codex/main-device-reauth.ts +217 -0
- package/src/codex/native-residue.ts +9 -2
- package/src/codex/quota-auto-refresh.ts +3 -2
- package/src/codex/quota-capacity.ts +98 -0
- package/src/codex/quota-history.ts +160 -0
- package/src/codex/quota-types.ts +8 -0
- package/src/codex/quota.ts +118 -91
- package/src/codex/refresh.ts +2 -1
- package/src/codex/routing.ts +90 -17
- package/src/codex/sync.ts +33 -4
- package/src/combos/request.ts +19 -1
- package/src/config/multi-agent-surface.ts +61 -0
- package/src/config/provider-validation.ts +176 -0
- package/src/config.ts +213 -11
- package/src/generated/compatibility-version.json +436 -168
- package/src/images/loop.ts +119 -36
- package/src/lib/admission.ts +12 -6
- package/src/lib/redact.ts +7 -0
- package/src/lib/translator-budget.ts +4 -3
- package/src/lib/windows-atomic-replace.ts +1 -0
- package/src/lib/windows-elevation.ts +1 -1
- package/src/oauth/chatgpt-device.ts +62 -5
- package/src/oauth/devin/cli-import.ts +130 -0
- package/src/oauth/devin.ts +63 -8
- package/src/oauth/index.ts +29 -14
- package/src/oauth/kiro.ts +18 -6
- package/src/oauth/login-cli.ts +9 -1
- package/src/oauth/meta-muse-device.ts +464 -0
- package/src/oauth/meta-muse.ts +123 -32
- package/src/oauth/pool-kernel.ts +9 -0
- package/src/oauth/pool-settings-capability.ts +2 -2
- package/src/oauth/store.ts +57 -0
- package/src/oauth/types.ts +31 -0
- package/src/providers/derive.ts +13 -3
- package/src/providers/devin-cli-authmode-migration.ts +57 -35
- package/src/providers/devin-provider-merge-migration.ts +240 -0
- package/src/providers/muse-key-quota.ts +117 -0
- package/src/providers/muse-subscription-usage.ts +14 -2
- package/src/providers/openai-sidecar.ts +25 -3
- package/src/providers/opencode-zen-rate-limit.ts +58 -0
- package/src/providers/provider-id-rewrite.ts +20 -5
- package/src/providers/quota-types.ts +12 -0
- package/src/providers/quota.ts +143 -102
- package/src/providers/reasoning-metadata.ts +543 -0
- package/src/providers/registry.ts +80 -49
- package/src/reasoning-effort.ts +26 -2
- package/src/remote/hub-usage.ts +32 -0
- package/src/remote-control/index.ts +192 -41
- package/src/remote-control/workspace-activation.ts +9 -0
- package/src/remote-control/workspace-agent-connection.ts +366 -0
- package/src/remote-control/workspace-claude-runtime.ts +243 -0
- package/src/remote-control/workspace-codex-runtime.ts +531 -0
- package/src/remote-control/workspace-codex-sandbox.ts +115 -0
- package/src/remote-control/workspace-command-runner.ts +748 -0
- package/src/remote-control/workspace-coordinator.ts +231 -0
- package/src/remote-control/workspace-device.ts +585 -0
- package/src/remote-control/workspace-executable.ts +43 -0
- package/src/remote-control/workspace-executor.ts +397 -0
- package/src/remote-control/workspace-hub.ts +519 -0
- package/src/remote-control/workspace-pi-runtime.ts +382 -0
- package/src/remote-control/workspace-process.ts +129 -0
- package/src/remote-control/workspace-rpc.ts +304 -0
- package/src/remote-control/workspace-runtime.ts +60 -0
- package/src/remote-control/workspace-secret-store.ts +39 -0
- package/src/remote-control/workspace-sessions.ts +799 -0
- package/src/remote-control/workspace-tool-bridge.ts +192 -0
- package/src/responses/code-mode-helper-compat.ts +22 -3
- package/src/responses/hosted-tool-policy.ts +0 -1
- package/src/responses/muse-tool-name-alias.ts +379 -0
- package/src/responses/plaintext-v2-agent-messages.ts +902 -0
- package/src/router.ts +7 -0
- package/src/routing/compatibility/behavior.ts +0 -1
- package/src/server/audio-client.ts +64 -0
- package/src/server/audio-dictation.ts +91 -0
- package/src/server/audio-live.ts +185 -0
- package/src/server/audio-transcriptions.ts +183 -0
- package/src/server/audio-upstream.ts +153 -0
- package/src/server/auth-cors.ts +61 -2
- package/src/server/chat-completions.ts +1 -1
- package/src/server/chat-native-sse.ts +92 -48
- package/src/server/chat-native.ts +37 -15
- package/src/server/hub-usage.ts +57 -0
- package/src/server/images.ts +4 -0
- package/src/server/index.ts +722 -57
- package/src/server/lifecycle.ts +5 -6
- package/src/server/live-call-bindings.ts +60 -0
- package/src/server/live.ts +12 -1
- package/src/server/management/agent-settings-routes.ts +25 -4
- package/src/server/management/api-access.ts +37 -0
- package/src/server/management/api-key-usage.ts +7 -2
- package/src/server/management/config-routes.ts +1 -18
- package/src/server/management/context.ts +15 -0
- package/src/server/management/logs-usage-routes.ts +2 -0
- package/src/server/management/oauth-account-routes.ts +39 -12
- package/src/server/management/provider-routes.ts +125 -2
- package/src/server/management/remote-workspace-routes.ts +140 -0
- package/src/server/management/route-registry.ts +15 -0
- package/src/server/management/usage-aggregate-cache.ts +14 -15
- package/src/server/management/usage-summary-cache.ts +2 -0
- package/src/server/management-api.ts +23 -0
- package/src/server/ports.ts +17 -0
- package/src/server/relay-eager.ts +4 -1
- package/src/server/relay.ts +70 -10
- package/src/server/request-decompress.ts +6 -3
- package/src/server/responses/agent-task-recovery.ts +25 -32
- package/src/server/responses/codex-auth-error.ts +11 -0
- package/src/server/responses/codex-ws-exchange.ts +52 -3
- package/src/server/responses/codex-ws-wire.ts +55 -0
- package/src/server/responses/compact.ts +9 -1
- package/src/server/responses/core.ts +337 -73
- package/src/server/responses/encrypted-payload.ts +45 -2
- package/src/server/responses/ws-upstream.ts +4 -1
- package/src/server/responses-self-named-namespace-scrub.ts +1 -3
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/search.ts +3 -0
- package/src/server/sse-payload-rewrite.ts +136 -51
- package/src/server/ws-bridge.ts +35 -1
- package/src/service/cli.ts +372 -0
- package/src/service/diagnostics.ts +340 -0
- package/src/service/guards.ts +303 -0
- package/src/service/health.ts +222 -0
- package/src/service/launchd.ts +853 -0
- package/src/service/orchestration.ts +617 -0
- package/src/service/repair.ts +334 -0
- package/src/service/state.ts +363 -0
- package/src/service/systemd.ts +229 -0
- package/src/service/windows-ops.ts +690 -0
- package/src/service/windows-scheduler.ts +769 -0
- package/src/service/windows-taskxml.ts +613 -0
- package/src/service.ts +22 -5550
- package/src/storage/cleanup/db.ts +258 -0
- package/src/storage/cleanup/execute.ts +358 -0
- package/src/storage/cleanup/paths.ts +189 -0
- package/src/storage/cleanup/pending.ts +140 -0
- package/src/storage/cleanup/preview.ts +292 -0
- package/src/storage/cleanup/reconcile.ts +347 -0
- package/src/storage/cleanup/restore.ts +932 -0
- package/src/storage/cleanup/satellite.ts +474 -0
- package/src/storage/cleanup/staging.ts +129 -0
- package/src/storage/cleanup/types.ts +98 -0
- package/src/storage/cleanup.ts +49 -3127
- package/src/types/accounts.ts +2 -0
- package/src/types/config.ts +13 -12
- package/src/types/provider.ts +37 -0
- package/src/types/request.ts +2 -0
- package/src/types/tools.ts +17 -5
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +127 -0
- package/src/usage/log.ts +58 -1
- package/src/vision/eligibility.ts +13 -2
- package/src/web-search/loop.ts +56 -3
- package/gui/dist/assets/index-CWXut3rG.js +0 -115
- package/gui/dist/assets/index-EdoPnm9_.css +0 -1
- package/src/adapters/devin-cli/acp.ts +0 -204
- package/src/adapters/devin-cli/adapter.ts +0 -345
- package/src/adapters/devin-cli/binary.ts +0 -69
- package/src/adapters/devin-cli/models.ts +0 -57
- package/src/oauth/devin-cli.ts +0 -149
- package/src/server/responses-reasoning-summary-rewrite.ts +0 -178
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { capturePoolQuotaWriter } from "../../codex/account-store";
|
|
1
2
|
import type { Server } from "bun";
|
|
2
3
|
import { recordContextSessionOwner } from "../../codex/context-owner";
|
|
3
4
|
import { contextRelayActivated } from "../../codex/context-compat";
|
|
@@ -104,7 +105,10 @@ import {
|
|
|
104
105
|
} from "../../lib/errors";
|
|
105
106
|
import { injectionDebugLog } from "../../lib/injection-debug-log";
|
|
106
107
|
import { resolveClientRetryAfter } from "../../lib/retry-after";
|
|
107
|
-
import {
|
|
108
|
+
import {
|
|
109
|
+
enrichOpenCodeZenUpstreamMessage,
|
|
110
|
+
isTransientConsoleGoUploadRejection,
|
|
111
|
+
} from "../../providers/opencode-zen-rate-limit";
|
|
108
112
|
import { CODE_MODE_EXEC_TOOL_NAME, modelInList, namespacedToolName } from "../../types";
|
|
109
113
|
import type {
|
|
110
114
|
AdapterEvent,
|
|
@@ -216,6 +220,7 @@ import {
|
|
|
216
220
|
isNonReplayableResponse,
|
|
217
221
|
isTransientUpstreamStatus,
|
|
218
222
|
prepareSameTarget429Wait,
|
|
223
|
+
sleepWithAbort,
|
|
219
224
|
} from "../../lib/upstream-retry";
|
|
220
225
|
import {
|
|
221
226
|
ForwardAdmissionCredentialError,
|
|
@@ -291,6 +296,7 @@ import {
|
|
|
291
296
|
} from "../lifecycle";
|
|
292
297
|
import { redactSecretString, sanitizeLogMetadataString } from "../../lib/redact";
|
|
293
298
|
import { readBoundedResponseBody } from "../../lib/bounded-body";
|
|
299
|
+
import { isReasoningEffortRejection, planReasoningEffortDowngrade } from "../../providers/reasoning-metadata";
|
|
294
300
|
import {
|
|
295
301
|
ENCRYPTED_FUNCTION_OUTPUT_REJECTION,
|
|
296
302
|
isRateLimitOrQuotaFailureMessage,
|
|
@@ -346,6 +352,7 @@ import {
|
|
|
346
352
|
markEagerRelaySseResponse,
|
|
347
353
|
markNativePassthroughSseResponse,
|
|
348
354
|
relaySseWithFailedTail,
|
|
355
|
+
codexSafetyBufferingFilterOptions,
|
|
349
356
|
relayWithAbort,
|
|
350
357
|
sanitizePassthroughHeaders,
|
|
351
358
|
} from "../relay";
|
|
@@ -364,17 +371,12 @@ import {
|
|
|
364
371
|
import { isWin32EagerRewrite, selectEagerPath } from "../../lib/bun-stream-caps";
|
|
365
372
|
import { cancelBodyOnAbort } from "../../lib/abort";
|
|
366
373
|
import { isCodexWsUpstreamResponse, type BunRuntimeGateInput } from "./ws-upstream";
|
|
374
|
+
import { readCodexWsStage } from "./codex-ws-wire";
|
|
367
375
|
import {
|
|
368
376
|
createResponsesItemIdPayloadRewrite,
|
|
369
377
|
hasResponsesItemIdRepair,
|
|
370
378
|
repairResponsesJsonItemIds,
|
|
371
379
|
} from "../responses-item-id-repair";
|
|
372
|
-
import {
|
|
373
|
-
createReasoningSummaryChannelPayloadRewrite,
|
|
374
|
-
rewriteReasoningSummaryInJson,
|
|
375
|
-
rewriteReasoningSummaryInJsonString,
|
|
376
|
-
routeUsesContentChannelReasoning,
|
|
377
|
-
} from "../responses-reasoning-summary-rewrite";
|
|
378
380
|
import {
|
|
379
381
|
createImageGenCallRestoreRewrite,
|
|
380
382
|
imageGenToolCallAliases,
|
|
@@ -431,6 +433,19 @@ import {
|
|
|
431
433
|
restoreRoutedNamespaceCallsInJson,
|
|
432
434
|
type RoutedNamespaceToolAliases,
|
|
433
435
|
} from "../../responses/namespace-tool-compat";
|
|
436
|
+
import {
|
|
437
|
+
createPlaintextV2AgentMessageCallRestoreRewrite,
|
|
438
|
+
PLAINTEXT_V2_AGENT_MESSAGE_RESTORE_OVERFLOW_MESSAGE,
|
|
439
|
+
restorePlaintextV2AgentMessageCalls,
|
|
440
|
+
restorePlaintextV2AgentMessageCallsInJsonResult,
|
|
441
|
+
shouldPreparePlaintextV2AgentMessages,
|
|
442
|
+
} from "../../responses/plaintext-v2-agent-messages";
|
|
443
|
+
import {
|
|
444
|
+
createMuseToolNameRestoreRewrite,
|
|
445
|
+
restoreMuseToolNames,
|
|
446
|
+
restoreMuseToolNamesInJson,
|
|
447
|
+
type MuseToolNameAliases,
|
|
448
|
+
} from "../../responses/muse-tool-name-alias";
|
|
434
449
|
import {
|
|
435
450
|
collectDeclaredBareWireToolNames,
|
|
436
451
|
collectDeclaredNamelessClientCallTypes,
|
|
@@ -830,6 +845,28 @@ export function shouldAttemptOpaqueBlobRecovery(args: {
|
|
|
830
845
|
&& isSelfIdentifiedOpaqueBlobRejection(args.errorBody);
|
|
831
846
|
}
|
|
832
847
|
|
|
848
|
+
/**
|
|
849
|
+
* Peek the upstream error body for the reasoning-effort downgrade. Only 400/403 are considered
|
|
850
|
+
* and the body must be complete and display-safe, the same contract the other rejection peeks
|
|
851
|
+
* use. The match is deliberately narrow: the upstream has to name reasoning effort, so an
|
|
852
|
+
* unrelated 400 never triggers a replay.
|
|
853
|
+
*/
|
|
854
|
+
async function reasoningEffortRejectionText(
|
|
855
|
+
response: Response,
|
|
856
|
+
alreadyAttempted: boolean,
|
|
857
|
+
signal: AbortSignal,
|
|
858
|
+
): Promise<string | undefined> {
|
|
859
|
+
if (alreadyAttempted) return undefined;
|
|
860
|
+
if (response.status !== 400 && response.status !== 403) return undefined;
|
|
861
|
+
try {
|
|
862
|
+
const body = await readBoundedResponseBody(response.clone(), { signal });
|
|
863
|
+
if (!body.displaySafe || body.truncated) return undefined;
|
|
864
|
+
return isReasoningEffortRejection(body.text) ? body.text : undefined;
|
|
865
|
+
} catch {
|
|
866
|
+
return undefined;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
833
870
|
async function opaqueBlobRejectionBodyForRecovery(
|
|
834
871
|
response: Response,
|
|
835
872
|
outboundBody: string | undefined,
|
|
@@ -853,6 +890,31 @@ async function opaqueBlobRejectionBodyForRecovery(
|
|
|
853
890
|
}
|
|
854
891
|
}
|
|
855
892
|
|
|
893
|
+
/**
|
|
894
|
+
* Backoff for the single exact-request replay after a canonical Console upload rejection.
|
|
895
|
+
*/
|
|
896
|
+
const CONSOLE_GO_UPLOAD_RETRY_DELAY_MS = 800;
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Peek the upstream error body for the Console Go transient-400 recovery. Only a complete,
|
|
900
|
+
* display-safe body may drive a retry decision (same contract as
|
|
901
|
+
* opaqueBlobRejectionBodyForRecovery), and reading a clone leaves the original response intact
|
|
902
|
+
* for the caller's own error surface when no retry is taken.
|
|
903
|
+
*/
|
|
904
|
+
async function consoleGoUploadRejectionBody(
|
|
905
|
+
response: Response,
|
|
906
|
+
alreadyAttempted: boolean,
|
|
907
|
+
signal: AbortSignal,
|
|
908
|
+
): Promise<string | undefined> {
|
|
909
|
+
if (isNonReplayableResponse(response) || response.status !== 400 || alreadyAttempted) return undefined;
|
|
910
|
+
try {
|
|
911
|
+
const body = await readBoundedResponseBody(response.clone(), { signal });
|
|
912
|
+
return body.displaySafe && !body.truncated ? body.text : undefined;
|
|
913
|
+
} catch {
|
|
914
|
+
return undefined;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
856
918
|
/**
|
|
857
919
|
* Materialize an upstream error body only when the bounded reader observed a complete,
|
|
858
920
|
* display-safe payload. Partial timeout and over-limit prefixes are attacker-controlled,
|
|
@@ -1044,7 +1106,7 @@ function codexWsQuotaObserver(authCtx: CodexAuthContext, provider: OcxProviderCo
|
|
|
1044
1106
|
const mainWriter = authCtx.kind === "main-pool" ? authCtx.mainQuotaWriter : undefined;
|
|
1045
1107
|
return headers => {
|
|
1046
1108
|
if (credentialGeneration !== undefined && !isCodexAccountGenerationLive(accountId, credentialGeneration)) return;
|
|
1047
|
-
applyCapturedCodexQuota(accountId, headers, writerGeneration, mainWriter, { modelId });
|
|
1109
|
+
applyCapturedCodexQuota(accountId, headers, writerGeneration, mainWriter, { modelId, poolWriter: authCtx.kind === "pool" ? authCtx.poolQuotaWriter : undefined });
|
|
1048
1110
|
};
|
|
1049
1111
|
}
|
|
1050
1112
|
|
|
@@ -1449,7 +1511,7 @@ async function retryCodexPoolOnAlternateAccount(
|
|
|
1449
1511
|
firstResponse.headers,
|
|
1450
1512
|
firstAuthCtx.writerGeneration,
|
|
1451
1513
|
firstAuthCtx.kind === "main-pool" ? firstAuthCtx.mainQuotaWriter : undefined,
|
|
1452
|
-
{ modelId: route.modelId },
|
|
1514
|
+
{ modelId: route.modelId, poolWriter: firstAuthCtx.kind === "pool" ? firstAuthCtx.poolQuotaWriter : undefined },
|
|
1453
1515
|
);
|
|
1454
1516
|
}
|
|
1455
1517
|
const deferFirstOutcome = shouldDeferCodexResetDerivedCooldown(
|
|
@@ -2423,6 +2485,7 @@ async function refreshPoolForwardAuth(args: {
|
|
|
2423
2485
|
accessToken: refreshed.accessToken,
|
|
2424
2486
|
chatgptAccountId: refreshed.chatgptAccountId,
|
|
2425
2487
|
generation: refreshed.generation,
|
|
2488
|
+
poolQuotaWriter: capturePoolQuotaWriter(authCtx.accountId, refreshed),
|
|
2426
2489
|
};
|
|
2427
2490
|
const provider = applyCodexAuthContextToProvider(
|
|
2428
2491
|
stripCodexRuntimeProviderFields(route.provider),
|
|
@@ -2572,6 +2635,19 @@ async function applyFinalRouteRequestNormalization(args: {
|
|
|
2572
2635
|
route.provider = resolveOpenCodeGoTransport(route.provider,
|
|
2573
2636
|
args.claudeGoAffinity ? args.claudeGoAffinity.sessionLane : getOrAllocateRequestSessionLane(req));
|
|
2574
2637
|
route.provider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire);
|
|
2638
|
+
parsed._plaintextV2AgentMessages = shouldPreparePlaintextV2AgentMessages({
|
|
2639
|
+
enabled: config.plaintextV2AgentMessages === true,
|
|
2640
|
+
inboundWire,
|
|
2641
|
+
canonicalChatGpt: isCanonicalOpenAiForwardProvider(route.provider),
|
|
2642
|
+
requestBody: parsed._rawBody,
|
|
2643
|
+
});
|
|
2644
|
+
// Recompute from the original wire preference on every route, including fallback.
|
|
2645
|
+
// A provider default never converts raw reasoning into a summary.
|
|
2646
|
+
if (inboundWire === "responses" && parsed._rawBody) {
|
|
2647
|
+
const summary = (parsed._rawBody as { reasoning?: { summary?: unknown } }).reasoning?.summary;
|
|
2648
|
+
parsed.options.hideThinkingSummary = summary === "none"
|
|
2649
|
+
|| (!summary && route.provider.showThinkingSummary !== true);
|
|
2650
|
+
}
|
|
2575
2651
|
if (preserveAnthropicResponseModel) parsed._responseModelId = responseModelId;
|
|
2576
2652
|
logCtx.model = route.modelId;
|
|
2577
2653
|
logCtx.provider = route.providerName;
|
|
@@ -2927,6 +3003,7 @@ export async function handleComboResponses(
|
|
|
2927
3003
|
pick.target,
|
|
2928
3004
|
comboDefaultEffort(config, comboId),
|
|
2929
3005
|
supportedLadderFor({ provider: targetRoute.provider, modelId: targetRoute.modelId }),
|
|
3006
|
+
combo.reasoningEffortMode,
|
|
2930
3007
|
);
|
|
2931
3008
|
const childHeaders = buildComboChildHeaders(req.headers);
|
|
2932
3009
|
const childRequest = new Request(req.url, {
|
|
@@ -4804,14 +4881,23 @@ async function handleResponsesInner(
|
|
|
4804
4881
|
}
|
|
4805
4882
|
|
|
4806
4883
|
let routedNamespaceToolAliases: RoutedNamespaceToolAliases = new Map();
|
|
4807
|
-
|
|
4884
|
+
let plaintextV2AgentMessageToolNames: ReadonlySet<string> = new Set();
|
|
4885
|
+
let plaintextV2AgentMessageAliasedToolNames: ReadonlySet<string> = new Set();
|
|
4886
|
+
let routedMuseToolNameAliases: MuseToolNameAliases = new Map();
|
|
4887
|
+
const refreshRequestToolAliases = (builtRequest: AdapterRequest): void => {
|
|
4808
4888
|
routedNamespaceToolAliases = builtRequest.convertedRoutedNamespaceToolAliases ?? new Map();
|
|
4889
|
+
plaintextV2AgentMessageToolNames = builtRequest.plaintextV2AgentMessageToolNames ?? new Set();
|
|
4890
|
+
plaintextV2AgentMessageAliasedToolNames = builtRequest.plaintextV2AgentMessageAliasedToolNames ?? new Set();
|
|
4891
|
+
routedMuseToolNameAliases = builtRequest.convertedMuseToolNameAliases ?? new Map();
|
|
4809
4892
|
};
|
|
4810
4893
|
|
|
4811
4894
|
if ("passthrough" in adapter && adapter.passthrough && !routedCompaction) {
|
|
4812
4895
|
let hostAdmissionLease = pendingHostAdmissionLease;
|
|
4813
4896
|
pendingHostAdmissionLease = null;
|
|
4814
4897
|
try {
|
|
4898
|
+
const codexSafetyBufferingOptions = isCanonicalOpenAiForwardProvider(route.provider)
|
|
4899
|
+
? codexSafetyBufferingFilterOptions(config)
|
|
4900
|
+
: undefined;
|
|
4815
4901
|
const imageGenCallAliases = route.provider.authMode === "forward"
|
|
4816
4902
|
? new Map<string, { namespace: string; name: string }>()
|
|
4817
4903
|
: imageGenToolCallAliases(toolBridgeMaps.toolNsMap, parsed._rawBody, translatorBudget);
|
|
@@ -4902,7 +4988,7 @@ async function handleResponsesInner(
|
|
|
4902
4988
|
// would incorrectly disable restoration for the exact ambiguous-name case the alias fixes.
|
|
4903
4989
|
routedToolSearchNames.add(name);
|
|
4904
4990
|
}
|
|
4905
|
-
|
|
4991
|
+
refreshRequestToolAliases(request);
|
|
4906
4992
|
// #1700: the bridged paths refuse a call to a tool the request never declared
|
|
4907
4993
|
// (`declaredToolNames`, src/bridge.ts). The passthrough had no equivalent, so a routed
|
|
4908
4994
|
// provider's top-level `apply_patch` — which under Codex code mode exists only as a nested
|
|
@@ -5008,7 +5094,7 @@ async function handleResponsesInner(
|
|
|
5008
5094
|
// `buildToolBridgeMaps` also aliases a namespaced tool under its bare name when the
|
|
5009
5095
|
// caller's `tool_choice` selected it unambiguously, which the bridge needs to route the
|
|
5010
5096
|
// call back. For `exec` alone that alias would also switch on nested-helper
|
|
5011
|
-
// normalization and re-authorize `exec_command`/`shell_command`/`apply_patch`, so it is
|
|
5097
|
+
// normalization and re-authorize `exec_command`/`shell_command`/`apply_patch`/`view_image`, so it is
|
|
5012
5098
|
// admitted here only when the caller's own catalog declared a bare `exec`. Selecting an
|
|
5013
5099
|
// MCP `exec` is not a declaration of the code-mode shell tool.
|
|
5014
5100
|
if (
|
|
@@ -5078,7 +5164,9 @@ async function handleResponsesInner(
|
|
|
5078
5164
|
// provider) every name looks undeclared, and flipping this would stop recording continuation
|
|
5079
5165
|
// state for exactly the passthrough traffic the guard deliberately stands down for.
|
|
5080
5166
|
if (undeclaredToolGuardActive && !inspectionSawUndeclaredTool && undeclaredToolCallName(
|
|
5081
|
-
restoreAuthorizedBareNamespaceToolCalls(
|
|
5167
|
+
restoreAuthorizedBareNamespaceToolCalls(
|
|
5168
|
+
restoreMuseToolNames(payload, routedMuseToolNameAliases).value,
|
|
5169
|
+
),
|
|
5082
5170
|
declaredWireToolNames,
|
|
5083
5171
|
declaredNamelessClientCallTypes,
|
|
5084
5172
|
providerExecutedCallTypes,
|
|
@@ -5088,7 +5176,7 @@ async function handleResponsesInner(
|
|
|
5088
5176
|
}
|
|
5089
5177
|
// The snapshot callback opts the inspector into output reconstruction. Compaction
|
|
5090
5178
|
// has no continuation cache, so use the parsed terminal here without adding retention.
|
|
5091
|
-
if (!rememberPassthroughResponse && payload && typeof payload === "object"
|
|
5179
|
+
if (plaintextV2AgentMessageToolNames.size === 0 && !rememberPassthroughResponse && payload && typeof payload === "object"
|
|
5092
5180
|
&& "type" in payload && payload.type === "response.completed"
|
|
5093
5181
|
&& "response" in payload && payload.response && typeof payload.response === "object"
|
|
5094
5182
|
&& !Array.isArray(payload.response)) {
|
|
@@ -5100,19 +5188,26 @@ async function handleResponsesInner(
|
|
|
5100
5188
|
) => {
|
|
5101
5189
|
if (inspectionSawUndeclaredTool) return;
|
|
5102
5190
|
const restored = restoreRoutedCustomCalls(
|
|
5103
|
-
restoreAuthorizedBareNamespaceToolCalls(
|
|
5191
|
+
restoreAuthorizedBareNamespaceToolCalls(
|
|
5192
|
+
restoreRoutedNamespaceCalls(
|
|
5193
|
+
restoreMuseToolNames(response, routedMuseToolNameAliases).value,
|
|
5194
|
+
routedNamespaceToolAliases,
|
|
5195
|
+
).value,
|
|
5196
|
+
),
|
|
5104
5197
|
routedCustomToolNames,
|
|
5105
5198
|
routedCustomToolRepairNames,
|
|
5106
5199
|
declaredWireToolNames,
|
|
5107
5200
|
).value;
|
|
5108
|
-
const
|
|
5201
|
+
const normalizedResponse = (functionRepairSchemas.size > 0
|
|
5109
5202
|
? JSON.parse(normalizeFunctionCompletionJson(JSON.stringify(restored)))
|
|
5110
5203
|
: restored) as { id?: unknown; output?: unknown; status?: unknown };
|
|
5204
|
+
const plaintextRestore = restorePlaintextV2AgentMessageCalls(
|
|
5205
|
+
normalizedResponse, plaintextV2AgentMessageToolNames, plaintextV2AgentMessageAliasedToolNames,
|
|
5206
|
+
);
|
|
5207
|
+
if (plaintextRestore.overflowed) return;
|
|
5208
|
+
const restoredResponse = plaintextRestore.value as typeof normalizedResponse;
|
|
5111
5209
|
// Replay overlap compares the items the client echoes, including visible reasoning shape.
|
|
5112
|
-
const replayResponse =
|
|
5113
|
-
&& routeUsesContentChannelReasoning(route.provider, route.modelId)
|
|
5114
|
-
? rewriteReasoningSummaryInJson(restoredResponse) as typeof restoredResponse
|
|
5115
|
-
: restoredResponse;
|
|
5210
|
+
const replayResponse = restoredResponse;
|
|
5116
5211
|
if (
|
|
5117
5212
|
undeclaredToolGuardActive
|
|
5118
5213
|
&& undeclaredToolCallNameInResponse(
|
|
@@ -5189,6 +5284,22 @@ async function handleResponsesInner(
|
|
|
5189
5284
|
}
|
|
5190
5285
|
hostAdmissionLease = null;
|
|
5191
5286
|
};
|
|
5287
|
+
/**
|
|
5288
|
+
* #4191: a Codex WS exchange pins its content-free stage record on the
|
|
5289
|
+
* Response it resolves (markCodexWsStage). Adopting the record here, at
|
|
5290
|
+
* the single funnel every physical upstream response passes through,
|
|
5291
|
+
* binds it to the attempt that actually served it — including the 502/504
|
|
5292
|
+
* pre-response JSON settles that never reach the SSE relay.
|
|
5293
|
+
*/
|
|
5294
|
+
const adoptCodexWsStage = (response: Response): void => {
|
|
5295
|
+
const stage = readCodexWsStage(response);
|
|
5296
|
+
if (stage && logCtx.activeAttempt) logCtx.activeAttempt.codexWsStage = stage;
|
|
5297
|
+
};
|
|
5298
|
+
const adoptObservedResponse = <T extends Response>(response: T): T => {
|
|
5299
|
+
settleObservedHostResponse();
|
|
5300
|
+
adoptCodexWsStage(response);
|
|
5301
|
+
return response;
|
|
5302
|
+
};
|
|
5192
5303
|
let passthroughEstimate = typeof request.usageLog?.inputTokens === "number"
|
|
5193
5304
|
? request.usageLog.inputTokens
|
|
5194
5305
|
: undefined;
|
|
@@ -5320,10 +5431,7 @@ async function handleResponsesInner(
|
|
|
5320
5431
|
route.provider.authMode === "forward")
|
|
5321
5432
|
// Every real attempt response — including an intermediate 5xx the
|
|
5322
5433
|
// retry wrapper replaces — proves the host was reached (#914 review).
|
|
5323
|
-
.then(
|
|
5324
|
-
settleObservedHostResponse();
|
|
5325
|
-
return res;
|
|
5326
|
-
});
|
|
5434
|
+
.then(adoptObservedResponse);
|
|
5327
5435
|
},
|
|
5328
5436
|
{ abortSignal: upstream.signal, label: safeHostLabel(request.url) },
|
|
5329
5437
|
);
|
|
@@ -5334,8 +5442,13 @@ async function handleResponsesInner(
|
|
|
5334
5442
|
}
|
|
5335
5443
|
|
|
5336
5444
|
const opaqueBlobRecoveryGuard: OpaqueBlobRecoveryGuard = { attempted: false };
|
|
5445
|
+
// At most one reasoning-effort downgrade per request.
|
|
5446
|
+
const reasoningEffortDowngradeGuard: { attempted: boolean } = { attempted: false };
|
|
5337
5447
|
let oauth401ReplayAttempted = false;
|
|
5338
5448
|
let codex401ReplayKind: "main" | "stored" | null = null;
|
|
5449
|
+
// Console Go answers a transient 400 "Invalid upload request." for bodies it accepts
|
|
5450
|
+
// moments later; at most one byte-identical replay is allowed per request.
|
|
5451
|
+
const consoleGoUploadRetryGuard: { attempted: boolean } = { attempted: false };
|
|
5339
5452
|
const rateLimitPolicy = rateLimitRetryPolicyFor(route.provider);
|
|
5340
5453
|
let rateLimitRetries = 0;
|
|
5341
5454
|
const rebuildAndRefetch = async (
|
|
@@ -5350,11 +5463,13 @@ async function handleResponsesInner(
|
|
|
5350
5463
|
return { failed: formatErrorResponse(502, "upstream_error", "Recovery changed the provider wire unexpectedly") };
|
|
5351
5464
|
}
|
|
5352
5465
|
try {
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5466
|
+
if (recovery !== "console-go-upload-retry") {
|
|
5467
|
+
request = await retryAdapter.buildRequest(parsed, {
|
|
5468
|
+
headers: selectedForwardHeaders,
|
|
5469
|
+
translatorBudget,
|
|
5470
|
+
});
|
|
5471
|
+
}
|
|
5472
|
+
refreshRequestToolAliases(request);
|
|
5358
5473
|
recordAdapterReasoning(logCtx, request);
|
|
5359
5474
|
recordAdapterTier(logCtx, request);
|
|
5360
5475
|
} catch (err) {
|
|
@@ -5396,10 +5511,7 @@ async function handleResponsesInner(
|
|
|
5396
5511
|
? createCodexReserveDispatchGuard(authCtx, options.codexAuthPolicy ?? config, route.modelId, options.admission, options.visionDescribeTerminal === true) : undefined,
|
|
5397
5512
|
}),
|
|
5398
5513
|
route.provider.authMode === "forward")
|
|
5399
|
-
.then(
|
|
5400
|
-
settleObservedHostResponse();
|
|
5401
|
-
return response;
|
|
5402
|
-
});
|
|
5514
|
+
.then(adoptObservedResponse);
|
|
5403
5515
|
},
|
|
5404
5516
|
{ abortSignal: upstream.signal, label: safeHostLabel(request.url) },
|
|
5405
5517
|
);
|
|
@@ -5471,7 +5583,7 @@ async function handleResponsesInner(
|
|
|
5471
5583
|
headers: selectedForwardHeaders,
|
|
5472
5584
|
translatorBudget,
|
|
5473
5585
|
});
|
|
5474
|
-
|
|
5586
|
+
refreshRequestToolAliases(request);
|
|
5475
5587
|
recordAdapterReasoning(logCtx, request);
|
|
5476
5588
|
recordAdapterTier(logCtx, request);
|
|
5477
5589
|
refreshUndeclaredToolGuard(request);
|
|
@@ -5504,10 +5616,7 @@ async function handleResponsesInner(
|
|
|
5504
5616
|
codex401ReplayKind === "stored" ? options.onStoredPool401ReplayDispatched : undefined,
|
|
5505
5617
|
),
|
|
5506
5618
|
route.provider.authMode === "forward",
|
|
5507
|
-
).then(
|
|
5508
|
-
settleObservedHostResponse();
|
|
5509
|
-
return response;
|
|
5510
|
-
});
|
|
5619
|
+
).then(adoptObservedResponse);
|
|
5511
5620
|
} catch (err) {
|
|
5512
5621
|
return transportFailureResponse(err);
|
|
5513
5622
|
} finally {
|
|
@@ -5592,7 +5701,7 @@ async function handleResponsesInner(
|
|
|
5592
5701
|
headers: selectedForwardHeaders,
|
|
5593
5702
|
translatorBudget,
|
|
5594
5703
|
});
|
|
5595
|
-
|
|
5704
|
+
refreshRequestToolAliases(request);
|
|
5596
5705
|
recordAdapterReasoning(logCtx, request);
|
|
5597
5706
|
recordAdapterTier(logCtx, request);
|
|
5598
5707
|
} catch (err) {
|
|
@@ -5622,10 +5731,7 @@ async function handleResponsesInner(
|
|
|
5622
5731
|
? createCodexReserveDispatchGuard(authCtx, options.codexAuthPolicy ?? config, route.modelId, options.admission, options.visionDescribeTerminal === true) : undefined,
|
|
5623
5732
|
}),
|
|
5624
5733
|
route.provider.authMode === "forward")
|
|
5625
|
-
.then(
|
|
5626
|
-
settleObservedHostResponse();
|
|
5627
|
-
return res;
|
|
5628
|
-
});
|
|
5734
|
+
.then(adoptObservedResponse);
|
|
5629
5735
|
},
|
|
5630
5736
|
{ abortSignal: upstream.signal, label: safeHostLabel(request.url) },
|
|
5631
5737
|
);
|
|
@@ -5722,10 +5828,7 @@ async function handleResponsesInner(
|
|
|
5722
5828
|
? createCodexReserveDispatchGuard(authCtx, options.codexAuthPolicy ?? config, route.modelId, options.admission, options.visionDescribeTerminal === true) : undefined,
|
|
5723
5829
|
}),
|
|
5724
5830
|
route.provider.authMode === "forward")
|
|
5725
|
-
.then(
|
|
5726
|
-
settleObservedHostResponse();
|
|
5727
|
-
return res;
|
|
5728
|
-
});
|
|
5831
|
+
.then(adoptObservedResponse);
|
|
5729
5832
|
},
|
|
5730
5833
|
{ abortSignal: upstream.signal, label: safeHostLabel(request.url) },
|
|
5731
5834
|
);
|
|
@@ -5804,6 +5907,7 @@ async function handleResponsesInner(
|
|
|
5804
5907
|
passthroughEstimate,
|
|
5805
5908
|
stream: parsed.stream,
|
|
5806
5909
|
onResponse: (response, retryAuthCtx, retryRequest) => {
|
|
5910
|
+
adoptCodexWsStage(response);
|
|
5807
5911
|
captureAffinityResponse(
|
|
5808
5912
|
response,
|
|
5809
5913
|
retryAuthCtx,
|
|
@@ -5819,7 +5923,7 @@ async function handleResponsesInner(
|
|
|
5819
5923
|
if (retry.kind === "retried") {
|
|
5820
5924
|
authCtx = retry.authCtx;
|
|
5821
5925
|
request = retry.request;
|
|
5822
|
-
|
|
5926
|
+
refreshRequestToolAliases(request);
|
|
5823
5927
|
refreshUndeclaredToolGuard(request);
|
|
5824
5928
|
upstreamResponse = retry.upstreamResponse;
|
|
5825
5929
|
selectedForwardHeaders = retry.selectedForwardHeaders;
|
|
@@ -5887,9 +5991,68 @@ async function handleResponsesInner(
|
|
|
5887
5991
|
logCtx.terminalIncompleteReason = preflightLog.terminalIncompleteReason;
|
|
5888
5992
|
}
|
|
5889
5993
|
}
|
|
5994
|
+
// Console Go (opencode-zen / opencode-go) intermittently rejects a body it accepts seconds
|
|
5995
|
+
// later with 400 invalid_request_error / "Invalid upload request." Replay the byte-identical
|
|
5996
|
+
// request once after the exact gateway rejection. Single-shot guard.
|
|
5997
|
+
// This recovery reuses the captured request; other recovery kinds still rebuild.
|
|
5998
|
+
if (!consoleGoUploadRetryGuard.attempted) {
|
|
5999
|
+
const uploadRejectionBody = await consoleGoUploadRejectionBody(
|
|
6000
|
+
upstreamResponse,
|
|
6001
|
+
consoleGoUploadRetryGuard.attempted,
|
|
6002
|
+
upstream.signal,
|
|
6003
|
+
);
|
|
6004
|
+
if (uploadRejectionBody !== undefined
|
|
6005
|
+
&& isTransientConsoleGoUploadRejection({
|
|
6006
|
+
status: upstreamResponse.status,
|
|
6007
|
+
errorBody: uploadRejectionBody,
|
|
6008
|
+
outboundUrl: request.url,
|
|
6009
|
+
})) {
|
|
6010
|
+
consoleGoUploadRetryGuard.attempted = true;
|
|
6011
|
+
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
6012
|
+
if (!upstream.signal.aborted) {
|
|
6013
|
+
try {
|
|
6014
|
+
await sleepWithAbort(CONSOLE_GO_UPLOAD_RETRY_DELAY_MS, upstream.signal);
|
|
6015
|
+
} catch { return clientCancelledResponse(); }
|
|
6016
|
+
}
|
|
6017
|
+
if (upstream.signal.aborted) return clientCancelledResponse();
|
|
6018
|
+
const result = await rebuildAndRefetch("console-go-upload-retry");
|
|
6019
|
+
if ("failed" in result) return result.failed;
|
|
6020
|
+
upstreamResponse = result;
|
|
6021
|
+
continue passthroughRecovery;
|
|
6022
|
+
}
|
|
6023
|
+
}
|
|
6024
|
+
// Reasoning-effort downgrade: a rung the catalog still advertises can be refused upstream --
|
|
6025
|
+
// the metadata records the model's ladder, not this account's entitlement (a Muse Code
|
|
6026
|
+
// subscription gates max on muse-spark-1.3-contributor, for example). Learn the refusal so
|
|
6027
|
+
// later turns clamp before dispatch, then replay once at the next lower published rung
|
|
6028
|
+
// instead of failing the turn; requestedEffort/effectiveEffort keep both values in usage.
|
|
6029
|
+
if (!reasoningEffortDowngradeGuard.attempted) {
|
|
6030
|
+
const rejectionText = await reasoningEffortRejectionText(
|
|
6031
|
+
upstreamResponse,
|
|
6032
|
+
reasoningEffortDowngradeGuard.attempted,
|
|
6033
|
+
upstream.signal,
|
|
6034
|
+
);
|
|
6035
|
+
const downgrade = rejectionText === undefined
|
|
6036
|
+
? undefined
|
|
6037
|
+
: planReasoningEffortDowngrade({
|
|
6038
|
+
provider: route.provider,
|
|
6039
|
+
modelId: parsed.modelId,
|
|
6040
|
+
requested: parsed.options.reasoning,
|
|
6041
|
+
rejectionText,
|
|
6042
|
+
});
|
|
6043
|
+
if (downgrade) {
|
|
6044
|
+
reasoningEffortDowngradeGuard.attempted = true;
|
|
6045
|
+
parsed.options.reasoning = downgrade.effort;
|
|
6046
|
+
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
6047
|
+
const result = await rebuildAndRefetch("reasoning-effort-downgrade");
|
|
6048
|
+
if ("failed" in result) return result.failed;
|
|
6049
|
+
upstreamResponse = result;
|
|
6050
|
+
continue passthroughRecovery;
|
|
6051
|
+
}
|
|
6052
|
+
}
|
|
5890
6053
|
break;
|
|
5891
6054
|
}
|
|
5892
|
-
const headers = sanitizePassthroughHeaders(upstreamResponse.headers);
|
|
6055
|
+
const headers = sanitizePassthroughHeaders(upstreamResponse.headers, codexSafetyBufferingOptions);
|
|
5893
6056
|
const resolvedModel = headers.get("openai-model")?.trim();
|
|
5894
6057
|
if (resolvedModel && !logCtx.preserveResolvedModelFromRoute) logCtx.resolvedModel = resolvedModel;
|
|
5895
6058
|
if (isUsageDebugEnabled()) {
|
|
@@ -5900,7 +6063,7 @@ async function handleResponsesInner(
|
|
|
5900
6063
|
// treating a successful body as SSE when the caller requested streaming.
|
|
5901
6064
|
const passthroughCt = headers.get("content-type")?.toLowerCase();
|
|
5902
6065
|
const isEventStream = passthroughCt?.includes("text/event-stream")
|
|
5903
|
-
|| (upstreamResponse.ok && !!upstreamResponse.body && !passthroughCt && parsed.stream);
|
|
6066
|
+
|| (plaintextV2AgentMessageToolNames.size === 0 && upstreamResponse.ok && !!upstreamResponse.body && !passthroughCt && parsed.stream);
|
|
5904
6067
|
const recordTerminalOutcome = codexForwardTerminalOutcomeRecorder(
|
|
5905
6068
|
config,
|
|
5906
6069
|
authCtx,
|
|
@@ -5926,7 +6089,7 @@ async function handleResponsesInner(
|
|
|
5926
6089
|
if (!isCodexWsQuotaObservedResponse(upstreamResponse)) {
|
|
5927
6090
|
applyAccountQuotaFromUpstreamHeaders(authCtx.accountId, upstreamResponse.headers,
|
|
5928
6091
|
authCtx.writerGeneration, authCtx.kind === "main-pool" ? authCtx.mainQuotaWriter : undefined,
|
|
5929
|
-
{ modelId: route.modelId });
|
|
6092
|
+
{ modelId: route.modelId, poolWriter: authCtx.kind === "pool" ? authCtx.poolQuotaWriter : undefined });
|
|
5930
6093
|
}
|
|
5931
6094
|
if (terminalBodyWillRecord) {
|
|
5932
6095
|
options.setTerminalOutcomeRecorder?.((status, httpStatusOverride) => {
|
|
@@ -5978,7 +6141,7 @@ async function handleResponsesInner(
|
|
|
5978
6141
|
return new Response(upstreamResponse.body, {
|
|
5979
6142
|
status: upstreamResponse.status,
|
|
5980
6143
|
statusText: upstreamResponse.statusText,
|
|
5981
|
-
headers: sanitizePassthroughHeaders(upstreamResponse.headers),
|
|
6144
|
+
headers: sanitizePassthroughHeaders(upstreamResponse.headers, codexSafetyBufferingOptions),
|
|
5982
6145
|
});
|
|
5983
6146
|
}
|
|
5984
6147
|
if (!upstreamResponse.ok) {
|
|
@@ -6040,6 +6203,8 @@ async function handleResponsesInner(
|
|
|
6040
6203
|
isPassthrough: true,
|
|
6041
6204
|
stream: parsed.stream === true,
|
|
6042
6205
|
});
|
|
6206
|
+
// Capture the binding that actually served the first leg, after its permitted reselection.
|
|
6207
|
+
const webSearchBridgeBinding = requestBindings.get(request);
|
|
6043
6208
|
// The bridge wraps the RAW upstream body, so terminal repair below still owns the single
|
|
6044
6209
|
// client-facing terminal — the bridge drops the terminal of every intercepted leg.
|
|
6045
6210
|
const upstreamSseBody = webSearchBridgePlan
|
|
@@ -6057,7 +6222,14 @@ async function handleResponsesInner(
|
|
|
6057
6222
|
connectMs,
|
|
6058
6223
|
true,
|
|
6059
6224
|
providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
6060
|
-
|
|
6225
|
+
// Pacing can outlive a manual selection change. A continuation must retain the
|
|
6226
|
+
// first leg's key and appended search result, never rebuild from the original turn.
|
|
6227
|
+
beforeDispatch: () => {
|
|
6228
|
+
if (webSearchBridgeBinding?.kind !== "api-key"
|
|
6229
|
+
|| !providerApiKeySelectionIsCurrent(config, route.providerName, webSearchBridgeBinding.provider)) {
|
|
6230
|
+
throw new Error("API key selection changed during a web-search continuation");
|
|
6231
|
+
}
|
|
6232
|
+
},
|
|
6061
6233
|
providerName: route.providerName,
|
|
6062
6234
|
modelId: route.modelId,
|
|
6063
6235
|
}),
|
|
@@ -6100,6 +6272,9 @@ async function handleResponsesInner(
|
|
|
6100
6272
|
createImageGenCallRestoreRewrite(imageGenCallAliases),
|
|
6101
6273
|
// #3217: a call whose namespace repeats its own name is unroutable in codex-rs.
|
|
6102
6274
|
createSelfNamedToolCallNamespaceScrubRewrite(selfNamedNamespaceScrubAuthorization),
|
|
6275
|
+
routedMuseToolNameAliases.size > 0
|
|
6276
|
+
? createMuseToolNameRestoreRewrite(routedMuseToolNameAliases)
|
|
6277
|
+
: undefined,
|
|
6103
6278
|
routedNamespaceToolAliases.size > 0
|
|
6104
6279
|
? createRoutedNamespaceCallRestoreRewrite(routedNamespaceToolAliases)
|
|
6105
6280
|
: undefined,
|
|
@@ -6110,15 +6285,23 @@ async function handleResponsesInner(
|
|
|
6110
6285
|
? createResponsesItemIdPayloadRewrite(repairConfig!, translatorBudget)
|
|
6111
6286
|
: undefined,
|
|
6112
6287
|
responseModelRewrite,
|
|
6113
|
-
parsed.options.hideThinkingSummary !== true
|
|
6114
|
-
&& routeUsesContentChannelReasoning(route.provider, route.modelId)
|
|
6115
|
-
? createReasoningSummaryChannelPayloadRewrite()
|
|
6116
|
-
: undefined,
|
|
6117
6288
|
].filter((rewrite): rewrite is NonNullable<typeof rewrite> => rewrite !== undefined);
|
|
6118
6289
|
// #893: sparse-snapshot gateways get field backfills AND lifecycle event
|
|
6119
6290
|
// injection at the block level, after payload rewrites. Defaults come
|
|
6120
6291
|
// from the finalized OUTBOUND body — the normalized internal tool shapes
|
|
6121
6292
|
// are not the Responses wire shapes the snapshot must mirror.
|
|
6293
|
+
// Only validated client blocks may publish plaintext continuation state.
|
|
6294
|
+
// Raw inspection precedes rewriting on eager relays, so it cannot own this write.
|
|
6295
|
+
const plaintextInspector = plaintextV2AgentMessageToolNames.size > 0
|
|
6296
|
+
? createSseInspector({ onCompletedResponse: rememberPassthroughResponseChecked })
|
|
6297
|
+
: undefined;
|
|
6298
|
+
const plaintextEncoder = plaintextInspector ? new TextEncoder() : undefined;
|
|
6299
|
+
const rememberPlaintextBlock = plaintextInspector
|
|
6300
|
+
? Object.assign((block: string): readonly string[] => {
|
|
6301
|
+
plaintextInspector.feed(plaintextEncoder!.encode(`${block}\n\n`));
|
|
6302
|
+
return [block];
|
|
6303
|
+
}, { dispose: () => plaintextInspector.dispose() })
|
|
6304
|
+
: undefined;
|
|
6122
6305
|
const blockRewrites = [
|
|
6123
6306
|
payloadRewrites.length > 0
|
|
6124
6307
|
? payloadRewriteAsBlockRewrite(composeSsePayloadRewrites(...payloadRewrites))
|
|
@@ -6146,6 +6329,11 @@ async function handleResponsesInner(
|
|
|
6146
6329
|
snapshotRepairEnabled
|
|
6147
6330
|
? createResponsesSnapshotBlockRewrite(outboundRequestBody, translatorBudget)
|
|
6148
6331
|
: undefined,
|
|
6332
|
+
plaintextV2AgentMessageToolNames.size > 0
|
|
6333
|
+
? payloadRewriteAsBlockRewrite(createPlaintextV2AgentMessageCallRestoreRewrite(
|
|
6334
|
+
plaintextV2AgentMessageToolNames, plaintextV2AgentMessageAliasedToolNames,
|
|
6335
|
+
))
|
|
6336
|
+
: undefined,
|
|
6149
6337
|
createResponsesFieldBackfillBlockRewrite(),
|
|
6150
6338
|
functionRepairSchemas.size > 0
|
|
6151
6339
|
? createResponsesFunctionToolRepairBlockRewrite(functionRepairSchemas, translatorBudget)
|
|
@@ -6160,6 +6348,7 @@ async function handleResponsesInner(
|
|
|
6160
6348
|
declaredBareWireToolNames,
|
|
6161
6349
|
)
|
|
6162
6350
|
: undefined,
|
|
6351
|
+
rememberPlaintextBlock,
|
|
6163
6352
|
].filter((rewrite): rewrite is NonNullable<typeof rewrite> => rewrite !== undefined);
|
|
6164
6353
|
const clientBlockRewrite = blockRewrites.length > 0
|
|
6165
6354
|
? composeSseBlockRewrites(...blockRewrites)
|
|
@@ -6207,7 +6396,7 @@ async function handleResponsesInner(
|
|
|
6207
6396
|
const inspector = createSseInspector({
|
|
6208
6397
|
onTerminal: reportNativeTerminal,
|
|
6209
6398
|
logCtx,
|
|
6210
|
-
onCompletedResponse: rememberPassthroughResponse ? rememberPassthroughResponseChecked : undefined,
|
|
6399
|
+
onCompletedResponse: rememberPassthroughResponse && plaintextV2AgentMessageToolNames.size === 0 ? rememberPassthroughResponseChecked : undefined,
|
|
6211
6400
|
onParsedPayload: noteInspectedPayload,
|
|
6212
6401
|
onFirstOutput: options.onFirstOutput,
|
|
6213
6402
|
pinCompletedResponseIdToFirstSeen: githubCopilotRepairEnabled,
|
|
@@ -6244,6 +6433,7 @@ async function handleResponsesInner(
|
|
|
6244
6433
|
onDone: () => unregisterTurn(turnAc),
|
|
6245
6434
|
}, {
|
|
6246
6435
|
clientGoneSignal: options.abortSignal,
|
|
6436
|
+
terminalBoundary: codexSafetyBufferingOptions,
|
|
6247
6437
|
...(inlineEagerRewrite ? { rewriteBudget: translatorBudget } : {}),
|
|
6248
6438
|
...(logCtx.upstreamError === undefined ? {} : { upstreamError: logCtx.upstreamError }),
|
|
6249
6439
|
});
|
|
@@ -6306,7 +6496,7 @@ async function handleResponsesInner(
|
|
|
6306
6496
|
responseCompletionCancelled = true;
|
|
6307
6497
|
options.onNativePassthroughCancel?.();
|
|
6308
6498
|
},
|
|
6309
|
-
rememberPassthroughResponse ? rememberPassthroughResponseChecked : undefined,
|
|
6499
|
+
rememberPassthroughResponse && plaintextV2AgentMessageToolNames.size === 0 ? rememberPassthroughResponseChecked : undefined,
|
|
6310
6500
|
options.onFirstOutput,
|
|
6311
6501
|
inspectionConsumerOptions,
|
|
6312
6502
|
);
|
|
@@ -6316,7 +6506,7 @@ async function handleResponsesInner(
|
|
|
6316
6506
|
logCtx,
|
|
6317
6507
|
turnAc.signal,
|
|
6318
6508
|
() => unregisterTurn(turnAc),
|
|
6319
|
-
rememberPassthroughResponse ? rememberPassthroughResponseChecked : undefined,
|
|
6509
|
+
rememberPassthroughResponse && plaintextV2AgentMessageToolNames.size === 0 ? rememberPassthroughResponseChecked : undefined,
|
|
6320
6510
|
options.onFirstOutput,
|
|
6321
6511
|
inspectionConsumerOptions,
|
|
6322
6512
|
);
|
|
@@ -6335,7 +6525,7 @@ async function handleResponsesInner(
|
|
|
6335
6525
|
responseCompletionCancelled = true;
|
|
6336
6526
|
clientGone.abort(reason);
|
|
6337
6527
|
},
|
|
6338
|
-
{ upstreamError: logCtx.upstreamError },
|
|
6528
|
+
{ upstreamError: logCtx.upstreamError, terminalBoundary: codexSafetyBufferingOptions },
|
|
6339
6529
|
);
|
|
6340
6530
|
return markNativePassthroughSseResponse(new Response(clientBody, {
|
|
6341
6531
|
status: upstreamResponse.status,
|
|
@@ -6358,10 +6548,14 @@ async function handleResponsesInner(
|
|
|
6358
6548
|
}
|
|
6359
6549
|
const text = bounded.text;
|
|
6360
6550
|
inspectResponseLogJson(logCtx, text);
|
|
6551
|
+
let plaintextV2RestoreFailed = false;
|
|
6361
6552
|
let clientJson = (() => {
|
|
6362
6553
|
const restoredNamespace = restoreRoutedNamespaceCallsInJson(
|
|
6363
6554
|
scrubSelfNamedToolCallNamespaceInJson(
|
|
6364
|
-
|
|
6555
|
+
restoreMuseToolNamesInJson(
|
|
6556
|
+
restoreImageGenCallsInJson(text, imageGenCallAliases),
|
|
6557
|
+
routedMuseToolNameAliases,
|
|
6558
|
+
),
|
|
6365
6559
|
selfNamedNamespaceScrubAuthorization,
|
|
6366
6560
|
),
|
|
6367
6561
|
routedNamespaceToolAliases,
|
|
@@ -6380,18 +6574,20 @@ async function handleResponsesInner(
|
|
|
6380
6574
|
restored,
|
|
6381
6575
|
routedToolSearchNames,
|
|
6382
6576
|
);
|
|
6383
|
-
const
|
|
6577
|
+
const normalizedJson = normalizeFunctionCompletionJson(restoredToolSearch);
|
|
6578
|
+
const plaintextRestore = restorePlaintextV2AgentMessageCallsInJsonResult(
|
|
6579
|
+
normalizedJson, plaintextV2AgentMessageToolNames, plaintextV2AgentMessageAliasedToolNames,
|
|
6580
|
+
);
|
|
6581
|
+
plaintextV2RestoreFailed = plaintextRestore.overflowed;
|
|
6582
|
+
const repaired = plaintextRestore.value;
|
|
6384
6583
|
const modelRewritten = parsed._responseModelId !== undefined && parsed._responseModelId !== parsed.modelId
|
|
6385
6584
|
? rewriteResponsesModelJson(repaired, parsed._responseModelId)
|
|
6386
6585
|
: repaired;
|
|
6387
|
-
|
|
6388
|
-
// channel reasoning needs the same normalization here for the plain
|
|
6389
|
-
// JSON answer and every reframed-SSE variant built from clientJson.
|
|
6390
|
-
return parsed.options.hideThinkingSummary !== true
|
|
6391
|
-
&& routeUsesContentChannelReasoning(route.provider, route.modelId)
|
|
6392
|
-
? rewriteReasoningSummaryInJsonString(modelRewritten)
|
|
6393
|
-
: modelRewritten;
|
|
6586
|
+
return modelRewritten;
|
|
6394
6587
|
})();
|
|
6588
|
+
if (plaintextV2RestoreFailed) {
|
|
6589
|
+
return formatErrorResponse(502, "upstream_error", PLAINTEXT_V2_AGENT_MESSAGE_RESTORE_OVERFLOW_MESSAGE);
|
|
6590
|
+
}
|
|
6395
6591
|
// #1700: same fail-closed policy as the SSE relay above. Both the plain JSON answer and
|
|
6396
6592
|
// the reframed-SSE branch below are built from this body, so one check covers them. This
|
|
6397
6593
|
// runs BEFORE the continuation cache write below: a refused turn must not become state a
|
|
@@ -6466,7 +6662,7 @@ async function handleResponsesInner(
|
|
|
6466
6662
|
}
|
|
6467
6663
|
throw error;
|
|
6468
6664
|
}
|
|
6469
|
-
const sseHeaders = sanitizePassthroughHeaders(headers);
|
|
6665
|
+
const sseHeaders = sanitizePassthroughHeaders(headers, codexSafetyBufferingOptions);
|
|
6470
6666
|
sseHeaders.set("content-type", "text/event-stream");
|
|
6471
6667
|
sseHeaders.set("cache-control", "no-store");
|
|
6472
6668
|
return new Response(stream, {
|
|
@@ -6499,6 +6695,10 @@ async function handleResponsesInner(
|
|
|
6499
6695
|
headers,
|
|
6500
6696
|
});
|
|
6501
6697
|
}
|
|
6698
|
+
if (plaintextV2AgentMessageToolNames.size > 0) {
|
|
6699
|
+
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already closed */ }
|
|
6700
|
+
return formatErrorResponse(502, "upstream_error", "plaintext V2 agent-message response used an unsupported content type");
|
|
6701
|
+
}
|
|
6502
6702
|
// An unclassified passthrough body is relayed directly and has no bounded completion observer;
|
|
6503
6703
|
// use the same non-error-status success boundary as SSE instead of retaining per-stream state.
|
|
6504
6704
|
commitReasoningReplayServingRoute(request.headers);
|
|
@@ -7267,7 +7467,7 @@ async function handleResponsesInner(
|
|
|
7267
7467
|
Math.max(1, budget - transientSendsUsed);
|
|
7268
7468
|
try {
|
|
7269
7469
|
initialRequest = await activeAdapter.buildRequest(parsed, { headers: selectedForwardHeaders, translatorBudget });
|
|
7270
|
-
|
|
7470
|
+
refreshRequestToolAliases(initialRequest);
|
|
7271
7471
|
recordAdapterReasoning(logCtx, initialRequest);
|
|
7272
7472
|
recordAdapterTier(logCtx, initialRequest);
|
|
7273
7473
|
inputTokenEstimate = typeof initialRequest.usageLog?.inputTokens === "number"
|
|
@@ -7376,7 +7576,14 @@ async function handleResponsesInner(
|
|
|
7376
7576
|
// 413→429 rotation cannot silently undo the tightening.
|
|
7377
7577
|
let imageRetryAttempted = false;
|
|
7378
7578
|
const opaqueBlobRecoveryGuard: OpaqueBlobRecoveryGuard = { attempted: false };
|
|
7579
|
+
// Console Go answers a transient 400 "Invalid upload request." for bodies it accepts
|
|
7580
|
+
// moments later; at most one byte-identical replay is allowed per request.
|
|
7581
|
+
const consoleGoUploadRetryGuard: { attempted: boolean } = { attempted: false };
|
|
7379
7582
|
let oauth401ReplayAttempted = false;
|
|
7583
|
+
// At most one reasoning-effort downgrade per request. This sits outside the recovery loop
|
|
7584
|
+
// below for the same reason the two guards above do: a guard declared inside it is reset by
|
|
7585
|
+
// every `continue recovery`, which would let one turn walk the whole ladder down.
|
|
7586
|
+
const reasoningEffortDowngradeGuard: { attempted: boolean } = { attempted: false };
|
|
7380
7587
|
/**
|
|
7381
7588
|
* Rebuild the request from the current parsed input (and any image-tier bias) and refetch
|
|
7382
7589
|
* it once, tagging the attempt with the given recovery kind. Rebuilds are deterministic
|
|
@@ -7412,7 +7619,7 @@ async function handleResponsesInner(
|
|
|
7412
7619
|
sameTargetParsed = parsed;
|
|
7413
7620
|
sameTargetToken = transportToken;
|
|
7414
7621
|
}
|
|
7415
|
-
|
|
7622
|
+
refreshRequestToolAliases(retryRequest);
|
|
7416
7623
|
const retryEstimate = typeof retryRequest.usageLog?.inputTokens === "number"
|
|
7417
7624
|
? retryRequest.usageLog.inputTokens
|
|
7418
7625
|
: undefined;
|
|
@@ -7760,6 +7967,63 @@ async function handleResponsesInner(
|
|
|
7760
7967
|
upstreamResponse = result;
|
|
7761
7968
|
continue recovery;
|
|
7762
7969
|
}
|
|
7970
|
+
// Console Go (opencode-zen / opencode-go) intermittently rejects a body it accepts seconds
|
|
7971
|
+
// later with 400 invalid_request_error / "Invalid upload request." Replay the
|
|
7972
|
+
// byte-identical request once after the exact gateway rejection.
|
|
7973
|
+
if (!consoleGoUploadRetryGuard.attempted) {
|
|
7974
|
+
const uploadRejectionBody = await consoleGoUploadRejectionBody(
|
|
7975
|
+
upstreamResponse,
|
|
7976
|
+
consoleGoUploadRetryGuard.attempted,
|
|
7977
|
+
upstream.signal,
|
|
7978
|
+
);
|
|
7979
|
+
if (uploadRejectionBody !== undefined
|
|
7980
|
+
&& isTransientConsoleGoUploadRejection({
|
|
7981
|
+
status: upstreamResponse.status,
|
|
7982
|
+
errorBody: uploadRejectionBody,
|
|
7983
|
+
outboundUrl: sameTargetRequest?.url,
|
|
7984
|
+
})) {
|
|
7985
|
+
consoleGoUploadRetryGuard.attempted = true;
|
|
7986
|
+
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
7987
|
+
if (!upstream.signal.aborted) {
|
|
7988
|
+
try {
|
|
7989
|
+
await sleepWithAbort(CONSOLE_GO_UPLOAD_RETRY_DELAY_MS, upstream.signal);
|
|
7990
|
+
} catch { cleanupUpstreamAbort(); return clientCancelledResponse(); }
|
|
7991
|
+
}
|
|
7992
|
+
if (upstream.signal.aborted) { cleanupUpstreamAbort(); return clientCancelledResponse(); }
|
|
7993
|
+
const result = await rebuildAndRefetch("console-go-upload-retry");
|
|
7994
|
+
if ("failed" in result) return result.failed;
|
|
7995
|
+
upstreamResponse = result;
|
|
7996
|
+
continue recovery;
|
|
7997
|
+
}
|
|
7998
|
+
}
|
|
7999
|
+
// Reasoning-effort downgrade, mirroring the passthroughRecovery loop above: learn the
|
|
8000
|
+
// refused rung, then replay once at the next published one.
|
|
8001
|
+
if (!reasoningEffortDowngradeGuard.attempted) {
|
|
8002
|
+
const rejectionText = await reasoningEffortRejectionText(
|
|
8003
|
+
upstreamResponse,
|
|
8004
|
+
reasoningEffortDowngradeGuard.attempted,
|
|
8005
|
+
upstream.signal,
|
|
8006
|
+
);
|
|
8007
|
+
const downgrade = rejectionText === undefined
|
|
8008
|
+
? undefined
|
|
8009
|
+
: planReasoningEffortDowngrade({
|
|
8010
|
+
provider: route.provider,
|
|
8011
|
+
modelId: parsed.modelId,
|
|
8012
|
+
requested: parsed.options.reasoning,
|
|
8013
|
+
rejectionText,
|
|
8014
|
+
});
|
|
8015
|
+
if (downgrade) {
|
|
8016
|
+
reasoningEffortDowngradeGuard.attempted = true;
|
|
8017
|
+
parsed.options.reasoning = downgrade.effort;
|
|
8018
|
+
// The same-target cache keys on parsed identity, so a mutated effort needs a token bump.
|
|
8019
|
+
invalidateSameTargetRequest();
|
|
8020
|
+
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
8021
|
+
const result = await rebuildAndRefetch("reasoning-effort-downgrade");
|
|
8022
|
+
if ("failed" in result) return result.failed;
|
|
8023
|
+
upstreamResponse = result;
|
|
8024
|
+
continue recovery;
|
|
8025
|
+
}
|
|
8026
|
+
}
|
|
7763
8027
|
break;
|
|
7764
8028
|
}
|
|
7765
8029
|
if (!upstreamResponse.ok) {
|