@bitkyc08/opencodex 2.86.0 → 2.88.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-CHC89EXQ.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-B5bC7AWL.js} +1 -1
- package/gui/dist/assets/index-B5bsvxor.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-D8y2wTsB.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-rejection-memo.ts +71 -0
- package/src/adapters/anthropic-replay.ts +135 -0
- package/src/adapters/anthropic-tool-catalog.ts +11 -0
- package/src/adapters/anthropic.ts +151 -33
- 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-progress-history.ts +53 -0
- package/src/adapters/google.ts +52 -9
- 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 +7 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +59 -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 +77 -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 +46 -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 +33 -16
- 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/loopback-family.ts +31 -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 +14 -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/websocket-activity.ts +161 -0
- package/src/codex/websocket-registry.ts +25 -1
- 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 +53 -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 +657 -297
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +116 -27
- 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/lib/request-failure-model.ts +2 -0
- 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 +40 -11
- 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 +6 -5
- 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/opencode-zen-rate-limit.ts +7 -2
- 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 +74 -6
- 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/analytics.ts +7 -15
- 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 +56 -414
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/live-sideband.ts +3 -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 +20 -6
- package/src/server/index/startup-warnings.ts +2 -0
- package/src/server/index/websocket-handler.ts +18 -4
- package/src/server/index.ts +8 -8
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/lifecycle.ts +13 -1
- 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 +117 -24
- 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 +47 -19
- package/src/server/responses/adapter-delivery.ts +72 -12
- package/src/server/responses/adapter-dispatch.ts +46 -3
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +73 -41
- package/src/server/responses/anthropic-thinking-recovery.ts +153 -0
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/combo-stream-preflight.ts +3 -2
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +20 -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 +121 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +8 -4
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +7 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +23 -3
- 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/passthrough-execution.ts +3 -2
- package/src/server/responses/request-prepare.ts +77 -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 +2 -0
- package/src/server/responses/response-tool-ownership.ts +21 -0
- package/src/server/responses/run-turn-execution.ts +96 -30
- package/src/server/responses/sidecar-execution.ts +63 -9
- package/src/server/responses/sidecar-send-budget.ts +2 -2
- package/src/server/responses/terminal-guard.ts +2 -16
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/ws-bridge.ts +2 -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/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 +35 -0
- package/src/types/provider.ts +32 -6
- package/src/types/request.ts +31 -2
- package/src/types/wire.ts +31 -37
- package/src/types.ts +2 -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 +30 -11
- package/src/usage/telemetry-contract.ts +1 -0
- 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/anthropic-history.ts +59 -0
- package/src/web-search/loop.ts +236 -73
- package/src/web-search/progress-stream.ts +15 -2
- 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
|
@@ -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 {
|
|
@@ -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),
|
|
@@ -1317,7 +1319,7 @@ export async function deliverPassthroughResponse(
|
|
|
1317
1319
|
const modelRewritten = parsed._responseModelId !== undefined && parsed._responseModelId !== parsed.modelId
|
|
1318
1320
|
? rewriteResponsesModelJson(repaired, parsed._responseModelId)
|
|
1319
1321
|
: repaired;
|
|
1320
|
-
return modelRewritten;
|
|
1322
|
+
return rewriteResponsesIncompleteReasonJson(modelRewritten);
|
|
1321
1323
|
})();
|
|
1322
1324
|
if (plaintextV2RestoreFailed) {
|
|
1323
1325
|
return formatErrorResponse(502, "upstream_error", PLAINTEXT_V2_AGENT_MESSAGE_RESTORE_OVERFLOW_MESSAGE);
|
|
@@ -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";
|
|
@@ -141,7 +143,7 @@ import { resolveWireProtocolOverride } from "../adapter-resolve";
|
|
|
141
143
|
import { refreshPoolForwardAuth, refreshNativeMainForwardAuth, withClaudeNativeSession } from "./core-auth";
|
|
142
144
|
import { bindRouteReasoningReplayScope } from "./core-replay";
|
|
143
145
|
import type { OAuthAccessSnapshot } from "../../oauth";
|
|
144
|
-
import { OAuthAccountPausedError, OAuthLoginRequiredError, publicOAuthAuthenticationErrorMessage } from "../../oauth";
|
|
146
|
+
import { OAuthAccountPausedError, OAuthLoginRequiredError, OAuthTokenRefreshBusyError, publicOAuthAuthenticationErrorMessage } from "../../oauth";
|
|
145
147
|
import { AnthropicAccountCooldownError } from "../../oauth/anthropic-routing";
|
|
146
148
|
import { resolveCopilotApiBaseUrl } from "../../oauth/github-copilot";
|
|
147
149
|
import {
|
|
@@ -154,7 +156,7 @@ import { captureCodexAffinityDiagnostic } from "../../codex/affinity-debug";
|
|
|
154
156
|
import { ACCOUNT_GATED_NATIVE_OPENAI_MODELS } from "../../codex/catalog/native-models";
|
|
155
157
|
import {
|
|
156
158
|
attemptOpaqueBlobRecovery,
|
|
157
|
-
isEncryptedFunctionOutputRejection,
|
|
159
|
+
isEncryptedFunctionOutputRejection, retainedReasoningStreamCandidate, retainedReasoningStreamRejection,
|
|
158
160
|
outboundResponsesBodyCarriesEncryptedFunctionOutput,
|
|
159
161
|
resetStreamedOpaqueBlobLogContext,
|
|
160
162
|
consoleGoUploadRejectionBody,
|
|
@@ -742,7 +744,9 @@ export async function preparePassthroughExchange(
|
|
|
742
744
|
const stage = readCodexWsStage(response);
|
|
743
745
|
if (stage && logCtx.activeAttempt) logCtx.activeAttempt.codexWsStage = stage;
|
|
744
746
|
};
|
|
747
|
+
let upstreamResponse: Response, lastDistributedResponse: Response | undefined;
|
|
745
748
|
const adoptObservedResponse = <T extends Response>(response: T): T => {
|
|
749
|
+
if (!isNonReplayableResponse(response)) lastDistributedResponse = response.status === 401 || response.status === 429 ? response : undefined;
|
|
746
750
|
settleObservedHostResponse();
|
|
747
751
|
adoptCodexWsStage(response);
|
|
748
752
|
return response;
|
|
@@ -759,7 +763,6 @@ export async function preparePassthroughExchange(
|
|
|
759
763
|
const upstream = new AbortController();
|
|
760
764
|
const cleanupUpstreamAbort = linkAbortSignal(upstream, options.abortSignal ?? req.signal);
|
|
761
765
|
const connectMs = config.connectTimeoutMs ?? 200_000;
|
|
762
|
-
let upstreamResponse: Response;
|
|
763
766
|
/**
|
|
764
767
|
* This leg's transient-5xx ladder cap, from the provider's own `transientRetryOn5xx`.
|
|
765
768
|
*
|
|
@@ -892,7 +895,6 @@ export async function preparePassthroughExchange(
|
|
|
892
895
|
): Response | undefined => {
|
|
893
896
|
const result = checkOutboundBodySize(builtRequest.body, config.maxUpstreamBodyBytes);
|
|
894
897
|
if (result.admitted) return undefined;
|
|
895
|
-
|
|
896
898
|
// This returns before the surrounding fetch/finally owns the observation, so release
|
|
897
899
|
// it here or one refused body holds translator budget for the process lifetime.
|
|
898
900
|
builtRequest.releaseBodyObservation?.();
|
|
@@ -930,6 +932,7 @@ export async function preparePassthroughExchange(
|
|
|
930
932
|
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
931
933
|
return clientCancelledResponse();
|
|
932
934
|
}
|
|
935
|
+
if (lastDistributedResponse) recordDistributedResponseFailure(lastDistributedResponse, config, route.providerName, options.abortSignal);
|
|
933
936
|
// A budget refusal is a proxy decision, not an upstream fault. Reporting it as
|
|
934
937
|
// 502 upstream_error would blame the provider for a limit this process applied, and
|
|
935
938
|
// would record a fake reachability failure against the account's health.
|
|
@@ -945,12 +948,12 @@ export async function preparePassthroughExchange(
|
|
|
945
948
|
bindRefreshedAdapter(refusal.adapter);
|
|
946
949
|
return handoffToChat(refusal.adapter);
|
|
947
950
|
}
|
|
948
|
-
const
|
|
949
|
-
if (
|
|
951
|
+
const dispatchRefusal = requestPacingOverloadResponse(refusal) ?? copilotRefusalResponse(refusal);
|
|
952
|
+
if (dispatchRefusal) {
|
|
950
953
|
releaseUpstreamHostAdmission(nativeHostState.lease);
|
|
951
954
|
nativeHostState.lease = null;
|
|
952
955
|
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
953
|
-
return
|
|
956
|
+
return dispatchRefusal;
|
|
954
957
|
}
|
|
955
958
|
// Pacing may outlive the selected account's admission. No fetch occurred, so do
|
|
956
959
|
// not turn an operator pause into a 502 or charge it to host/account health.
|
|
@@ -1206,7 +1209,6 @@ export async function preparePassthroughExchange(
|
|
|
1206
1209
|
|
|
1207
1210
|
// Keep recovery kinds in sync with the generic `recovery:` loop below.
|
|
1208
1211
|
passthroughRecovery: for (;;) {
|
|
1209
|
-
|
|
1210
1212
|
if (
|
|
1211
1213
|
upstreamResponse.status === 401
|
|
1212
1214
|
&& (admissionState.authCtx.kind === "main-pool" || admissionState.authCtx.kind === "pool")
|
|
@@ -1358,6 +1360,8 @@ export async function preparePassthroughExchange(
|
|
|
1358
1360
|
} catch (err) {
|
|
1359
1361
|
upstream.abort();
|
|
1360
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" });
|
|
1361
1365
|
return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(err));
|
|
1362
1366
|
}
|
|
1363
1367
|
if (route.provider.googleMode === "cloud-code-assist" && !refreshed.projectId) {
|
|
@@ -1526,7 +1530,6 @@ export async function preparePassthroughExchange(
|
|
|
1526
1530
|
)) noteAttemptRecoveryWithheld(logCtx.activeAttempt, "rotation-send-budget");
|
|
1527
1531
|
}
|
|
1528
1532
|
}
|
|
1529
|
-
|
|
1530
1533
|
// Same-target 429 wait-and-retry (opt-in `retryOn429`) for key-auth providers on the
|
|
1531
1534
|
// passthrough wire. This branch returns before the recovery loop below, so Responses-shaped
|
|
1532
1535
|
// key-auth gateways (e.g. the built-in DeepSeek preset) would otherwise surface 429
|
|
@@ -1597,7 +1600,6 @@ export async function preparePassthroughExchange(
|
|
|
1597
1600
|
return transportFailureResponse(err);
|
|
1598
1601
|
}
|
|
1599
1602
|
}
|
|
1600
|
-
|
|
1601
1603
|
const captureAffinityResponse = (
|
|
1602
1604
|
response: Response,
|
|
1603
1605
|
captureAuthCtx: CodexAuthContext = admissionState.authCtx,
|
|
@@ -1619,8 +1621,8 @@ export async function preparePassthroughExchange(
|
|
|
1619
1621
|
status: response.status,
|
|
1620
1622
|
});
|
|
1621
1623
|
};
|
|
1624
|
+
recordDistributedResponseFailure(lastDistributedResponse ?? upstreamResponse, config, route.providerName, options.abortSignal);
|
|
1622
1625
|
captureAffinityResponse(upstreamResponse);
|
|
1623
|
-
|
|
1624
1626
|
if (usesCodexForwardPoolAuth(admissionState.authCtx, route.provider)) {
|
|
1625
1627
|
let poolRetryOutcome: number | undefined;
|
|
1626
1628
|
// A success is the freshest evidence there is about this pair, and it outranks any earlier
|
|
@@ -1749,7 +1751,7 @@ export async function preparePassthroughExchange(
|
|
|
1749
1751
|
&& (recoveryContentType.includes("text/event-stream") || (!recoveryContentType && upstreamRequestsStream))
|
|
1750
1752
|
&& !opaqueBlobRecoveryGuard.attempted
|
|
1751
1753
|
&& !configuredTransientSendBudgetExhausted()
|
|
1752
|
-
&& outboundResponsesBodyCarriesEncryptedFunctionOutput(request.body);
|
|
1754
|
+
&& (outboundResponsesBodyCarriesEncryptedFunctionOutput(request.body) || (!sendBudgetState.ambiguousResendSpent && retainedReasoningStreamCandidate(parsed, request.body)));
|
|
1753
1755
|
if (streamedFunctionOutputCandidate) {
|
|
1754
1756
|
const preflightLog: RequestLogContext = { model: logCtx.model, provider: logCtx.provider };
|
|
1755
1757
|
const preflight = await preflightComboStreamResponse(upstreamResponse, preflightLog,
|
|
@@ -1762,7 +1764,7 @@ export async function preparePassthroughExchange(
|
|
|
1762
1764
|
// intentionally understands only Responses error/message fields. Preserve only this
|
|
1763
1765
|
// exact identity so the projected 502 can enter the existing single-shot recovery.
|
|
1764
1766
|
if (decryptRejection) preflightLog.upstreamError = ENCRYPTED_FUNCTION_OUTPUT_REJECTION;
|
|
1765
|
-
return decryptRejection;
|
|
1767
|
+
return decryptRejection || (!sendBudgetState.ambiguousResendSpent && retainedReasoningStreamRejection(payload, parsed, request.body, preflightLog));
|
|
1766
1768
|
}, {
|
|
1767
1769
|
allowMissingContentType: !recoveryContentType && upstreamRequestsStream,
|
|
1768
1770
|
replayReadErrors: true,
|
|
@@ -14,6 +14,7 @@ import { guardDirectPassthroughBodyInactivity } from "../../lib/response-body-in
|
|
|
14
14
|
import { resolveStallTimeoutMs } from "../../stall-timeout";
|
|
15
15
|
import { releaseUpstreamHostAdmission } from "../../codex/upstream-host-health";
|
|
16
16
|
import { releaseCodexAuthContextProbeLease } from "../../codex/auth-context";
|
|
17
|
+
import { attachResponseToolOwnership } from "./response-tool-ownership";
|
|
17
18
|
|
|
18
19
|
/** Owns the native host lease across dispatch, recovery, and response construction. */
|
|
19
20
|
export async function executePassthroughResponse(
|
|
@@ -50,11 +51,11 @@ export async function executePassthroughResponse(
|
|
|
50
51
|
// Delivery has already classified SSE (including a missing upstream content type)
|
|
51
52
|
// and consumed bounded JSON/errors. Guard only its remaining direct body, outside
|
|
52
53
|
// the relay/lifetime wrappers so their prefetch cannot arm our inactivity clock.
|
|
53
|
-
return guardDirectPassthroughBodyInactivity(
|
|
54
|
+
return attachResponseToolOwnership(guardDirectPassthroughBodyInactivity(
|
|
54
55
|
response,
|
|
55
56
|
nativeExchange.upstream.signal,
|
|
56
57
|
resolveStallTimeoutMs(requestContext.config.stallTimeoutSec, { localUpstream: nativeExchange.localUpstream }),
|
|
57
|
-
);
|
|
58
|
+
), nativeExchange.providerExecutedCallTypes);
|
|
58
59
|
} finally {
|
|
59
60
|
if (nativeHostState.lease) {
|
|
60
61
|
releaseUpstreamHostAdmission(nativeHostState.lease);
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
unreadableEncryptedAgentTaskResponse,
|
|
18
18
|
} from "./core-errors";
|
|
19
19
|
import { parseSyntheticRowId } from "../fast-row";
|
|
20
|
-
import { resolveComboId, comboIdFromRawBody, NoAvailableComboTargetsError } from "../../combos";
|
|
20
|
+
import { resolveComboId, comboIdFromRawBody, comboExclusionReasons, NoAvailableComboTargetsError, pickComboTarget } from "../../combos";
|
|
21
21
|
import { INTERCEPT_TARGET_UNAVAILABLE_CODE, interceptTargetUnavailableResponse, resolveChosenTarget, resolveShadowCallTarget } from "./shadow-target-availability";
|
|
22
22
|
import {
|
|
23
23
|
MEMORY_MODEL_TARGET_UNAVAILABLE_CODE,
|
|
@@ -84,6 +84,7 @@ import {
|
|
|
84
84
|
} from "../../router";
|
|
85
85
|
import { evidenceFromBody } from "../../routing/request-evidence";
|
|
86
86
|
import { OPENAI_CODEX_PROVIDER_ID, isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
87
|
+
import { resolveNativeReasoningRetention } from "../../config/schema/native-reasoning-retention";
|
|
87
88
|
import { isThreadSpawnRequest } from "../effort-policy";
|
|
88
89
|
import {
|
|
89
90
|
resolveSubagentFallbackChain,
|
|
@@ -133,6 +134,7 @@ import {
|
|
|
133
134
|
} from "../../codex/loopback-target";
|
|
134
135
|
import { checkComboTargetInputAdmission, checkInputAdmission } from "./input-admission";
|
|
135
136
|
import {
|
|
137
|
+
admissionMayUseMeteredComboTargets,
|
|
136
138
|
admissionModelDeniedResponse,
|
|
137
139
|
AdmissionModelDeniedError,
|
|
138
140
|
assertRouteAllowedByScope,
|
|
@@ -140,6 +142,13 @@ import {
|
|
|
140
142
|
routeAllowedByScope,
|
|
141
143
|
} from "../admission-model-scope";
|
|
142
144
|
import { nativeContextLimits } from "../../codex/catalog";
|
|
145
|
+
import type { ClientToolCatalogRestriction } from "../../lib/empty-tool-catalog";
|
|
146
|
+
import {
|
|
147
|
+
currentTurnWireToolCatalogBody,
|
|
148
|
+
hasExplicitEmptyToolCatalog,
|
|
149
|
+
collectDeclaredWireToolNames,
|
|
150
|
+
collectDeclaredBareCustomWireToolNames,
|
|
151
|
+
} from "../responses-undeclared-tool-guard";
|
|
143
152
|
import { streamingContextOverflowResponse } from "./context-overflow";
|
|
144
153
|
import {
|
|
145
154
|
preAuthUpstreamHostCircuitKey,
|
|
@@ -149,6 +158,7 @@ import {
|
|
|
149
158
|
} from "./core-codex-account";
|
|
150
159
|
import { acquireUpstreamHostAdmission } from "../../codex/upstream-host-health";
|
|
151
160
|
import { applyCompactionRoutingOverride, compactionRoutingKeepsProviderIdentity } from "./compaction-routing";
|
|
161
|
+
import { applyMainHardLockFallbackOverride } from "./main-hard-lock-routing";
|
|
152
162
|
import { codexAuthContextLogLabel } from "../../codex/account-label";
|
|
153
163
|
import {
|
|
154
164
|
conversationStateBindingFromAuth,
|
|
@@ -286,14 +296,14 @@ export async function prepareResponsesRequest(
|
|
|
286
296
|
effort: comboEffortRow.effort,
|
|
287
297
|
};
|
|
288
298
|
}
|
|
299
|
+
const isCompactionTrigger = body !== null && typeof body === "object" && !Array.isArray(body)
|
|
300
|
+
&& Array.isArray((body as { input?: unknown }).input)
|
|
301
|
+
&& ((body as { input: unknown[] }).input).some((item: unknown) =>
|
|
302
|
+
typeof item === "object" && item !== null && (item as { type?: string }).type === "compaction_trigger");
|
|
289
303
|
// Compaction may send the last client-visible bare model after a combo switch.
|
|
290
304
|
// Configured selectors take precedence; otherwise recall before combo dispatch (#3891).
|
|
291
305
|
if (!concreteSelection && !options.compactionRoutingOverride && body && typeof body === "object" && !Array.isArray(body)) {
|
|
292
306
|
const rawModel = (body as { model?: unknown }).model;
|
|
293
|
-
const rawInput = (body as { input?: unknown }).input;
|
|
294
|
-
const isCompactionTrigger = Array.isArray(rawInput)
|
|
295
|
-
&& rawInput.some((item: unknown) =>
|
|
296
|
-
typeof item === "object" && item !== null && (item as { type?: string }).type === "compaction_trigger");
|
|
297
307
|
if (typeof rawModel === "string" && !rawModel.includes("/") && isCompactionTrigger
|
|
298
308
|
&& !comboRows.fastRow && !comboEffortRow
|
|
299
309
|
&& !resolveComboId(config, rawModel)) {
|
|
@@ -322,6 +332,7 @@ export async function prepareResponsesRequest(
|
|
|
322
332
|
}
|
|
323
333
|
}
|
|
324
334
|
let shadowCallIntercepted = false;
|
|
335
|
+
let shadowCallMatches = false;
|
|
325
336
|
// Luna is also selectable by users/children. Both intercept sites use the same
|
|
326
337
|
// per-turn provenance; WS frames must not inherit the handshake's classification.
|
|
327
338
|
const threadSpawn = isThreadSpawnRequest(req.headers);
|
|
@@ -331,6 +342,7 @@ export async function prepareResponsesRequest(
|
|
|
331
342
|
const rawShadowModel = (body as { model?: unknown }).model;
|
|
332
343
|
if (shadowIntercept?.enabled && shadowIntercept.model && typeof rawShadowModel === "string"
|
|
333
344
|
&& isShadowSourceModel(rawShadowModel, shadowIntercept.sourceModels)) {
|
|
345
|
+
shadowCallMatches = true;
|
|
334
346
|
const shadowComboId = resolveComboId(config, shadowIntercept.model);
|
|
335
347
|
if (shadowComboId && Object.hasOwn(config.combos ?? {}, shadowComboId)) {
|
|
336
348
|
shadowCallIntercepted = true;
|
|
@@ -344,6 +356,16 @@ export async function prepareResponsesRequest(
|
|
|
344
356
|
}
|
|
345
357
|
}
|
|
346
358
|
}
|
|
359
|
+
// The hard-lock fallback owns ordinary turns only, and only after every more specific
|
|
360
|
+
// routing setting had its chance above: a shadow-intercepted, memory-phased, compaction
|
|
361
|
+
// or decision turn keeps its own destination, and an intercepted combo selector is
|
|
362
|
+
// already owned by the intercept. A spawned sub-agent turn names its model on purpose,
|
|
363
|
+
// the same reason the shadow intercept leaves it alone. Like the sites above it, this
|
|
364
|
+
// must run before comboIdFromRawBody reads `model`.
|
|
365
|
+
if (!concreteSelection && !options.compactionRoutingOverride && !isCompactionTrigger && !options.internalDecisionCall
|
|
366
|
+
&& !shadowCallIntercepted && !shadowCallMatches && !memoryModelApplies && !threadSpawn && inboundWire === "responses") {
|
|
367
|
+
options.mainHardLockFallbackOverride = applyMainHardLockFallbackOverride(body, config);
|
|
368
|
+
}
|
|
347
369
|
const comboId = !concreteSelection ? comboIdFromRawBody(body, config) : null;
|
|
348
370
|
// A decision model may be a plain model or a non-JEV combo. A JEV combo here would recurse
|
|
349
371
|
// into another decision, so the call is refused and the parent decision fails open.
|
|
@@ -430,15 +452,31 @@ export async function prepareResponsesRequest(
|
|
|
430
452
|
|
|
431
453
|
let parsed: OcxParsedRequest;
|
|
432
454
|
let toolBridgeMaps: ReturnType<typeof buildToolBridgeMaps>;
|
|
455
|
+
let clientToolCatalogRestriction: ClientToolCatalogRestriction | undefined;
|
|
433
456
|
try {
|
|
434
457
|
parsed = parseRequest(body);
|
|
435
458
|
// Antigravity direct lane: the body carried only the request settings, so the parse above
|
|
436
459
|
// produced them with an empty conversation; the conversation itself comes straight from the
|
|
437
460
|
// client body. Inside this try so a decode error answers exactly like a parse error.
|
|
438
461
|
if (options.directRequest) parsed = options.directRequest.decode(parsed);
|
|
462
|
+
parsed._nativeReasoningRetention = resolveNativeReasoningRetention(config);
|
|
463
|
+
// Capture before route rewrites or hosted sidecars can add tools; replayed catalogs are history.
|
|
464
|
+
// The direct settings body omits tools, including current legacy Chat functions. Recover
|
|
465
|
+
// their wire names only when this restriction needs a non-empty decoded catalog.
|
|
466
|
+
const catalogBody = options.directRequest && options.clientDeclaredEmptyToolCatalog === true && parsed.context.tools?.length
|
|
467
|
+
? options.directRequest.bridgeConversation() : parsed._rawBody;
|
|
468
|
+
const currentCatalog = currentTurnWireToolCatalogBody(catalogBody, parsed._replayPrefixLen);
|
|
469
|
+
if (options.clientDeclaredEmptyToolCatalog === true || hasExplicitEmptyToolCatalog(currentCatalog)) {
|
|
470
|
+
clientToolCatalogRestriction = {
|
|
471
|
+
declaredToolNames: collectDeclaredWireToolNames(currentCatalog),
|
|
472
|
+
bareCustomToolNames: collectDeclaredBareCustomWireToolNames(currentCatalog),
|
|
473
|
+
};
|
|
474
|
+
}
|
|
439
475
|
if (options.inboundWire === "anthropic") {
|
|
440
476
|
parsed._nativeReasoningReplay = options.nativeReasoningReplay;
|
|
441
477
|
parsed._nativeReasoningMint = options.nativeReasoningMint;
|
|
478
|
+
parsed._anthropicServerTools = options.anthropicServerTools;
|
|
479
|
+
parsed._anthropicServerReplay = options.anthropicServerReplay;
|
|
442
480
|
}
|
|
443
481
|
parsed._promptCacheKeyIsSharedCohort = options.promptCacheKeyIsSharedCohort;
|
|
444
482
|
// The body may have been rebuilt since the inbound observation (previous-response
|
|
@@ -561,7 +599,10 @@ export async function prepareResponsesRequest(
|
|
|
561
599
|
if (!logCtx.conversationId) {
|
|
562
600
|
logCtx.conversationId = resolvedConversationId;
|
|
563
601
|
}
|
|
564
|
-
|
|
602
|
+
// A rewritten turn logs the selector the client asked for, so the usage log never
|
|
603
|
+
// claims the operator typed the fallback model themselves.
|
|
604
|
+
logCtx.requestedModel = options.mainHardLockFallbackOverride?.sourceModel
|
|
605
|
+
?? options.compactionRoutingOverride?.sourceModel ?? parsed.modelId;
|
|
565
606
|
logCtx.requestedEffort = parsed.options.reasoning;
|
|
566
607
|
// What this request may spend beyond its input, for the durable spend reservation (#4707).
|
|
567
608
|
// Read from the caller rather than from the adapter's serialized body, because the
|
|
@@ -582,7 +623,7 @@ export async function prepareResponsesRequest(
|
|
|
582
623
|
// The selector the caller actually sent, captured before shadow interception
|
|
583
624
|
// or a subagent fallback rewrites it, so a refusal names the client's own
|
|
584
625
|
// request rather than a destination it never asked for.
|
|
585
|
-
const inboundSelector = parsed.modelId;
|
|
626
|
+
const inboundSelector = options.mainHardLockFallbackOverride?.sourceModel ?? parsed.modelId;
|
|
586
627
|
const selectorTargetsSecondary = (selector: string): boolean => {
|
|
587
628
|
const slash = selector.indexOf("/");
|
|
588
629
|
if (slash <= 0) return false;
|
|
@@ -602,6 +643,19 @@ export async function prepareResponsesRequest(
|
|
|
602
643
|
}
|
|
603
644
|
const admissionScope = resolveAdmissionModelScope(config, options.admission);
|
|
604
645
|
const captureInboundRoutePolicy = (candidate: RouteResult, captureRequestPolicy = true): RouteResult => {
|
|
646
|
+
// Router-collapsed aliases bypass the combo loop. Re-pick before capturing destination
|
|
647
|
+
// policy so random/RR cannot refuse while an unmetered target is still usable.
|
|
648
|
+
if (candidate.combo?.target.metered && !admissionMayUseMeteredComboTargets(
|
|
649
|
+
config, options.admission, config.combos?.[candidate.combo.comboId],
|
|
650
|
+
)) {
|
|
651
|
+
const pick = pickComboTarget(config, candidate.combo.comboId, { eligible: target => !target.metered });
|
|
652
|
+
if (!pick) {
|
|
653
|
+
throw new NoAvailableComboTargetsError(candidate.combo.comboId,
|
|
654
|
+
comboExclusionReasons(config, candidate.combo.comboId, target => !target.metered));
|
|
655
|
+
}
|
|
656
|
+
const routed = routeConcreteModel(config, `${pick.target.provider}/${pick.target.model}`);
|
|
657
|
+
candidate = { ...routed, combo: pick, routeKind: "combo", routeReason: routed.routeReason === "blocked-model-redirect" ? "blocked-model-redirect" : "combo-pick" };
|
|
658
|
+
}
|
|
605
659
|
// Every route this request path produces passes through here: the direct
|
|
606
660
|
// name, an alias, a policy or combo selection, a compaction override, a
|
|
607
661
|
// shadow-intercept target and both subagent-fallback re-routes. Checking
|
|
@@ -668,7 +722,7 @@ export async function prepareResponsesRequest(
|
|
|
668
722
|
}
|
|
669
723
|
const _sci = config.shadowCallIntercept;
|
|
670
724
|
let shadowRoute: RouteResult | undefined;
|
|
671
|
-
if (!concreteSelection && !memoryRoute && !options.memoryModelPhase && !options.compactionRoutingOverride && !options.internalDecisionCall && !preserveShadowModel && _sci?.enabled && _sci.model && isShadowSourceModel(parsed.modelId, _sci.sourceModels)) {
|
|
725
|
+
if (!concreteSelection && !memoryRoute && !options.memoryModelPhase && !options.compactionRoutingOverride && !options.mainHardLockFallbackOverride && !options.internalDecisionCall && !preserveShadowModel && _sci?.enabled && _sci.model && isShadowSourceModel(parsed.modelId, _sci.sourceModels)) {
|
|
672
726
|
const sourcePrefix = shadowSourceModelPrefix(parsed.modelId, _sci.sourceModels)!;
|
|
673
727
|
let sourceIdentity = { providerName: OPENAI_CODEX_PROVIDER_ID, modelId: sourcePrefix };
|
|
674
728
|
try {
|
|
@@ -721,15 +775,22 @@ export async function prepareResponsesRequest(
|
|
|
721
775
|
};
|
|
722
776
|
}
|
|
723
777
|
}
|
|
724
|
-
if (options.compactionRoutingOverride && !compactionRoutingKeepsProviderIdentity(config, options.compactionRoutingOverride, route))
|
|
778
|
+
if ((options.compactionRoutingOverride && !compactionRoutingKeepsProviderIdentity(config, options.compactionRoutingOverride, route))
|
|
779
|
+
|| options.mainHardLockFallbackOverride) {
|
|
725
780
|
credentialDomainWasRewritten = true;
|
|
726
781
|
// The destination does not share the conversation's credential domain, so it can neither
|
|
727
782
|
// verify the source backend's reasoning ciphertext nor decode its native compaction blob.
|
|
728
783
|
// This is the same condition an account change already reports (account-change-state.ts),
|
|
729
784
|
// and the serializer turns a stored summary into readable text instead of dropping it.
|
|
730
785
|
parsed._stripReasoningEncryptedContent = true;
|
|
786
|
+
parsed._stripNativeCompactionEncryptedContent = true;
|
|
731
787
|
if (parsed._compactionRequest === true) parsed._portableCompaction = true;
|
|
732
788
|
}
|
|
789
|
+
// A v1 fallback needs an ocx1 summary for replacement-history decoding, even when this
|
|
790
|
+
// custom destination separately accepts native v2 triggers. Legacy defaults stay intact.
|
|
791
|
+
if (options.compactionRecoveryKind === "compaction-v1" && parsed._compactionRequest === true
|
|
792
|
+
&& route.provider.adapter === "openai-responses" && route.provider.supportsNativeCompactionTrigger === true
|
|
793
|
+
&& !isCanonicalOpenAiForwardProvider(route.provider)) parsed._portableCompaction = true;
|
|
733
794
|
logCtx.routeDecision = route.routeDecision;
|
|
734
795
|
logCtx.policyEligibility = route.policyEligibility;
|
|
735
796
|
} catch (err) {
|
|
@@ -740,7 +801,7 @@ export async function prepareResponsesRequest(
|
|
|
740
801
|
if (policyRefusal) return policyRefusal;
|
|
741
802
|
if (err instanceof AdmissionModelDeniedError) return admissionModelDeniedResponse(err);
|
|
742
803
|
if (err instanceof NoAvailableComboTargetsError) {
|
|
743
|
-
return comboUnavailable(err.comboId);
|
|
804
|
+
return comboUnavailable(err.comboId, undefined, err.reasons);
|
|
744
805
|
}
|
|
745
806
|
if (err instanceof NoEligiblePolicyCandidateError) {
|
|
746
807
|
// Persist the evaluation trace (per-candidate exclusions + the
|
|
@@ -958,7 +1019,7 @@ export async function prepareResponsesRequest(
|
|
|
958
1019
|
if (policyRefusal) return policyRefusal;
|
|
959
1020
|
if (err instanceof AdmissionModelDeniedError) return admissionModelDeniedResponse(err);
|
|
960
1021
|
if (err instanceof NoAvailableComboTargetsError) {
|
|
961
|
-
return comboUnavailable(err.comboId);
|
|
1022
|
+
return comboUnavailable(err.comboId, undefined, err.reasons);
|
|
962
1023
|
}
|
|
963
1024
|
if (err instanceof NoEligiblePolicyCandidateError) {
|
|
964
1025
|
logCtx.routeDecision = err.trace;
|
|
@@ -1030,6 +1091,7 @@ export async function prepareResponsesRequest(
|
|
|
1030
1091
|
"_promptCacheKeyIsSharedCohort",
|
|
1031
1092
|
"_cursorClientThreadId",
|
|
1032
1093
|
"_reasoningReplayScope",
|
|
1094
|
+
"_nativeReasoningRetention",
|
|
1033
1095
|
"_cursorIsolateConversation",
|
|
1034
1096
|
];
|
|
1035
1097
|
for (const key of kept) {
|
|
@@ -1175,7 +1237,7 @@ export async function prepareResponsesRequest(
|
|
|
1175
1237
|
if (policyRefusal) return policyRefusal;
|
|
1176
1238
|
if (err instanceof AdmissionModelDeniedError) return admissionModelDeniedResponse(err);
|
|
1177
1239
|
if (err instanceof NoAvailableComboTargetsError) {
|
|
1178
|
-
return comboUnavailable(err.comboId);
|
|
1240
|
+
return comboUnavailable(err.comboId, undefined, err.reasons);
|
|
1179
1241
|
}
|
|
1180
1242
|
if (err instanceof NoEligiblePolicyCandidateError) {
|
|
1181
1243
|
logCtx.routeDecision = err.trace;
|
|
@@ -1492,6 +1554,8 @@ export async function prepareResponsesRequest(
|
|
|
1492
1554
|
applyAccountChangeConversationStateScrub({
|
|
1493
1555
|
body: parsed._rawBody,
|
|
1494
1556
|
parsed,
|
|
1557
|
+
preserveReasoningEncryptedContent: isCanonicalOpenAiForwardProvider(route.provider)
|
|
1558
|
+
&& parsed._nativeReasoningRetention?.accountSwitch === true,
|
|
1495
1559
|
bindingKey: binding.bindingKey,
|
|
1496
1560
|
servingAccountId: binding.accountId,
|
|
1497
1561
|
logCtx,
|
|
@@ -1511,6 +1575,7 @@ export async function prepareResponsesRequest(
|
|
|
1511
1575
|
return {
|
|
1512
1576
|
inboundWire,
|
|
1513
1577
|
clientDeclaredEmptyToolCatalog: options.clientDeclaredEmptyToolCatalog === true,
|
|
1578
|
+
clientToolCatalogRestriction,
|
|
1514
1579
|
// Admission facts later route rewrites must not re-derive: the selector the caller sent and
|
|
1515
1580
|
// the key scope as it stood when this request was admitted.
|
|
1516
1581
|
inboundSelector,
|
|
@@ -2,7 +2,7 @@ import type { ResponsesRequestContext } from "./core-options";
|
|
|
2
2
|
import type { PreparedResponsesRequest } from "./request-prepare";
|
|
3
3
|
import type { ResponsesTransport } from "./request-transport";
|
|
4
4
|
import type { ResolvedOpenAiForwardSidecar } from "../../providers/openai-sidecar";
|
|
5
|
-
import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
5
|
+
import { isCanonicalOpenAiForwardProvider, supportsNativeResponsesCompactionTrigger } from "../../providers/openai-tiers";
|
|
6
6
|
import { omitEarlierCompactionImages } from "../../responses/compaction-images";
|
|
7
7
|
import {
|
|
8
8
|
shouldResolveOpenAiVisionSidecar,
|
|
@@ -51,7 +51,7 @@ export async function prepareResponsesSidecarAuth(
|
|
|
51
51
|
let openAiSidecar: ResolvedOpenAiForwardSidecar | undefined;
|
|
52
52
|
const visionDescribeTerminal = options.visionDescribeTerminal === true;
|
|
53
53
|
const routedCompaction = parsed._compactionRequest === true
|
|
54
|
-
&& (!
|
|
54
|
+
&& (!supportsNativeResponsesCompactionTrigger(route.provider) || parsed._portableCompaction === true);
|
|
55
55
|
if (routedCompaction) parsed.context.messages = omitEarlierCompactionImages(parsed.context.messages);
|
|
56
56
|
const needsOpenAiVision = !visionDescribeTerminal
|
|
57
57
|
&& shouldResolveOpenAiVisionSidecar(config, route.provider, route.modelId, parsed, route.providerName);
|