@bitkyc08/opencodex 2.84.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/README.md +0 -4
- package/bin/ocx.mjs +61 -3
- package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-C9TGtwad.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 -2
- package/src/adapters/anthropic/beta-allowlist.ts +91 -11
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +16 -3
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic.ts +108 -21
- package/src/adapters/azure.ts +19 -1
- 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/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +40 -11
- package/src/adapters/cursor/arg-codec.ts +17 -5
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/freeform-args.ts +75 -0
- package/src/adapters/cursor/protobuf-events.ts +36 -23
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +37 -10
- 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-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- 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 +44 -3
- 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 +223 -0
- package/src/advisor/context.ts +238 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +353 -0
- package/src/advisor/settings.ts +144 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -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/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/desktop-3p-library.ts +72 -7
- package/src/claude/desktop-3p.ts +75 -19
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +11 -0
- package/src/claude/inbound.ts +154 -50
- package/src/claude/intercept/local-ca.ts +8 -1
- 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/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +51 -19
- 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 +165 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +23 -2
- package/src/cli/capabilities-base.ts +39 -11
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +46 -6
- 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 +30 -5
- package/src/cli/doctor.ts +15 -1
- package/src/cli/ensure-desktop-cold-start.ts +382 -0
- package/src/cli/export-command.ts +1 -1
- package/src/cli/help.ts +4 -2
- package/src/cli/index.ts +14 -5
- package/src/cli/integration-plan-dto.ts +3 -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 +41 -9
- package/src/cli/restart-scope.ts +3 -1
- 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 +3 -1
- package/src/clients/config-export/model-metadata.ts +3 -2
- package/src/clients/config-export/qoder.ts +93 -0
- package/src/clients/config-export.ts +35 -48
- 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/pool-mode-gate.ts +5 -2
- 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 +81 -7
- package/src/codex/autostart-health.ts +4 -0
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +19 -2
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +4 -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 +42 -19
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +35 -86
- package/src/codex/catalog/provider-models.ts +40 -34
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +33 -16
- package/src/codex/catalog/vendor-context.ts +82 -0
- 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 +63 -4
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app/types.ts +2 -0
- package/src/codex/desktop-app/windows.ts +29 -16
- package/src/codex/desktop-app-restart.ts +25 -2
- package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
- package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
- package/src/codex/desktop-compatibility/connection-store.ts +87 -0
- package/src/codex/desktop-compatibility/installed-build.ts +73 -0
- package/src/codex/desktop-compatibility/json-body.ts +29 -0
- package/src/codex/desktop-compatibility/native-identity.ts +62 -0
- package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
- package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
- package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
- package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
- package/src/codex/desktop-compatibility/runtime.ts +182 -0
- package/src/codex/desktop-compatibility/service.ts +30 -0
- package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
- package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
- package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
- package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
- package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
- package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
- package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
- package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
- package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
- package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
- package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
- package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
- package/src/codex/features.ts +85 -45
- package/src/codex/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/remove.ts +18 -6
- 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/journal.ts +16 -1
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/main-account.ts +99 -15
- package/src/codex/native-main-refresh-mode.ts +217 -0
- 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 +2 -2
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +7 -2
- 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 +35 -7
- package/src/config/live-reconcile.ts +51 -2
- package/src/config/load-degrade.ts +23 -1
- 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/paths.ts +10 -3
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +39 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +39 -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 +962 -350
- package/src/generated/model-metadata.ts +1 -1
- 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/config-io.ts +20 -2
- package/src/integrations/journal.ts +15 -1
- package/src/integrations/mutation-plan.ts +2 -0
- package/src/integrations/registry.ts +15 -0
- package/src/integrations/store.ts +3 -0
- package/src/integrations/writer.ts +10 -1
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/background-work.ts +65 -0
- package/src/lib/bounded-file-read.ts +58 -0
- package/src/lib/bun-path-runtime.d.mts +10 -1
- package/src/lib/bun-path-runtime.mjs +62 -3
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +72 -0
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +26 -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/local-internal-call-capability.ts +55 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/redact.ts +2 -2
- package/src/lib/socks5-fetch.ts +48 -71
- package/src/lib/socks5-handshake.ts +112 -0
- package/src/lib/translator-budget.ts +7 -1
- 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 +62 -12
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +188 -111
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +7 -1
- 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/codecs/chat-ir.ts +467 -0
- package/src/protocols/codecs/chat.ts +10 -1
- package/src/protocols/codecs/messages-ir.ts +500 -0
- package/src/protocols/encoders/adapter-events.ts +5 -4
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/features.ts +10 -2
- package/src/protocols/path.ts +11 -1
- package/src/protocols/plan-snapshot.ts +39 -16
- package/src/protocols/plan.ts +9 -1
- package/src/protocols/provider-summary.ts +2 -1
- package/src/protocols/settings.ts +8 -0
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- 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 +5 -0
- package/src/providers/model-rename-migration.ts +7 -5
- 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 +44 -14
- package/src/providers/registry/entries-extended.ts +19 -0
- 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 +78 -22
- 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/responses/state.ts +32 -29
- package/src/router.ts +8 -321
- package/src/routing/health.ts +37 -22
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +254 -221
- package/src/routing/history/ingest.ts +242 -0
- package/src/routing/history/rebuild-job.ts +111 -0
- package/src/routing/history/rebuild-protocol.ts +15 -0
- package/src/routing/history/rebuild-worker.ts +34 -0
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +21 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +15 -0
- package/src/server/chat-completions.ts +82 -10
- 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 +164 -433
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- 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 +11 -3
- package/src/server/index/startup-warnings.ts +11 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +14 -11
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +202 -0
- package/src/server/management/agent-settings-routes.ts +22 -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 +4 -0
- package/src/server/management/config-routes.ts +77 -6
- package/src/server/management/context.ts +4 -0
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/desktop-compatibility-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
- package/src/server/management/model-routes.ts +13 -8
- 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/protocol-settings-patch.ts +1 -0
- 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 +21 -1
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management-api.ts +9 -1
- package/src/server/messages-antigravity-direct.ts +195 -0
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +106 -22
- 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 +13 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +48 -19
- package/src/server/responses/adapter-delivery.ts +43 -8
- package/src/server/responses/adapter-dispatch.ts +48 -4
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +368 -0
- package/src/server/responses/code-mode-collab-surface.ts +116 -8
- 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 +12 -1
- package/src/server/responses/core-combo.ts +141 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +7 -2
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +51 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +25 -6
- package/src/server/responses/core.ts +3 -116
- 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 +19 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -16
- package/src/server/responses/request-prepare.ts +144 -17
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +120 -27
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +166 -38
- package/src/server/responses/sidecar-execution.ts +98 -5
- package/src/server/responses/terminal-guard.ts +4 -17
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +16 -2
- package/src/server/startup-health-cache.ts +1 -0
- 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/desktop-startup.ts +14 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +99 -61
- package/src/service/health-sentinel-probe.ts +24 -7
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +135 -0
- 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/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +124 -6
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +29 -0
- package/src/types/provider.ts +39 -6
- package/src/types/request.ts +38 -2
- package/src/types/tools.ts +2 -0
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -0
- package/src/update/async-check.ts +1 -1
- package/src/update/badge.ts +2 -2
- package/src/update/index.ts +88 -15
- package/src/update/job-supervised-restart.ts +107 -0
- package/src/update/job.ts +32 -32
- package/src/update/notify.ts +1 -1
- package/src/update/npm-invocation.mjs +12 -2
- package/src/update/refresh-scheduler.ts +2 -2
- package/src/update/supervised-recovery.d.mts +27 -0
- package/src/update/supervised-recovery.mjs +157 -0
- package/src/usage/cache-diagnostic.ts +2 -0
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +15 -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/gemini-executor.ts +2 -2
- 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/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/App-CZtqiGcw.js +0 -56
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
- package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
|
@@ -12,6 +12,7 @@ import type { WsData } from "../ws-bridge";
|
|
|
12
12
|
import {
|
|
13
13
|
requestPacingMaxConcurrentRequests,
|
|
14
14
|
sendTrackingRequestSlot,
|
|
15
|
+
trackProviderRequestSlotBody,
|
|
15
16
|
waitForProviderRequestSlot,
|
|
16
17
|
type ProviderRequestSlot,
|
|
17
18
|
} from "../../providers/request-pacing";
|
|
@@ -49,6 +50,9 @@ const UPSTREAM_REWRITTEN = Symbol.for("opencodex.plugins.upstream-rewritten");
|
|
|
49
50
|
const PHYSICAL_DISPATCH = Symbol.for("opencodex.physical-dispatch");
|
|
50
51
|
// One WS attempt and its HTTP fallback are one logical physical-send receipt.
|
|
51
52
|
const WS_FALLBACK_RECEIPT = Symbol.for("opencodex.ws-fallback-receipt");
|
|
53
|
+
// Replacement admission waits on the client signal, outside the response-header deadline.
|
|
54
|
+
const HEADER_DEADLINE = Symbol("header-deadline");
|
|
55
|
+
type HeaderDeadline = { clientSignal: AbortSignal; pause(): void; resume(): void };
|
|
52
56
|
|
|
53
57
|
/**
|
|
54
58
|
* Announce once, per provider, that an explicit egress route moved this provider off the
|
|
@@ -139,6 +143,7 @@ export function wantsFreshConnection(
|
|
|
139
143
|
export interface PaceAwareFetch {
|
|
140
144
|
waitForPacing?: (signal?: AbortSignal) => Promise<ProviderRequestSlot | undefined>;
|
|
141
145
|
unpacedFetch?: typeof globalThis.fetch;
|
|
146
|
+
fetchWithPacingSlot?: (slot: ProviderRequestSlot | undefined, input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => Promise<Response>;
|
|
142
147
|
}
|
|
143
148
|
|
|
144
149
|
export type ProviderFetch = typeof globalThis.fetch & PaceAwareFetch;
|
|
@@ -252,7 +257,8 @@ export interface ProviderFetchOptions {
|
|
|
252
257
|
*/
|
|
253
258
|
preOutputRefusalHold?: CodexWsPreOutputRefusalHold;
|
|
254
259
|
/** Revalidate/rebuild a queued request at its physical send boundary, after pacing. */
|
|
255
|
-
dispatchOverride?: (input: Parameters<typeof globalThis.fetch>[0], init: RequestInit, execute: typeof globalThis.fetch
|
|
260
|
+
dispatchOverride?: (input: Parameters<typeof globalThis.fetch>[0], init: RequestInit, execute: typeof globalThis.fetch,
|
|
261
|
+
reacquirePacing?: (provider: OcxProviderConfig, modelId?: string) => Promise<void>) => Promise<Response>;
|
|
256
262
|
}
|
|
257
263
|
|
|
258
264
|
/** Compose provider pacing and transport admission; receipts run only at executor entry. */
|
|
@@ -301,7 +307,8 @@ export function providerFetch(
|
|
|
301
307
|
{ preconnect },
|
|
302
308
|
) as typeof globalThis.fetch);
|
|
303
309
|
const httpFetch = Object.assign(
|
|
304
|
-
async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit
|
|
310
|
+
async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit,
|
|
311
|
+
reacquirePacing?: (provider: OcxProviderConfig, modelId?: string) => Promise<void>) => {
|
|
305
312
|
// Refuse before any dispatch side effect where that is sound. `beforeDispatch` commits
|
|
306
313
|
// attempt accounting and consumes admission state, so a refusal firing after it would
|
|
307
314
|
// charge an attempt for a send that never happens, and a throwing hook would mask the
|
|
@@ -329,30 +336,33 @@ export function providerFetch(
|
|
|
329
336
|
} } : {}),
|
|
330
337
|
};
|
|
331
338
|
return options.dispatchOverride
|
|
332
|
-
? options.dispatchOverride(input, dispatchInit, dispatch)
|
|
339
|
+
? options.dispatchOverride(input, dispatchInit, dispatch, reacquirePacing)
|
|
333
340
|
: dispatch(input, dispatchInit);
|
|
334
341
|
},
|
|
335
342
|
{ preconnect },
|
|
336
|
-
)
|
|
343
|
+
);
|
|
337
344
|
// ChatGPT Codex backend: streaming turns ride the responses_websockets
|
|
338
345
|
// transport (measured ~3s faster TTFT than the SSE POST queue); everything
|
|
339
346
|
// else keeps the provider's HTTP fetch. See ws-upstream.ts for the details.
|
|
340
|
-
const unpaced = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit
|
|
347
|
+
const unpaced = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit,
|
|
348
|
+
reacquirePacing?: (provider: OcxProviderConfig, modelId?: string) => Promise<void>) => {
|
|
341
349
|
const upstreamWebsocket = provider.upstreamWebsocket;
|
|
342
350
|
if (!options.httpOnly && typeof input === "string" && init
|
|
343
351
|
&& shouldUseCodexWsUpstream(input, init, runtime, upstreamWebsocket)) {
|
|
344
|
-
|
|
352
|
+
const keyedPacing = provider.requestPacing?.enabled
|
|
353
|
+
&& (provider.requestPacing.keys || provider.requestPacing.keySelection === "round-robin");
|
|
354
|
+
if (keyedPacing || (options.providerName && requestPacingMaxConcurrentRequests(provider, options.modelId) > 0)) {
|
|
345
355
|
if (!pacingWebsocketDowngradeWarned.has(providerName)
|
|
346
356
|
&& pacingWebsocketDowngradeWarned.size < EGRESS_DOWNGRADE_NOTICE_LIMIT) {
|
|
347
357
|
pacingWebsocketDowngradeWarned.add(providerName);
|
|
348
|
-
console.warn(`[opencodex] provider ${JSON.stringify(redactSecretString(providerName))}
|
|
358
|
+
console.warn(`[opencodex] provider ${JSON.stringify(redactSecretString(providerName))} requires guarded request pacing; serving over HTTP/SSE.`);
|
|
349
359
|
}
|
|
350
|
-
return httpFetch(input, init);
|
|
360
|
+
return httpFetch(input, init, reacquirePacing);
|
|
351
361
|
}
|
|
352
362
|
const egress = egressFor(input);
|
|
353
363
|
if (providerEgressIsExplicit(egress)) {
|
|
354
364
|
warnEgressWebsocketDowngradeOnce(providerName, describeProviderEgressForLog(egress));
|
|
355
|
-
return httpFetch(input, init);
|
|
365
|
+
return httpFetch(input, init, reacquirePacing);
|
|
356
366
|
}
|
|
357
367
|
// The fallback has to be the same HTTP fetch the non-WS branch would have
|
|
358
368
|
// used, protocol pin included: a WS turn that falls back is serving the
|
|
@@ -365,11 +375,11 @@ export function providerFetch(
|
|
|
365
375
|
options.onPhysicalDispatch?.();
|
|
366
376
|
};
|
|
367
377
|
const fallback = ((url: Parameters<typeof globalThis.fetch>[0], fallbackInit?: RequestInit) =>
|
|
368
|
-
httpFetch(url, { ...fallbackInit, [WS_FALLBACK_RECEIPT]: notifyOnce } as RequestInit)) as typeof globalThis.fetch;
|
|
378
|
+
httpFetch(url, { ...fallbackInit, [WS_FALLBACK_RECEIPT]: notifyOnce } as RequestInit, reacquirePacing)) as typeof globalThis.fetch;
|
|
369
379
|
return codexWsUpstreamFetch(input, init, fallback, runtime, options.onCodexWsQuota, options.beforeDispatch, options.nativeControl,
|
|
370
380
|
async () => { (await waitForPacing(init.signal ?? undefined))?.release(); }, notifyOnce, options.preOutputRefusalHold);
|
|
371
381
|
}
|
|
372
|
-
return httpFetch(input, init);
|
|
382
|
+
return httpFetch(input, init, reacquirePacing);
|
|
373
383
|
};
|
|
374
384
|
let pacingSlotAcquired = options.pacingSlotAcquired === true;
|
|
375
385
|
const waitForPacing = async (signal?: AbortSignal): Promise<ProviderRequestSlot | undefined> => {
|
|
@@ -392,15 +402,35 @@ export function providerFetch(
|
|
|
392
402
|
throw new Error("A provider name is required for request concurrency pacing");
|
|
393
403
|
}
|
|
394
404
|
};
|
|
395
|
-
const
|
|
396
|
-
|
|
405
|
+
const fetchWithPacingSlot = async (slot: ProviderRequestSlot | undefined, input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
|
|
406
|
+
const reacquirePacing = async (refreshed: OcxProviderConfig, modelId?: string) => {
|
|
407
|
+
if (options.turnScopedPacing) throw new Error("Cannot replace a turn-scoped pacing lease during dispatch");
|
|
408
|
+
if (!options.providerName) throw new Error("Refreshed dispatch requires a named pacing owner");
|
|
409
|
+
slot?.release();
|
|
410
|
+
slot = undefined;
|
|
411
|
+
const deadline = (init as Record<symbol, HeaderDeadline> | undefined)?.[HEADER_DEADLINE];
|
|
412
|
+
deadline?.pause();
|
|
413
|
+
try {
|
|
414
|
+
slot = await waitForProviderRequestSlot(providerName, refreshed, modelId, deadline?.clientSignal ?? init?.signal ?? undefined);
|
|
415
|
+
} finally { deadline?.resume(); }
|
|
416
|
+
};
|
|
417
|
+
try {
|
|
418
|
+
const response = await unpaced(input, init, reacquirePacing);
|
|
419
|
+
return trackProviderRequestSlotBody(slot, response);
|
|
420
|
+
} catch (error) {
|
|
421
|
+
slot?.release();
|
|
422
|
+
throw error;
|
|
423
|
+
}
|
|
397
424
|
};
|
|
425
|
+
const wrapped = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) =>
|
|
426
|
+
fetchWithPacingSlot(await waitForPacing(init?.signal ?? undefined), input, init);
|
|
398
427
|
// The returned wrapper forwards its init down to `dispatch`, which applies the route at the
|
|
399
428
|
// physical send. Adapters that hand this executor back as `provider.fetch` (Cursor does)
|
|
400
429
|
// therefore still carry a per-provider route instead of being refused as opaque.
|
|
401
430
|
const paceAware = Object.assign(wrapped, {
|
|
402
431
|
preconnect,
|
|
403
432
|
waitForPacing,
|
|
433
|
+
fetchWithPacingSlot,
|
|
404
434
|
unpacedFetch: Object.assign(unpaced, { preconnect }),
|
|
405
435
|
});
|
|
406
436
|
markEgressTransparentExecutor(paceAware as unknown as typeof globalThis.fetch);
|
|
@@ -432,6 +462,9 @@ export function storedPoolReplayDispatchNotifier(
|
|
|
432
462
|
notified = true;
|
|
433
463
|
onDispatch();
|
|
434
464
|
};
|
|
465
|
+
const notifyWithSlot = (slot: ProviderRequestSlot | undefined): void => {
|
|
466
|
+
try { notifyOnce(); } catch (error) { slot?.release(); throw error; }
|
|
467
|
+
};
|
|
435
468
|
const unpacedSource = executor.unpacedFetch ?? executor;
|
|
436
469
|
const unpaced = Object.assign(
|
|
437
470
|
(input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
|
|
@@ -441,12 +474,21 @@ export function storedPoolReplayDispatchNotifier(
|
|
|
441
474
|
{ preconnect: unpacedSource.preconnect },
|
|
442
475
|
) as ProviderFetch["unpacedFetch"];
|
|
443
476
|
const wrapped = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
|
|
477
|
+
if (executor.fetchWithPacingSlot) {
|
|
478
|
+
const slot = await executor.waitForPacing?.(init?.signal ?? undefined);
|
|
479
|
+
notifyWithSlot(slot);
|
|
480
|
+
return executor.fetchWithPacingSlot(slot, input, init);
|
|
481
|
+
}
|
|
444
482
|
return sendTrackingRequestSlot(await executor.waitForPacing?.(init?.signal ?? undefined), () => unpaced!(input, init));
|
|
445
483
|
};
|
|
446
484
|
return Object.assign(wrapped, {
|
|
447
485
|
preconnect: executor.preconnect,
|
|
448
486
|
waitForPacing: executor.waitForPacing,
|
|
449
487
|
unpacedFetch: unpaced,
|
|
488
|
+
...(executor.fetchWithPacingSlot ? { fetchWithPacingSlot: (slot: ProviderRequestSlot | undefined, input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
|
|
489
|
+
notifyWithSlot(slot);
|
|
490
|
+
return executor.fetchWithPacingSlot!(slot, input, init);
|
|
491
|
+
} } : {}),
|
|
450
492
|
}) as ProviderFetch;
|
|
451
493
|
}
|
|
452
494
|
|
|
@@ -470,24 +512,36 @@ export async function fetchWithHeaderTimeout(
|
|
|
470
512
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
471
513
|
try {
|
|
472
514
|
const timeout = new AbortController();
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
515
|
+
const deadline: HeaderDeadline = {
|
|
516
|
+
clientSignal: abortSignal,
|
|
517
|
+
pause() { if (timer) clearTimeout(timer); timer = undefined; },
|
|
518
|
+
resume() {
|
|
519
|
+
this.pause();
|
|
520
|
+
timer = setTimeout(() => {
|
|
521
|
+
if (!timeout.signal.aborted) timeout.abort(new DOMException("Timeout elapsed", "TimeoutError"));
|
|
522
|
+
}, timeoutMs);
|
|
523
|
+
},
|
|
524
|
+
};
|
|
525
|
+
deadline.resume();
|
|
476
526
|
const headers = new Headers(init.headers);
|
|
477
527
|
// Compressed SSE can be held until the decompressor has a complete block. Streaming calls
|
|
478
528
|
// default to identity for low-latency frame delivery, while an explicit caller choice wins.
|
|
479
529
|
if (preferIdentityEncoding && !headers.has("accept-encoding")) {
|
|
480
530
|
headers.set("accept-encoding", "identity");
|
|
481
531
|
}
|
|
482
|
-
|
|
532
|
+
const dispatchInit = {
|
|
483
533
|
...init,
|
|
484
534
|
headers,
|
|
485
535
|
// Never replay provider credentials or request bodies to a redirect destination.
|
|
486
536
|
// Preserve the 3xx for the owner's existing response/health policy (#914, #1471).
|
|
487
|
-
redirect: "manual",
|
|
537
|
+
redirect: "manual" as const,
|
|
488
538
|
signal: AbortSignal.any([abortSignal, timeout.signal]),
|
|
539
|
+
[HEADER_DEADLINE]: deadline,
|
|
489
540
|
timeout: 0,
|
|
490
|
-
}
|
|
541
|
+
};
|
|
542
|
+
return pacing.fetchWithPacingSlot
|
|
543
|
+
? await pacing.fetchWithPacingSlot(slot, url, dispatchInit)
|
|
544
|
+
: await sendTrackingRequestSlot(slot, () => fetchExecutor(url, dispatchInit));
|
|
491
545
|
} finally {
|
|
492
546
|
if (timer) clearTimeout(timer);
|
|
493
547
|
if (!slot?.bodyTracked) slot?.release();
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { OcxConfig } from "../../types";
|
|
2
|
+
import { resolveComboId } from "../../combos";
|
|
3
|
+
import { isBareOpenAiFamilyModel } from "../../router";
|
|
4
|
+
import { NATIVE_RESERVE_MODEL } from "../../codex/catalog/native-models";
|
|
5
|
+
import {
|
|
6
|
+
getMainAccountHardLockStatus,
|
|
7
|
+
resolveMainAccountHardLockFallback,
|
|
8
|
+
} from "../../codex/main-account-hard-lock";
|
|
9
|
+
import { codexQuotaScopeForModel } from "../../codex/routing/health-store";
|
|
10
|
+
import { getEligiblePoolAccounts } from "../../codex/routing/selection";
|
|
11
|
+
import { OPENAI_CODEX_PROVIDER_ID, isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
12
|
+
import { providerCodexAccountMode } from "../../providers/registry";
|
|
13
|
+
|
|
14
|
+
function isConfiguredComboSelector(config: OcxConfig, modelId: string): boolean {
|
|
15
|
+
const comboId = resolveComboId(config, modelId);
|
|
16
|
+
return Boolean(comboId && Object.hasOwn(config.combos ?? {}, comboId));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function targetsBlockedCanonicalOpenAi(config: OcxConfig, modelId: string): boolean {
|
|
20
|
+
if (isBareOpenAiFamilyModel(modelId) && !isConfiguredComboSelector(config, modelId)) return true;
|
|
21
|
+
const slash = modelId.indexOf("/");
|
|
22
|
+
if (slash <= 0) return false;
|
|
23
|
+
const providerName = modelId.slice(0, slash);
|
|
24
|
+
const rest = modelId.slice(slash + 1);
|
|
25
|
+
if (providerName !== OPENAI_CODEX_PROVIDER_ID || !isBareOpenAiFamilyModel(rest)) return false;
|
|
26
|
+
const provider = config.providers?.[OPENAI_CODEX_PROVIDER_ID];
|
|
27
|
+
return !provider || isCanonicalOpenAiForwardProvider({
|
|
28
|
+
...provider,
|
|
29
|
+
...(provider.authMode === undefined ? { authMode: "forward" as const } : {}),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* What one rewritten request carries so the log can show the model the client asked for
|
|
35
|
+
* while the pipeline routes the fallback. The rewrite happens before combo dispatch reads
|
|
36
|
+
* `model`, so the rest of the pipeline sees an ordinary request for the target selector.
|
|
37
|
+
*/
|
|
38
|
+
export interface MainHardLockFallbackOverride {
|
|
39
|
+
sourceModel: string;
|
|
40
|
+
targetModel: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function rawModelOf(body: unknown): { raw: Record<string, unknown>; model: string } | undefined {
|
|
44
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return undefined;
|
|
45
|
+
const raw = body as Record<string, unknown>;
|
|
46
|
+
return typeof raw.model === "string" && raw.model.trim().length > 0
|
|
47
|
+
? { raw, model: raw.model }
|
|
48
|
+
: undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extended main-account hard-lock settings: when the lock blocks and no Codex account can
|
|
53
|
+
* serve the request, rewrite it to the configured fallback model instead of letting the
|
|
54
|
+
* dispatch refuse.
|
|
55
|
+
*
|
|
56
|
+
* Every guard bounds the rewrite to exactly the traffic the lock would otherwise fail, so
|
|
57
|
+
* the setting can never widen what the lock protects:
|
|
58
|
+
*
|
|
59
|
+
* - The lock must be enabled AND reading `blocked` right now — `ready`, `unknown` and the
|
|
60
|
+
* explicit opt-out all admit, and the release is this same observation, so the reroute
|
|
61
|
+
* ends on its own the moment the blocking windows drop below their thresholds.
|
|
62
|
+
* - `getEligiblePoolAccounts` is the selection choke point the dispatch itself consults,
|
|
63
|
+
* so "the pool can serve it" is one shared answer, not a second opinion: a usable pool
|
|
64
|
+
* account (or any eligible account) keeps the request on Codex, and a pool drained to
|
|
65
|
+
* empty is what makes the fallback the request's only way through. The main account is
|
|
66
|
+
* already excluded there while blocked.
|
|
67
|
+
* - Only bare Codex-native selectors qualify. Provider-qualified rows, combos, account
|
|
68
|
+
* namespaces and policy routes name their own destination on purpose; rewriting those
|
|
69
|
+
* would override an explicit choice, so they keep the lock's refusal.
|
|
70
|
+
* - A Codex-native fallback target is refused outright: rerouting gpt-* to gpt-* would
|
|
71
|
+
* re-enter the same refusal, and the honest behavior is the refusal, not a loop.
|
|
72
|
+
* - Luna Reserve keeps its own admission policy (`assertReserveAdmission`); its helper
|
|
73
|
+
* traffic is not operator chat and must not silently change models.
|
|
74
|
+
*/
|
|
75
|
+
export function applyMainHardLockFallbackOverride(
|
|
76
|
+
body: unknown,
|
|
77
|
+
config: OcxConfig,
|
|
78
|
+
now = Date.now(),
|
|
79
|
+
): MainHardLockFallbackOverride | null {
|
|
80
|
+
const parsed = rawModelOf(body);
|
|
81
|
+
if (!parsed) return null;
|
|
82
|
+
const fallback = resolveMainAccountHardLockFallback(config);
|
|
83
|
+
if (!fallback) return null;
|
|
84
|
+
if (targetsBlockedCanonicalOpenAi(config, fallback.model)) return null;
|
|
85
|
+
if (!isBareOpenAiFamilyModel(parsed.model)
|
|
86
|
+
|| parsed.model === NATIVE_RESERVE_MODEL
|
|
87
|
+
|| isConfiguredComboSelector(config, parsed.model)) return null;
|
|
88
|
+
if (getMainAccountHardLockStatus(config, now).state !== "blocked") return null;
|
|
89
|
+
const accountMode = providerCodexAccountMode(
|
|
90
|
+
OPENAI_CODEX_PROVIDER_ID,
|
|
91
|
+
config.providers?.[OPENAI_CODEX_PROVIDER_ID],
|
|
92
|
+
);
|
|
93
|
+
if (accountMode !== "direct"
|
|
94
|
+
&& getEligiblePoolAccounts(config, undefined, now, codexQuotaScopeForModel(parsed.model)).length > 0) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
parsed.raw.model = fallback.model;
|
|
99
|
+
if (fallback.reasoningEffort !== undefined) {
|
|
100
|
+
const reasoning = parsed.raw.reasoning;
|
|
101
|
+
parsed.raw.reasoning = {
|
|
102
|
+
...(reasoning && typeof reasoning === "object" && !Array.isArray(reasoning)
|
|
103
|
+
? reasoning as Record<string, unknown>
|
|
104
|
+
: {}),
|
|
105
|
+
effort: fallback.reasoningEffort,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return { sourceModel: parsed.model, targetModel: fallback.model };
|
|
109
|
+
}
|
|
@@ -4,6 +4,7 @@ import type { NativeSteeringReplayObserver } from "./native-steering-replay";
|
|
|
4
4
|
import type { ProviderExecutedCallType } from "../responses-undeclared-tool-guard";
|
|
5
5
|
|
|
6
6
|
import { isInjectionRequest } from "./native-injection-protocol";
|
|
7
|
+
import { registerProviderResponseProvenance } from "../../providers/request-pacing";
|
|
7
8
|
|
|
8
9
|
/** Shared transport ownership, not a shared steer/inject protocol state machine. */
|
|
9
10
|
export interface NativeResponseControl {
|
|
@@ -25,9 +26,10 @@ export interface NativeResponseControl {
|
|
|
25
26
|
|
|
26
27
|
export const OPENAI_API_RESPONSES_URL = "https://api.openai.com/v1/responses";
|
|
27
28
|
|
|
28
|
-
const nativeControlResponses = new
|
|
29
|
+
const nativeControlResponses = new WeakMap<Response, true>();
|
|
30
|
+
registerProviderResponseProvenance(nativeControlResponses);
|
|
29
31
|
/** Mark the exact response for multi-response delivery without serializing a wire field. */
|
|
30
|
-
export function markNativeControlResponse(response: Response): Response { nativeControlResponses.
|
|
32
|
+
export function markNativeControlResponse(response: Response): Response { nativeControlResponses.set(response, true); return response; }
|
|
31
33
|
/** Recognize a marked native response by identity, not by caller-controlled content. */
|
|
32
34
|
export function isNativeControlResponse(response: Response): boolean { return nativeControlResponses.has(response); }
|
|
33
35
|
|
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
hasResponsesSnapshotRepair,
|
|
59
59
|
createResponsesSnapshotBlockRewrite,
|
|
60
60
|
} from "../responses-snapshot-repair";
|
|
61
|
+
import { rewriteResponsesIncompleteReasonJson } from "../../responses/incomplete-reason";
|
|
61
62
|
import { createResponsesModelPayloadRewrite, rewriteResponsesModelJson } from "../responses-model-rewrite";
|
|
62
63
|
import { createImageGenCallRestoreRewrite, restoreImageGenCallsInJson } from "../responses-image-gen-repair";
|
|
63
64
|
import {
|
|
@@ -108,6 +109,7 @@ import { createResponsesFunctionToolRepairBlockRewrite } from "../responses-func
|
|
|
108
109
|
import {
|
|
109
110
|
createUndeclaredToolCallGuardBlockRewrite,
|
|
110
111
|
currentTurnWireToolCatalogBody,
|
|
112
|
+
UNDECLARED_TOOL_CALL_ERROR_CODE,
|
|
111
113
|
undeclaredToolCallNameInResponse,
|
|
112
114
|
undeclaredToolCallMessage,
|
|
113
115
|
normalizeDefaultNamespaceInJson,
|
|
@@ -437,8 +439,7 @@ export async function deliverPassthroughResponse(
|
|
|
437
439
|
normalizeFunctionCompletionJson,
|
|
438
440
|
} = nativeExchange;
|
|
439
441
|
const { commitReasoningReplayServingRoute, recordTerminalOutcomes } = responseEffects;
|
|
440
|
-
const { parsed, subagentQuotaFailureModel, clientRequestedStream, translatorBudget
|
|
441
|
-
const enforceDeclaredToolNames = inboundWire !== "chat" && inboundWire !== "anthropic";
|
|
442
|
+
const { parsed, subagentQuotaFailureModel, clientRequestedStream, translatorBudget } = requestState;
|
|
442
443
|
const { openAiSidecar } = sidecarState;
|
|
443
444
|
const { requestBindings } = transportState;
|
|
444
445
|
|
|
@@ -768,6 +769,7 @@ export async function deliverPassthroughResponse(
|
|
|
768
769
|
: undefined;
|
|
769
770
|
// Compose opt-in payload rewrites into one parse/stringify pass (image-gen restore first).
|
|
770
771
|
const payloadRewrites = [
|
|
772
|
+
rewriteResponsesIncompleteReasonJson,
|
|
771
773
|
createImageGenCallRestoreRewrite(imageGenCallAliases),
|
|
772
774
|
// #3217: a call whose namespace repeats its own name is unroutable in codex-rs.
|
|
773
775
|
createSelfNamedToolCallNamespaceScrubRewrite(selfNamedNamespaceScrubAuthorization),
|
|
@@ -857,14 +859,17 @@ export async function deliverPassthroughResponse(
|
|
|
857
859
|
: undefined,
|
|
858
860
|
// Last: every rewrite above can still rename or reshape a call item, so the guard must
|
|
859
861
|
// compare the names the client will actually receive against the declared catalog.
|
|
860
|
-
|
|
862
|
+
// Deferred-catalog wires keep the rewrite for its normalization half only: a provider's
|
|
863
|
+
// `default.X` call still collapses to the declared `X`, but an undeclared name is the
|
|
864
|
+
// client's own deferred registry's business, not this proxy's verdict.
|
|
865
|
+
nativeExchange.undeclaredToolGuardActive || declaredWireToolNames.size > 0
|
|
861
866
|
? createUndeclaredToolCallGuardBlockRewrite(
|
|
862
867
|
declaredWireToolNames,
|
|
863
868
|
declaredNamelessClientCallTypes,
|
|
864
869
|
providerExecutedCallTypes,
|
|
865
870
|
declaredBareWireToolNames,
|
|
866
871
|
recoverableBareCustomWireToolNames,
|
|
867
|
-
|
|
872
|
+
{ refuseUndeclared: nativeExchange.undeclaredToolGuardActive },
|
|
868
873
|
)
|
|
869
874
|
: undefined,
|
|
870
875
|
grokUpstreamEchoEnabled
|
|
@@ -1314,7 +1319,7 @@ export async function deliverPassthroughResponse(
|
|
|
1314
1319
|
const modelRewritten = parsed._responseModelId !== undefined && parsed._responseModelId !== parsed.modelId
|
|
1315
1320
|
? rewriteResponsesModelJson(repaired, parsed._responseModelId)
|
|
1316
1321
|
: repaired;
|
|
1317
|
-
return modelRewritten;
|
|
1322
|
+
return rewriteResponsesIncompleteReasonJson(modelRewritten);
|
|
1318
1323
|
})();
|
|
1319
1324
|
if (plaintextV2RestoreFailed) {
|
|
1320
1325
|
return formatErrorResponse(502, "upstream_error", PLAINTEXT_V2_AGENT_MESSAGE_RESTORE_OVERFLOW_MESSAGE);
|
|
@@ -1322,10 +1327,7 @@ export async function deliverPassthroughResponse(
|
|
|
1322
1327
|
if (grokUpstreamEchoEnabled) {
|
|
1323
1328
|
clientJson = stripGrokUpstreamEnvelopeEchoFromResponsesJson(clientJson);
|
|
1324
1329
|
}
|
|
1325
|
-
//
|
|
1326
|
-
// the reframed-SSE branch below are built from this body, so one check covers them. This
|
|
1327
|
-
// runs BEFORE the continuation cache write below: a refused turn must not become state a
|
|
1328
|
-
// later `previous_response_id` replay can expand from.
|
|
1330
|
+
// Both response formats use this body, before a refused turn can enter continuation state.
|
|
1329
1331
|
if (nativeExchange.undeclaredToolGuardActive) {
|
|
1330
1332
|
const undeclared = (() => {
|
|
1331
1333
|
try {
|
|
@@ -1341,9 +1343,15 @@ export async function deliverPassthroughResponse(
|
|
|
1341
1343
|
return undefined;
|
|
1342
1344
|
}
|
|
1343
1345
|
})();
|
|
1344
|
-
if (
|
|
1345
|
-
return formatErrorResponse(502, "upstream_error", undeclaredToolCallMessage(undeclared)
|
|
1346
|
+
if (undeclared !== undefined) {
|
|
1347
|
+
return formatErrorResponse(502, "upstream_error", undeclaredToolCallMessage(undeclared), {
|
|
1348
|
+
code: UNDECLARED_TOOL_CALL_ERROR_CODE,
|
|
1349
|
+
});
|
|
1346
1350
|
}
|
|
1351
|
+
}
|
|
1352
|
+
// Same split as the SSE rewrite: normalization applies whenever a declared name exists,
|
|
1353
|
+
// even on wires whose deferred catalog stands the refusal down.
|
|
1354
|
+
if (declaredWireToolNames.size > 0) {
|
|
1347
1355
|
clientJson = normalizeDefaultNamespaceInJson(
|
|
1348
1356
|
clientJson,
|
|
1349
1357
|
declaredWireToolNames,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { spendLedgerStorageErrorResponse } from "./spend-storage-error";
|
|
2
|
+
import { requestPacingOverloadResponse } from "./pacing-overload";
|
|
3
|
+
import { recordDistributedResponseFailure } from "../../providers/api-key-response-ownership";
|
|
2
4
|
import { createSteeringSettingsNormalizer } from "./native-steering-policy";
|
|
3
5
|
import { nativeResponseControlEligible } from "./native-response-control";
|
|
4
6
|
import { NativeInjectionReplay } from "./native-injection-replay";
|
|
@@ -21,6 +23,7 @@ import { imageGenToolCallAliases } from "../responses-image-gen-repair";
|
|
|
21
23
|
import { rememberResponseState, isBodyNonPersistable } from "../../responses/state";
|
|
22
24
|
import {
|
|
23
25
|
currentTurnWireToolCatalogBody,
|
|
26
|
+
hasExplicitEmptyToolCatalog,
|
|
24
27
|
hasExplicitWireToolCatalog,
|
|
25
28
|
collectDeclaredWireToolNames,
|
|
26
29
|
collectDeclaredBareWireToolNames,
|
|
@@ -140,7 +143,7 @@ import { resolveWireProtocolOverride } from "../adapter-resolve";
|
|
|
140
143
|
import { refreshPoolForwardAuth, refreshNativeMainForwardAuth, withClaudeNativeSession } from "./core-auth";
|
|
141
144
|
import { bindRouteReasoningReplayScope } from "./core-replay";
|
|
142
145
|
import type { OAuthAccessSnapshot } from "../../oauth";
|
|
143
|
-
import { OAuthAccountPausedError, OAuthLoginRequiredError, publicOAuthAuthenticationErrorMessage } from "../../oauth";
|
|
146
|
+
import { OAuthAccountPausedError, OAuthLoginRequiredError, OAuthTokenRefreshBusyError, publicOAuthAuthenticationErrorMessage } from "../../oauth";
|
|
144
147
|
import { AnthropicAccountCooldownError } from "../../oauth/anthropic-routing";
|
|
145
148
|
import { resolveCopilotApiBaseUrl } from "../../oauth/github-copilot";
|
|
146
149
|
import {
|
|
@@ -153,7 +156,7 @@ import { captureCodexAffinityDiagnostic } from "../../codex/affinity-debug";
|
|
|
153
156
|
import { ACCOUNT_GATED_NATIVE_OPENAI_MODELS } from "../../codex/catalog/native-models";
|
|
154
157
|
import {
|
|
155
158
|
attemptOpaqueBlobRecovery,
|
|
156
|
-
isEncryptedFunctionOutputRejection,
|
|
159
|
+
isEncryptedFunctionOutputRejection, retainedReasoningStreamCandidate, retainedReasoningStreamRejection,
|
|
157
160
|
outboundResponsesBodyCarriesEncryptedFunctionOutput,
|
|
158
161
|
resetStreamedOpaqueBlobLogContext,
|
|
159
162
|
consoleGoUploadRejectionBody,
|
|
@@ -187,6 +190,7 @@ export async function preparePassthroughExchange(
|
|
|
187
190
|
| "selectedForwardHeaders"
|
|
188
191
|
| "clientRequestedStream"
|
|
189
192
|
| "inboundWire"
|
|
193
|
+
| "clientDeclaredEmptyToolCatalog"
|
|
190
194
|
| "substituteMainCredential"
|
|
191
195
|
| "callerAuthHeaders"
|
|
192
196
|
| "subagentFallbackAccountId"
|
|
@@ -253,6 +257,7 @@ export async function preparePassthroughExchange(
|
|
|
253
257
|
responseStateOptions,
|
|
254
258
|
clientRequestedStream,
|
|
255
259
|
inboundWire,
|
|
260
|
+
clientDeclaredEmptyToolCatalog,
|
|
256
261
|
substituteMainCredential,
|
|
257
262
|
callerAuthHeaders,
|
|
258
263
|
} = requestState;
|
|
@@ -531,10 +536,18 @@ export async function preparePassthroughExchange(
|
|
|
531
536
|
declaredWireToolNames.add(name);
|
|
532
537
|
}
|
|
533
538
|
}
|
|
539
|
+
// Chat and Anthropic inbounds may deliver tools deferred/partially: their client registry
|
|
540
|
+
// owns the full catalog, so this guard cannot read the translated body as the authorization
|
|
541
|
+
// boundary. An explicit empty catalog — flagged by the translating ingress or present
|
|
542
|
+
// verbatim in a replayed body — is still a deny-all the caller declared; everything else
|
|
543
|
+
// stays deferred to the caller's own tool handling.
|
|
534
544
|
undeclaredToolGuardActive = (
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
545
|
+
inboundWire === "responses"
|
|
546
|
+
? declaredWireToolNames.size > 0
|
|
547
|
+
|| clientDeclaredNamelessCallTypes.size > 0
|
|
548
|
+
|| clientExplicitWireToolCatalog
|
|
549
|
+
: clientDeclaredEmptyToolCatalog
|
|
550
|
+
|| hasExplicitEmptyToolCatalog(clientToolAuthorizationBody)
|
|
538
551
|
) && route.provider.authMode !== "forward";
|
|
539
552
|
options.nativeControl?.configureToolAuthorization?.(
|
|
540
553
|
undeclaredToolGuardActive,
|
|
@@ -731,7 +744,9 @@ export async function preparePassthroughExchange(
|
|
|
731
744
|
const stage = readCodexWsStage(response);
|
|
732
745
|
if (stage && logCtx.activeAttempt) logCtx.activeAttempt.codexWsStage = stage;
|
|
733
746
|
};
|
|
747
|
+
let upstreamResponse: Response, lastDistributedResponse: Response | undefined;
|
|
734
748
|
const adoptObservedResponse = <T extends Response>(response: T): T => {
|
|
749
|
+
if (!isNonReplayableResponse(response)) lastDistributedResponse = response.status === 401 || response.status === 429 ? response : undefined;
|
|
735
750
|
settleObservedHostResponse();
|
|
736
751
|
adoptCodexWsStage(response);
|
|
737
752
|
return response;
|
|
@@ -748,7 +763,6 @@ export async function preparePassthroughExchange(
|
|
|
748
763
|
const upstream = new AbortController();
|
|
749
764
|
const cleanupUpstreamAbort = linkAbortSignal(upstream, options.abortSignal ?? req.signal);
|
|
750
765
|
const connectMs = config.connectTimeoutMs ?? 200_000;
|
|
751
|
-
let upstreamResponse: Response;
|
|
752
766
|
/**
|
|
753
767
|
* This leg's transient-5xx ladder cap, from the provider's own `transientRetryOn5xx`.
|
|
754
768
|
*
|
|
@@ -881,7 +895,6 @@ export async function preparePassthroughExchange(
|
|
|
881
895
|
): Response | undefined => {
|
|
882
896
|
const result = checkOutboundBodySize(builtRequest.body, config.maxUpstreamBodyBytes);
|
|
883
897
|
if (result.admitted) return undefined;
|
|
884
|
-
|
|
885
898
|
// This returns before the surrounding fetch/finally owns the observation, so release
|
|
886
899
|
// it here or one refused body holds translator budget for the process lifetime.
|
|
887
900
|
builtRequest.releaseBodyObservation?.();
|
|
@@ -919,6 +932,7 @@ export async function preparePassthroughExchange(
|
|
|
919
932
|
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
920
933
|
return clientCancelledResponse();
|
|
921
934
|
}
|
|
935
|
+
if (lastDistributedResponse) recordDistributedResponseFailure(lastDistributedResponse, config, route.providerName, options.abortSignal);
|
|
922
936
|
// A budget refusal is a proxy decision, not an upstream fault. Reporting it as
|
|
923
937
|
// 502 upstream_error would blame the provider for a limit this process applied, and
|
|
924
938
|
// would record a fake reachability failure against the account's health.
|
|
@@ -934,12 +948,12 @@ export async function preparePassthroughExchange(
|
|
|
934
948
|
bindRefreshedAdapter(refusal.adapter);
|
|
935
949
|
return handoffToChat(refusal.adapter);
|
|
936
950
|
}
|
|
937
|
-
const
|
|
938
|
-
if (
|
|
951
|
+
const dispatchRefusal = requestPacingOverloadResponse(refusal) ?? copilotRefusalResponse(refusal);
|
|
952
|
+
if (dispatchRefusal) {
|
|
939
953
|
releaseUpstreamHostAdmission(nativeHostState.lease);
|
|
940
954
|
nativeHostState.lease = null;
|
|
941
955
|
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
942
|
-
return
|
|
956
|
+
return dispatchRefusal;
|
|
943
957
|
}
|
|
944
958
|
// Pacing may outlive the selected account's admission. No fetch occurred, so do
|
|
945
959
|
// not turn an operator pause into a 502 or charge it to host/account health.
|
|
@@ -1195,7 +1209,6 @@ export async function preparePassthroughExchange(
|
|
|
1195
1209
|
|
|
1196
1210
|
// Keep recovery kinds in sync with the generic `recovery:` loop below.
|
|
1197
1211
|
passthroughRecovery: for (;;) {
|
|
1198
|
-
|
|
1199
1212
|
if (
|
|
1200
1213
|
upstreamResponse.status === 401
|
|
1201
1214
|
&& (admissionState.authCtx.kind === "main-pool" || admissionState.authCtx.kind === "pool")
|
|
@@ -1347,6 +1360,8 @@ export async function preparePassthroughExchange(
|
|
|
1347
1360
|
} catch (err) {
|
|
1348
1361
|
upstream.abort();
|
|
1349
1362
|
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
1363
|
+
if (options.abortSignal?.aborted || req.signal.aborted) return clientCancelledResponse();
|
|
1364
|
+
if (err instanceof OAuthTokenRefreshBusyError) return formatErrorResponse(503, "server_error", publicOAuthAuthenticationErrorMessage(err), { retryAfter: "1" });
|
|
1350
1365
|
return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(err));
|
|
1351
1366
|
}
|
|
1352
1367
|
if (route.provider.googleMode === "cloud-code-assist" && !refreshed.projectId) {
|
|
@@ -1515,7 +1530,6 @@ export async function preparePassthroughExchange(
|
|
|
1515
1530
|
)) noteAttemptRecoveryWithheld(logCtx.activeAttempt, "rotation-send-budget");
|
|
1516
1531
|
}
|
|
1517
1532
|
}
|
|
1518
|
-
|
|
1519
1533
|
// Same-target 429 wait-and-retry (opt-in `retryOn429`) for key-auth providers on the
|
|
1520
1534
|
// passthrough wire. This branch returns before the recovery loop below, so Responses-shaped
|
|
1521
1535
|
// key-auth gateways (e.g. the built-in DeepSeek preset) would otherwise surface 429
|
|
@@ -1586,7 +1600,6 @@ export async function preparePassthroughExchange(
|
|
|
1586
1600
|
return transportFailureResponse(err);
|
|
1587
1601
|
}
|
|
1588
1602
|
}
|
|
1589
|
-
|
|
1590
1603
|
const captureAffinityResponse = (
|
|
1591
1604
|
response: Response,
|
|
1592
1605
|
captureAuthCtx: CodexAuthContext = admissionState.authCtx,
|
|
@@ -1608,8 +1621,8 @@ export async function preparePassthroughExchange(
|
|
|
1608
1621
|
status: response.status,
|
|
1609
1622
|
});
|
|
1610
1623
|
};
|
|
1624
|
+
recordDistributedResponseFailure(lastDistributedResponse ?? upstreamResponse, config, route.providerName, options.abortSignal);
|
|
1611
1625
|
captureAffinityResponse(upstreamResponse);
|
|
1612
|
-
|
|
1613
1626
|
if (usesCodexForwardPoolAuth(admissionState.authCtx, route.provider)) {
|
|
1614
1627
|
let poolRetryOutcome: number | undefined;
|
|
1615
1628
|
// A success is the freshest evidence there is about this pair, and it outranks any earlier
|
|
@@ -1738,7 +1751,7 @@ export async function preparePassthroughExchange(
|
|
|
1738
1751
|
&& (recoveryContentType.includes("text/event-stream") || (!recoveryContentType && upstreamRequestsStream))
|
|
1739
1752
|
&& !opaqueBlobRecoveryGuard.attempted
|
|
1740
1753
|
&& !configuredTransientSendBudgetExhausted()
|
|
1741
|
-
&& outboundResponsesBodyCarriesEncryptedFunctionOutput(request.body);
|
|
1754
|
+
&& (outboundResponsesBodyCarriesEncryptedFunctionOutput(request.body) || (!sendBudgetState.ambiguousResendSpent && retainedReasoningStreamCandidate(parsed, request.body)));
|
|
1742
1755
|
if (streamedFunctionOutputCandidate) {
|
|
1743
1756
|
const preflightLog: RequestLogContext = { model: logCtx.model, provider: logCtx.provider };
|
|
1744
1757
|
const preflight = await preflightComboStreamResponse(upstreamResponse, preflightLog,
|
|
@@ -1751,7 +1764,7 @@ export async function preparePassthroughExchange(
|
|
|
1751
1764
|
// intentionally understands only Responses error/message fields. Preserve only this
|
|
1752
1765
|
// exact identity so the projected 502 can enter the existing single-shot recovery.
|
|
1753
1766
|
if (decryptRejection) preflightLog.upstreamError = ENCRYPTED_FUNCTION_OUTPUT_REJECTION;
|
|
1754
|
-
return decryptRejection;
|
|
1767
|
+
return decryptRejection || (!sendBudgetState.ambiguousResendSpent && retainedReasoningStreamRejection(payload, parsed, request.body, preflightLog));
|
|
1755
1768
|
}, {
|
|
1756
1769
|
allowMissingContentType: !recoveryContentType && upstreamRequestsStream,
|
|
1757
1770
|
replayReadErrors: true,
|