@bitkyc08/opencodex 2.83.0 → 2.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -4
- package/bin/ocx.mjs +61 -3
- package/gui/dist/assets/App-BjlttVLy.js +58 -0
- package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
- package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
- package/gui/dist/assets/index-BMssP4k3.js +94 -0
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +2 -3
- package/src/adapters/anthropic/beta-allowlist.ts +87 -8
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +33 -4
- package/src/adapters/azure.ts +19 -1
- package/src/adapters/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +15 -8
- package/src/adapters/coding-agent/turn.ts +22 -9
- package/src/adapters/cursor/arg-codec.ts +17 -5
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/freeform-args.ts +75 -0
- package/src/adapters/cursor/protobuf-events.ts +36 -23
- package/src/adapters/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +2 -2
- package/src/adapters/ollama-native.ts +29 -1
- package/src/adapters/openai-chat/messages.ts +8 -4
- package/src/adapters/openai-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- package/src/adapters/openai-responses/empty-assistant.ts +32 -0
- package/src/adapters/openai-responses/passthrough.ts +3 -0
- package/src/adapters/openai-responses/request-strips.ts +10 -2
- package/src/adapters/qoder/adapter.ts +56 -15
- package/src/advisor/consult.ts +220 -0
- package/src/advisor/context.ts +226 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/runtime.ts +305 -0
- package/src/advisor/settings.ts +135 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -0
- package/src/bridge.ts +1 -1
- package/src/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/context-windows.ts +54 -1
- package/src/claude/desktop-3p-library.ts +43 -3
- package/src/claude/desktop-3p.ts +22 -8
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/inbound.ts +121 -50
- package/src/claude/intercept/connect-proxy.ts +10 -3
- package/src/claude/intercept/local-ca.ts +8 -1
- package/src/claude/intercept/picker-ca-persistence.ts +3 -1
- package/src/claude/intercept/picker-listener.ts +49 -1
- package/src/claude/intercept/settings-no-proxy.ts +146 -0
- package/src/claude/intercept/settings.ts +101 -9
- package/src/claude/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +13 -6
- package/src/cli/account-api.ts +65 -0
- package/src/cli/account-auth.ts +26 -2
- package/src/cli/account.ts +41 -5
- package/src/cli/advisor.ts +151 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-access-remote.ts +8 -0
- package/src/cli/capabilities-agents-routing.ts +18 -0
- package/src/cli/capabilities-base.ts +14 -2
- package/src/cli/catalog-command-result.ts +2 -1
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +60 -11
- package/src/cli/config-command.ts +8 -2
- package/src/cli/debug.ts +8 -0
- package/src/cli/dispatch.ts +21 -4
- package/src/cli/doctor.ts +9 -0
- package/src/cli/ensure-desktop-cold-start.ts +329 -0
- package/src/cli/export-command.ts +15 -2
- package/src/cli/help.ts +2 -1
- package/src/cli/hub-disable.ts +99 -0
- package/src/cli/hub.ts +4 -2
- package/src/cli/index.ts +26 -8
- package/src/cli/integration-plan-dto.ts +2 -0
- package/src/cli/link-child.ts +104 -0
- package/src/cli/link-enrollment.ts +96 -0
- package/src/cli/link.ts +39 -24
- package/src/cli/observe-stream.ts +6 -1
- package/src/cli/observe.ts +10 -2
- package/src/cli/provider.ts +12 -0
- package/src/cli/registry.ts +20 -7
- package/src/cli/restart-scope.ts +3 -1
- package/src/cli/runtime-api.ts +27 -2
- package/src/cli/status.ts +104 -20
- package/src/client/connect.ts +39 -4
- package/src/client/hub-client.ts +14 -7
- package/src/client/hub-state.ts +25 -14
- package/src/client/link-home.ts +248 -0
- package/src/client/link-management-relay.ts +61 -0
- package/src/client/link-relay.ts +6 -1
- package/src/client/link-status.ts +14 -0
- package/src/client/machine-api.ts +14 -7
- package/src/client/machine-listener.ts +29 -4
- package/src/clients/config-export/contracts.ts +1 -0
- package/src/clients/config-export/model-metadata.ts +3 -2
- package/src/clients/config-export/qoder.ts +93 -0
- package/src/clients/config-export.ts +59 -16
- package/src/clients/omo-role-models.ts +6 -3
- package/src/codex/agent-role-models.ts +26 -8
- package/src/codex/auth-api/account-list.ts +17 -2
- package/src/codex/auth-api/account-selection.ts +17 -7
- package/src/codex/auth-api/pool-mode-gate.ts +5 -2
- package/src/codex/auth-api/routes.ts +5 -1
- package/src/codex/auth-context.ts +79 -5
- package/src/codex/autostart-health.ts +7 -1
- package/src/codex/catalog/build-entries.ts +11 -2
- package/src/codex/catalog/bundled.ts +5 -1
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +6 -3
- package/src/codex/catalog/metadata.ts +4 -2
- package/src/codex/catalog/model-hints.ts +21 -3
- package/src/codex/catalog/native-models.ts +16 -2
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +5 -0
- package/src/codex/catalog/provider-models.ts +4 -0
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +22 -4
- package/src/codex/catalog/vendor-context.ts +82 -0
- package/src/codex/config-write-lock.ts +40 -2
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desktop-app/types.ts +2 -0
- package/src/codex/desktop-app/windows.ts +29 -16
- package/src/codex/desktop-app-restart.ts +4 -2
- package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
- package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
- package/src/codex/desktop-compatibility/connection-store.ts +87 -0
- package/src/codex/desktop-compatibility/installed-build.ts +55 -0
- package/src/codex/desktop-compatibility/json-body.ts +29 -0
- package/src/codex/desktop-compatibility/native-identity.ts +62 -0
- package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
- package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
- package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
- package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
- package/src/codex/desktop-compatibility/runtime.ts +173 -0
- package/src/codex/desktop-compatibility/service.ts +30 -0
- package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
- package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
- package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
- package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
- package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
- package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
- package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
- package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
- package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
- package/src/codex/desktop-compatibility/windows-key-protection.ts +59 -0
- package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
- package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
- package/src/codex/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/remove.ts +18 -6
- package/src/codex/journal.ts +16 -1
- package/src/codex/main-account.ts +99 -15
- package/src/codex/native-main-refresh-mode.ts +217 -0
- package/src/codex/native-profile-startup.ts +4 -1
- package/src/codex/prompt-journal.ts +20 -2
- package/src/codex/prompt-layers/encoding.ts +49 -0
- package/src/codex/prompt-layers/import-source.ts +30 -0
- package/src/codex/prompt-layers/toml-edit.ts +269 -37
- package/src/codex/prompt-layers/toml-read.ts +52 -4
- package/src/codex/prompt-layers.ts +383 -20
- package/src/codex/prompt-lock-claim.ts +13 -3
- package/src/codex/prompt-lock-evidence.ts +3 -3
- package/src/codex/prompt-lock-owner.ts +14 -6
- package/src/codex/prompt-text-probe.ts +34 -5
- package/src/codex/quota.ts +13 -6
- package/src/codex/role-auto-assign.ts +2 -5
- package/src/codex/routing/active-account.ts +21 -0
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing.ts +1 -1
- package/src/codex/runtime.ts +196 -8
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +15 -6
- package/src/codex/shim.ts +1 -1
- package/src/combos/failover.ts +3 -0
- package/src/config/api-key-reconcile.ts +34 -0
- package/src/config/diagnostics.ts +18 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/live-reconcile.ts +56 -29
- package/src/config/live-topology.ts +29 -0
- package/src/config/load-degrade.ts +8 -2
- package/src/config/paths.ts +13 -3
- package/src/config/salvage.ts +3 -1
- package/src/config/schema/config-schema.ts +19 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +1 -0
- package/src/generated/compatibility-version.json +713 -285
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/grpc-web.ts +23 -1
- package/src/grok/reset-coupon-ledger.ts +75 -5
- package/src/grok/reset-coupons.ts +4 -2
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/config-io.ts +20 -2
- package/src/integrations/journal.ts +15 -1
- package/src/integrations/mutation-plan.ts +14 -7
- package/src/integrations/registry.ts +52 -11
- package/src/integrations/state.ts +19 -7
- package/src/integrations/store.ts +3 -0
- package/src/integrations/target.ts +42 -5
- package/src/integrations/writer.ts +24 -7
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/app-owned-memory-stores.ts +4 -0
- package/src/lib/app-owned-memory.ts +11 -0
- package/src/lib/background-work.ts +65 -0
- package/src/lib/bounded-file-read.ts +58 -0
- package/src/lib/bun-path-runtime.d.mts +10 -1
- package/src/lib/bun-path-runtime.mjs +62 -3
- package/src/lib/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +107 -0
- package/src/lib/errors.ts +7 -0
- package/src/lib/json-byte-size.ts +31 -0
- package/src/lib/local-internal-call-capability.ts +55 -0
- package/src/lib/optional-shutdown-hooks.ts +35 -0
- package/src/lib/outbound-credential-registry.ts +210 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/passive-read.ts +35 -0
- package/src/lib/shadow-call.ts +44 -5
- package/src/lib/socks5-fetch.ts +48 -71
- package/src/lib/socks5-handshake.ts +112 -0
- package/src/lib/translator-budget.ts +7 -1
- package/src/link/codex-settings.ts +155 -0
- package/src/link/management-routes.ts +89 -0
- package/src/link/remote-probe.ts +38 -0
- package/src/link/ssh-argv.ts +26 -5
- package/src/link/ssh-runner.ts +58 -27
- package/src/link/status-projection.ts +22 -4
- package/src/link/supervisor.ts +144 -49
- package/src/link/tunnel-state.ts +11 -4
- package/src/oauth/anthropic-continuity.ts +4 -3
- package/src/oauth/anthropic-identity.ts +13 -2
- package/src/oauth/anthropic-model-quota.ts +13 -1
- package/src/oauth/anthropic-routing.ts +230 -23
- package/src/oauth/anthropic.ts +18 -4
- package/src/oauth/generic-account-failover.ts +29 -5
- package/src/oauth/google-antigravity.ts +24 -7
- package/src/oauth/index.ts +28 -17
- package/src/oauth/kiro-account-identity.ts +106 -0
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +55 -6
- package/src/oauth/plan.ts +29 -0
- package/src/oauth/store.ts +40 -3
- package/src/oauth/types.ts +2 -0
- package/src/plugins/loader.ts +11 -0
- package/src/protocols/codecs/chat-ir.ts +467 -0
- package/src/protocols/codecs/chat.ts +10 -1
- package/src/protocols/codecs/messages-ir.ts +500 -0
- package/src/protocols/features.ts +10 -2
- package/src/protocols/path.ts +11 -1
- package/src/protocols/plan-snapshot.ts +39 -16
- package/src/protocols/plan.ts +9 -1
- package/src/protocols/settings.ts +8 -0
- package/src/providers/anthropic-billing-ownership.ts +74 -0
- package/src/providers/anthropic-billing-recovery.ts +31 -0
- package/src/providers/anthropic-billing-refusal.ts +55 -0
- package/src/providers/anthropic-reset-admission.ts +62 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- package/src/providers/api-key-resolve.ts +17 -2
- package/src/providers/key-failover.ts +52 -5
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/model-rename-migration.ts +5 -3
- package/src/providers/model-rename-startup.ts +5 -3
- package/src/providers/quota/account-cache.ts +11 -0
- package/src/providers/quota/anthropic-account-quota.ts +4 -2
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +3 -3
- package/src/providers/quota.ts +4 -1
- package/src/providers/registry/entries-core.ts +21 -20
- package/src/providers/registry/entries-extended.ts +19 -1
- package/src/providers/registry/model-seeds.ts +17 -0
- package/src/providers/request-pacing.ts +9 -0
- package/src/providers/stale-no-vision-seed-migration.ts +72 -0
- package/src/responses/compaction.ts +14 -0
- package/src/responses/parser.ts +37 -20
- package/src/responses/state.ts +32 -29
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +248 -216
- package/src/routing/history/ingest.ts +238 -0
- package/src/routing/history/rebuild-job.ts +111 -0
- package/src/routing/history/rebuild-protocol.ts +15 -0
- package/src/routing/history/rebuild-worker.ts +34 -0
- package/src/server/admission-model-scope.ts +4 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +46 -0
- package/src/server/chat-completions.ts +75 -9
- package/src/server/claude-messages.ts +110 -20
- package/src/server/configured-credentials.ts +194 -0
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- package/src/server/index/link-management.ts +58 -0
- package/src/server/index/optional-listeners.ts +5 -1
- package/src/server/index/serve-options.ts +15 -2
- package/src/server/index/startup-warnings.ts +9 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +13 -10
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/lifecycle.ts +2 -1
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +191 -0
- package/src/server/management/agent-settings-routes.ts +50 -14
- package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
- package/src/server/management/codex-agent-role-routes.ts +19 -6
- package/src/server/management/codex-prompt-routes.ts +136 -5
- package/src/server/management/codex-role-auto-assign.ts +1 -1
- package/src/server/management/companion-routes.ts +4 -0
- package/src/server/management/config-routes.ts +2 -2
- package/src/server/management/context.ts +4 -0
- package/src/server/management/desktop-compatibility-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
- package/src/server/management/grok-coupon-routes.ts +78 -27
- package/src/server/management/link-enrollment.ts +192 -0
- package/src/server/management/link-route-state.ts +185 -0
- package/src/server/management/link-routes.ts +82 -402
- package/src/server/management/model-routes.ts +2 -2
- package/src/server/management/model-rows.ts +3 -1
- package/src/server/management/protocol-settings-patch.ts +1 -0
- package/src/server/management/provider-overwrite-carry.ts +10 -0
- package/src/server/management/provider-routes.ts +1 -0
- package/src/server/management/route-registry.ts +16 -3
- package/src/server/management/shared.ts +3 -0
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management-api.ts +9 -1
- package/src/server/messages-antigravity-direct.ts +195 -0
- package/src/server/messages-native.ts +73 -43
- package/src/server/relay-eager.ts +2 -0
- package/src/server/request-log-filter.ts +6 -1
- package/src/server/request-log-terminal-status.ts +63 -6
- package/src/server/request-log.ts +7 -0
- package/src/server/responses/adapter-continuation.ts +35 -18
- package/src/server/responses/adapter-delivery.ts +24 -2
- package/src/server/responses/adapter-dispatch.ts +261 -81
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-slot.ts +336 -0
- package/src/server/responses/code-mode-collab-surface.ts +313 -0
- package/src/server/responses/codex-ws-exchange.ts +147 -6
- package/src/server/responses/codex-ws-wire.ts +203 -0
- package/src/server/responses/collaboration.ts +36 -7
- package/src/server/responses/compact.ts +126 -48
- package/src/server/responses/context-overflow.ts +13 -1
- package/src/server/responses/core-codex-account.ts +105 -1
- package/src/server/responses/core-combo-failure.ts +24 -1
- package/src/server/responses/core-combo.ts +28 -2
- package/src/server/responses/core-errors.ts +29 -1
- package/src/server/responses/core-lifetime.ts +2 -0
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-options.ts +48 -0
- package/src/server/responses/core-replay.ts +7 -4
- package/src/server/responses/core.ts +1 -1
- package/src/server/responses/deferred-collab-surface.ts +158 -0
- package/src/server/responses/deferred-send-reporter.ts +25 -0
- package/src/server/responses/fetch-helpers.ts +12 -1
- package/src/server/responses/passthrough-delivery.ts +20 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -7
- package/src/server/responses/request-prepare.ts +74 -9
- package/src/server/responses/request-transport.ts +22 -8
- package/src/server/responses/run-turn-execution.ts +70 -8
- package/src/server/responses/sidecar-execution.ts +89 -5
- package/src/server/responses/terminal-guard.ts +2 -1
- package/src/server/responses/ws-upstream.ts +9 -1
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +38 -4
- package/src/server/startup-health-cache.ts +5 -0
- package/src/server/system-env-shell.ts +7 -2
- package/src/server/system-env.ts +11 -1
- package/src/service/cli.ts +5 -1
- package/src/service/desktop-startup.ts +161 -34
- package/src/service/desktop-supervision.d.mts +27 -1
- package/src/service/desktop-supervision.mjs +111 -5
- package/src/service/health-sentinel-launch.ts +176 -0
- package/src/service/health-sentinel-main.ts +466 -0
- package/src/service/health-sentinel-policy.ts +241 -0
- package/src/service/health-sentinel-probe.ts +71 -0
- package/src/service/health-sentinel-windows.ts +87 -0
- package/src/service/linux-linger.ts +20 -0
- package/src/service/logging.ts +72 -0
- package/src/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +68 -6
- package/src/types/provider.ts +7 -0
- package/src/types/request.ts +15 -0
- package/src/types/tools.ts +2 -0
- package/src/update/async-check.ts +1 -1
- package/src/update/badge.ts +2 -2
- package/src/update/index.ts +88 -15
- package/src/update/job-supervised-restart.ts +107 -0
- package/src/update/job.ts +32 -32
- package/src/update/notify.ts +1 -1
- package/src/update/refresh-scheduler.ts +2 -2
- package/src/update/supervised-recovery.d.mts +27 -0
- package/src/update/supervised-recovery.mjs +157 -0
- package/src/usage/cache-diagnostic.ts +2 -0
- package/src/usage/expected-prices.ts +16 -0
- package/src/usage/log.ts +19 -1
- package/src/web-search/gemini-executor.ts +2 -2
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-Ba8uG696.js +0 -54
- package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
- package/gui/dist/assets/index-BHRZdJuv.js +0 -94
- package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { deferPhysicalSendReporter } from "./deferred-send-reporter";
|
|
1
2
|
import { spendLedgerStorageErrorResponse } from "./spend-storage-error";
|
|
2
3
|
import { classifyAnthropic429 } from "../../oauth/anthropic-rate-limit-policy";
|
|
3
4
|
import { rotateAnthropicAccountOnResponseForInstance } from "../../oauth/anthropic-account-refusal";
|
|
@@ -23,11 +24,13 @@ import {
|
|
|
23
24
|
sealRequestAttemptIdentity,
|
|
24
25
|
recordAttemptCredentialSource,
|
|
25
26
|
} from "../request-log";
|
|
26
|
-
import { clientCancelledResponse, readDisplaySafeErrorText, normalizeUpstreamErrorText } from "./core-errors";
|
|
27
|
+
import { anthropicPoolCooldownResponse, clientCancelledResponse, readDisplaySafeErrorText, normalizeUpstreamErrorText } from "./core-errors";
|
|
28
|
+
import { isTranslatorBudgetExceededError } from "../../lib/translator-budget";
|
|
27
29
|
import { redactSecretString } from "../../lib/redact";
|
|
28
30
|
import { rewriteUpstreamPolicyRefusal } from "./policy-refusal";
|
|
29
31
|
import { withProviderRequestSlot } from "../../providers/request-pacing";
|
|
30
32
|
import { providerFetch, fetchWithHeaderTimeout, safeHostLabel } from "./fetch-helpers";
|
|
33
|
+
import { isAnthropicBillingKeyPool, rotateAnthropicBillingKeyOnResponse } from "../../providers/anthropic-billing-recovery";
|
|
31
34
|
import {
|
|
32
35
|
transientRetryPolicyFor,
|
|
33
36
|
resetReplayPolicyFor,
|
|
@@ -91,7 +94,8 @@ import {
|
|
|
91
94
|
} from "../../providers/opencode-zen-rate-limit";
|
|
92
95
|
import { planReasoningEffortDowngrade } from "../../providers/reasoning-metadata";
|
|
93
96
|
import { consumeComboFailure } from "./core-combo-failure";
|
|
94
|
-
import { streamingContextOverflowResponse, jsonContextOverflowResponse } from "./context-overflow";
|
|
97
|
+
import { streamingContextOverflowResponse, jsonContextOverflowResponse, lateAttributionLimitResponse } from "./context-overflow";
|
|
98
|
+
import { OllamaLateAttributionBudgetError } from "../../adapters/ollama-native";
|
|
95
99
|
import { isFixedCodexAccount } from "./core-codex-account";
|
|
96
100
|
import { recordSubagentQuotaFailureForThreadSpawn } from "../../codex/subagent-model-fallback";
|
|
97
101
|
import {
|
|
@@ -108,6 +112,7 @@ import { mapCodexAuthContextErrorToResponse } from "./codex-auth-error";
|
|
|
108
112
|
import { chargeWorkflowSends } from "../../lib/workflow-budget";
|
|
109
113
|
import { isAntigravityValidationRefusal } from "./antigravity-validation-refusal";
|
|
110
114
|
import { unboundPoolSpendRefusalResponse } from "../workflow-refusal";
|
|
115
|
+
import { AdmissionModelDeniedError, admissionModelDeniedResponse } from "../admission-model-scope";
|
|
111
116
|
|
|
112
117
|
/** One responsibility of the Responses request pipeline; state owners are explicit. */
|
|
113
118
|
export async function prepareAdapterExchange(
|
|
@@ -221,8 +226,13 @@ export async function prepareAdapterExchange(
|
|
|
221
226
|
|
|
222
227
|
|
|
223
228
|
let selfContainedJudgment: boolean | undefined;
|
|
229
|
+
// On the Antigravity direct lane the raw body carries only settings; judge the body the bridge
|
|
230
|
+
// would have sent, built only if an ambiguous reset actually asks.
|
|
231
|
+
const directRequest = options.directRequest;
|
|
224
232
|
const requestIsSelfContained = (): boolean =>
|
|
225
|
-
selfContainedJudgment ??= selfContainedResponsesBody(
|
|
233
|
+
selfContainedJudgment ??= selfContainedResponsesBody(directRequest
|
|
234
|
+
? { ...(parsed._rawBody as Record<string, unknown>), ...directRequest.bridgeConversation() }
|
|
235
|
+
: parsed._rawBody);
|
|
226
236
|
const claimPreHeaderResend = (): boolean => authorizeResendForRecovery(
|
|
227
237
|
"pre-header", "connection-reset",
|
|
228
238
|
ambiguousResendAllowanceFor(route.provider, requestIsSelfContained, claimAmbiguousResend),
|
|
@@ -341,6 +351,14 @@ export async function prepareAdapterExchange(
|
|
|
341
351
|
cleanupUpstreamAbort();
|
|
342
352
|
upstream.abort();
|
|
343
353
|
if (options.abortSignal?.aborted) return clientCancelledResponse();
|
|
354
|
+
if (err instanceof OllamaLateAttributionBudgetError) return lateAttributionLimitResponse();
|
|
355
|
+
// An adapter can also throw the shared translator budget while sizing generated
|
|
356
|
+
// request content (e.g. Ollama late-tool attribution); keep its established 413.
|
|
357
|
+
if (isTranslatorBudgetExceededError(err)) {
|
|
358
|
+
return formatErrorResponse(413, "request_too_large", "request translation buffer exceeded the safe limit", {
|
|
359
|
+
code: "translation_buffer_limit",
|
|
360
|
+
});
|
|
361
|
+
}
|
|
344
362
|
const msg = err instanceof Error ? err.message : String(err);
|
|
345
363
|
return formatErrorResponse(400, "invalid_request_error", redactSecretString(msg));
|
|
346
364
|
}
|
|
@@ -362,6 +380,23 @@ export async function prepareAdapterExchange(
|
|
|
362
380
|
: transportState.oauth401ReplayAttempted ? "oauth-401" : undefined
|
|
363
381
|
: undefined;
|
|
364
382
|
let upstreamResponse: Response;
|
|
383
|
+
// Preserve bounded terminal bytes without retaining the refused send's pacing lease.
|
|
384
|
+
// Reading the owned body to EOF (or cancelling on its bound) frees capacity BEFORE
|
|
385
|
+
// replacement admission; keeping that live body would wait on its own concurrency slot.
|
|
386
|
+
const retainFailureResponse = async (): Promise<Response> => {
|
|
387
|
+
const original = upstreamResponse;
|
|
388
|
+
const text = await readDisplaySafeErrorText(original, upstream.signal, "");
|
|
389
|
+
const headers = new Headers(original.headers);
|
|
390
|
+
headers.delete("content-length");
|
|
391
|
+
headers.delete("content-encoding");
|
|
392
|
+
upstreamResponse = new Response(text, { status: original.status, statusText: original.statusText, headers });
|
|
393
|
+
return upstreamResponse;
|
|
394
|
+
};
|
|
395
|
+
const billingPoolRequest = isAnthropicBillingKeyPool(route.provider);
|
|
396
|
+
let billingRecoveryActive = false;
|
|
397
|
+
const genericRefetchCountsSends = () => sendBudgetState.adapterSendBudget?.spendEnforced === true
|
|
398
|
+
|| transientRetryPolicyFor(route.provider) !== null || resetReplayPolicyFor(route.provider) !== null
|
|
399
|
+
|| billingPoolRequest || billingRecoveryActive;
|
|
365
400
|
let handoffPermit: ResponsesSendBudget["pendingHopPermit"];
|
|
366
401
|
try {
|
|
367
402
|
if (transportState.activeAdapter.fetchResponse) {
|
|
@@ -434,6 +469,8 @@ export async function prepareAdapterExchange(
|
|
|
434
469
|
const fetchWithRetryPolicy = (route.provider.adapter === "google" || transientPolicy)
|
|
435
470
|
? fetchWithTransientRetry
|
|
436
471
|
: fetchWithResetRetry;
|
|
472
|
+
const initialReporter = transientSendReporter(compactPrepaid ?? options.comboDispatchPermit);
|
|
473
|
+
const deferredInitial = billingPoolRequest && !receiptMode ? deferPhysicalSendReporter(initialReporter) : undefined;
|
|
437
474
|
try { upstreamResponse = await fetchWithRetryPolicy(
|
|
438
475
|
recovery => {
|
|
439
476
|
// Unconfigured generic sends may omit the counting reporter. Capture policy
|
|
@@ -442,10 +479,11 @@ export async function prepareAdapterExchange(
|
|
|
442
479
|
poolId: logCtx.spendPoolId ?? route.providerName, identityId: logCtx.accountLogLabel,
|
|
443
480
|
});
|
|
444
481
|
// Receipt mode books the physical send at executor admission instead of here.
|
|
445
|
-
if (!receiptMode && compactPrepaid && !compactPrepaidUsed) {
|
|
482
|
+
if (!deferredInitial && !receiptMode && compactPrepaid && !compactPrepaidUsed) {
|
|
446
483
|
if (!compactPrepaid.use()) throw new SendBudgetExhaustedError(safeHostLabel(builtInitialRequest.url));
|
|
447
484
|
compactPrepaidUsed = true;
|
|
448
485
|
}
|
|
486
|
+
// Key transport keeps this metadata pending until it binds the actual selection.
|
|
449
487
|
transportState.noteRoutedAttemptSend(inputTokenEstimate, recovery ?? initialRecovery);
|
|
450
488
|
return fetchWithHeaderTimeout(builtInitialRequest.url, applyUpstreamRecoveryInit({
|
|
451
489
|
method: builtInitialRequest.method,
|
|
@@ -457,6 +495,13 @@ export async function prepareAdapterExchange(
|
|
|
457
495
|
providerName: route.providerName,
|
|
458
496
|
modelId: route.modelId,
|
|
459
497
|
onPhysicalDispatch: () => {
|
|
498
|
+
if (deferredInitial) {
|
|
499
|
+
if (!deferredInitial.beforeDispatch()) throw new SendBudgetExhaustedError(safeHostLabel(builtInitialRequest.url));
|
|
500
|
+
if (compactPrepaid && !compactPrepaidUsed) {
|
|
501
|
+
if (!compactPrepaid.use()) throw new SendBudgetExhaustedError(safeHostLabel(builtInitialRequest.url));
|
|
502
|
+
compactPrepaidUsed = true;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
460
505
|
// Preserve the unconfigured direct-adapter accounting contract; Combo admission
|
|
461
506
|
// and explicit retry/compaction budgets require a receipt for their booked send.
|
|
462
507
|
if (receiptMode) {
|
|
@@ -473,6 +518,7 @@ export async function prepareAdapterExchange(
|
|
|
473
518
|
label: safeHostLabel(builtInitialRequest.url),
|
|
474
519
|
claimAmbiguousResend: claimPreHeaderResend,
|
|
475
520
|
...(sendBudgetState.adapterSendBudget?.spendEnforced || options.comboDispatchPermit || transientPolicy || resetPolicy || compactPrepaid || options.comboAttempt
|
|
521
|
+
|| billingPoolRequest
|
|
476
522
|
? {
|
|
477
523
|
// A pending compaction permit already booked this leg's first physical send.
|
|
478
524
|
...(transientPolicy || resetPolicy || compactPrepaid || options.comboAttempt
|
|
@@ -485,7 +531,7 @@ export async function prepareAdapterExchange(
|
|
|
485
531
|
// settled by `onPhysicalDispatch`, so a reporter here would count them twice.
|
|
486
532
|
...(receiptMode
|
|
487
533
|
? {}
|
|
488
|
-
: { onSendsConsumed:
|
|
534
|
+
: { onSendsConsumed: deferredInitial?.report ?? initialReporter }),
|
|
489
535
|
}
|
|
490
536
|
: {}),
|
|
491
537
|
},
|
|
@@ -501,6 +547,10 @@ export async function prepareAdapterExchange(
|
|
|
501
547
|
return storageRefusal;
|
|
502
548
|
}
|
|
503
549
|
const refusal = err instanceof UpstreamRetryEvidenceError ? err.cause : err;
|
|
550
|
+
if (refusal instanceof AdmissionModelDeniedError) {
|
|
551
|
+
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
552
|
+
return admissionModelDeniedResponse(refusal);
|
|
553
|
+
}
|
|
504
554
|
const codexRefusal = mapCodexAuthContextErrorToResponse(unwrapUpstreamRetryEvidenceError(err), {
|
|
505
555
|
now: Date.now(), accountSelector: route.codexAccountNamespace,
|
|
506
556
|
});
|
|
@@ -516,8 +566,7 @@ export async function prepareAdapterExchange(
|
|
|
516
566
|
return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(refusal));
|
|
517
567
|
}
|
|
518
568
|
if (refusal instanceof AnthropicAccountCooldownError) {
|
|
519
|
-
return
|
|
520
|
-
refusal.retryAfterSeconds === null ? undefined : { retryAfter: String(refusal.retryAfterSeconds) });
|
|
569
|
+
return anthropicPoolCooldownResponse(refusal, { comboAttempt: options.comboAttempt, logCtx });
|
|
521
570
|
}
|
|
522
571
|
// A budget refusal is a decision this process made, not an upstream fault. Reporting it as
|
|
523
572
|
// 502 does more than mislabel it: the Codex client retries 5xx and does not retry a 429, so
|
|
@@ -604,13 +653,26 @@ export async function prepareAdapterExchange(
|
|
|
604
653
|
recordAdapterReasoning(logCtx, retryRequest);
|
|
605
654
|
recordAdapterTier(logCtx, retryRequest);
|
|
606
655
|
} catch (err) {
|
|
607
|
-
|
|
656
|
+
// A rotated credential that cannot be shaped keeps the refusal it was meant to
|
|
657
|
+
// replace. The image-tier rebuild is different: the request itself failed to shape,
|
|
658
|
+
// and its established 413/400 build-error contract stays authoritative.
|
|
659
|
+
if (preserveFailureResponse && recovery !== "image-413" && !options.abortSignal?.aborted) {
|
|
660
|
+
return { failed: preserveFailureResponse };
|
|
661
|
+
}
|
|
608
662
|
// A rotated/rebuilt adapter build failure is a request-shaping error, not an
|
|
609
|
-
// upstream connect failure: tear the abort link down and map it as 400
|
|
610
|
-
// translator-budget
|
|
663
|
+
// upstream connect failure: tear the abort link down and map it as 400. A
|
|
664
|
+
// translator-budget refusal keeps its established 413 here as well — adapters
|
|
665
|
+
// can throw it while sizing generated content (e.g. Ollama late-tool
|
|
666
|
+
// attribution), not only parseRequest/buildToolBridgeMaps.
|
|
611
667
|
cleanupUpstreamAbort();
|
|
612
668
|
upstream.abort();
|
|
613
669
|
if (options.abortSignal?.aborted) return { failed: clientCancelledResponse() };
|
|
670
|
+
if (err instanceof OllamaLateAttributionBudgetError) return { failed: lateAttributionLimitResponse() };
|
|
671
|
+
if (isTranslatorBudgetExceededError(err)) {
|
|
672
|
+
return { failed: formatErrorResponse(413, "request_too_large", "request translation buffer exceeded the safe limit", {
|
|
673
|
+
code: "translation_buffer_limit",
|
|
674
|
+
}) };
|
|
675
|
+
}
|
|
614
676
|
const msg = err instanceof Error ? err.message : String(err);
|
|
615
677
|
return { failed: formatErrorResponse(400, "invalid_request_error", redactSecretString(msg)) };
|
|
616
678
|
}
|
|
@@ -673,7 +735,7 @@ export async function prepareAdapterExchange(
|
|
|
673
735
|
const refetchWithPolicy = (route.provider.adapter === "google" || refetchTransientPolicy)
|
|
674
736
|
? fetchWithTransientRetry
|
|
675
737
|
: fetchWithResetRetry;
|
|
676
|
-
const helperCountsSends =
|
|
738
|
+
const helperCountsSends = genericRefetchCountsSends();
|
|
677
739
|
const prepaid = sendBudgetState.pendingHopPermit;
|
|
678
740
|
const configuredTotal = refetchTransientPolicy?.attempts;
|
|
679
741
|
const refetchCap = transientSendCapFor(configuredTotal, sendBudgetState.targetSendsUsed);
|
|
@@ -689,27 +751,41 @@ export async function prepareAdapterExchange(
|
|
|
689
751
|
{ allowFinalRecoveryReserve: configuredTotal === undefined },
|
|
690
752
|
)
|
|
691
753
|
: undefined;
|
|
754
|
+
let refetchPermitUsed = false;
|
|
755
|
+
const refetchReporter = refetchAllowance ? transientSendReporter(refetchAllowance.permit) : undefined;
|
|
756
|
+
const deferredRefetch = billingPoolRequest && refetchReporter ? deferPhysicalSendReporter(refetchReporter) : undefined;
|
|
692
757
|
try {
|
|
693
758
|
return await refetchWithPolicy(
|
|
694
759
|
recoveryKind => {
|
|
695
760
|
sendBudgetState.adapterSendBudget?.startRequest?.({
|
|
696
761
|
poolId: logCtx.spendPoolId ?? route.providerName, identityId: logCtx.accountLogLabel,
|
|
697
762
|
});
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
763
|
+
// Prepare key metadata before the guarded transport commits its attempt;
|
|
764
|
+
// counters and permits still wait for the physical executor after pacing.
|
|
765
|
+
if (deferredRefetch) transportState.noteRoutedAttemptSend(retryEstimate, recoveryKind ?? recovery);
|
|
766
|
+
const confirmDispatch = () => {
|
|
767
|
+
if (refetchAllowance?.permit && !refetchPermitUsed) {
|
|
768
|
+
if (!refetchAllowance.permit.use()) throw new SendBudgetExhaustedError(safeHostLabel(retryRequest.url));
|
|
769
|
+
refetchPermitUsed = true;
|
|
770
|
+
}
|
|
771
|
+
if (!deferredRefetch) transportState.noteRoutedAttemptSend(retryEstimate, recoveryKind ?? recovery);
|
|
772
|
+
// Eligible pools confirm after pacing at the physical executor; legacy
|
|
773
|
+
// helper paths retain their existing thunk admission boundary.
|
|
774
|
+
onDispatch?.();
|
|
775
|
+
if (preserveFailureResponse) replacementAdmitted = true;
|
|
776
|
+
if (!refetchAllowance) chargeFastDowngradeWorkflowSend();
|
|
777
|
+
};
|
|
778
|
+
if (!deferredRefetch) confirmDispatch();
|
|
707
779
|
return fetchWithHeaderTimeout(retryRequest.url,
|
|
708
780
|
applyUpstreamRecoveryInit({
|
|
709
781
|
method: retryRequest.method, headers: retryRequest.headers, body: retryRequest.body,
|
|
710
782
|
}, recoveryKind), upstream.signal, connectMs, parsed.stream,
|
|
711
783
|
providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
712
784
|
dispatchOverride: oauthDispatch(retryRequest),
|
|
785
|
+
onPhysicalDispatch: deferredRefetch ? () => {
|
|
786
|
+
if (!deferredRefetch.beforeDispatch()) throw new SendBudgetExhaustedError(safeHostLabel(retryRequest.url));
|
|
787
|
+
confirmDispatch();
|
|
788
|
+
} : undefined,
|
|
713
789
|
providerName: route.providerName,
|
|
714
790
|
modelId: route.modelId,
|
|
715
791
|
beforeDispatch: isCanonicalOpenAiForwardProvider(route.provider)
|
|
@@ -723,14 +799,14 @@ export async function prepareAdapterExchange(
|
|
|
723
799
|
...(refetchAllowance
|
|
724
800
|
? {
|
|
725
801
|
attempts: refetchAllowance.attempts,
|
|
726
|
-
onSendsConsumed:
|
|
802
|
+
onSendsConsumed: deferredRefetch?.report ?? refetchReporter,
|
|
727
803
|
}
|
|
728
804
|
: {}),
|
|
729
805
|
},
|
|
730
806
|
);
|
|
731
807
|
} finally {
|
|
732
|
-
//
|
|
733
|
-
//
|
|
808
|
+
// Refund only a reservation whose send never reached its admission boundary.
|
|
809
|
+
// A used or externally settled permit ignores this.
|
|
734
810
|
refetchAllowance?.permit?.release();
|
|
735
811
|
}
|
|
736
812
|
} finally {
|
|
@@ -744,6 +820,13 @@ export async function prepareAdapterExchange(
|
|
|
744
820
|
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
745
821
|
return { failed: storageRefusal };
|
|
746
822
|
}
|
|
823
|
+
const scopeRefusal = unwrapUpstreamRetryEvidenceError(err);
|
|
824
|
+
if (!options.abortSignal?.aborted && scopeRefusal instanceof AdmissionModelDeniedError) {
|
|
825
|
+
cleanupUpstreamAbort();
|
|
826
|
+
upstream.abort();
|
|
827
|
+
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
828
|
+
return { failed: admissionModelDeniedResponse(scopeRefusal) };
|
|
829
|
+
}
|
|
747
830
|
const codexRefusal = !options.abortSignal?.aborted && mapCodexAuthContextErrorToResponse(unwrapUpstreamRetryEvidenceError(err), {
|
|
748
831
|
now: Date.now(), accountSelector: route.codexAccountNamespace,
|
|
749
832
|
});
|
|
@@ -768,8 +851,7 @@ export async function prepareAdapterExchange(
|
|
|
768
851
|
return { failed: formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(refusal)) };
|
|
769
852
|
}
|
|
770
853
|
if (refusal instanceof AnthropicAccountCooldownError) {
|
|
771
|
-
return { failed:
|
|
772
|
-
refusal.retryAfterSeconds === null ? undefined : { retryAfter: String(refusal.retryAfterSeconds) }) };
|
|
854
|
+
return { failed: anthropicPoolCooldownResponse(refusal, { comboAttempt: options.comboAttempt, logCtx }) };
|
|
773
855
|
}
|
|
774
856
|
// Same rule on the recovery leg: the ladder refused to send again, so the answer names
|
|
775
857
|
// this proxy rather than the provider it never reached.
|
|
@@ -980,32 +1062,83 @@ export async function prepareAdapterExchange(
|
|
|
980
1062
|
promptCacheKey: parsed.options.promptCacheKey,
|
|
981
1063
|
});
|
|
982
1064
|
if (!rotated) break;
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1065
|
+
if (billingRecoveryActive && (options.abortSignal?.aborted || upstream.signal.aborted)) {
|
|
1066
|
+
cleanupUpstreamAbort(); upstream.abort(); return clientCancelledResponse();
|
|
1067
|
+
}
|
|
1068
|
+
const billingHop = (billingPoolRequest || billingRecoveryActive)
|
|
1069
|
+
? reserveCredentialHop("auth-recovery", `${route.providerName}|${route.modelId}|key-401`,
|
|
1070
|
+
transportState.activeAdapter.fetchResponse === undefined)
|
|
1071
|
+
: undefined;
|
|
1072
|
+
if (billingHop && !billingHop.allowed) break;
|
|
1073
|
+
const failedResponse = billingPoolRequest || billingRecoveryActive ? await retainFailureResponse() : undefined;
|
|
1074
|
+
try {
|
|
1075
|
+
// Release the failed response's socket before retrying; unread bodies otherwise linger
|
|
1076
|
+
// until runtime cleanup (one per rotated key).
|
|
1077
|
+
if (!failedResponse) try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
1078
|
+
route.provider = rotated;
|
|
1079
|
+
const selectionFailure = await resolveRotatedCopilotSelection();
|
|
1080
|
+
if (selectionFailure) return selectionFailure;
|
|
1081
|
+
invalidateSameTargetRequest();
|
|
1082
|
+
transportState.activeAdapter = resolveSelectionAdapter(
|
|
1083
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
1084
|
+
config.cacheRetention,
|
|
1085
|
+
);
|
|
1086
|
+
bindRouteReasoningReplayScope({
|
|
1087
|
+
parsed,
|
|
1088
|
+
providerName: route.providerName,
|
|
1089
|
+
provider: route.provider,
|
|
1090
|
+
adapterName: transportState.activeAdapter.name,
|
|
1091
|
+
});
|
|
1092
|
+
if (billingHop) sendBudgetState.pendingHopPermit = billingHop.permit;
|
|
1093
|
+
const result = await rebuildAndRefetch("key-401",
|
|
1094
|
+
billingHop ? () => { billingHop.permit?.use(); } : undefined, failedResponse);
|
|
1095
|
+
if ("failed" in result) {
|
|
1096
|
+
if (result.failed !== failedResponse) return result.failed;
|
|
1097
|
+
break;
|
|
1098
|
+
}
|
|
1099
|
+
if (failedResponse) {
|
|
1100
|
+
try { void failedResponse.body?.cancel().catch(() => {}); } catch { /* closed */ }
|
|
1101
|
+
}
|
|
1102
|
+
upstreamResponse = result;
|
|
1103
|
+
// A recovery refetch can itself die on an ambiguous pre-header reset, and the refusal
|
|
1104
|
+
// that answers it is a 429. Every arm below keys on 429, so letting it fall through
|
|
1105
|
+
// hands the marked refusal to the next waiting arm and replays the send it exists to
|
|
1106
|
+
// stop. Re-enter the loop guard instead, which returns it unchanged.
|
|
1107
|
+
if (isNonReplayableResponse(upstreamResponse)) continue recovery;
|
|
1108
|
+
} finally {
|
|
1109
|
+
if (billingHop) { sendBudgetState.pendingHopPermit = undefined; billingHop.permit?.release(); }
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
while ((upstreamResponse.status === 400 || upstreamResponse.status === 402) && hasKeyPoolFailover(route.provider)) {
|
|
1114
|
+
const rotated = await rotateAnthropicBillingKeyOnResponse(upstreamResponse, config, route.providerName, route.provider,
|
|
1115
|
+
{ signal: upstream.signal, promptCacheKey: parsed.options.promptCacheKey });
|
|
1116
|
+
if (upstream.signal.aborted || options.abortSignal?.aborted) {
|
|
1117
|
+
cleanupUpstreamAbort(); upstream.abort(); return clientCancelledResponse();
|
|
1118
|
+
}
|
|
1119
|
+
if (!rotated) break;
|
|
1120
|
+
billingRecoveryActive = true;
|
|
1121
|
+
const hop = reserveCredentialHop("auth-recovery", `${route.providerName}|${route.modelId}|key-401`,
|
|
1122
|
+
transportState.activeAdapter.fetchResponse === undefined);
|
|
1123
|
+
if (!hop.allowed) break;
|
|
1124
|
+
const failedResponse = await retainFailureResponse();
|
|
1125
|
+
try {
|
|
1126
|
+
route.provider = rotated;
|
|
1127
|
+
invalidateSameTargetRequest();
|
|
1128
|
+
transportState.activeAdapter = resolveSelectionAdapter(
|
|
1129
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy), config.cacheRetention);
|
|
1130
|
+
bindRouteReasoningReplayScope({ parsed, providerName: route.providerName, provider: route.provider, adapterName: transportState.activeAdapter.name });
|
|
1131
|
+
sendBudgetState.pendingHopPermit = hop.permit;
|
|
1132
|
+
const result = await rebuildAndRefetch("key-401", () => { hop.permit?.use(); }, failedResponse);
|
|
1133
|
+
if ("failed" in result) {
|
|
1134
|
+
if (result.failed !== failedResponse) return result.failed;
|
|
1135
|
+
break;
|
|
1136
|
+
}
|
|
1137
|
+
try { void failedResponse.body?.cancel().catch(() => {}); } catch { /* closed */ }
|
|
1138
|
+
upstreamResponse = result;
|
|
1139
|
+
continue recovery;
|
|
1140
|
+
} finally { sendBudgetState.pendingHopPermit = undefined; hop.permit?.release(); }
|
|
1141
|
+
}
|
|
1009
1142
|
|
|
1010
1143
|
// Anthropic fast mode refused (no usage credits, organization not enabled, model outside
|
|
1011
1144
|
// the lane, fast pool empty): resend once at standard speed, as Claude Code does. This
|
|
@@ -1024,7 +1157,7 @@ export async function prepareAdapterExchange(
|
|
|
1024
1157
|
const hop = reserveCredentialHop(
|
|
1025
1158
|
"repair",
|
|
1026
1159
|
`${route.providerName}|${route.modelId}|anthropic-fast-downgrade`,
|
|
1027
|
-
!adapterOwnsDispatch && (
|
|
1160
|
+
!adapterOwnsDispatch && genericRefetchCountsSends(),
|
|
1028
1161
|
);
|
|
1029
1162
|
if (hop.allowed) {
|
|
1030
1163
|
anthropicFastDowngradeGuard.attempted = true;
|
|
@@ -1093,7 +1226,9 @@ export async function prepareAdapterExchange(
|
|
|
1093
1226
|
upstreamResponse = result;
|
|
1094
1227
|
// The refusal is a 429 too: without this the while condition is still true and the
|
|
1095
1228
|
// next configured attempt replays it on the same target.
|
|
1096
|
-
if (isNonReplayableResponse(upstreamResponse)
|
|
1229
|
+
if (isNonReplayableResponse(upstreamResponse)
|
|
1230
|
+
|| (isAnthropicBillingKeyPool(route.provider)
|
|
1231
|
+
&& [400, 402].includes(upstreamResponse.status))) continue recovery;
|
|
1097
1232
|
}
|
|
1098
1233
|
|
|
1099
1234
|
// Multi-key 429 failover: rotate to the next pool key (cooldown-aware) and retry the
|
|
@@ -1128,30 +1263,53 @@ export async function prepareAdapterExchange(
|
|
|
1128
1263
|
quotaResetAt,
|
|
1129
1264
|
});
|
|
1130
1265
|
if (!rotated) break;
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1266
|
+
if (billingRecoveryActive && (options.abortSignal?.aborted || upstream.signal.aborted)) {
|
|
1267
|
+
cleanupUpstreamAbort(); upstream.abort(); return clientCancelledResponse();
|
|
1268
|
+
}
|
|
1269
|
+
const billingHop = (billingPoolRequest || billingRecoveryActive)
|
|
1270
|
+
? reserveCredentialHop("auth-recovery", `${route.providerName}|${route.modelId}|key-429`,
|
|
1271
|
+
transportState.activeAdapter.fetchResponse === undefined)
|
|
1272
|
+
: undefined;
|
|
1273
|
+
if (billingHop && !billingHop.allowed) break;
|
|
1274
|
+
const failedResponse = billingPoolRequest || billingRecoveryActive ? await retainFailureResponse() : undefined;
|
|
1275
|
+
try {
|
|
1276
|
+
// Release the failed response's socket before retrying; unread bodies otherwise linger
|
|
1277
|
+
// until runtime cleanup (one per rotated key under a rate-limit storm).
|
|
1278
|
+
if (!failedResponse) try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
1279
|
+
route.provider = rotated;
|
|
1280
|
+
const selectionFailure = await resolveRotatedCopilotSelection();
|
|
1281
|
+
if (selectionFailure) return selectionFailure;
|
|
1282
|
+
invalidateSameTargetRequest();
|
|
1283
|
+
transportState.activeAdapter = resolveSelectionAdapter(
|
|
1284
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
1285
|
+
config.cacheRetention,
|
|
1286
|
+
);
|
|
1287
|
+
bindRouteReasoningReplayScope({
|
|
1288
|
+
parsed,
|
|
1289
|
+
providerName: route.providerName,
|
|
1290
|
+
provider: route.provider,
|
|
1291
|
+
adapterName: transportState.activeAdapter.name,
|
|
1292
|
+
});
|
|
1293
|
+
if (billingHop) sendBudgetState.pendingHopPermit = billingHop.permit;
|
|
1294
|
+
const result = await rebuildAndRefetch("key-429",
|
|
1295
|
+
billingHop ? () => { billingHop.permit?.use(); } : undefined, failedResponse);
|
|
1296
|
+
if ("failed" in result) {
|
|
1297
|
+
if (result.failed !== failedResponse) return result.failed;
|
|
1298
|
+
break;
|
|
1299
|
+
}
|
|
1300
|
+
if (failedResponse) {
|
|
1301
|
+
try { void failedResponse.body?.cancel().catch(() => {}); } catch { /* closed */ }
|
|
1302
|
+
}
|
|
1303
|
+
upstreamResponse = result;
|
|
1304
|
+
// Rotating on the refusal would also write a cooldown against a key that rate-limited
|
|
1305
|
+
// nothing, which outlives the request.
|
|
1306
|
+
if (isNonReplayableResponse(upstreamResponse)
|
|
1307
|
+
|| (billingRecoveryActive && upstreamResponse.status === 401)
|
|
1308
|
+
|| (route.provider.adapter === "anthropic" && [400, 402].includes(upstreamResponse.status))) continue recovery;
|
|
1309
|
+
} finally {
|
|
1310
|
+
if (billingHop) { sendBudgetState.pendingHopPermit = undefined; billingHop.permit?.release(); }
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1155
1313
|
|
|
1156
1314
|
// Anthropic OAuth: recover a rate limit or proven account entitlement refusal
|
|
1157
1315
|
// before output, within the shared request and account rotation limits.
|
|
@@ -1398,7 +1556,17 @@ export async function prepareAdapterExchange(
|
|
|
1398
1556
|
// The hop's permit is already settled by the dispatch boundary above; continuing
|
|
1399
1557
|
// only skips the remaining arms, it does not abandon a reservation.
|
|
1400
1558
|
if (isNonReplayableResponse(upstreamResponse)) continue recovery;
|
|
1401
|
-
} catch {
|
|
1559
|
+
} catch (err) {
|
|
1560
|
+
// A local admission denial is terminal for the whole request: rotating to another
|
|
1561
|
+
// account cannot re-scope the key, so the refusal answers in place of the 429.
|
|
1562
|
+
const scopeRefusal = unwrapUpstreamRetryEvidenceError(err);
|
|
1563
|
+
if (!options.abortSignal?.aborted && scopeRefusal instanceof AdmissionModelDeniedError) {
|
|
1564
|
+
hop.permit?.release();
|
|
1565
|
+
cleanupUpstreamAbort();
|
|
1566
|
+
upstream.abort();
|
|
1567
|
+
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
1568
|
+
return admissionModelDeniedResponse(scopeRefusal);
|
|
1569
|
+
}
|
|
1402
1570
|
// A throw before the send — snapshot fetch, credential application, adapter
|
|
1403
1571
|
// resolution — must hand the reservation back. Without this the ladder charges the
|
|
1404
1572
|
// request for a send it never made, and a later recovery in the same request is
|
|
@@ -1539,9 +1707,13 @@ export async function prepareAdapterExchange(
|
|
|
1539
1707
|
imageRetryAttempted = true;
|
|
1540
1708
|
transportState.imageTierBias = 1;
|
|
1541
1709
|
invalidateSameTargetRequest();
|
|
1542
|
-
|
|
1543
|
-
const result = await rebuildAndRefetch("image-413");
|
|
1544
|
-
if ("failed" in result)
|
|
1710
|
+
const failedResponse = await retainFailureResponse();
|
|
1711
|
+
const result = await rebuildAndRefetch("image-413", undefined, failedResponse);
|
|
1712
|
+
if ("failed" in result) {
|
|
1713
|
+
if (result.failed !== failedResponse) return result.failed;
|
|
1714
|
+
break recovery;
|
|
1715
|
+
}
|
|
1716
|
+
try { void failedResponse.body?.cancel().catch(() => {}); } catch { /* closed */ }
|
|
1545
1717
|
upstreamResponse = result;
|
|
1546
1718
|
continue recovery;
|
|
1547
1719
|
}
|
|
@@ -1658,6 +1830,14 @@ export async function prepareAdapterExchange(
|
|
|
1658
1830
|
requestState.subagentFallbackAccountId,
|
|
1659
1831
|
);
|
|
1660
1832
|
}
|
|
1833
|
+
// The last pool account just refused on spent quota and recovery found no replacement:
|
|
1834
|
+
// answer the usage limit now (#6991), so Codex stops instead of retrying into the local
|
|
1835
|
+
// refusal that every later attempt meets until the first window reopens.
|
|
1836
|
+
const poolUsageLimit = upstreamResponse.status === 429 && anthropicInstance && route.provider.authMode === "oauth"
|
|
1837
|
+
? anthropicRoutingFor(anthropicInstance).anthropicPoolUsageLimitAfterRefusal(Date.now(), transportState.anthropicRouteDecision, route.modelId)
|
|
1838
|
+
: null;
|
|
1839
|
+
// Combo children returned above; the flag is passed anyway so this site states the same rule.
|
|
1840
|
+
if (poolUsageLimit) return anthropicPoolCooldownResponse(poolUsageLimit, { comboAttempt: options.comboAttempt, logCtx });
|
|
1661
1841
|
// Upstreams occasionally echo request details in error bodies — scrub token-shaped
|
|
1662
1842
|
// material before it reaches the client-facing error surface.
|
|
1663
1843
|
const upstreamRetryAfter = upstreamResponse.headers.get("retry-after");
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Core-owned, config-scoped registration. An inactive install has no Advisor factory. */
|
|
2
|
+
import type { OcxConfig, OcxParsedRequest, OcxTool } from "../../types";
|
|
3
|
+
|
|
4
|
+
export interface AdvisorPlanInput {
|
|
5
|
+
config: OcxConfig;
|
|
6
|
+
workerIdentity: string;
|
|
7
|
+
workerModelId: string;
|
|
8
|
+
abortSignal?: AbortSignal;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RegisteredAdvisorPlan {
|
|
12
|
+
tool: OcxTool;
|
|
13
|
+
preflightInject(parsed: OcxParsedRequest): Promise<boolean>;
|
|
14
|
+
attachGuard(parsed: OcxParsedRequest): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type AdvisorPlanFactory = (input: AdvisorPlanInput) => Promise<RegisteredAdvisorPlan | null>;
|
|
18
|
+
const factories = new WeakMap<OcxConfig, AdvisorPlanFactory>();
|
|
19
|
+
|
|
20
|
+
export function setAdvisorPlanFactory(config: OcxConfig, factory: AdvisorPlanFactory | null): void {
|
|
21
|
+
if (factory) factories.set(config, factory);
|
|
22
|
+
else factories.delete(config);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createRegisteredAdvisorPlan(input: AdvisorPlanInput): Promise<RegisteredAdvisorPlan | null> | null {
|
|
26
|
+
return factories.get(input.config)?.(input) ?? null;
|
|
27
|
+
}
|