@bitkyc08/opencodex 2.86.0 → 2.87.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-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-DCd-A1az.js} +1 -1
- package/gui/dist/assets/index-CfYDMj34.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +9 -8
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/passthrough.ts +7 -23
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic.ts +108 -21
- package/src/adapters/base.ts +2 -0
- package/src/adapters/claude-cli/adapter.ts +111 -23
- package/src/adapters/claude-cli/profiles.ts +2 -1
- package/src/adapters/claude-cli/stable-replay.ts +108 -0
- package/src/adapters/claude-cli/usage-admission.ts +230 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +18 -2
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google.ts +35 -8
- package/src/adapters/openai-chat/messages.ts +4 -3
- package/src/adapters/openai-chat/passthrough.ts +8 -2
- package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
- package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
- package/src/adapters/openai-responses/passthrough.ts +57 -125
- package/src/adapters/openai-responses/request-strips.ts +25 -3
- package/src/adapters/openai-responses/transport.ts +81 -0
- package/src/adapters/qoder/adapter.ts +4 -2
- package/src/adapters/registry.ts +5 -0
- package/src/adapters/zed.ts +26 -1
- package/src/advisor/activity.ts +55 -0
- package/src/advisor/budget.ts +62 -0
- package/src/advisor/consult.ts +4 -1
- package/src/advisor/context.ts +16 -4
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +79 -31
- package/src/advisor/settings.ts +11 -2
- package/src/bridge/errors.ts +19 -1
- package/src/bridge/internal.ts +3 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +58 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/claude/desktop-3p-library.ts +32 -7
- package/src/claude/desktop-3p.ts +55 -13
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +11 -0
- package/src/claude/inbound.ts +33 -0
- package/src/claude/intercept/picker-ca-startup.ts +2 -1
- package/src/claude/intercept/picker-runtime.ts +6 -5
- package/src/claude/intercept/picker-trust-windows.ts +338 -0
- package/src/claude/intercept/picker-trust.ts +19 -0
- package/src/claude/intercept/port-policy.ts +30 -0
- package/src/claude/intercept/runtime.ts +97 -94
- package/src/claude/outbound.ts +38 -13
- package/src/cli/account-api.ts +1 -0
- package/src/cli/account-policy-dto.ts +6 -0
- package/src/cli/account-policy.ts +34 -5
- package/src/cli/account-preservation.ts +44 -0
- package/src/cli/account.ts +8 -2
- package/src/cli/advisor.ts +17 -3
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +5 -2
- package/src/cli/capabilities-base.ts +38 -10
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/codex-cli-update.ts +151 -41
- package/src/cli/codex-provider.ts +44 -0
- package/src/cli/combo-input.ts +10 -1
- package/src/cli/combo-result.ts +1 -1
- package/src/cli/combo.ts +2 -1
- package/src/cli/cross-home-owner.ts +17 -14
- package/src/cli/dispatch.ts +25 -4
- package/src/cli/doctor.ts +6 -1
- package/src/cli/ensure-desktop-cold-start.ts +60 -7
- package/src/cli/help.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/models-runtime-subcommands.ts +1 -0
- package/src/cli/models-runtime.ts +72 -8
- package/src/cli/observe-stream.ts +2 -0
- package/src/cli/observe.ts +16 -4
- package/src/cli/provider.ts +27 -5
- package/src/cli/registry.ts +28 -8
- package/src/cli/runtime-api.ts +5 -3
- package/src/cli/system-command.ts +40 -8
- package/src/cli/uninstall-client-state.ts +32 -0
- package/src/cli/usage-compare.ts +309 -0
- package/src/cli/usage-model-search.ts +4 -4
- package/src/cli/usage-report.ts +2 -2
- package/src/cli/v2.ts +1 -1
- package/src/client/machine-listener.ts +1 -3
- package/src/client/runtime.ts +1 -0
- package/src/clients/client-path.ts +51 -0
- package/src/clients/config-export/contracts.ts +2 -1
- package/src/clients/config-export.ts +18 -44
- package/src/clients/gentle-shell-path.ts +87 -0
- package/src/codex/account-auto-switch.ts +2 -0
- package/src/codex/account-identity.ts +29 -0
- package/src/codex/account-label.ts +1 -1
- package/src/codex/account-lifecycle.ts +21 -5
- package/src/codex/account-preservation.ts +28 -0
- package/src/codex/account-use-remaining.ts +23 -0
- package/src/codex/app-server-processes.ts +97 -7
- package/src/codex/app-server-restart-service.ts +55 -4
- package/src/codex/auth-api/account-list.ts +4 -0
- package/src/codex/auth-api/account-pause-group.ts +6 -50
- package/src/codex/auth-api/account-preservation.ts +57 -0
- package/src/codex/auth-api/routes.ts +8 -0
- package/src/codex/auth-api/use-remaining-route.ts +36 -0
- package/src/codex/auth-api.ts +0 -1
- package/src/codex/auth-context.ts +2 -2
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +9 -1
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +1 -1
- package/src/codex/catalog/gather-capture.ts +2 -0
- package/src/codex/catalog/metadata.ts +11 -9
- package/src/codex/catalog/model-hints.ts +21 -16
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/parsing.ts +30 -86
- package/src/codex/catalog/provider-models.ts +36 -34
- package/src/codex/catalog/routed-gather.ts +12 -13
- package/src/codex/cli-install-provenance.ts +105 -17
- package/src/codex/cli-update-plan.ts +545 -0
- package/src/codex/config-write-lock.ts +23 -2
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app-restart.ts +21 -0
- package/src/codex/desktop-compatibility/installed-build.ts +24 -6
- package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
- package/src/codex/desktop-compatibility/runtime.ts +12 -3
- package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
- package/src/codex/features.ts +85 -45
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/restore.ts +24 -2
- package/src/codex/inject/routing-classify.ts +4 -3
- package/src/codex/inject.ts +18 -27
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/native-profile-startup.ts +1 -1
- package/src/codex/ocx-compaction-history.ts +12 -10
- package/src/codex/paths.ts +86 -1
- package/src/codex/provider-routing.ts +174 -0
- package/src/codex/refresh.ts +2 -2
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/signed-in-identity.ts +43 -0
- package/src/codex/subagent-model-fallback.ts +1 -1
- package/src/codex/sync.ts +6 -1
- package/src/codex/user-identity.ts +16 -5
- package/src/codex/windows-local-appdata.ts +84 -0
- package/src/combos/failover.ts +6 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/resolve.ts +46 -10
- package/src/combos/types.ts +15 -0
- package/src/config/diagnostics.ts +33 -6
- package/src/config/live-reconcile.ts +38 -2
- package/src/config/load-degrade.ts +17 -0
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +52 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +22 -0
- package/src/config/schema/leaf-validators.ts +38 -9
- package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
- package/src/config/schema/native-reasoning-retention.ts +26 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +608 -284
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +46 -8
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +1 -0
- package/src/integrations/registry.ts +8 -0
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-upstream-tunnel.ts +18 -53
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +19 -3
- package/src/lib/gcp-adc.ts +96 -9
- package/src/lib/gcp-credential-input.ts +12 -0
- package/src/lib/lab-live-route-production.ts +5 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/redact.ts +2 -2
- package/src/oauth/account-quota-rank.ts +69 -2
- package/src/oauth/anthropic-account-refusal.ts +20 -2
- package/src/oauth/anthropic-oauth-definitions.ts +3 -1
- package/src/oauth/anthropic-routing.ts +1 -21
- package/src/oauth/anthropic.ts +38 -7
- package/src/oauth/callback-server.ts +27 -1
- package/src/oauth/chatgpt-device.ts +1 -1
- package/src/oauth/chatgpt-shared.ts +83 -0
- package/src/oauth/chatgpt.ts +8 -72
- package/src/oauth/dsh.ts +203 -0
- package/src/oauth/generic-account-failover.ts +33 -7
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +184 -109
- package/src/oauth/local-token-detect.ts +21 -1
- package/src/oauth/login-cli-errors.ts +44 -0
- package/src/oauth/login-cli.ts +69 -7
- package/src/oauth/login-flow-state.ts +371 -23
- package/src/oauth/meta-muse-errors.ts +41 -0
- package/src/oauth/meta-muse-keychain.ts +52 -0
- package/src/oauth/meta-muse.ts +51 -84
- package/src/oauth/model-discovery-auth.ts +3 -0
- package/src/oauth/pool-settings-capability.ts +4 -2
- package/src/oauth/refresh-flight-retirement.ts +48 -0
- package/src/oauth/session-affinity-key.ts +23 -0
- package/src/oauth/store-anthropic-instance.ts +9 -2
- package/src/oauth/store.ts +12 -5
- package/src/protocols/encoders/adapter-events.ts +5 -4
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/provider-summary.ts +2 -1
- package/src/providers/api-key-cooldowns.ts +49 -0
- package/src/providers/api-key-resolve.ts +11 -6
- package/src/providers/api-key-response-ownership.ts +57 -0
- package/src/providers/api-key-selection.ts +31 -6
- package/src/providers/api-keys.ts +214 -3
- package/src/providers/claude-cli-usage.ts +96 -0
- package/src/providers/dsh-account-destination.ts +31 -0
- package/src/providers/dsh-account-models.ts +41 -0
- package/src/providers/gcp-credential-cleanup.ts +59 -0
- package/src/providers/key-failover.ts +54 -50
- package/src/providers/key-store.ts +7 -2
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/model-rename-migration.ts +2 -2
- package/src/providers/openai-tiers-destination.ts +11 -2
- package/src/providers/openai-tiers.ts +5 -12
- package/src/providers/opencode-go-transport.ts +3 -1
- package/src/providers/quota/account-cache.ts +6 -5
- package/src/providers/quota/account-row.ts +34 -0
- package/src/providers/quota/antigravity.ts +28 -7
- package/src/providers/quota/dsh-account.ts +70 -0
- package/src/providers/quota/vendor-probes-oauth.ts +1 -93
- package/src/providers/quota-types.ts +2 -0
- package/src/providers/quota.ts +14 -20
- package/src/providers/registry/entries-core.ts +37 -8
- package/src/providers/registry/types.ts +1 -1
- package/src/providers/request-pacing.ts +77 -19
- package/src/providers/routed-config.ts +339 -0
- package/src/providers/service-tier.ts +2 -1
- package/src/quota/window-mapping.ts +5 -2
- package/src/responses/anthropic-progress.ts +34 -0
- package/src/responses/assistant-replay.ts +35 -0
- package/src/responses/compaction-history-framing.ts +65 -0
- package/src/responses/compaction.ts +6 -1
- package/src/responses/incomplete-reason.ts +31 -0
- package/src/responses/parser.ts +50 -5
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +24 -2
- package/src/responses/reasoning-retention.ts +173 -0
- package/src/responses/retained-compaction.ts +46 -0
- package/src/responses/schema.ts +23 -0
- package/src/router.ts +8 -321
- package/src/routing/health.ts +37 -22
- package/src/routing/history/indexer.ts +6 -5
- package/src/routing/history/ingest.ts +6 -2
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +17 -0
- package/src/server/auth-cors.ts +11 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native-reasoning.ts +123 -0
- package/src/server/chat-native-sse.ts +110 -7
- package/src/server/chat-native.ts +76 -17
- package/src/server/claude-messages.ts +55 -414
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/model-alias-id.ts +36 -0
- package/src/server/index/optional-listeners.ts +2 -0
- package/src/server/index/serve-options.ts +9 -1
- package/src/server/index/startup-warnings.ts +2 -0
- package/src/server/index.ts +1 -1
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/management/advisor-routes.ts +15 -4
- package/src/server/management/claude-desktop-picker-routes.ts +1 -1
- package/src/server/management/combo-routes.ts +176 -50
- package/src/server/management/companion-routes.ts +1 -1
- package/src/server/management/config-routes.ts +75 -4
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/model-routes.ts +11 -6
- package/src/server/management/model-rows.ts +14 -0
- package/src/server/management/native-integration-routes.ts +2 -7
- package/src/server/management/native-reasoning-retention-routes.ts +41 -0
- package/src/server/management/oauth-account-routes.ts +87 -14
- package/src/server/management/provider-gcp-credential-routes.ts +29 -0
- package/src/server/management/provider-key-pacing-routes.ts +31 -0
- package/src/server/management/provider-overwrite-carry.ts +7 -0
- package/src/server/management/provider-routes.ts +25 -30
- package/src/server/management/route-registry.ts +10 -1
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +101 -21
- package/src/server/messages-shared.ts +431 -0
- package/src/server/relay.ts +1 -1
- package/src/server/request-log-terminal-status.ts +1 -1
- package/src/server/request-log.ts +6 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +13 -1
- package/src/server/responses/adapter-delivery.ts +19 -6
- package/src/server/responses/adapter-dispatch.ts +19 -2
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +73 -41
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +18 -10
- package/src/server/responses/compaction-retention.ts +61 -0
- package/src/server/responses/context-overflow.ts +11 -6
- package/src/server/responses/core-auth.ts +6 -0
- package/src/server/responses/core-codex-account.ts +11 -1
- package/src/server/responses/core-combo-failure.ts +2 -0
- package/src/server/responses/core-combo.ts +120 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +5 -2
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +18 -2
- package/src/server/responses/core.ts +2 -115
- package/src/server/responses/fetch-helpers.ts +73 -19
- package/src/server/responses/main-hard-lock-routing.ts +109 -0
- package/src/server/responses/native-response-control.ts +4 -2
- package/src/server/responses/passthrough-delivery.ts +3 -1
- package/src/server/responses/passthrough-dispatch.ts +15 -13
- package/src/server/responses/request-prepare.ts +76 -12
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +104 -27
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +96 -30
- package/src/server/responses/sidecar-execution.ts +20 -2
- package/src/server/responses/terminal-guard.ts +2 -16
- package/src/service/auth-preflight.ts +71 -0
- package/src/service/cli.ts +4 -2
- package/src/service/definition.ts +97 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +76 -57
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +72 -9
- package/src/service/repair.ts +2 -1
- package/src/service/state.ts +7 -8
- package/src/service/systemd.ts +1 -1
- package/src/service/windows-guarded-manager.ts +2 -1
- package/src/service/windows-ops.ts +1 -1
- package/src/service/windows-scheduler.ts +2 -7
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service/windows-wscript.ts +14 -0
- package/src/service.ts +4 -2
- package/src/sidecar/auth.ts +2 -0
- package/src/types/config.ts +64 -3
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +29 -0
- package/src/types/provider.ts +32 -6
- package/src/types/request.ts +23 -2
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -0
- package/src/update/npm-invocation.mjs +12 -2
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +3 -0
- package/src/usage/ledger-retention.ts +2 -1
- package/src/usage/ledger-scanner.ts +6 -5
- package/src/usage/log.ts +17 -8
- package/src/usage/summary.ts +22 -9
- package/src/usage/user-cost-overlays.ts +14 -19
- package/src/usage/user-cost-validation.ts +38 -0
- package/src/vision/eligibility.ts +13 -0
- package/src/web-search/loop.ts +55 -5
- package/src/web-search/progress-stream.ts +4 -1
- package/src/web-search/run-turn-loop.ts +9 -1
- package/gui/dist/assets/App-BjlttVLy.js +0 -58
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-BMssP4k3.js +0 -94
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { spendLedgerStorageErrorResponse } from "./spend-storage-error";
|
|
2
|
-
import { createPhysicalSendReporter } from "../../lib/request-execution-budget";
|
|
2
|
+
import { createPhysicalSendReporter, rebindPhysicalSend } from "../../lib/request-execution-budget";
|
|
3
3
|
import { createInferenceSendBudget } from "../inference/context";
|
|
4
4
|
import { unboundPoolSpendRefusalResponse } from "../workflow-refusal";
|
|
5
5
|
import { capturePoolQuotaWriter } from "../../codex/account-store";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { rmSync } from "node:fs";
|
|
6
8
|
import { previewXaiOauthWireModel } from "./core-normalize";
|
|
7
9
|
import {
|
|
8
10
|
admissionModelDeniedResponse,
|
|
@@ -14,12 +16,15 @@ import type { Server } from "bun";
|
|
|
14
16
|
import { bridgeToResponsesSSE, buildResponseJSON, formatErrorResponse, type ResponsesTerminalStatus } from "../../bridge";
|
|
15
17
|
import {
|
|
16
18
|
getConfigPath,
|
|
19
|
+
getConfigDir,
|
|
17
20
|
multiAgentGuidanceEnabled,
|
|
18
21
|
} from "../../config";
|
|
19
22
|
import { resolveProviderApiKey } from "../../providers/key-store";
|
|
20
23
|
import { parseRequest } from "../../responses/parser";
|
|
21
24
|
import { buildCompactV1Output, COMPACT_PROMPT, decodeCompactionSummary, extractCompactUserMessages } from "../../responses/compaction";
|
|
25
|
+
import { applyReasoningRetention, appendRetentionNotice, type ReasoningRetentionResult } from "../../responses/reasoning-retention";
|
|
22
26
|
import { FORWARD_HEADERS, sanitizeReasoningInputContent } from "../../adapters/openai-responses";
|
|
27
|
+
import { stripProviderInputItemMetadata } from "../../adapters/openai-responses/request-strips";
|
|
23
28
|
import { expandPreviousResponseInput, previousResponseProviderState, rememberResponseState } from "../../responses/state";
|
|
24
29
|
import { repairLegacyDottedToolCallNames } from "../../responses/legacy-dotted-tool-name-repair";
|
|
25
30
|
import { NoEligiblePolicyCandidateError, routeCompactionModel } from "../../router";
|
|
@@ -127,6 +132,14 @@ import {
|
|
|
127
132
|
import type { DataPlaneAdmission } from "../auth-cors";
|
|
128
133
|
import { listOpenAiForwardSidecarCandidates, resolveFirstUsableOpenAiSidecar, type ResolvedOpenAiForwardSidecar } from "../../providers/openai-sidecar";
|
|
129
134
|
import { CODEX_FORWARD_BASE_URL, isCanonicalOpenAiForwardProvider, supportsNativeResponsesCompactEndpoint } from "../../providers/openai-tiers";
|
|
135
|
+
import { buildResponsesTransport, copyResponsesClientMetadata } from "../../adapters/openai-responses/transport";
|
|
136
|
+
import { scrubOcxCompactionItems, stripCanonicalOnlyTopLevelFields, stripInternalChatMessageMetadataPassthrough } from "../../adapters/openai-responses/request-strips";
|
|
137
|
+
import { dropResponsesReasoningInputItems } from "../../adapters/openai-responses/reasoning";
|
|
138
|
+
import { requiresPlaintextReasoningReplay } from "../../adapters/openai-responses/passthrough";
|
|
139
|
+
import { destinationDecodesNativeCompactionBlob, isOpenAiOperatedResponsesDestination } from "../../providers/openai-tiers-destination";
|
|
140
|
+
import { bindRouteReasoningReplayScope } from "./core-replay";
|
|
141
|
+
import { commitReasoningReplayServingIdentity } from "../../responses/reasoning-replay-cache";
|
|
142
|
+
import { nativeCompactResponseSchema } from "../../responses/schema";
|
|
130
143
|
import { NATIVE_RESERVE_MODEL } from "../../codex/catalog/native-models";
|
|
131
144
|
import {
|
|
132
145
|
isCodexReserveOptInMissing,
|
|
@@ -151,7 +164,11 @@ import {
|
|
|
151
164
|
UnsupportedContentEncodingError,
|
|
152
165
|
} from "../request-decompress";
|
|
153
166
|
import { resolveAdapter, resolveWireProtocolOverride } from "../adapter-resolve";
|
|
154
|
-
import { hasKeyPoolFailover, rotateProviderTransportOn429, selectProactiveApiKeyTransport } from "../../providers/key-failover";
|
|
167
|
+
import { hasKeyPoolFailover, rotateProviderTransportOn429, selectProactiveApiKeyTransport, transientRetryPolicyFor } from "../../providers/key-failover";
|
|
168
|
+
import { transientSendCapFor } from "./request-send-budget";
|
|
169
|
+
import { distributedKeyPhysicalFetch, recordDistributedResponseFailure } from "../../providers/api-key-response-ownership";
|
|
170
|
+
import { providerApiKeySelectionIsCurrent, resolveCurrentProviderApiKeyTransport } from "../../providers/api-key-selection";
|
|
171
|
+
import { stampApiKeyAccountLabel } from "../../providers/label";
|
|
155
172
|
import { shouldAttemptImageTierRetry } from "../image-retry";
|
|
156
173
|
import { resolveProviderTransport } from "../../providers/xai-transport";
|
|
157
174
|
import type { WsData } from "../ws-bridge";
|
|
@@ -168,6 +185,7 @@ import {
|
|
|
168
185
|
catalogModelSupportsServiceTier,
|
|
169
186
|
finishRequestAttempt,
|
|
170
187
|
inspectResponseLogJson,
|
|
188
|
+
noteProviderAttemptSend,
|
|
171
189
|
readConfiguredCodexServiceTier,
|
|
172
190
|
requestLogSpeedLabel,
|
|
173
191
|
sealRequestAttemptIdentity,
|
|
@@ -199,10 +217,11 @@ import {
|
|
|
199
217
|
upstreamHostCircuitOpenResponse,
|
|
200
218
|
usesCodexForwardPoolAuth,
|
|
201
219
|
} from "./core";
|
|
202
|
-
import { fetchWithHeaderTimeout, providerFetch, safeHostLabel, safeOriginLabel } from "./fetch-helpers";
|
|
220
|
+
import { fetchWithHeaderTimeout, providerFetch, safeHostLabel, safeOriginLabel, sendWithConnectionPolicy } from "./fetch-helpers";
|
|
221
|
+
import { requestPacingOverloadResponse } from "./pacing-overload";
|
|
203
222
|
import { shouldRetryCodexPoolAccountQuota, shouldRetryCodexPoolAccountTransient } from "./core-codex-account";
|
|
204
223
|
import { mapCodexAuthContextErrorToResponse, nativeMainRefreshFailureResponse } from "./codex-auth-error";
|
|
205
|
-
import { linkRequestSessionLane, sessionLaneIdFromRequest } from "../request-log-conversation";
|
|
224
|
+
import { linkRequestSessionLane, sessionLaneIdFromRequest, reasoningReplayConversationIdFromResponsesRequest, sessionIdHeaderFromRequest } from "../request-log-conversation";
|
|
206
225
|
import { recallComboForLane } from "./combo-session-recall";
|
|
207
226
|
import { redactHostedImageDisplayPaths } from "../responses-hosted-image-display";
|
|
208
227
|
|
|
@@ -719,7 +738,8 @@ export async function handleResponsesCompact(
|
|
|
719
738
|
// reads the account namespace as a routed provider prefix — so keying on `raw.model` sent
|
|
720
739
|
// exactly the selector form back down the native compact endpoint this guard exists to avoid.
|
|
721
740
|
// `route.modelId` is the same value `applyCodexAccountGatedWireNormalization` uses in core.ts.
|
|
722
|
-
const accountGatedCompactWireModel =
|
|
741
|
+
const accountGatedCompactWireModel = isCanonicalOpenAiForwardProvider(route.provider)
|
|
742
|
+
? codexAccountGatedCanonicalWireModel(selectedModelId) : undefined;
|
|
723
743
|
logCtx.requestedModel = compactRequestedModel;
|
|
724
744
|
logCtx.model = selectedModelId;
|
|
725
745
|
logCtx.routeDecision = route.routeDecision;
|
|
@@ -799,8 +819,8 @@ export async function handleResponsesCompact(
|
|
|
799
819
|
}
|
|
800
820
|
|
|
801
821
|
// Native /responses/compact exists on the canonical ChatGPT backend and on the
|
|
802
|
-
// official OpenAI API
|
|
803
|
-
//
|
|
822
|
+
// official OpenAI API, or a gateway explicitly declaring the native endpoint. Other
|
|
823
|
+
// Responses-shaped gateways still take the routed summarizer path (#422).
|
|
804
824
|
// Combo-resolved targets skip native compact so failover can advance through the
|
|
805
825
|
// combo target list when the picked model returns 429/5xx — the routed path below
|
|
806
826
|
// dispatches through handleResponses → handleComboResponses with full failover.
|
|
@@ -839,7 +859,7 @@ export async function handleResponsesCompact(
|
|
|
839
859
|
compactHostAdmissionLease = admission.lease;
|
|
840
860
|
}
|
|
841
861
|
try {
|
|
842
|
-
// Native
|
|
862
|
+
// Native-capable destination: forward the compact request to its configured backend.
|
|
843
863
|
// Resolve the SAME pool/thread auth context as /v1/responses — forwarding the caller's raw
|
|
844
864
|
// headers would run compaction on the wrong account (or 401) whenever a pool account is
|
|
845
865
|
// active for this thread while normal turns succeed.
|
|
@@ -906,19 +926,35 @@ export async function handleResponsesCompact(
|
|
|
906
926
|
// codexAccountMode cases, and the picker returns null for them anyway.
|
|
907
927
|
// Transport variant, for the same reason as the Responses core: the persisted row has no
|
|
908
928
|
// registry backfills, and compactProvider is read for its transport fields below.
|
|
909
|
-
|
|
929
|
+
let warmKeyProvider;
|
|
930
|
+
try { warmKeyProvider = selectProactiveApiKeyTransport(config, route.providerName, compactProvider); }
|
|
931
|
+
catch (error) {
|
|
932
|
+
const refusal = requestPacingOverloadResponse(error);
|
|
933
|
+
if (refusal) return refusal;
|
|
934
|
+
throw error;
|
|
935
|
+
}
|
|
910
936
|
if (warmKeyProvider?.apiKey) compactProvider = warmKeyProvider;
|
|
911
937
|
headers.set("authorization", `Bearer ${resolveProviderApiKey(compactProvider.apiKey)}`);
|
|
938
|
+
stampApiKeyAccountLabel(logCtx, route.providerName, compactProvider);
|
|
912
939
|
}
|
|
913
940
|
const { reasoning: _reasoning, ...compactBodyRaw } = raw as typeof raw & { reasoning?: unknown };
|
|
941
|
+
const requireCompactPlaintextReasoning = !isOpenAiOperatedResponsesDestination(compactProvider)
|
|
942
|
+
&& requiresPlaintextReasoningReplay(compactProvider);
|
|
914
943
|
// The regular /v1/responses path applies sanitizeReasoningInputContent via the adapter's
|
|
915
944
|
// buildRequest, but the compact endpoint forwards directly. Apply the same sanitizer here
|
|
916
|
-
// so
|
|
945
|
+
// so canonical reasoning stays blank and custom gateways honor their explicit plaintext policy.
|
|
917
946
|
// #5095: the compact endpoint forwards `raw` directly, so it needs the same legacy dotted
|
|
918
947
|
// call-name repair the adapter applies. A damaged item here refuses the compaction itself,
|
|
919
948
|
// which is the request a long task depends on to keep going.
|
|
949
|
+
// This branch is OpenAI-only and bypasses the adapter's Google replay projection (#7111).
|
|
920
950
|
const compactBody = repairLegacyDottedToolCallNames(
|
|
921
|
-
sanitizeReasoningInputContent(compactBodyRaw
|
|
951
|
+
stripProviderInputItemMetadata(sanitizeReasoningInputContent(compactBodyRaw, {
|
|
952
|
+
preserveRawReasoningContent: !isOpenAiOperatedResponsesDestination(compactProvider)
|
|
953
|
+
&& compactProvider.preserveResponsesReasoningContent === true,
|
|
954
|
+
dropNullContentChannel: !isOpenAiOperatedResponsesDestination(compactProvider),
|
|
955
|
+
stripEncryptedContent: requireCompactPlaintextReasoning,
|
|
956
|
+
requirePlaintextReasoning: requireCompactPlaintextReasoning,
|
|
957
|
+
})),
|
|
922
958
|
) as typeof compactBodyRaw;
|
|
923
959
|
{
|
|
924
960
|
const binding = conversationStateBindingFromAuth(authCtx, codexPoolAffinityKey(req.headers));
|
|
@@ -945,9 +981,30 @@ export async function handleResponsesCompact(
|
|
|
945
981
|
});
|
|
946
982
|
}
|
|
947
983
|
}
|
|
948
|
-
|
|
984
|
+
// Gateway protocol capability never grants official credential authority. Reuse the normal
|
|
985
|
+
// Responses transport for custom paths, static auth and explicitly forwarded client metadata.
|
|
986
|
+
const gatewayTransport = !isCanonicalOpenAiForwardProvider(compactProvider)
|
|
987
|
+
&& compactProvider.supportsNativeCompactEndpoint === true
|
|
988
|
+
? buildResponsesTransport({ ...compactProvider, apiKey: compactProvider.apiKey ? resolveProviderApiKey(compactProvider.apiKey) : undefined }, { headers: req.headers })
|
|
989
|
+
: undefined;
|
|
990
|
+
if (gatewayTransport) headers = new Headers(gatewayTransport.headers);
|
|
991
|
+
// Native v1 bypasses the adapter serializer. Bind the same replay provenance as ordinary
|
|
992
|
+
// turns, but leave canonical compact's established account-state handling unchanged.
|
|
993
|
+
const gatewayReplayParsed = gatewayTransport ? parseRequest(raw) : undefined;
|
|
994
|
+
if (gatewayReplayParsed) {
|
|
995
|
+
gatewayReplayParsed.modelId = route.modelId;
|
|
996
|
+
gatewayReplayParsed._clientThreadId = req.headers.get("x-codex-parent-thread-id")?.trim() || undefined;
|
|
997
|
+
const clientThreadId = reasoningReplayConversationIdFromResponsesRequest({
|
|
998
|
+
clientThreadId: gatewayReplayParsed._clientThreadId,
|
|
999
|
+
threadIdHeader: req.headers.get("thread-id"),
|
|
1000
|
+
cursorConversationId: gatewayReplayParsed._cursorConversationId,
|
|
1001
|
+
sessionIdHeader: sessionIdHeaderFromRequest(req.headers),
|
|
1002
|
+
});
|
|
1003
|
+
if (clientThreadId) gatewayReplayParsed._reasoningReplayScope = { clientThreadId };
|
|
1004
|
+
}
|
|
1005
|
+
let compactUrl = gatewayTransport ? `${gatewayTransport.url.replace(/\/+$/, "")}/compact` : `${base}/responses/compact`;
|
|
949
1006
|
const compactTargetKey = `${route.providerName}|${route.modelId}|compact`;
|
|
950
|
-
|
|
1007
|
+
let actualCompactHostKey = upstreamHostHealthKey(
|
|
951
1008
|
route.providerName,
|
|
952
1009
|
safeOriginLabel(compactUrl),
|
|
953
1010
|
);
|
|
@@ -999,7 +1056,8 @@ export async function handleResponsesCompact(
|
|
|
999
1056
|
promoteAccountId?: string;
|
|
1000
1057
|
} = {},
|
|
1001
1058
|
) => {
|
|
1002
|
-
|
|
1059
|
+
// A gateway response is not evidence about any Codex account.
|
|
1060
|
+
if (gatewayTransport || !usesCodexForwardPoolAuth(ctx, route.provider)) return;
|
|
1003
1061
|
recordCodexUpstreamOutcome(config, ctx.accountId, outcome, {
|
|
1004
1062
|
...meta,
|
|
1005
1063
|
threadId: ctx.kind === "pool" || ctx.kind === "main-pool" ? ctx.affinityKey : undefined,
|
|
@@ -1038,7 +1096,7 @@ export async function handleResponsesCompact(
|
|
|
1038
1096
|
try {
|
|
1039
1097
|
if (report.beforeSend?.() === false) throw new SendBudgetExhaustedError(safeHostLabel(compactUrl));
|
|
1040
1098
|
started = true;
|
|
1041
|
-
return await doFetch();
|
|
1099
|
+
return await (report.execute ? report.execute(doFetch) : doFetch());
|
|
1042
1100
|
} finally {
|
|
1043
1101
|
try { if (started) report(1); }
|
|
1044
1102
|
finally { report.close?.(); }
|
|
@@ -1049,12 +1107,14 @@ export async function handleResponsesCompact(
|
|
|
1049
1107
|
targetKey: compactTargetKey,
|
|
1050
1108
|
});
|
|
1051
1109
|
if (!decision.allowed) return Promise.reject(new SendBudgetExhaustedError(safeHostLabel(compactUrl)));
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1110
|
+
const dispatch = () => {
|
|
1111
|
+
if (!decision.permit.use()) {
|
|
1112
|
+
decision.permit.release();
|
|
1113
|
+
throw new SendBudgetExhaustedError(safeHostLabel(compactUrl));
|
|
1114
|
+
}
|
|
1115
|
+
return doFetch();
|
|
1116
|
+
};
|
|
1117
|
+
return decision.permit.execute ? decision.permit.execute(dispatch) : dispatch();
|
|
1058
1118
|
};
|
|
1059
1119
|
const sendCompactAttempt = (
|
|
1060
1120
|
sendProvider: OcxProviderConfig,
|
|
@@ -1063,12 +1123,42 @@ export async function handleResponsesCompact(
|
|
|
1063
1123
|
sendAuthCtx: CodexAuthContext,
|
|
1064
1124
|
onPhysicalDispatch?: () => void,
|
|
1065
1125
|
): Promise<Response> => {
|
|
1126
|
+
// A gateway send carries only its own transport headers, whatever recovery handed in.
|
|
1127
|
+
let wireHeaders = gatewayTransport ? new Headers(gatewayTransport.headers) : sendHeaders;
|
|
1128
|
+
sendHeaders = wireHeaders;
|
|
1129
|
+
const buildCompactSendBody = () => {
|
|
1130
|
+
if (gatewayReplayParsed) bindRouteReasoningReplayScope({
|
|
1131
|
+
parsed: gatewayReplayParsed, providerName: route.providerName, provider: sendProvider,
|
|
1132
|
+
adapterName: sendProvider.adapter, codexAuthContext: sendAuthCtx, forwardHeaders: wireHeaders,
|
|
1133
|
+
});
|
|
1134
|
+
const requireSendPlaintextReasoning = !isOpenAiOperatedResponsesDestination(sendProvider)
|
|
1135
|
+
&& requiresPlaintextReasoningReplay(sendProvider);
|
|
1136
|
+
let replayBody = gatewayReplayParsed ? sanitizeReasoningInputContent(scrubOcxCompactionItems(
|
|
1137
|
+
compactBody, destinationDecodesNativeCompactionBlob(sendProvider),
|
|
1138
|
+
gatewayReplayParsed._stripReasoningEncryptedContent === true,
|
|
1139
|
+
), {
|
|
1140
|
+
preserveRawReasoningContent: !isOpenAiOperatedResponsesDestination(sendProvider)
|
|
1141
|
+
&& sendProvider.preserveResponsesReasoningContent === true,
|
|
1142
|
+
dropNullContentChannel: !isOpenAiOperatedResponsesDestination(sendProvider),
|
|
1143
|
+
stripEncryptedContent: gatewayReplayParsed._stripReasoningEncryptedContent === true || requireSendPlaintextReasoning,
|
|
1144
|
+
dropForeignItemId: gatewayReplayParsed._dropForeignReasoningItemIds === true,
|
|
1145
|
+
requirePlaintextReasoning: requireSendPlaintextReasoning,
|
|
1146
|
+
}) : compactBody;
|
|
1147
|
+
if (!isOpenAiOperatedResponsesDestination(sendProvider) && sendProvider.dropResponsesReasoningItems === true) {
|
|
1148
|
+
replayBody = dropResponsesReasoningInputItems(replayBody) as typeof replayBody;
|
|
1149
|
+
}
|
|
1150
|
+
if (!isOpenAiOperatedResponsesDestination(sendProvider) && sendProvider.preserveResponsesMessageMetadata !== true) {
|
|
1151
|
+
replayBody = stripInternalChatMessageMetadataPassthrough(replayBody) as typeof replayBody;
|
|
1152
|
+
}
|
|
1153
|
+
return isOpenAiOperatedResponsesDestination(sendProvider)
|
|
1154
|
+
? replayBody : stripCanonicalOnlyTopLevelFields(replayBody);
|
|
1155
|
+
};
|
|
1066
1156
|
const doFetch = (upstreamRecovery?: UpstreamSendRecovery) => fetchWithHeaderTimeout(
|
|
1067
1157
|
compactUrl,
|
|
1068
1158
|
applyUpstreamRecoveryInit({
|
|
1069
1159
|
method: "POST",
|
|
1070
|
-
headers:
|
|
1071
|
-
body: JSON.stringify({ ...compactBody, model: route.modelId }),
|
|
1160
|
+
headers: wireHeaders,
|
|
1161
|
+
body: JSON.stringify({ ...buildCompactSendBody() as typeof compactBody, model: route.modelId }),
|
|
1072
1162
|
}, upstreamRecovery),
|
|
1073
1163
|
req.signal,
|
|
1074
1164
|
connectMs,
|
|
@@ -1078,6 +1168,47 @@ export async function handleResponsesCompact(
|
|
|
1078
1168
|
modelId: route.modelId,
|
|
1079
1169
|
beforeDispatch: isCanonicalOpenAiForwardProvider(sendProvider)
|
|
1080
1170
|
? createCodexAuthDispatchGuard(sendAuthCtx, config, selectedModelId, admission) : undefined,
|
|
1171
|
+
dispatchOverride: sendProvider.authMode !== "forward" && sendProvider.apiKey
|
|
1172
|
+
? async (_input, init, execute, reacquirePacing) => {
|
|
1173
|
+
for (let attempt = 0; !providerApiKeySelectionIsCurrent(config, route.providerName, sendProvider); attempt++) {
|
|
1174
|
+
if (attempt >= 3) throw new Error("Provider key selection changed repeatedly before compact dispatch");
|
|
1175
|
+
let current = resolveCurrentProviderApiKeyTransport(config, route.providerName, sendProvider);
|
|
1176
|
+
if (!current && sendProvider.requestPacing?.enabled
|
|
1177
|
+
&& (sendProvider.requestPacing.keys || sendProvider.requestPacing.keySelection === "round-robin")) {
|
|
1178
|
+
const fresh = resolveCurrentProviderApiKeyTransport(config, route.providerName, { ...sendProvider, _apiKeyAttempt: undefined });
|
|
1179
|
+
if (fresh) current = selectProactiveApiKeyTransport(config, route.providerName, fresh) ?? fresh;
|
|
1180
|
+
}
|
|
1181
|
+
if (!current?.apiKey || !supportsNativeResponsesCompactEndpoint(route.providerName, current)) {
|
|
1182
|
+
throw new Error("Provider key selection is no longer available for compact");
|
|
1183
|
+
}
|
|
1184
|
+
const rebuilt = new Headers(sendHeaders);
|
|
1185
|
+
for (const name of Object.keys(sendProvider.headers ?? {})) rebuilt.delete(name);
|
|
1186
|
+
for (const [name, value] of Object.entries(current.headers ?? {})) rebuilt.set(name, value);
|
|
1187
|
+
rebuilt.set("authorization", `Bearer ${resolveProviderApiKey(current.apiKey)}`);
|
|
1188
|
+
const currentGateway = gatewayTransport
|
|
1189
|
+
? buildResponsesTransport({ ...current, apiKey: resolveProviderApiKey(current.apiKey) }, { headers: req.headers })
|
|
1190
|
+
: undefined;
|
|
1191
|
+
sendHeaders = wireHeaders = headers = currentGateway ? new Headers(currentGateway.headers) : rebuilt;
|
|
1192
|
+
sendProvider = compactProvider = current;
|
|
1193
|
+
compactUrl = currentGateway ? `${currentGateway.url.replace(/\/+$/, "")}/compact`
|
|
1194
|
+
: `${(current.baseUrl ?? "").replace(/\/+$/, "")}/responses/compact`;
|
|
1195
|
+
actualCompactHostKey = upstreamHostHealthKey(route.providerName, safeOriginLabel(compactUrl));
|
|
1196
|
+
if (current.requestPacing?.enabled
|
|
1197
|
+
&& (current.requestPacing.keys || current.requestPacing.keySelection === "round-robin")) {
|
|
1198
|
+
if (!reacquirePacing) throw new Error("Refreshed compact dispatch requires pacing readmission");
|
|
1199
|
+
await reacquirePacing(current, route.modelId);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (req.signal.aborted) throw req.signal.reason;
|
|
1203
|
+
stampApiKeyAccountLabel(logCtx, route.providerName, sendProvider);
|
|
1204
|
+
if (!rebindPhysicalSend(sendBudget, { poolId: logCtx.spendPoolId ?? route.providerName,
|
|
1205
|
+
identityId: logCtx.accountLogLabel })) throw new SendBudgetExhaustedError(safeHostLabel(compactUrl));
|
|
1206
|
+
noteProviderAttemptSend(logCtx, route.providerName, sendProvider, logCtx.usageLogInputTokens, upstreamRecovery);
|
|
1207
|
+
return sendWithConnectionPolicy(
|
|
1208
|
+
distributedKeyPhysicalFetch((sendProvider as { fetch?: typeof fetch }).fetch ?? execute, route.providerName, sendProvider, compactUrl), compactUrl,
|
|
1209
|
+
{ ...init, headers: sendHeaders, body: JSON.stringify({ ...buildCompactSendBody() as typeof compactBody, model: route.modelId }) },
|
|
1210
|
+
{ providerName: route.providerName, provider: sendProvider });
|
|
1211
|
+
} : undefined,
|
|
1081
1212
|
...(onPhysicalDispatch ? { onPhysicalDispatch } : {}),
|
|
1082
1213
|
}),
|
|
1083
1214
|
// Every credential-bearing forward send gets manual redirects, not only
|
|
@@ -1094,9 +1225,11 @@ export async function handleResponsesCompact(
|
|
|
1094
1225
|
: fetchWithTransientRetry(doFetch, {
|
|
1095
1226
|
abortSignal: req.signal,
|
|
1096
1227
|
label: safeHostLabel(compactUrl),
|
|
1097
|
-
//
|
|
1098
|
-
//
|
|
1099
|
-
attempts: sendBudget.remainingBaseSends(
|
|
1228
|
+
// Key-auth policy is a logical-request total; canonical forward keeps its default
|
|
1229
|
+
// ladder. Both intersect the same shared base remainder as ordinary Responses.
|
|
1230
|
+
attempts: sendBudget.remainingBaseSends(transientSendCapFor(
|
|
1231
|
+
transientRetryPolicyFor(sendProvider)?.attempts, sendBudget.used,
|
|
1232
|
+
)),
|
|
1100
1233
|
onSendsConsumed: createPhysicalSendReporter(sendBudget, () => ({ poolId: logCtx.spendPoolId ?? route.providerName,
|
|
1101
1234
|
identityId: logCtx.accountLogLabel })),
|
|
1102
1235
|
});
|
|
@@ -1107,7 +1240,7 @@ export async function handleResponsesCompact(
|
|
|
1107
1240
|
let outcomeCtx = authCtx;
|
|
1108
1241
|
const localDispatchRefusal = (error: unknown): Response | undefined => {
|
|
1109
1242
|
const cause = unwrapUpstreamRetryEvidenceError(error);
|
|
1110
|
-
const response = spendLedgerStorageErrorResponse(error, logCtx) ?? (cause instanceof SendBudgetExhaustedError
|
|
1243
|
+
const response = requestPacingOverloadResponse(cause) ?? spendLedgerStorageErrorResponse(error, logCtx) ?? (cause instanceof SendBudgetExhaustedError
|
|
1111
1244
|
? unboundPoolSpendRefusalResponse(logCtx) ?? formatErrorResponse(429, "request_send_budget_exhausted", cause.message)
|
|
1112
1245
|
: mapCodexAuthContextErrorToResponse(cause, {
|
|
1113
1246
|
now: Date.now(), accountSelector: route.codexAccountNamespace,
|
|
@@ -1152,9 +1285,11 @@ export async function handleResponsesCompact(
|
|
|
1152
1285
|
return formatErrorResponse(502, "upstream_error", "Failed to connect to compact upstream");
|
|
1153
1286
|
}
|
|
1154
1287
|
|
|
1288
|
+
recordDistributedResponseFailure(upstream, config, route.providerName, req.signal);
|
|
1155
1289
|
if (
|
|
1156
1290
|
upstream.status === 401
|
|
1157
1291
|
&& (authCtx.kind === "main-pool" || authCtx.kind === "pool")
|
|
1292
|
+
&& !gatewayTransport
|
|
1158
1293
|
&& usesCodexForwardPoolAuth(authCtx, compactProvider)
|
|
1159
1294
|
&& !req.signal.aborted
|
|
1160
1295
|
) {
|
|
@@ -1282,6 +1417,7 @@ export async function handleResponsesCompact(
|
|
|
1282
1417
|
// compact turn that may already have been processed.
|
|
1283
1418
|
&& !isNonReplayableResponse(upstream)
|
|
1284
1419
|
&& !storedPool401ReplayAttempted
|
|
1420
|
+
&& !gatewayTransport
|
|
1285
1421
|
&& usesCodexForwardPoolAuth(authCtx, route.provider)
|
|
1286
1422
|
&& !authCtx.fixedAccount
|
|
1287
1423
|
&& route.codexAccountMode
|
|
@@ -1386,6 +1522,7 @@ export async function handleResponsesCompact(
|
|
|
1386
1522
|
if (
|
|
1387
1523
|
shouldRetryCodexPoolAccountTransient(upstream)
|
|
1388
1524
|
&& !storedPool401ReplayAttempted
|
|
1525
|
+
&& !gatewayTransport
|
|
1389
1526
|
&& usesCodexForwardPoolAuth(authCtx, route.provider)
|
|
1390
1527
|
&& !authCtx.fixedAccount
|
|
1391
1528
|
&& route.codexAccountMode
|
|
@@ -1494,7 +1631,15 @@ export async function handleResponsesCompact(
|
|
|
1494
1631
|
// request log; the routed branch gets the same through handleResponses. The
|
|
1495
1632
|
// synthetic buffer errors are not upstream bodies and stay uninspected.
|
|
1496
1633
|
if (buffered.ok) {
|
|
1497
|
-
|
|
1634
|
+
const compactText = await buffered.clone().text();
|
|
1635
|
+
if (gatewayReplayParsed) {
|
|
1636
|
+
try {
|
|
1637
|
+
if (nativeCompactResponseSchema.safeParse(JSON.parse(compactText)).success) {
|
|
1638
|
+
commitReasoningReplayServingIdentity(gatewayReplayParsed._reasoningReplayScope);
|
|
1639
|
+
}
|
|
1640
|
+
} catch { /* A malformed body cannot establish serving provenance. */ }
|
|
1641
|
+
}
|
|
1642
|
+
inspectResponseLogJson(logCtx, compactText);
|
|
1498
1643
|
if (!options.compactionRoutingOverride) forgetCompactHandoffRoute(req, admission);
|
|
1499
1644
|
rememberServingConversationStateIssuer(outcomeCtx, codexPoolAffinityKey(req.headers));
|
|
1500
1645
|
} else if (!options.compactionRoutingOverride && quotaFailure && !storedPool401ReplayAttempted) {
|
|
@@ -1535,112 +1680,145 @@ export async function handleResponsesCompact(
|
|
|
1535
1680
|
|
|
1536
1681
|
// ROUTED model: run the v2 synthetic-compaction turn internally (appends COMPACT_PROMPT, no
|
|
1537
1682
|
// tools) and decode the resulting ocx1 envelope into plain v1 replacement-history items.
|
|
1538
|
-
const
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
const
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
let json: { output?: unknown[]; status?: unknown; error?: unknown };
|
|
1566
|
-
if (response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
1567
|
-
if (!response.body) {
|
|
1568
|
-
return formatErrorResponse(502, "server_error", "compaction turn returned an empty event stream");
|
|
1683
|
+
const rawInputItems = Array.isArray(raw.input) ? (raw.input as unknown[]) : [];
|
|
1684
|
+
// Keep readable reasoning local until success, outside the summarizer request.
|
|
1685
|
+
// Opaque compaction responses below keep their original protocol and ciphertext.
|
|
1686
|
+
const retention: ReasoningRetentionResult = config.reasoningRetention?.enabled === true
|
|
1687
|
+
&& !isCanonicalOpenAiForwardProvider(route.provider) ? applyReasoningRetention(rawInputItems, {
|
|
1688
|
+
archiveDir: join(getConfigDir(), "reasoning-archive"),
|
|
1689
|
+
contextWindow: route.staticPolicy.model.contextWindow ?? route.staticPolicy.model.maxInputTokens,
|
|
1690
|
+
maxContextPercent: config.reasoningRetention?.maxContextPercent,
|
|
1691
|
+
maxTokens: config.reasoningRetention?.maxTokens,
|
|
1692
|
+
}) : { input: rawInputItems };
|
|
1693
|
+
let keepArchive = false;
|
|
1694
|
+
try {
|
|
1695
|
+
const inputItems = retention.input;
|
|
1696
|
+
const internalBody = {
|
|
1697
|
+
...raw,
|
|
1698
|
+
// Canonical ChatGPT Responses rejects non-streaming turns. Daybreak cannot use the
|
|
1699
|
+
// native compact endpoint either, so run its synthetic compaction as SSE and collapse
|
|
1700
|
+
// the completed event back into the v1 compact JSON contract below. Combo-dispatched
|
|
1701
|
+
// turns also go out as SSE: failover can land on a canonical child that rejects a
|
|
1702
|
+
// non-streaming turn, and every combo-capable provider already serves streaming traffic.
|
|
1703
|
+
stream: isCanonicalOpenAiForwardProvider(route.provider) || accountGatedCompactWireModel || route.combo ? true : false,
|
|
1704
|
+
input: [...inputItems, { type: "compaction_trigger" }],
|
|
1705
|
+
};
|
|
1706
|
+
const internalHeaders = new Headers({ "content-type": "application/json" });
|
|
1707
|
+
for (const name of FORWARD_HEADERS) {
|
|
1708
|
+
const value = req.headers.get(name);
|
|
1709
|
+
if (value) internalHeaders.set(name, value);
|
|
1569
1710
|
}
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
status => { terminal.status = status; },
|
|
1576
|
-
req.signal,
|
|
1577
|
-
resolve,
|
|
1578
|
-
undefined,
|
|
1579
|
-
undefined,
|
|
1580
|
-
value => { completed = value; },
|
|
1581
|
-
);
|
|
1711
|
+
copyResponsesClientMetadata(internalHeaders, req.headers, route.provider);
|
|
1712
|
+
const internalReq = new Request("http://localhost/v1/responses", {
|
|
1713
|
+
method: "POST",
|
|
1714
|
+
headers: internalHeaders,
|
|
1715
|
+
body: JSON.stringify(internalBody),
|
|
1582
1716
|
});
|
|
1717
|
+
linkRequestSessionLane(req, internalReq);
|
|
1718
|
+
// The routed compaction turn is a handoff inside the same logical request, so it draws the
|
|
1719
|
+
// REMAINDER. Minting here is what let a native attempt spend three sends and the routed
|
|
1720
|
+
// fallback spend four more.
|
|
1721
|
+
const response = await handleResponses(internalReq, config, logCtx, { abortSignal: req.signal, turnAdmissionLease, sendBudget, compactionRecoveryKind: "compaction-v1", compactionRoutingOverride: options.compactionRoutingOverride, ...(admission ? { admission } : {}) });
|
|
1722
|
+
if (req.signal.aborted) {
|
|
1723
|
+
void response.body?.cancel(req.signal.reason).catch(() => undefined);
|
|
1724
|
+
return formatErrorResponse(499, "client_cancelled", "Client cancelled compact request");
|
|
1725
|
+
}
|
|
1726
|
+
if (!response.ok) return response;
|
|
1727
|
+
let json: { output?: unknown[]; status?: unknown; error?: unknown };
|
|
1728
|
+
if (response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
1729
|
+
if (!response.body) {
|
|
1730
|
+
return formatErrorResponse(502, "server_error", "compaction turn returned an empty event stream");
|
|
1731
|
+
}
|
|
1732
|
+
const terminal = { status: "incomplete" as "completed" | "failed" | "incomplete" };
|
|
1733
|
+
let completed: { id?: unknown; output?: unknown; status?: unknown } | undefined;
|
|
1734
|
+
await new Promise<void>(resolve => {
|
|
1735
|
+
consumeForInspection(
|
|
1736
|
+
response.body!,
|
|
1737
|
+
status => { terminal.status = status; },
|
|
1738
|
+
req.signal,
|
|
1739
|
+
resolve,
|
|
1740
|
+
undefined,
|
|
1741
|
+
undefined,
|
|
1742
|
+
value => { completed = value; },
|
|
1743
|
+
);
|
|
1744
|
+
});
|
|
1745
|
+
if (req.signal.aborted) {
|
|
1746
|
+
return formatErrorResponse(499, "client_cancelled", "Client cancelled compact request");
|
|
1747
|
+
}
|
|
1748
|
+
if (terminal.status !== "completed" || !completed) {
|
|
1749
|
+
return formatErrorResponse(502, "upstream_error", `compaction turn did not complete (status: ${terminal.status})`);
|
|
1750
|
+
}
|
|
1751
|
+
json = completed as { output?: unknown[]; status?: unknown; error?: unknown };
|
|
1752
|
+
} else {
|
|
1753
|
+
try {
|
|
1754
|
+
json = await response.json() as { output?: unknown[]; status?: unknown; error?: unknown };
|
|
1755
|
+
} catch {
|
|
1756
|
+
return formatErrorResponse(502, "server_error", "compaction turn returned a non-JSON response");
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1583
1759
|
if (req.signal.aborted) {
|
|
1584
1760
|
return formatErrorResponse(499, "client_cancelled", "Client cancelled compact request");
|
|
1585
1761
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1762
|
+
// The internal turn answers 200 even when it failed or was truncated, so the body
|
|
1763
|
+
// has to be inspected. Reporting a failure beats installing "(no summary
|
|
1764
|
+
// available)" as replacement history and silently losing the conversation (#422).
|
|
1765
|
+
if (json.error) {
|
|
1766
|
+
const message = typeof json.error === "string"
|
|
1767
|
+
? json.error
|
|
1768
|
+
: (json.error as { message?: unknown })?.message;
|
|
1769
|
+
return formatErrorResponse(502, "upstream_error", typeof message === "string" ? message : "compaction turn failed");
|
|
1588
1770
|
}
|
|
1589
|
-
json
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1771
|
+
if (json.status !== "completed") {
|
|
1772
|
+
return formatErrorResponse(
|
|
1773
|
+
502,
|
|
1774
|
+
"upstream_error",
|
|
1775
|
+
`compaction turn did not complete (status: ${String(json.status ?? "unknown")})`,
|
|
1776
|
+
);
|
|
1595
1777
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
// available)" as replacement history and silently losing the conversation (#422).
|
|
1600
|
-
if (json.error) {
|
|
1601
|
-
const message = typeof json.error === "string"
|
|
1602
|
-
? json.error
|
|
1603
|
-
: (json.error as { message?: unknown })?.message;
|
|
1604
|
-
return formatErrorResponse(502, "upstream_error", typeof message === "string" ? message : "compaction turn failed");
|
|
1605
|
-
}
|
|
1606
|
-
if (json.status !== "completed") {
|
|
1607
|
-
return formatErrorResponse(
|
|
1608
|
-
502,
|
|
1609
|
-
"upstream_error",
|
|
1610
|
-
`compaction turn did not complete (status: ${String(json.status ?? "unknown")})`,
|
|
1611
|
-
);
|
|
1612
|
-
}
|
|
1613
|
-
const compactionItems = (json.output ?? []).filter(
|
|
1614
|
-
(item): item is { type: string; encrypted_content?: string } =>
|
|
1615
|
-
!!item && typeof item === "object" && (item as { type?: string }).type === "compaction",
|
|
1616
|
-
);
|
|
1617
|
-
if (compactionItems.length !== 1) {
|
|
1618
|
-
return formatErrorResponse(
|
|
1619
|
-
502,
|
|
1620
|
-
"invalid_response_error",
|
|
1621
|
-
`compaction turn produced ${compactionItems.length} compaction items, expected exactly 1`,
|
|
1778
|
+
const compactionItems = (json.output ?? []).filter(
|
|
1779
|
+
(item): item is { type: string; encrypted_content?: string } =>
|
|
1780
|
+
!!item && typeof item === "object" && (item as { type?: string }).type === "compaction",
|
|
1622
1781
|
);
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1782
|
+
if (compactionItems.length !== 1) {
|
|
1783
|
+
return formatErrorResponse(
|
|
1784
|
+
502,
|
|
1785
|
+
"invalid_response_error",
|
|
1786
|
+
`compaction turn produced ${compactionItems.length} compaction items, expected exactly 1`,
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
// Native Responses backends return a real opaque OpenAI-encrypted compaction item. OCX cannot
|
|
1790
|
+
// and should not decrypt it; preserve that item for /responses/compact callers. Synthetic
|
|
1791
|
+
// routed summaries are our `ocx1:` envelope and must be decoded into v1 history items.
|
|
1792
|
+
if (typeof compactionItems[0]!.encrypted_content === "string"
|
|
1793
|
+
&& compactionItems[0]!.encrypted_content.trim().length > 0
|
|
1794
|
+
&& !compactionItems[0]!.encrypted_content.startsWith("ocx1:")) {
|
|
1795
|
+
// Failover may reach native compaction after readable reasoning was withheld locally.
|
|
1796
|
+
// The opaque item cannot contain that text, so publish it (or its archive notice) alongside.
|
|
1797
|
+
const output = [...compactionItems, ...(retention.retainedReasoning ?? [])];
|
|
1798
|
+
if (retention.archived) output.push({ type: "message", role: "user",
|
|
1799
|
+
content: [{ type: "input_text", text: appendRetentionNotice("", retention.archived).trimStart() }] });
|
|
1800
|
+
const result = new Response(JSON.stringify({ output }), {
|
|
1801
|
+
headers: { "Content-Type": "application/json" },
|
|
1802
|
+
});
|
|
1803
|
+
if (!options.compactionRoutingOverride) rememberCompactHandoffRoute(req, admission, raw.model);
|
|
1804
|
+
keepArchive = true;
|
|
1805
|
+
return result;
|
|
1806
|
+
}
|
|
1807
|
+
const encrypted = compactionItems[0]!.encrypted_content;
|
|
1808
|
+
const decoded = typeof encrypted === "string" ? decodeCompactionSummary(encrypted) : null;
|
|
1809
|
+
// An empty `ocx1:` envelope decodes to "" rather than null, so length is what matters.
|
|
1810
|
+
if (decoded === null || decoded.trim().length === 0) {
|
|
1811
|
+
return formatErrorResponse(502, "invalid_response_error", "compaction turn produced an empty summary");
|
|
1812
|
+
}
|
|
1813
|
+
const summary = appendRetentionNotice(decoded, retention.archived);
|
|
1814
|
+
const output = [...buildCompactV1Output(extractCompactUserMessages(inputItems), summary),
|
|
1815
|
+
...(retention.retainedReasoning ?? [])];
|
|
1633
1816
|
if (!options.compactionRoutingOverride) rememberCompactHandoffRoute(req, admission, raw.model);
|
|
1817
|
+
const result = new Response(JSON.stringify({ output }), { headers: { "Content-Type": "application/json" } });
|
|
1818
|
+
keepArchive = true;
|
|
1634
1819
|
return result;
|
|
1820
|
+
} finally {
|
|
1821
|
+
// Keep an archive only after successful replacement history publishes its path.
|
|
1822
|
+
if (!keepArchive && retention.archived) rmSync(retention.archived.path, { force: true });
|
|
1635
1823
|
}
|
|
1636
|
-
const encrypted = compactionItems[0]!.encrypted_content;
|
|
1637
|
-
const decoded = typeof encrypted === "string" ? decodeCompactionSummary(encrypted) : null;
|
|
1638
|
-
// An empty `ocx1:` envelope decodes to "" rather than null, so length is what matters.
|
|
1639
|
-
if (decoded === null || decoded.trim().length === 0) {
|
|
1640
|
-
return formatErrorResponse(502, "invalid_response_error", "compaction turn produced an empty summary");
|
|
1641
|
-
}
|
|
1642
|
-
const summary = decoded;
|
|
1643
|
-
const output = buildCompactV1Output(extractCompactUserMessages(inputItems), summary);
|
|
1644
|
-
if (!options.compactionRoutingOverride) rememberCompactHandoffRoute(req, admission, raw.model);
|
|
1645
|
-
return new Response(JSON.stringify({ output }), { headers: { "Content-Type": "application/json" } });
|
|
1646
1824
|
}
|