@bitkyc08/opencodex 2.86.0 → 2.87.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-DCd-A1az.js} +1 -1
- package/gui/dist/assets/index-CfYDMj34.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +9 -8
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/passthrough.ts +7 -23
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic.ts +108 -21
- package/src/adapters/base.ts +2 -0
- package/src/adapters/claude-cli/adapter.ts +111 -23
- package/src/adapters/claude-cli/profiles.ts +2 -1
- package/src/adapters/claude-cli/stable-replay.ts +108 -0
- package/src/adapters/claude-cli/usage-admission.ts +230 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +18 -2
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google.ts +35 -8
- package/src/adapters/openai-chat/messages.ts +4 -3
- package/src/adapters/openai-chat/passthrough.ts +8 -2
- package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
- package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
- package/src/adapters/openai-responses/passthrough.ts +57 -125
- package/src/adapters/openai-responses/request-strips.ts +25 -3
- package/src/adapters/openai-responses/transport.ts +81 -0
- package/src/adapters/qoder/adapter.ts +4 -2
- package/src/adapters/registry.ts +5 -0
- package/src/adapters/zed.ts +26 -1
- package/src/advisor/activity.ts +55 -0
- package/src/advisor/budget.ts +62 -0
- package/src/advisor/consult.ts +4 -1
- package/src/advisor/context.ts +16 -4
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +79 -31
- package/src/advisor/settings.ts +11 -2
- package/src/bridge/errors.ts +19 -1
- package/src/bridge/internal.ts +3 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +58 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/claude/desktop-3p-library.ts +32 -7
- package/src/claude/desktop-3p.ts +55 -13
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +11 -0
- package/src/claude/inbound.ts +33 -0
- package/src/claude/intercept/picker-ca-startup.ts +2 -1
- package/src/claude/intercept/picker-runtime.ts +6 -5
- package/src/claude/intercept/picker-trust-windows.ts +338 -0
- package/src/claude/intercept/picker-trust.ts +19 -0
- package/src/claude/intercept/port-policy.ts +30 -0
- package/src/claude/intercept/runtime.ts +97 -94
- package/src/claude/outbound.ts +38 -13
- package/src/cli/account-api.ts +1 -0
- package/src/cli/account-policy-dto.ts +6 -0
- package/src/cli/account-policy.ts +34 -5
- package/src/cli/account-preservation.ts +44 -0
- package/src/cli/account.ts +8 -2
- package/src/cli/advisor.ts +17 -3
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +5 -2
- package/src/cli/capabilities-base.ts +38 -10
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/codex-cli-update.ts +151 -41
- package/src/cli/codex-provider.ts +44 -0
- package/src/cli/combo-input.ts +10 -1
- package/src/cli/combo-result.ts +1 -1
- package/src/cli/combo.ts +2 -1
- package/src/cli/cross-home-owner.ts +17 -14
- package/src/cli/dispatch.ts +25 -4
- package/src/cli/doctor.ts +6 -1
- package/src/cli/ensure-desktop-cold-start.ts +60 -7
- package/src/cli/help.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/models-runtime-subcommands.ts +1 -0
- package/src/cli/models-runtime.ts +72 -8
- package/src/cli/observe-stream.ts +2 -0
- package/src/cli/observe.ts +16 -4
- package/src/cli/provider.ts +27 -5
- package/src/cli/registry.ts +28 -8
- package/src/cli/runtime-api.ts +5 -3
- package/src/cli/system-command.ts +40 -8
- package/src/cli/uninstall-client-state.ts +32 -0
- package/src/cli/usage-compare.ts +309 -0
- package/src/cli/usage-model-search.ts +4 -4
- package/src/cli/usage-report.ts +2 -2
- package/src/cli/v2.ts +1 -1
- package/src/client/machine-listener.ts +1 -3
- package/src/client/runtime.ts +1 -0
- package/src/clients/client-path.ts +51 -0
- package/src/clients/config-export/contracts.ts +2 -1
- package/src/clients/config-export.ts +18 -44
- package/src/clients/gentle-shell-path.ts +87 -0
- package/src/codex/account-auto-switch.ts +2 -0
- package/src/codex/account-identity.ts +29 -0
- package/src/codex/account-label.ts +1 -1
- package/src/codex/account-lifecycle.ts +21 -5
- package/src/codex/account-preservation.ts +28 -0
- package/src/codex/account-use-remaining.ts +23 -0
- package/src/codex/app-server-processes.ts +97 -7
- package/src/codex/app-server-restart-service.ts +55 -4
- package/src/codex/auth-api/account-list.ts +4 -0
- package/src/codex/auth-api/account-pause-group.ts +6 -50
- package/src/codex/auth-api/account-preservation.ts +57 -0
- package/src/codex/auth-api/routes.ts +8 -0
- package/src/codex/auth-api/use-remaining-route.ts +36 -0
- package/src/codex/auth-api.ts +0 -1
- package/src/codex/auth-context.ts +2 -2
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +9 -1
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +1 -1
- package/src/codex/catalog/gather-capture.ts +2 -0
- package/src/codex/catalog/metadata.ts +11 -9
- package/src/codex/catalog/model-hints.ts +21 -16
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/parsing.ts +30 -86
- package/src/codex/catalog/provider-models.ts +36 -34
- package/src/codex/catalog/routed-gather.ts +12 -13
- package/src/codex/cli-install-provenance.ts +105 -17
- package/src/codex/cli-update-plan.ts +545 -0
- package/src/codex/config-write-lock.ts +23 -2
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app-restart.ts +21 -0
- package/src/codex/desktop-compatibility/installed-build.ts +24 -6
- package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
- package/src/codex/desktop-compatibility/runtime.ts +12 -3
- package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
- package/src/codex/features.ts +85 -45
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/restore.ts +24 -2
- package/src/codex/inject/routing-classify.ts +4 -3
- package/src/codex/inject.ts +18 -27
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/native-profile-startup.ts +1 -1
- package/src/codex/ocx-compaction-history.ts +12 -10
- package/src/codex/paths.ts +86 -1
- package/src/codex/provider-routing.ts +174 -0
- package/src/codex/refresh.ts +2 -2
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/signed-in-identity.ts +43 -0
- package/src/codex/subagent-model-fallback.ts +1 -1
- package/src/codex/sync.ts +6 -1
- package/src/codex/user-identity.ts +16 -5
- package/src/codex/windows-local-appdata.ts +84 -0
- package/src/combos/failover.ts +6 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/resolve.ts +46 -10
- package/src/combos/types.ts +15 -0
- package/src/config/diagnostics.ts +33 -6
- package/src/config/live-reconcile.ts +38 -2
- package/src/config/load-degrade.ts +17 -0
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +52 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +22 -0
- package/src/config/schema/leaf-validators.ts +38 -9
- package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
- package/src/config/schema/native-reasoning-retention.ts +26 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +608 -284
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +46 -8
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +1 -0
- package/src/integrations/registry.ts +8 -0
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-upstream-tunnel.ts +18 -53
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +19 -3
- package/src/lib/gcp-adc.ts +96 -9
- package/src/lib/gcp-credential-input.ts +12 -0
- package/src/lib/lab-live-route-production.ts +5 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/redact.ts +2 -2
- package/src/oauth/account-quota-rank.ts +69 -2
- package/src/oauth/anthropic-account-refusal.ts +20 -2
- package/src/oauth/anthropic-oauth-definitions.ts +3 -1
- package/src/oauth/anthropic-routing.ts +1 -21
- package/src/oauth/anthropic.ts +38 -7
- package/src/oauth/callback-server.ts +27 -1
- package/src/oauth/chatgpt-device.ts +1 -1
- package/src/oauth/chatgpt-shared.ts +83 -0
- package/src/oauth/chatgpt.ts +8 -72
- package/src/oauth/dsh.ts +203 -0
- package/src/oauth/generic-account-failover.ts +33 -7
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +184 -109
- package/src/oauth/local-token-detect.ts +21 -1
- package/src/oauth/login-cli-errors.ts +44 -0
- package/src/oauth/login-cli.ts +69 -7
- package/src/oauth/login-flow-state.ts +371 -23
- package/src/oauth/meta-muse-errors.ts +41 -0
- package/src/oauth/meta-muse-keychain.ts +52 -0
- package/src/oauth/meta-muse.ts +51 -84
- package/src/oauth/model-discovery-auth.ts +3 -0
- package/src/oauth/pool-settings-capability.ts +4 -2
- package/src/oauth/refresh-flight-retirement.ts +48 -0
- package/src/oauth/session-affinity-key.ts +23 -0
- package/src/oauth/store-anthropic-instance.ts +9 -2
- package/src/oauth/store.ts +12 -5
- package/src/protocols/encoders/adapter-events.ts +5 -4
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/provider-summary.ts +2 -1
- package/src/providers/api-key-cooldowns.ts +49 -0
- package/src/providers/api-key-resolve.ts +11 -6
- package/src/providers/api-key-response-ownership.ts +57 -0
- package/src/providers/api-key-selection.ts +31 -6
- package/src/providers/api-keys.ts +214 -3
- package/src/providers/claude-cli-usage.ts +96 -0
- package/src/providers/dsh-account-destination.ts +31 -0
- package/src/providers/dsh-account-models.ts +41 -0
- package/src/providers/gcp-credential-cleanup.ts +59 -0
- package/src/providers/key-failover.ts +54 -50
- package/src/providers/key-store.ts +7 -2
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/model-rename-migration.ts +2 -2
- package/src/providers/openai-tiers-destination.ts +11 -2
- package/src/providers/openai-tiers.ts +5 -12
- package/src/providers/opencode-go-transport.ts +3 -1
- package/src/providers/quota/account-cache.ts +6 -5
- package/src/providers/quota/account-row.ts +34 -0
- package/src/providers/quota/antigravity.ts +28 -7
- package/src/providers/quota/dsh-account.ts +70 -0
- package/src/providers/quota/vendor-probes-oauth.ts +1 -93
- package/src/providers/quota-types.ts +2 -0
- package/src/providers/quota.ts +14 -20
- package/src/providers/registry/entries-core.ts +37 -8
- package/src/providers/registry/types.ts +1 -1
- package/src/providers/request-pacing.ts +77 -19
- package/src/providers/routed-config.ts +339 -0
- package/src/providers/service-tier.ts +2 -1
- package/src/quota/window-mapping.ts +5 -2
- package/src/responses/anthropic-progress.ts +34 -0
- package/src/responses/assistant-replay.ts +35 -0
- package/src/responses/compaction-history-framing.ts +65 -0
- package/src/responses/compaction.ts +6 -1
- package/src/responses/incomplete-reason.ts +31 -0
- package/src/responses/parser.ts +50 -5
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +24 -2
- package/src/responses/reasoning-retention.ts +173 -0
- package/src/responses/retained-compaction.ts +46 -0
- package/src/responses/schema.ts +23 -0
- package/src/router.ts +8 -321
- package/src/routing/health.ts +37 -22
- package/src/routing/history/indexer.ts +6 -5
- package/src/routing/history/ingest.ts +6 -2
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +17 -0
- package/src/server/auth-cors.ts +11 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native-reasoning.ts +123 -0
- package/src/server/chat-native-sse.ts +110 -7
- package/src/server/chat-native.ts +76 -17
- package/src/server/claude-messages.ts +55 -414
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/model-alias-id.ts +36 -0
- package/src/server/index/optional-listeners.ts +2 -0
- package/src/server/index/serve-options.ts +9 -1
- package/src/server/index/startup-warnings.ts +2 -0
- package/src/server/index.ts +1 -1
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/management/advisor-routes.ts +15 -4
- package/src/server/management/claude-desktop-picker-routes.ts +1 -1
- package/src/server/management/combo-routes.ts +176 -50
- package/src/server/management/companion-routes.ts +1 -1
- package/src/server/management/config-routes.ts +75 -4
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/model-routes.ts +11 -6
- package/src/server/management/model-rows.ts +14 -0
- package/src/server/management/native-integration-routes.ts +2 -7
- package/src/server/management/native-reasoning-retention-routes.ts +41 -0
- package/src/server/management/oauth-account-routes.ts +87 -14
- package/src/server/management/provider-gcp-credential-routes.ts +29 -0
- package/src/server/management/provider-key-pacing-routes.ts +31 -0
- package/src/server/management/provider-overwrite-carry.ts +7 -0
- package/src/server/management/provider-routes.ts +25 -30
- package/src/server/management/route-registry.ts +10 -1
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +101 -21
- package/src/server/messages-shared.ts +431 -0
- package/src/server/relay.ts +1 -1
- package/src/server/request-log-terminal-status.ts +1 -1
- package/src/server/request-log.ts +6 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +13 -1
- package/src/server/responses/adapter-delivery.ts +19 -6
- package/src/server/responses/adapter-dispatch.ts +19 -2
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +73 -41
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +18 -10
- package/src/server/responses/compaction-retention.ts +61 -0
- package/src/server/responses/context-overflow.ts +11 -6
- package/src/server/responses/core-auth.ts +6 -0
- package/src/server/responses/core-codex-account.ts +11 -1
- package/src/server/responses/core-combo-failure.ts +2 -0
- package/src/server/responses/core-combo.ts +120 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +5 -2
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +18 -2
- package/src/server/responses/core.ts +2 -115
- package/src/server/responses/fetch-helpers.ts +73 -19
- package/src/server/responses/main-hard-lock-routing.ts +109 -0
- package/src/server/responses/native-response-control.ts +4 -2
- package/src/server/responses/passthrough-delivery.ts +3 -1
- package/src/server/responses/passthrough-dispatch.ts +15 -13
- package/src/server/responses/request-prepare.ts +76 -12
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +104 -27
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +96 -30
- package/src/server/responses/sidecar-execution.ts +20 -2
- package/src/server/responses/terminal-guard.ts +2 -16
- package/src/service/auth-preflight.ts +71 -0
- package/src/service/cli.ts +4 -2
- package/src/service/definition.ts +97 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +76 -57
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +72 -9
- package/src/service/repair.ts +2 -1
- package/src/service/state.ts +7 -8
- package/src/service/systemd.ts +1 -1
- package/src/service/windows-guarded-manager.ts +2 -1
- package/src/service/windows-ops.ts +1 -1
- package/src/service/windows-scheduler.ts +2 -7
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service/windows-wscript.ts +14 -0
- package/src/service.ts +4 -2
- package/src/sidecar/auth.ts +2 -0
- package/src/types/config.ts +64 -3
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +29 -0
- package/src/types/provider.ts +32 -6
- package/src/types/request.ts +23 -2
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -0
- package/src/update/npm-invocation.mjs +12 -2
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +3 -0
- package/src/usage/ledger-retention.ts +2 -1
- package/src/usage/ledger-scanner.ts +6 -5
- package/src/usage/log.ts +17 -8
- package/src/usage/summary.ts +22 -9
- package/src/usage/user-cost-overlays.ts +14 -19
- package/src/usage/user-cost-validation.ts +38 -0
- package/src/vision/eligibility.ts +13 -0
- package/src/web-search/loop.ts +55 -5
- package/src/web-search/progress-stream.ts +4 -1
- package/src/web-search/run-turn-loop.ts +9 -1
- package/gui/dist/assets/App-BjlttVLy.js +0 -58
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-BMssP4k3.js +0 -94
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
|
@@ -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,
|
|
@@ -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,30 @@ 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;
|
|
442
479
|
}
|
|
443
480
|
parsed._promptCacheKeyIsSharedCohort = options.promptCacheKeyIsSharedCohort;
|
|
444
481
|
// The body may have been rebuilt since the inbound observation (previous-response
|
|
@@ -561,7 +598,10 @@ export async function prepareResponsesRequest(
|
|
|
561
598
|
if (!logCtx.conversationId) {
|
|
562
599
|
logCtx.conversationId = resolvedConversationId;
|
|
563
600
|
}
|
|
564
|
-
|
|
601
|
+
// A rewritten turn logs the selector the client asked for, so the usage log never
|
|
602
|
+
// claims the operator typed the fallback model themselves.
|
|
603
|
+
logCtx.requestedModel = options.mainHardLockFallbackOverride?.sourceModel
|
|
604
|
+
?? options.compactionRoutingOverride?.sourceModel ?? parsed.modelId;
|
|
565
605
|
logCtx.requestedEffort = parsed.options.reasoning;
|
|
566
606
|
// What this request may spend beyond its input, for the durable spend reservation (#4707).
|
|
567
607
|
// Read from the caller rather than from the adapter's serialized body, because the
|
|
@@ -582,7 +622,7 @@ export async function prepareResponsesRequest(
|
|
|
582
622
|
// The selector the caller actually sent, captured before shadow interception
|
|
583
623
|
// or a subagent fallback rewrites it, so a refusal names the client's own
|
|
584
624
|
// request rather than a destination it never asked for.
|
|
585
|
-
const inboundSelector = parsed.modelId;
|
|
625
|
+
const inboundSelector = options.mainHardLockFallbackOverride?.sourceModel ?? parsed.modelId;
|
|
586
626
|
const selectorTargetsSecondary = (selector: string): boolean => {
|
|
587
627
|
const slash = selector.indexOf("/");
|
|
588
628
|
if (slash <= 0) return false;
|
|
@@ -602,6 +642,19 @@ export async function prepareResponsesRequest(
|
|
|
602
642
|
}
|
|
603
643
|
const admissionScope = resolveAdmissionModelScope(config, options.admission);
|
|
604
644
|
const captureInboundRoutePolicy = (candidate: RouteResult, captureRequestPolicy = true): RouteResult => {
|
|
645
|
+
// Router-collapsed aliases bypass the combo loop. Re-pick before capturing destination
|
|
646
|
+
// policy so random/RR cannot refuse while an unmetered target is still usable.
|
|
647
|
+
if (candidate.combo?.target.metered && !admissionMayUseMeteredComboTargets(
|
|
648
|
+
config, options.admission, config.combos?.[candidate.combo.comboId],
|
|
649
|
+
)) {
|
|
650
|
+
const pick = pickComboTarget(config, candidate.combo.comboId, { eligible: target => !target.metered });
|
|
651
|
+
if (!pick) {
|
|
652
|
+
throw new NoAvailableComboTargetsError(candidate.combo.comboId,
|
|
653
|
+
comboExclusionReasons(config, candidate.combo.comboId, target => !target.metered));
|
|
654
|
+
}
|
|
655
|
+
const routed = routeConcreteModel(config, `${pick.target.provider}/${pick.target.model}`);
|
|
656
|
+
candidate = { ...routed, combo: pick, routeKind: "combo", routeReason: routed.routeReason === "blocked-model-redirect" ? "blocked-model-redirect" : "combo-pick" };
|
|
657
|
+
}
|
|
605
658
|
// Every route this request path produces passes through here: the direct
|
|
606
659
|
// name, an alias, a policy or combo selection, a compaction override, a
|
|
607
660
|
// shadow-intercept target and both subagent-fallback re-routes. Checking
|
|
@@ -668,7 +721,7 @@ export async function prepareResponsesRequest(
|
|
|
668
721
|
}
|
|
669
722
|
const _sci = config.shadowCallIntercept;
|
|
670
723
|
let shadowRoute: RouteResult | undefined;
|
|
671
|
-
if (!concreteSelection && !memoryRoute && !options.memoryModelPhase && !options.compactionRoutingOverride && !options.internalDecisionCall && !preserveShadowModel && _sci?.enabled && _sci.model && isShadowSourceModel(parsed.modelId, _sci.sourceModels)) {
|
|
724
|
+
if (!concreteSelection && !memoryRoute && !options.memoryModelPhase && !options.compactionRoutingOverride && !options.mainHardLockFallbackOverride && !options.internalDecisionCall && !preserveShadowModel && _sci?.enabled && _sci.model && isShadowSourceModel(parsed.modelId, _sci.sourceModels)) {
|
|
672
725
|
const sourcePrefix = shadowSourceModelPrefix(parsed.modelId, _sci.sourceModels)!;
|
|
673
726
|
let sourceIdentity = { providerName: OPENAI_CODEX_PROVIDER_ID, modelId: sourcePrefix };
|
|
674
727
|
try {
|
|
@@ -721,15 +774,22 @@ export async function prepareResponsesRequest(
|
|
|
721
774
|
};
|
|
722
775
|
}
|
|
723
776
|
}
|
|
724
|
-
if (options.compactionRoutingOverride && !compactionRoutingKeepsProviderIdentity(config, options.compactionRoutingOverride, route))
|
|
777
|
+
if ((options.compactionRoutingOverride && !compactionRoutingKeepsProviderIdentity(config, options.compactionRoutingOverride, route))
|
|
778
|
+
|| options.mainHardLockFallbackOverride) {
|
|
725
779
|
credentialDomainWasRewritten = true;
|
|
726
780
|
// The destination does not share the conversation's credential domain, so it can neither
|
|
727
781
|
// verify the source backend's reasoning ciphertext nor decode its native compaction blob.
|
|
728
782
|
// This is the same condition an account change already reports (account-change-state.ts),
|
|
729
783
|
// and the serializer turns a stored summary into readable text instead of dropping it.
|
|
730
784
|
parsed._stripReasoningEncryptedContent = true;
|
|
785
|
+
parsed._stripNativeCompactionEncryptedContent = true;
|
|
731
786
|
if (parsed._compactionRequest === true) parsed._portableCompaction = true;
|
|
732
787
|
}
|
|
788
|
+
// A v1 fallback needs an ocx1 summary for replacement-history decoding, even when this
|
|
789
|
+
// custom destination separately accepts native v2 triggers. Legacy defaults stay intact.
|
|
790
|
+
if (options.compactionRecoveryKind === "compaction-v1" && parsed._compactionRequest === true
|
|
791
|
+
&& route.provider.adapter === "openai-responses" && route.provider.supportsNativeCompactionTrigger === true
|
|
792
|
+
&& !isCanonicalOpenAiForwardProvider(route.provider)) parsed._portableCompaction = true;
|
|
733
793
|
logCtx.routeDecision = route.routeDecision;
|
|
734
794
|
logCtx.policyEligibility = route.policyEligibility;
|
|
735
795
|
} catch (err) {
|
|
@@ -740,7 +800,7 @@ export async function prepareResponsesRequest(
|
|
|
740
800
|
if (policyRefusal) return policyRefusal;
|
|
741
801
|
if (err instanceof AdmissionModelDeniedError) return admissionModelDeniedResponse(err);
|
|
742
802
|
if (err instanceof NoAvailableComboTargetsError) {
|
|
743
|
-
return comboUnavailable(err.comboId);
|
|
803
|
+
return comboUnavailable(err.comboId, undefined, err.reasons);
|
|
744
804
|
}
|
|
745
805
|
if (err instanceof NoEligiblePolicyCandidateError) {
|
|
746
806
|
// Persist the evaluation trace (per-candidate exclusions + the
|
|
@@ -958,7 +1018,7 @@ export async function prepareResponsesRequest(
|
|
|
958
1018
|
if (policyRefusal) return policyRefusal;
|
|
959
1019
|
if (err instanceof AdmissionModelDeniedError) return admissionModelDeniedResponse(err);
|
|
960
1020
|
if (err instanceof NoAvailableComboTargetsError) {
|
|
961
|
-
return comboUnavailable(err.comboId);
|
|
1021
|
+
return comboUnavailable(err.comboId, undefined, err.reasons);
|
|
962
1022
|
}
|
|
963
1023
|
if (err instanceof NoEligiblePolicyCandidateError) {
|
|
964
1024
|
logCtx.routeDecision = err.trace;
|
|
@@ -1030,6 +1090,7 @@ export async function prepareResponsesRequest(
|
|
|
1030
1090
|
"_promptCacheKeyIsSharedCohort",
|
|
1031
1091
|
"_cursorClientThreadId",
|
|
1032
1092
|
"_reasoningReplayScope",
|
|
1093
|
+
"_nativeReasoningRetention",
|
|
1033
1094
|
"_cursorIsolateConversation",
|
|
1034
1095
|
];
|
|
1035
1096
|
for (const key of kept) {
|
|
@@ -1175,7 +1236,7 @@ export async function prepareResponsesRequest(
|
|
|
1175
1236
|
if (policyRefusal) return policyRefusal;
|
|
1176
1237
|
if (err instanceof AdmissionModelDeniedError) return admissionModelDeniedResponse(err);
|
|
1177
1238
|
if (err instanceof NoAvailableComboTargetsError) {
|
|
1178
|
-
return comboUnavailable(err.comboId);
|
|
1239
|
+
return comboUnavailable(err.comboId, undefined, err.reasons);
|
|
1179
1240
|
}
|
|
1180
1241
|
if (err instanceof NoEligiblePolicyCandidateError) {
|
|
1181
1242
|
logCtx.routeDecision = err.trace;
|
|
@@ -1492,6 +1553,8 @@ export async function prepareResponsesRequest(
|
|
|
1492
1553
|
applyAccountChangeConversationStateScrub({
|
|
1493
1554
|
body: parsed._rawBody,
|
|
1494
1555
|
parsed,
|
|
1556
|
+
preserveReasoningEncryptedContent: isCanonicalOpenAiForwardProvider(route.provider)
|
|
1557
|
+
&& parsed._nativeReasoningRetention?.accountSwitch === true,
|
|
1495
1558
|
bindingKey: binding.bindingKey,
|
|
1496
1559
|
servingAccountId: binding.accountId,
|
|
1497
1560
|
logCtx,
|
|
@@ -1511,6 +1574,7 @@ export async function prepareResponsesRequest(
|
|
|
1511
1574
|
return {
|
|
1512
1575
|
inboundWire,
|
|
1513
1576
|
clientDeclaredEmptyToolCatalog: options.clientDeclaredEmptyToolCatalog === true,
|
|
1577
|
+
clientToolCatalogRestriction,
|
|
1514
1578
|
// Admission facts later route rewrites must not re-derive: the selector the caller sent and
|
|
1515
1579
|
// the key scope as it stood when this request was admitted.
|
|
1516
1580
|
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);
|