@bitkyc08/opencodex 2.84.0 → 2.87.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -4
- package/bin/ocx.mjs +61 -3
- package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
- package/gui/dist/assets/index-CfYDMj34.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -2
- package/src/adapters/anthropic/beta-allowlist.ts +91 -11
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +16 -3
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic.ts +108 -21
- package/src/adapters/azure.ts +19 -1
- package/src/adapters/base.ts +2 -0
- package/src/adapters/claude-cli/adapter.ts +111 -23
- package/src/adapters/claude-cli/profiles.ts +2 -1
- package/src/adapters/claude-cli/stable-replay.ts +108 -0
- package/src/adapters/claude-cli/usage-admission.ts +230 -0
- package/src/adapters/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +40 -11
- package/src/adapters/cursor/arg-codec.ts +17 -5
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/freeform-args.ts +75 -0
- package/src/adapters/cursor/protobuf-events.ts +36 -23
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +37 -10
- package/src/adapters/openai-chat/messages.ts +4 -3
- package/src/adapters/openai-chat/passthrough.ts +8 -2
- package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
- package/src/adapters/openai-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
- package/src/adapters/openai-responses/passthrough.ts +57 -125
- package/src/adapters/openai-responses/request-strips.ts +25 -3
- package/src/adapters/openai-responses/transport.ts +81 -0
- package/src/adapters/qoder/adapter.ts +44 -3
- package/src/adapters/registry.ts +5 -0
- package/src/adapters/zed.ts +26 -1
- package/src/advisor/activity.ts +55 -0
- package/src/advisor/budget.ts +62 -0
- package/src/advisor/consult.ts +223 -0
- package/src/advisor/context.ts +238 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +353 -0
- package/src/advisor/settings.ts +144 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -1
- package/src/bridge/internal.ts +3 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +58 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/desktop-3p-library.ts +72 -7
- package/src/claude/desktop-3p.ts +75 -19
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +11 -0
- package/src/claude/inbound.ts +154 -50
- package/src/claude/intercept/local-ca.ts +8 -1
- package/src/claude/intercept/picker-ca-startup.ts +2 -1
- package/src/claude/intercept/picker-runtime.ts +6 -5
- package/src/claude/intercept/picker-trust-windows.ts +338 -0
- package/src/claude/intercept/picker-trust.ts +19 -0
- package/src/claude/intercept/port-policy.ts +30 -0
- package/src/claude/intercept/runtime.ts +97 -94
- package/src/claude/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +51 -19
- package/src/cli/account-api.ts +1 -0
- package/src/cli/account-policy-dto.ts +6 -0
- package/src/cli/account-policy.ts +34 -5
- package/src/cli/account-preservation.ts +44 -0
- package/src/cli/account.ts +8 -2
- package/src/cli/advisor.ts +165 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +23 -2
- package/src/cli/capabilities-base.ts +39 -11
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +46 -6
- package/src/cli/codex-cli-update.ts +151 -41
- package/src/cli/codex-provider.ts +44 -0
- package/src/cli/combo-input.ts +10 -1
- package/src/cli/combo-result.ts +1 -1
- package/src/cli/combo.ts +2 -1
- package/src/cli/cross-home-owner.ts +17 -14
- package/src/cli/dispatch.ts +30 -5
- package/src/cli/doctor.ts +15 -1
- package/src/cli/ensure-desktop-cold-start.ts +382 -0
- package/src/cli/export-command.ts +1 -1
- package/src/cli/help.ts +4 -2
- package/src/cli/index.ts +14 -5
- package/src/cli/integration-plan-dto.ts +3 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/models-runtime-subcommands.ts +1 -0
- package/src/cli/models-runtime.ts +72 -8
- package/src/cli/observe-stream.ts +2 -0
- package/src/cli/observe.ts +16 -4
- package/src/cli/provider.ts +27 -5
- package/src/cli/registry.ts +41 -9
- package/src/cli/restart-scope.ts +3 -1
- package/src/cli/runtime-api.ts +5 -3
- package/src/cli/system-command.ts +40 -8
- package/src/cli/uninstall-client-state.ts +32 -0
- package/src/cli/usage-compare.ts +309 -0
- package/src/cli/usage-model-search.ts +4 -4
- package/src/cli/usage-report.ts +2 -2
- package/src/cli/v2.ts +1 -1
- package/src/client/machine-listener.ts +1 -3
- package/src/client/runtime.ts +1 -0
- package/src/clients/client-path.ts +51 -0
- package/src/clients/config-export/contracts.ts +3 -1
- package/src/clients/config-export/model-metadata.ts +3 -2
- package/src/clients/config-export/qoder.ts +93 -0
- package/src/clients/config-export.ts +35 -48
- package/src/clients/gentle-shell-path.ts +87 -0
- package/src/codex/account-auto-switch.ts +2 -0
- package/src/codex/account-identity.ts +29 -0
- package/src/codex/account-label.ts +1 -1
- package/src/codex/account-lifecycle.ts +21 -5
- package/src/codex/account-preservation.ts +28 -0
- package/src/codex/account-use-remaining.ts +23 -0
- package/src/codex/app-server-processes.ts +97 -7
- package/src/codex/app-server-restart-service.ts +55 -4
- package/src/codex/auth-api/account-list.ts +4 -0
- package/src/codex/auth-api/account-pause-group.ts +6 -50
- package/src/codex/auth-api/account-preservation.ts +57 -0
- package/src/codex/auth-api/pool-mode-gate.ts +5 -2
- package/src/codex/auth-api/routes.ts +8 -0
- package/src/codex/auth-api/use-remaining-route.ts +36 -0
- package/src/codex/auth-api.ts +0 -1
- package/src/codex/auth-context.ts +81 -7
- package/src/codex/autostart-health.ts +4 -0
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +19 -2
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +4 -1
- package/src/codex/catalog/gather-capture.ts +2 -0
- package/src/codex/catalog/metadata.ts +11 -9
- package/src/codex/catalog/model-hints.ts +42 -19
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +35 -86
- package/src/codex/catalog/provider-models.ts +40 -34
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +33 -16
- package/src/codex/catalog/vendor-context.ts +82 -0
- package/src/codex/cli-install-provenance.ts +105 -17
- package/src/codex/cli-update-plan.ts +545 -0
- package/src/codex/config-write-lock.ts +63 -4
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app/types.ts +2 -0
- package/src/codex/desktop-app/windows.ts +29 -16
- package/src/codex/desktop-app-restart.ts +25 -2
- package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
- package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
- package/src/codex/desktop-compatibility/connection-store.ts +87 -0
- package/src/codex/desktop-compatibility/installed-build.ts +73 -0
- package/src/codex/desktop-compatibility/json-body.ts +29 -0
- package/src/codex/desktop-compatibility/native-identity.ts +62 -0
- package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
- package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
- package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
- package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
- package/src/codex/desktop-compatibility/runtime.ts +182 -0
- package/src/codex/desktop-compatibility/service.ts +30 -0
- package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
- package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
- package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
- package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
- package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
- package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
- package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
- package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
- package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
- package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
- package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
- package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
- package/src/codex/features.ts +85 -45
- package/src/codex/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/remove.ts +18 -6
- package/src/codex/inject/restore.ts +24 -2
- package/src/codex/inject/routing-classify.ts +4 -3
- package/src/codex/inject.ts +18 -27
- package/src/codex/journal.ts +16 -1
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/main-account.ts +99 -15
- package/src/codex/native-main-refresh-mode.ts +217 -0
- package/src/codex/native-profile-startup.ts +1 -1
- package/src/codex/ocx-compaction-history.ts +12 -10
- package/src/codex/paths.ts +86 -1
- package/src/codex/provider-routing.ts +174 -0
- package/src/codex/refresh.ts +2 -2
- package/src/codex/routing/health-store.ts +2 -2
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +7 -2
- package/src/codex/signed-in-identity.ts +43 -0
- package/src/codex/subagent-model-fallback.ts +1 -1
- package/src/codex/sync.ts +6 -1
- package/src/codex/user-identity.ts +16 -5
- package/src/codex/windows-local-appdata.ts +84 -0
- package/src/combos/failover.ts +6 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/resolve.ts +46 -10
- package/src/combos/types.ts +15 -0
- package/src/config/diagnostics.ts +35 -7
- package/src/config/live-reconcile.ts +51 -2
- package/src/config/load-degrade.ts +23 -1
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +52 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/paths.ts +10 -3
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +39 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +39 -9
- package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
- package/src/config/schema/native-reasoning-retention.ts +26 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +962 -350
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +46 -8
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/config-io.ts +20 -2
- package/src/integrations/journal.ts +15 -1
- package/src/integrations/mutation-plan.ts +2 -0
- package/src/integrations/registry.ts +15 -0
- package/src/integrations/store.ts +3 -0
- package/src/integrations/writer.ts +10 -1
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/background-work.ts +65 -0
- package/src/lib/bounded-file-read.ts +58 -0
- package/src/lib/bun-path-runtime.d.mts +10 -1
- package/src/lib/bun-path-runtime.mjs +62 -3
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +72 -0
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +26 -3
- package/src/lib/gcp-adc.ts +96 -9
- package/src/lib/gcp-credential-input.ts +12 -0
- package/src/lib/lab-live-route-production.ts +5 -0
- package/src/lib/local-internal-call-capability.ts +55 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/redact.ts +2 -2
- package/src/lib/socks5-fetch.ts +48 -71
- package/src/lib/socks5-handshake.ts +112 -0
- package/src/lib/translator-budget.ts +7 -1
- package/src/oauth/account-quota-rank.ts +69 -2
- package/src/oauth/anthropic-account-refusal.ts +20 -2
- package/src/oauth/anthropic-oauth-definitions.ts +3 -1
- package/src/oauth/anthropic-routing.ts +1 -21
- package/src/oauth/anthropic.ts +38 -7
- package/src/oauth/callback-server.ts +27 -1
- package/src/oauth/chatgpt-device.ts +1 -1
- package/src/oauth/chatgpt-shared.ts +83 -0
- package/src/oauth/chatgpt.ts +8 -72
- package/src/oauth/dsh.ts +203 -0
- package/src/oauth/generic-account-failover.ts +62 -12
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +188 -111
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +7 -1
- package/src/oauth/local-token-detect.ts +21 -1
- package/src/oauth/login-cli-errors.ts +44 -0
- package/src/oauth/login-cli.ts +69 -7
- package/src/oauth/login-flow-state.ts +371 -23
- package/src/oauth/meta-muse-errors.ts +41 -0
- package/src/oauth/meta-muse-keychain.ts +52 -0
- package/src/oauth/meta-muse.ts +51 -84
- package/src/oauth/model-discovery-auth.ts +3 -0
- package/src/oauth/pool-settings-capability.ts +4 -2
- package/src/oauth/refresh-flight-retirement.ts +48 -0
- package/src/oauth/session-affinity-key.ts +23 -0
- package/src/oauth/store-anthropic-instance.ts +9 -2
- package/src/oauth/store.ts +12 -5
- package/src/protocols/codecs/chat-ir.ts +467 -0
- package/src/protocols/codecs/chat.ts +10 -1
- package/src/protocols/codecs/messages-ir.ts +500 -0
- package/src/protocols/encoders/adapter-events.ts +5 -4
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/features.ts +10 -2
- package/src/protocols/path.ts +11 -1
- package/src/protocols/plan-snapshot.ts +39 -16
- package/src/protocols/plan.ts +9 -1
- package/src/protocols/provider-summary.ts +2 -1
- package/src/protocols/settings.ts +8 -0
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- package/src/providers/api-key-cooldowns.ts +49 -0
- package/src/providers/api-key-resolve.ts +11 -6
- package/src/providers/api-key-response-ownership.ts +57 -0
- package/src/providers/api-key-selection.ts +31 -6
- package/src/providers/api-keys.ts +214 -3
- package/src/providers/claude-cli-usage.ts +96 -0
- package/src/providers/dsh-account-destination.ts +31 -0
- package/src/providers/dsh-account-models.ts +41 -0
- package/src/providers/gcp-credential-cleanup.ts +59 -0
- package/src/providers/key-failover.ts +54 -50
- package/src/providers/key-store.ts +7 -2
- package/src/providers/model-rename-fields.ts +5 -0
- package/src/providers/model-rename-migration.ts +7 -5
- package/src/providers/openai-tiers-destination.ts +11 -2
- package/src/providers/openai-tiers.ts +5 -12
- package/src/providers/opencode-go-transport.ts +3 -1
- package/src/providers/quota/account-cache.ts +6 -5
- package/src/providers/quota/account-row.ts +34 -0
- package/src/providers/quota/antigravity.ts +28 -7
- package/src/providers/quota/dsh-account.ts +70 -0
- package/src/providers/quota/vendor-probes-oauth.ts +1 -93
- package/src/providers/quota-types.ts +2 -0
- package/src/providers/quota.ts +14 -20
- package/src/providers/registry/entries-core.ts +44 -14
- package/src/providers/registry/entries-extended.ts +19 -0
- package/src/providers/registry/types.ts +1 -1
- package/src/providers/request-pacing.ts +77 -19
- package/src/providers/routed-config.ts +339 -0
- package/src/providers/service-tier.ts +2 -1
- package/src/quota/window-mapping.ts +5 -2
- package/src/responses/anthropic-progress.ts +34 -0
- package/src/responses/assistant-replay.ts +35 -0
- package/src/responses/compaction-history-framing.ts +65 -0
- package/src/responses/compaction.ts +6 -1
- package/src/responses/incomplete-reason.ts +31 -0
- package/src/responses/parser.ts +78 -22
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +24 -2
- package/src/responses/reasoning-retention.ts +173 -0
- package/src/responses/retained-compaction.ts +46 -0
- package/src/responses/schema.ts +23 -0
- package/src/responses/state.ts +32 -29
- package/src/router.ts +8 -321
- package/src/routing/health.ts +37 -22
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +254 -221
- package/src/routing/history/ingest.ts +242 -0
- package/src/routing/history/rebuild-job.ts +111 -0
- package/src/routing/history/rebuild-protocol.ts +15 -0
- package/src/routing/history/rebuild-worker.ts +34 -0
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +21 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +15 -0
- package/src/server/chat-completions.ts +82 -10
- package/src/server/chat-native-reasoning.ts +123 -0
- package/src/server/chat-native-sse.ts +110 -7
- package/src/server/chat-native.ts +76 -17
- package/src/server/claude-messages.ts +164 -433
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- package/src/server/index/model-alias-id.ts +36 -0
- package/src/server/index/optional-listeners.ts +2 -0
- package/src/server/index/serve-options.ts +11 -3
- package/src/server/index/startup-warnings.ts +11 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +14 -11
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +202 -0
- package/src/server/management/agent-settings-routes.ts +22 -4
- package/src/server/management/claude-desktop-picker-routes.ts +1 -1
- package/src/server/management/combo-routes.ts +176 -50
- package/src/server/management/companion-routes.ts +4 -0
- package/src/server/management/config-routes.ts +77 -6
- package/src/server/management/context.ts +4 -0
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/desktop-compatibility-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
- package/src/server/management/model-routes.ts +13 -8
- package/src/server/management/model-rows.ts +14 -0
- package/src/server/management/native-integration-routes.ts +2 -7
- package/src/server/management/native-reasoning-retention-routes.ts +41 -0
- package/src/server/management/oauth-account-routes.ts +87 -14
- package/src/server/management/protocol-settings-patch.ts +1 -0
- package/src/server/management/provider-gcp-credential-routes.ts +29 -0
- package/src/server/management/provider-key-pacing-routes.ts +31 -0
- package/src/server/management/provider-overwrite-carry.ts +7 -0
- package/src/server/management/provider-routes.ts +25 -30
- package/src/server/management/route-registry.ts +21 -1
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management-api.ts +9 -1
- package/src/server/messages-antigravity-direct.ts +195 -0
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +106 -22
- package/src/server/messages-shared.ts +431 -0
- package/src/server/relay.ts +1 -1
- package/src/server/request-log-terminal-status.ts +1 -1
- package/src/server/request-log.ts +13 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +48 -19
- package/src/server/responses/adapter-delivery.ts +43 -8
- package/src/server/responses/adapter-dispatch.ts +48 -4
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +368 -0
- package/src/server/responses/code-mode-collab-surface.ts +116 -8
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +18 -10
- package/src/server/responses/compaction-retention.ts +61 -0
- package/src/server/responses/context-overflow.ts +11 -6
- package/src/server/responses/core-auth.ts +6 -0
- package/src/server/responses/core-codex-account.ts +11 -1
- package/src/server/responses/core-combo-failure.ts +12 -1
- package/src/server/responses/core-combo.ts +141 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +7 -2
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +51 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +25 -6
- package/src/server/responses/core.ts +3 -116
- package/src/server/responses/fetch-helpers.ts +73 -19
- package/src/server/responses/main-hard-lock-routing.ts +109 -0
- package/src/server/responses/native-response-control.ts +4 -2
- package/src/server/responses/passthrough-delivery.ts +19 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -16
- package/src/server/responses/request-prepare.ts +144 -17
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +120 -27
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +166 -38
- package/src/server/responses/sidecar-execution.ts +98 -5
- package/src/server/responses/terminal-guard.ts +4 -17
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +16 -2
- package/src/server/startup-health-cache.ts +1 -0
- package/src/service/auth-preflight.ts +71 -0
- package/src/service/cli.ts +4 -2
- package/src/service/definition.ts +97 -0
- package/src/service/desktop-startup.ts +14 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +99 -61
- package/src/service/health-sentinel-probe.ts +24 -7
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +135 -0
- package/src/service/repair.ts +2 -1
- package/src/service/state.ts +7 -8
- package/src/service/systemd.ts +1 -1
- package/src/service/windows-guarded-manager.ts +2 -1
- package/src/service/windows-ops.ts +1 -1
- package/src/service/windows-scheduler.ts +2 -7
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service/windows-wscript.ts +14 -0
- package/src/service.ts +4 -2
- package/src/sidecar/auth.ts +2 -0
- package/src/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +124 -6
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +29 -0
- package/src/types/provider.ts +39 -6
- package/src/types/request.ts +38 -2
- package/src/types/tools.ts +2 -0
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -0
- package/src/update/async-check.ts +1 -1
- package/src/update/badge.ts +2 -2
- package/src/update/index.ts +88 -15
- package/src/update/job-supervised-restart.ts +107 -0
- package/src/update/job.ts +32 -32
- package/src/update/notify.ts +1 -1
- package/src/update/npm-invocation.mjs +12 -2
- package/src/update/refresh-scheduler.ts +2 -2
- package/src/update/supervised-recovery.d.mts +27 -0
- package/src/update/supervised-recovery.mjs +157 -0
- package/src/usage/cache-diagnostic.ts +2 -0
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +15 -0
- package/src/usage/ledger-retention.ts +2 -1
- package/src/usage/ledger-scanner.ts +6 -5
- package/src/usage/log.ts +17 -8
- package/src/usage/summary.ts +22 -9
- package/src/usage/user-cost-overlays.ts +14 -19
- package/src/usage/user-cost-validation.ts +38 -0
- package/src/vision/eligibility.ts +13 -0
- package/src/web-search/gemini-executor.ts +2 -2
- package/src/web-search/loop.ts +55 -5
- package/src/web-search/progress-stream.ts +4 -1
- package/src/web-search/run-turn-loop.ts +9 -1
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/App-CZtqiGcw.js +0 -56
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
- package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
|
@@ -7,9 +7,11 @@ import { readBoundedResponseBytes } from "../../lib/bounded-body";
|
|
|
7
7
|
import { isRequestExecutionBudget, type RequestExecutionBudget, type SingleUseDispatchPermit } from "../../lib/request-execution-budget";
|
|
8
8
|
import { isNonReplayableResponse, isNonReplayableUpstreamCode, markResponseNonReplayable, TRANSIENT_RETRY_MAX_ATTEMPTS } from "../../lib/upstream-retry";
|
|
9
9
|
import { isCyberPolicyCode, isTerminalRefusalCode } from "../../lib/errors";
|
|
10
|
-
import { isCanonicalOpenAiForwardProvider, supportsNativeResponsesCompactEndpoint } from "../../providers/openai-tiers";
|
|
10
|
+
import { isCanonicalOpenAiForwardProvider, supportsNativeResponsesCompactEndpoint, supportsNativeResponsesCompactionTrigger } from "../../providers/openai-tiers";
|
|
11
11
|
import { bridgeToResponsesSSE, formatErrorResponse } from "../../bridge";
|
|
12
12
|
import { buildCompactV1Output, decodeCompactionSummary, encodeCompactionSummary, extractCompactUserMessages } from "../../responses/compaction";
|
|
13
|
+
import { decodeRetainedCompaction, encodeRetainedCompaction } from "../../responses/retained-compaction";
|
|
14
|
+
import { collectReasoningTexts } from "../../responses/reasoning-retention";
|
|
13
15
|
import { finishRequestAttempt, usageFromResponsesPayload, type RequestLogContext } from "../request-log";
|
|
14
16
|
import { linkRequestSessionLane } from "../request-log-conversation";
|
|
15
17
|
import { isNativePassthroughSseResponse, markNativePassthroughSseResponse, isEagerRelaySseResponse, markEagerRelaySseResponse } from "../relay";
|
|
@@ -55,15 +57,17 @@ function portableBody(body: Record<string, unknown>): boolean {
|
|
|
55
57
|
if (!Array.isArray(body.input) || body.store === true || conversationCarriesUploadedFiles(body)) return false;
|
|
56
58
|
// Native ciphertext cannot be summarized by another provider. Never silently replace it with a note.
|
|
57
59
|
if (body.input.some(item => record(item) && ["compaction", "compaction_summary", "context_compaction"].includes(String(item.type))
|
|
58
|
-
&& typeof item.encrypted_content === "string" && !item.encrypted_content.startsWith("ocx1:"))) return false;
|
|
60
|
+
&& typeof item.encrypted_content === "string" && !item.encrypted_content.startsWith("ocx1:") && !decodeRetainedCompaction(item.encrypted_content))) return false;
|
|
59
61
|
const input = body.input.filter(item => !record(item) || item.type !== "compaction_trigger");
|
|
60
62
|
return selfContainedResponsesBody({ ...body, store: false, input });
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
function routed(route: RouteResult): boolean {
|
|
65
|
+
function routed(route: RouteResult, kind: "compaction-v1" | "compaction-v2"): boolean {
|
|
64
66
|
return !route.combo && route.routeKind !== "policy" && route.routeReason !== "default-provider"
|
|
65
67
|
&& !isCanonicalOpenAiForwardProvider(route.provider)
|
|
66
|
-
&& !
|
|
68
|
+
&& !(kind === "compaction-v1"
|
|
69
|
+
? supportsNativeResponsesCompactEndpoint(route.providerName, route.provider)
|
|
70
|
+
: supportsNativeResponsesCompactionTrigger(route.provider));
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
/** One reader, bounded bytes, and an exact replacement body; never clone a live stream. */
|
|
@@ -89,6 +93,7 @@ export async function runWithCompactionRecovery(
|
|
|
89
93
|
req: Request, config: OcxConfig, logCtx: RequestLogContext, options: Options, dispatch: Dispatch,
|
|
90
94
|
): Promise<Response> {
|
|
91
95
|
const recovery = readCompactionRecoveryConfig(config.compactionRecovery);
|
|
96
|
+
const kind = options.compactionRecoveryKind ?? "compaction-v2";
|
|
92
97
|
if (!recovery || options.compactionRecoveryAttempted || options.comboAttempt || (options.inboundWire && options.inboundWire !== "responses")) {
|
|
93
98
|
return dispatch(req, config, logCtx, options);
|
|
94
99
|
}
|
|
@@ -132,7 +137,7 @@ export async function runWithCompactionRecovery(
|
|
|
132
137
|
},
|
|
133
138
|
onCompactionRecoveryRoute(route) {
|
|
134
139
|
options.onCompactionRecoveryRoute?.(route);
|
|
135
|
-
if (snapshot && routed(route)) sourceRoute = { ...route };
|
|
140
|
+
if (snapshot && routed(route, kind)) sourceRoute = { ...route };
|
|
136
141
|
},
|
|
137
142
|
onCompactionRecoveryAdapterEvent(event) {
|
|
138
143
|
options.onCompactionRecoveryAdapterEvent?.(event);
|
|
@@ -151,7 +156,7 @@ export async function runWithCompactionRecovery(
|
|
|
151
156
|
if (!snapshot || !sourceRoute || signal.aborted || req.signal.aborted || isNonReplayableResponse(response)) return keep(response);
|
|
152
157
|
let target: RouteResult;
|
|
153
158
|
try { target = routeConcreteModel(config, recovery.model); } catch { return keep(response); }
|
|
154
|
-
if (!routed(target) || identity(sourceRoute) === identity(target)) return keep(response);
|
|
159
|
+
if (!routed(target, kind) || identity(sourceRoute) === identity(target)) return keep(response);
|
|
155
160
|
const originalModel = firstOptions.compactionRoutingOverride?.sourceModel ?? String(snapshot.model);
|
|
156
161
|
// Use the established protocol commit boundary. For runTurn streams the direct event
|
|
157
162
|
// observer additionally preserves side-effect heartbeats that the bridge does not publish.
|
|
@@ -289,7 +294,8 @@ export async function runWithCompactionRecovery(
|
|
|
289
294
|
const compactions = items.filter(value => record(value) && value.type === "compaction");
|
|
290
295
|
const permitted = items.every(value => record(value) && (value.type === "compaction" || value.type === "reasoning"));
|
|
291
296
|
const item = permitted && compactions.length === 1 ? compactions[0] as Record<string, unknown> : undefined;
|
|
292
|
-
const
|
|
297
|
+
const structured = item && typeof item.encrypted_content === "string" ? decodeRetainedCompaction(item.encrypted_content) : null;
|
|
298
|
+
const summary = structured?.summary ?? (item && typeof item.encrypted_content === "string" ? decodeCompactionSummary(item.encrypted_content) : null);
|
|
293
299
|
if (json?.status !== "completed" || !summary?.trim()) {
|
|
294
300
|
void completed.response.body?.cancel().catch(() => undefined);
|
|
295
301
|
restoreSourceLog?.(completed.response.status);
|
|
@@ -298,11 +304,13 @@ export async function runWithCompactionRecovery(
|
|
|
298
304
|
// v1 unpacks this text through buildCompactV1Output, which already re-adds retained user
|
|
299
305
|
// messages as items; embedding them here too would duplicate the same text in the output.
|
|
300
306
|
const preserved = options.compactionRecoveryKind === "compaction-v1" ? summary : (() => {
|
|
301
|
-
const
|
|
307
|
+
const usersOnly = Array.isArray(snapshot.input) ? snapshot.input.filter(item => collectReasoningTexts([item]).length === 0) : snapshot.input;
|
|
308
|
+
const retained = buildCompactV1Output(extractCompactUserMessages(usersOnly), summary).slice(0, -1);
|
|
302
309
|
const userText = extractCompactUserMessages(retained).map((text, index) => `User message ${index + 1}:\n${text}`).join("\n\n");
|
|
303
310
|
return `${summary}\n\nRetained original user messages (verbatim; preserve their goals and constraints):\n${userText}`;
|
|
304
311
|
})();
|
|
305
|
-
|
|
312
|
+
const encryptedContent = structured ? encodeRetainedCompaction(preserved, structured.reasoning) : encodeCompactionSummary(preserved);
|
|
313
|
+
item!.encrypted_content = encryptedContent;
|
|
306
314
|
json.model = originalModel;
|
|
307
315
|
void completed.response.body?.cancel().catch(() => undefined);
|
|
308
316
|
void response.body?.cancel().catch(() => undefined);
|
|
@@ -310,7 +318,7 @@ export async function runWithCompactionRecovery(
|
|
|
310
318
|
const usage = usageFromResponsesPayload(json.usage);
|
|
311
319
|
async function* events(): AsyncGenerator<AdapterEvent> {
|
|
312
320
|
yield { type: "text_delta", text: preserved };
|
|
313
|
-
yield { type: "done", ...(usage ? { usage } : {}) };
|
|
321
|
+
yield { type: "done", compactionEncryptedContent: encryptedContent, ...(usage ? { usage } : {}) };
|
|
314
322
|
}
|
|
315
323
|
return new Response(bridgeToResponsesSSE(events(), originalModel, undefined, undefined, undefined, undefined, 2_000,
|
|
316
324
|
{ compaction: true, translatorBudget: options.translatorBudget, onCompletedResponse: () => options.onResponseComplete?.(originalModel) }), { headers: { "content-type": "text/event-stream" } });
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { rmSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getConfigDir } from "../../config";
|
|
4
|
+
import type { OcxConfig, OcxParsedRequest } from "../../types";
|
|
5
|
+
import type { TranslatorBudget } from "../../lib/translator-budget";
|
|
6
|
+
import { parseRequest } from "../../responses/parser";
|
|
7
|
+
import { applyReasoningRetention, appendRetentionNotice } from "../../responses/reasoning-retention";
|
|
8
|
+
import { encodeRetainedCompaction } from "../../responses/retained-compaction";
|
|
9
|
+
import { finalizeAccountLease } from "./core-lifetime";
|
|
10
|
+
|
|
11
|
+
export class InvalidRetainedCompactionSummary extends Error {}
|
|
12
|
+
|
|
13
|
+
/** One routed v2 attempt owns its local retention until its response ends or is cancelled. */
|
|
14
|
+
export function prepareCompactionRetention(
|
|
15
|
+
parsed: OcxParsedRequest,
|
|
16
|
+
config: OcxConfig,
|
|
17
|
+
contextWindow: number | undefined,
|
|
18
|
+
budget: TranslatorBudget,
|
|
19
|
+
signal: AbortSignal,
|
|
20
|
+
) {
|
|
21
|
+
if (config.reasoningRetention?.enabled !== true) return undefined;
|
|
22
|
+
const raw = parsed._rawBody as Record<string, unknown>;
|
|
23
|
+
const retention = applyReasoningRetention(raw.input, {
|
|
24
|
+
archiveDir: join(getConfigDir(), "reasoning-archive"), contextWindow,
|
|
25
|
+
...config.reasoningRetention,
|
|
26
|
+
});
|
|
27
|
+
if (!retention.retainedReasoning && !retention.archived) return undefined;
|
|
28
|
+
let committed = false;
|
|
29
|
+
let disposed = false;
|
|
30
|
+
let bytes = 0;
|
|
31
|
+
const dispose = () => {
|
|
32
|
+
if (disposed) return;
|
|
33
|
+
disposed = true;
|
|
34
|
+
signal.removeEventListener("abort", dispose);
|
|
35
|
+
delete parsed._compactionRetention;
|
|
36
|
+
budget.releaseRetained(bytes, { kind: "retained_collectors" });
|
|
37
|
+
if (!committed && retention.archived) rmSync(retention.archived.path, { force: true });
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
const reasoning = retention.retainedReasoning?.map(item => item.content[0]!.text) ?? [];
|
|
41
|
+
const retainedBytes = Buffer.byteLength(JSON.stringify(reasoning));
|
|
42
|
+
budget.chargeRetained(retainedBytes, { kind: "retained_collectors" });
|
|
43
|
+
bytes = retainedBytes;
|
|
44
|
+
const body = { ...raw, model: parsed.modelId, input: retention.input };
|
|
45
|
+
const messages = parseRequest(body, { replayCacheScope: parsed._reasoningReplayScope }).context.messages;
|
|
46
|
+
parsed._rawBody = body;
|
|
47
|
+
parsed.context.messages = messages;
|
|
48
|
+
const callbacks: NonNullable<OcxParsedRequest["_compactionRetention"]> = {
|
|
49
|
+
encode(summary) {
|
|
50
|
+
if (disposed || signal.aborted) throw new Error("Compaction was cancelled");
|
|
51
|
+
if (!summary.trim()) throw new InvalidRetainedCompactionSummary("Compaction returned an empty summary");
|
|
52
|
+
return encodeRetainedCompaction(appendRetentionNotice(summary, retention.archived), reasoning);
|
|
53
|
+
},
|
|
54
|
+
commit() { if (!disposed && !signal.aborted) committed = true; },
|
|
55
|
+
};
|
|
56
|
+
Object.defineProperty(parsed, "_compactionRetention", { value: callbacks, configurable: true });
|
|
57
|
+
if (signal.aborted) { dispose(); throw signal.reason; }
|
|
58
|
+
signal.addEventListener("abort", dispose, { once: true });
|
|
59
|
+
return { dispose, deliver: (response: Response) => finalizeAccountLease(response, dispose, dispose) };
|
|
60
|
+
} catch (error) { dispose(); throw error; }
|
|
61
|
+
}
|
|
@@ -17,21 +17,25 @@ export function lateAttributionLimitResponse(): Response {
|
|
|
17
17
|
export const PROVIDER_INPUT_TOO_LARGE_MESSAGE =
|
|
18
18
|
"The provider rejected this turn because its input exceeds the provider size or context limit. Reduce the current input or compact the conversation before retrying.";
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Preserve non-streaming HTTP failure semantics without exposing an upstream body. `message` is
|
|
22
|
+
* replaced only for the proxy's own wording, never with upstream text: when every combo target
|
|
23
|
+
* was refused by local input admission, no provider saw the turn and saying one did is false.
|
|
24
|
+
*/
|
|
25
|
+
export function jsonContextOverflowResponse(message = PROVIDER_INPUT_TOO_LARGE_MESSAGE): Response {
|
|
22
26
|
return Response.json({
|
|
23
27
|
error: {
|
|
24
|
-
message
|
|
28
|
+
message,
|
|
25
29
|
type: "invalid_request_error",
|
|
26
30
|
code: "context_length_exceeded",
|
|
27
31
|
},
|
|
28
32
|
}, { status: 413, headers: { "Cache-Control": "no-store" } });
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
async function* contextOverflowEvents(): AsyncGenerator<AdapterEvent> {
|
|
35
|
+
async function* contextOverflowEvents(message: string): AsyncGenerator<AdapterEvent> {
|
|
32
36
|
yield {
|
|
33
37
|
type: "error",
|
|
34
|
-
message
|
|
38
|
+
message,
|
|
35
39
|
status: 413,
|
|
36
40
|
errorType: "invalid_request_error",
|
|
37
41
|
code: "context_length_exceeded",
|
|
@@ -51,9 +55,10 @@ async function* contextOverflowEvents(): AsyncGenerator<AdapterEvent> {
|
|
|
51
55
|
export function streamingContextOverflowResponse(
|
|
52
56
|
modelId: string,
|
|
53
57
|
translatorBudget: TranslatorBudget,
|
|
58
|
+
message = PROVIDER_INPUT_TOO_LARGE_MESSAGE,
|
|
54
59
|
): Response {
|
|
55
60
|
return new Response(bridgeToResponsesSSE(
|
|
56
|
-
contextOverflowEvents(),
|
|
61
|
+
contextOverflowEvents(message),
|
|
57
62
|
modelId,
|
|
58
63
|
undefined,
|
|
59
64
|
undefined,
|
|
@@ -43,6 +43,7 @@ import { codexAuthContextLogLabel } from "../../codex/account-label";
|
|
|
43
43
|
import { forceRefreshMainAccountToken } from "../../codex/main-account";
|
|
44
44
|
import { rebindPoolCreditPolicy } from "../../codex/pool-credit-policy";
|
|
45
45
|
import { safeCallerSessionId } from "../caller-session-identity";
|
|
46
|
+
import { copyResponsesClientMetadata } from "../../adapters/openai-responses/transport";
|
|
46
47
|
|
|
47
48
|
/** Normalize caller aliases only on native egress; preserve ingress and fallback identity. */
|
|
48
49
|
export function withClaudeNativeSession(headers: Headers, provider: OcxProviderConfig, sessionId?: string): Headers {
|
|
@@ -335,6 +336,11 @@ export async function resolveResponsesCodexAuth(
|
|
|
335
336
|
headers.delete("chatgpt-account-id");
|
|
336
337
|
}
|
|
337
338
|
}
|
|
339
|
+
// Codex auth materialization keeps only its credential allowlist. A compact-v1 handoff
|
|
340
|
+
// must also retain the permitted client fingerprint for its routed summarizer send.
|
|
341
|
+
if (options.compactionRecoveryKind === "compaction-v1") {
|
|
342
|
+
copyResponsesClientMetadata(headers, authInputHeaders, route.provider);
|
|
343
|
+
}
|
|
338
344
|
return {
|
|
339
345
|
ok: true,
|
|
340
346
|
authCtx,
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
type CodexWsPreOutputRefusalHold,
|
|
33
33
|
} from "./codex-ws-wire";
|
|
34
34
|
import type { RequestLogContext } from "../request-log";
|
|
35
|
+
import type { AttemptRecoveryKind } from "../../usage/log";
|
|
35
36
|
import type { DataPlaneAdmission } from "../auth-cors";
|
|
36
37
|
import type { InboundWire } from "../../providers/registry";
|
|
37
38
|
import type { BunRuntimeGateInput } from "./ws-upstream";
|
|
@@ -909,6 +910,8 @@ export async function retryCodexPoolOnAlternateAccount(
|
|
|
909
910
|
applyAccountChangeConversationStateScrub({
|
|
910
911
|
body: parsed._rawBody,
|
|
911
912
|
parsed,
|
|
913
|
+
preserveReasoningEncryptedContent: isCanonicalOpenAiForwardProvider(retryProvider)
|
|
914
|
+
&& parsed._nativeReasoningRetention?.accountSwitch === true,
|
|
912
915
|
bindingKey: binding.bindingKey,
|
|
913
916
|
servingAccountId: binding.accountId,
|
|
914
917
|
priorAccountId: firstAuthCtx.accountId,
|
|
@@ -971,6 +974,10 @@ export async function retryCodexPoolOnAlternateAccount(
|
|
|
971
974
|
// What this diff does fix is that the rungs are now CHARGED instead of free.
|
|
972
975
|
const maxRetrySends = retrySameConfirmedAccount ? 7 : 1;
|
|
973
976
|
let retrySendCount = 0;
|
|
977
|
+
// The gated-model 400 has no roster kind; it stays unattributed as before.
|
|
978
|
+
const moveRecovery: AttemptRecoveryKind | undefined = outcomeStatus === 429 || outcomeStatus === 402
|
|
979
|
+
? "oauth-account-429"
|
|
980
|
+
: isTransientUpstreamStatus(outcomeStatus) ? "transient-5xx" : undefined;
|
|
974
981
|
let upstreamResponse: Response;
|
|
975
982
|
try {
|
|
976
983
|
while (true) {
|
|
@@ -1005,7 +1012,10 @@ export async function retryCodexPoolOnAlternateAccount(
|
|
|
1005
1012
|
// The move is a physical send like any other, so the root workflow is charged too.
|
|
1006
1013
|
chargeWorkflowSends(args.options.workflowRootId, 1);
|
|
1007
1014
|
}
|
|
1008
|
-
|
|
1015
|
+
// A move seals the first account's attempt here. Give it the status that account actually
|
|
1016
|
+
// answered, not the 502 the split infers, and name why the second send exists.
|
|
1017
|
+
if (logCtx.activeAttempt && logCtx.activeAttempt.status < 100) logCtx.activeAttempt.status = firstResponse.status;
|
|
1018
|
+
noteProviderAttemptSend(logCtx, route.providerName, route.provider, passthroughEstimate, moveRecovery);
|
|
1009
1019
|
try {
|
|
1010
1020
|
upstreamResponse = await fetchWithHeaderTimeout(
|
|
1011
1021
|
request.url,
|
|
@@ -3,6 +3,7 @@ import { parseRetryAfterMs } from "../../combos";
|
|
|
3
3
|
import type { ConsumedComboFailure, HandleResponsesOptions } from "./core-options";
|
|
4
4
|
import type { OcxUsage } from "../../types";
|
|
5
5
|
import { readBoundedResponseBody } from "../../lib/bounded-body";
|
|
6
|
+
import { carryAnthropicRefusalCredential } from "../../oauth/anthropic-account-refusal";
|
|
6
7
|
import { codexQuotaFailureMessage, codexQuotaOutcomeMeta } from "./core-codex-account";
|
|
7
8
|
import {
|
|
8
9
|
isRateLimitOrQuotaFailureMessage,
|
|
@@ -16,6 +17,7 @@ import { resolveClientRetryAfter } from "../../lib/retry-after";
|
|
|
16
17
|
import { formatErrorResponse } from "../../bridge";
|
|
17
18
|
import { OLLAMA_LATE_ATTRIBUTION_LIMIT_MESSAGE } from "../../adapters/ollama-native";
|
|
18
19
|
import { isNonReplayableResponse, markResponseNonReplayable } from "../../lib/upstream-retry";
|
|
20
|
+
import { MODEL_NOT_ALLOWED_FOR_KEY } from "../admission-model-scope";
|
|
19
21
|
import { usageFromResponsesPayload } from "../request-log";
|
|
20
22
|
import type { ResponsesTerminalStatus } from "../../bridge";
|
|
21
23
|
|
|
@@ -49,6 +51,9 @@ export async function consumeComboFailure(
|
|
|
49
51
|
// display-safe body carries a recognized quota message.
|
|
50
52
|
let quotaConfirmedByBody = false;
|
|
51
53
|
const serverError = response.status >= 500 && response.status < 600;
|
|
54
|
+
// A local key-scope refusal is already the client's answer: rebuilding it as an upstream_error
|
|
55
|
+
// would hide the stable model_not_allowed_for_key type clients branch on.
|
|
56
|
+
let scopeRefusalBody: string | undefined;
|
|
52
57
|
try {
|
|
53
58
|
const body = await readBoundedResponseBody(response, { signal, reportUtf8Validity: serverError });
|
|
54
59
|
// A 5xx body counts as quota or classification evidence only when it decoded as valid
|
|
@@ -71,6 +76,9 @@ export async function consumeComboFailure(
|
|
|
71
76
|
upstreamMessage = normalized.message;
|
|
72
77
|
upstreamType = normalized.type;
|
|
73
78
|
}
|
|
79
|
+
if (nonReplayable && response.status === 403 && upstreamType === MODEL_NOT_ALLOWED_FOR_KEY) {
|
|
80
|
+
scopeRefusalBody = body.text;
|
|
81
|
+
}
|
|
74
82
|
}
|
|
75
83
|
} catch (error) {
|
|
76
84
|
if (signal?.aborted) throw error;
|
|
@@ -106,7 +114,9 @@ export async function consumeComboFailure(
|
|
|
106
114
|
now,
|
|
107
115
|
includeDefault: false,
|
|
108
116
|
});
|
|
109
|
-
const failureResponse =
|
|
117
|
+
const failureResponse = scopeRefusalBody !== undefined
|
|
118
|
+
? new Response(scopeRefusalBody, { status: 403, headers: { "Content-Type": "application/json" } })
|
|
119
|
+
: formatErrorResponse(
|
|
110
120
|
response.status,
|
|
111
121
|
cyberFailure ? (upstreamType ?? CYBER_POLICY_ERROR_CODE) : "upstream_error",
|
|
112
122
|
message,
|
|
@@ -116,6 +126,7 @@ export async function consumeComboFailure(
|
|
|
116
126
|
},
|
|
117
127
|
);
|
|
118
128
|
if (nonReplayable) markResponseNonReplayable(failureResponse);
|
|
129
|
+
carryAnthropicRefusalCredential(response, failureResponse);
|
|
119
130
|
return {
|
|
120
131
|
response: failureResponse,
|
|
121
132
|
...(nonReplayable ? { nonReplayable: true } : {}),
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { effectiveAzureOutputBudget } from "../../adapters/azure";
|
|
2
|
+
import { resolveWireProtocolOverride } from "../adapter-resolve";
|
|
1
3
|
import type { CodexAccountModelRefusal } from "../../combos/failover";
|
|
2
4
|
import {
|
|
3
5
|
isCodexReasoningEffort,
|
|
4
6
|
isDeclaredReasoningEffort,
|
|
5
7
|
resolveEffortAtOrBelow,
|
|
6
8
|
} from "../../reasoning-effort";
|
|
7
|
-
import { resolveAdmissionModelScope, routeAllowedByScope } from "../admission-model-scope";
|
|
9
|
+
import { admissionMayUseMeteredComboTargets, resolveAdmissionModelScope, routeAllowedByScope } from "../admission-model-scope";
|
|
8
10
|
import { recordAttemptRequestedEffort } from "../request-log";
|
|
9
11
|
import {
|
|
10
12
|
CODEX_TEXT_GUARDED_BUDGET_POLICY,
|
|
@@ -29,6 +31,7 @@ import {
|
|
|
29
31
|
comboDefaultEffort,
|
|
30
32
|
isComboTargetInCooldown,
|
|
31
33
|
snapshotComboQuotaCooldowns,
|
|
34
|
+
comboExclusionReasons,
|
|
32
35
|
noteComboSuccess,
|
|
33
36
|
comboFailureDecision,
|
|
34
37
|
advanceComboAfterFailure,
|
|
@@ -50,14 +53,17 @@ import {
|
|
|
50
53
|
import { hasUnreadableEncryptedAgentTask } from "./encrypted-payload";
|
|
51
54
|
import { routeConcreteModel, comboRouteDecisionTrace } from "../../router";
|
|
52
55
|
import { memoryModelRouteReason } from "./memory-models";
|
|
53
|
-
import { poolAccountProviderLabel } from "../../providers/label";
|
|
56
|
+
import { baseProviderLabel, poolAccountProviderLabel } from "../../providers/label";
|
|
54
57
|
import { getAccountSet } from "../../oauth/store";
|
|
58
|
+
import { anthropicRefusalCredentialIsCurrent } from "../../oauth/anthropic-account-refusal";
|
|
55
59
|
import { configuredAnthropicInstance } from "../../providers/anthropic-instance";
|
|
60
|
+
import { isAnthropicBillingFailure } from "../../providers/anthropic-billing-refusal";
|
|
56
61
|
import {
|
|
57
62
|
anthropicRoutingFor,
|
|
58
63
|
formatAnthropicProviderForLog,
|
|
59
64
|
} from "../../oauth/anthropic-routing";
|
|
60
|
-
import { codexAccountLogLabel } from "../../codex/account-label";
|
|
65
|
+
import { codexAccountLogLabel, oauthAccountLogLabel } from "../../codex/account-label";
|
|
66
|
+
import { isGenericFailoverProvider, rotateGenericOAuthAccountOn429 } from "../../oauth/generic-account-failover";
|
|
61
67
|
import { codexQuotaScopeForModel, getCodexQuotaHealthSnapshot } from "../../codex/routing";
|
|
62
68
|
import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
63
69
|
import type { AgentTaskRecoveryFailureReason } from "./agent-task-recovery";
|
|
@@ -133,6 +139,48 @@ function cooledPoolAccountLabel(config: OcxConfig, providerName: string, modelId
|
|
|
133
139
|
? label : undefined;
|
|
134
140
|
}
|
|
135
141
|
|
|
142
|
+
/**
|
|
143
|
+
* The generic OAuth pool's counterpart of `cooledPoolAccountLabel`. Its 429 names one account
|
|
144
|
+
* through `accountLogLabel`, but the per-request account budget can end the pool's own rotation
|
|
145
|
+
* before the last refused account is recorded. Hand that account to the pool's rotator, which
|
|
146
|
+
* cools it under the pool's own Retry-After policy, and spare the target only when the pool still
|
|
147
|
+
* has an eligible sibling. A lone account or an all-cooled pool returns null and cools the target.
|
|
148
|
+
*/
|
|
149
|
+
function cooledGenericOAuthAccountLabel(
|
|
150
|
+
config: OcxConfig, providerName: string, modelId: string, label: string | undefined, retryAfter: string | undefined,
|
|
151
|
+
): string | undefined {
|
|
152
|
+
const provider = config.providers[providerName];
|
|
153
|
+
if (!label || !provider || !isGenericFailoverProvider(providerName, provider)) return undefined;
|
|
154
|
+
const base = baseProviderLabel(providerName);
|
|
155
|
+
const matches = getAccountSet(providerName)?.accounts.filter(account =>
|
|
156
|
+
oauthAccountLogLabel(account.id, base) === label) ?? [];
|
|
157
|
+
return matches.length === 1
|
|
158
|
+
&& rotateGenericOAuthAccountOn429(config, providerName, matches[0]!.id, retryAfter, Date.now(), modelId)
|
|
159
|
+
? label : undefined;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* A 402 credit-balance refusal names one Anthropic OAuth account -- the evidence the pool already
|
|
164
|
+
* accepts on a 403 -- but the pool rotates only on 401/403/429, so the 402 reaches the combo with
|
|
165
|
+
* that account still selectable. Cool it under the pool's entitlement backoff and spare the target
|
|
166
|
+
* only while a sibling is eligible; a lone account or an exhausted pool still cools the target.
|
|
167
|
+
*/
|
|
168
|
+
function creditRefusedAnthropicAccountLabel(
|
|
169
|
+
config: OcxConfig, providerName: string, modelId: string, label: string | undefined,
|
|
170
|
+
message: string, code: string | null | undefined, response: Response,
|
|
171
|
+
): string | undefined {
|
|
172
|
+
const instance = configuredAnthropicInstance(config, providerName);
|
|
173
|
+
if (!label || !instance || config.providers[providerName]?.authMode !== "oauth"
|
|
174
|
+
|| !isAnthropicBillingFailure(402, message, code)) return undefined;
|
|
175
|
+
const routing = anthropicRoutingFor(instance);
|
|
176
|
+
const matches = getAccountSet(instance)?.accounts.filter(account =>
|
|
177
|
+
formatAnthropicProviderForLog(instance, account.id) === label) ?? [];
|
|
178
|
+
return matches.length === 1
|
|
179
|
+
&& anthropicRefusalCredentialIsCurrent(response, instance, matches[0]!.id)
|
|
180
|
+
&& routing.recordAnthropicAccountRefusal(config, matches[0]!.id, 403, null)
|
|
181
|
+
&& routing.getEligibleAnthropicAccounts(Date.now(), modelId).length > 0
|
|
182
|
+
? label : undefined;
|
|
183
|
+
}
|
|
136
184
|
|
|
137
185
|
/**
|
|
138
186
|
* True only for the Anthropic OAuth pool's OWN "all accounts cooled" 429. Its Retry-After is the
|
|
@@ -249,6 +297,30 @@ interface JevComboChoice {
|
|
|
249
297
|
candidate: JevCandidate;
|
|
250
298
|
}
|
|
251
299
|
|
|
300
|
+
/** A target's JEV effort choices, or undefined when an explicit allowlist has none still supported. */
|
|
301
|
+
function jevChoiceEfforts(
|
|
302
|
+
config: OcxConfig,
|
|
303
|
+
target: NonNullable<ReturnType<typeof getCombo>>["targets"][number],
|
|
304
|
+
): OcxComboDefaultEffort[] | undefined {
|
|
305
|
+
let ladder: string[] | undefined;
|
|
306
|
+
try {
|
|
307
|
+
const route = routeConcreteModel(config, targetKey(target));
|
|
308
|
+
ladder = supportedLadderFor({ provider: route.provider, modelId: route.modelId });
|
|
309
|
+
} catch {
|
|
310
|
+
// Preserve the existing routing-failure surface. Unknown capability becomes the explicit
|
|
311
|
+
// no-effort choice rather than broadening JEV's effort allowlist.
|
|
312
|
+
ladder = undefined;
|
|
313
|
+
}
|
|
314
|
+
const supportedEfforts = (ladder ?? []).filter(isCodexReasoningEffort) as OcxComboDefaultEffort[];
|
|
315
|
+
const configuredEfforts = target.reasoningEfforts;
|
|
316
|
+
const reasoningEfforts = configuredEfforts === undefined
|
|
317
|
+
? supportedEfforts
|
|
318
|
+
: configuredEfforts.filter(effort => supportedEfforts.includes(effort));
|
|
319
|
+
// An explicit allowlist is restrictive. If catalog capabilities drift until no configured
|
|
320
|
+
// effort remains supported, omit the target instead of silently broadening JEV's choices.
|
|
321
|
+
return configuredEfforts !== undefined && reasoningEfforts.length === 0 ? undefined : reasoningEfforts;
|
|
322
|
+
}
|
|
323
|
+
|
|
252
324
|
/** Enumerate the current ordinary Combo eligibility set without retaining attempted picks. */
|
|
253
325
|
function eligibleJevComboChoices(
|
|
254
326
|
config: OcxConfig,
|
|
@@ -267,23 +339,8 @@ function eligibleJevComboChoices(
|
|
|
267
339
|
excluded.add(key);
|
|
268
340
|
// The TypeSafe row owns a decision credential, not an inference transport.
|
|
269
341
|
if (pick.target.provider === JEV_PROVIDER_ID) continue;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const route = routeConcreteModel(config, key);
|
|
273
|
-
ladder = supportedLadderFor({ provider: route.provider, modelId: route.modelId });
|
|
274
|
-
} catch {
|
|
275
|
-
// Preserve the existing routing-failure surface. Unknown capability becomes the explicit
|
|
276
|
-
// no-effort choice rather than broadening JEV's effort allowlist.
|
|
277
|
-
ladder = undefined;
|
|
278
|
-
}
|
|
279
|
-
const supportedEfforts = (ladder ?? []).filter(isCodexReasoningEffort) as OcxComboDefaultEffort[];
|
|
280
|
-
const configuredEfforts = pick.target.reasoningEfforts;
|
|
281
|
-
const reasoningEfforts = configuredEfforts === undefined
|
|
282
|
-
? supportedEfforts
|
|
283
|
-
: configuredEfforts.filter(effort => supportedEfforts.includes(effort));
|
|
284
|
-
// An explicit allowlist is restrictive. If catalog capabilities drift until no configured
|
|
285
|
-
// effort remains supported, omit the target instead of silently broadening JEV's choices.
|
|
286
|
-
if (configuredEfforts !== undefined && reasoningEfforts.length === 0) continue;
|
|
342
|
+
const reasoningEfforts = jevChoiceEfforts(config, pick.target);
|
|
343
|
+
if (!reasoningEfforts) continue;
|
|
287
344
|
choices.push({
|
|
288
345
|
pick: { ...pick, attempted: [key] },
|
|
289
346
|
candidate: {
|
|
@@ -315,9 +372,10 @@ export async function executeComboResponses(
|
|
|
315
372
|
options: HandleResponsesOptions & { translatorBudget: TranslatorBudget },
|
|
316
373
|
requestDispatchers: ResponsesDispatchers,
|
|
317
374
|
): Promise<Response> {
|
|
318
|
-
const requestedModel =
|
|
319
|
-
|
|
320
|
-
|
|
375
|
+
const requestedModel = options.mainHardLockFallbackOverride?.sourceModel
|
|
376
|
+
?? (typeof (rawBody as { model?: unknown } | null)?.model === "string"
|
|
377
|
+
? (rawBody as { model: string }).model
|
|
378
|
+
: `combo/${comboId}`);
|
|
321
379
|
Object.assign(logCtx, {
|
|
322
380
|
requestedModel,
|
|
323
381
|
model: requestedModel,
|
|
@@ -447,8 +505,10 @@ export async function executeComboResponses(
|
|
|
447
505
|
let comboPayloadReadable = false;
|
|
448
506
|
const payloadEligible = (target: (typeof combo.targets)[number]): boolean =>
|
|
449
507
|
comboPayloadReadable || !unreadableEncryptedAgentTask || canDecryptUnreadableAgentTask(target);
|
|
508
|
+
const meteredAllowed = admissionMayUseMeteredComboTargets(config, options.admission, combo);
|
|
450
509
|
const targetEligible = (target: (typeof combo.targets)[number]): boolean =>
|
|
451
|
-
(
|
|
510
|
+
(meteredAllowed || !target.metered)
|
|
511
|
+
&& (combo.strategy !== "jev" || target.provider !== JEV_PROVIDER_ID)
|
|
452
512
|
&& payloadEligible(target)
|
|
453
513
|
&& reasoningReplayEligible(target)
|
|
454
514
|
&& (protocolLanes?.pickable(target) ?? true);
|
|
@@ -522,6 +582,9 @@ export async function executeComboResponses(
|
|
|
522
582
|
now?: number;
|
|
523
583
|
}) => pickComboTargetWithWait(config, comboId, {
|
|
524
584
|
...pickOptions,
|
|
585
|
+
// Every pick, including the recovery picks that skip `targetEligible`, keeps metered
|
|
586
|
+
// targets away from callers that may not spend on them.
|
|
587
|
+
eligible: target => (meteredAllowed || !target.metered) && (pickOptions.eligible?.(target) ?? true),
|
|
525
588
|
waitForCooldownMs: combo.waitForCooldownMs,
|
|
526
589
|
abortSignal: options.abortSignal,
|
|
527
590
|
});
|
|
@@ -541,7 +604,8 @@ export async function executeComboResponses(
|
|
|
541
604
|
);
|
|
542
605
|
return options.abortSignal?.aborted
|
|
543
606
|
? clientCancelledResponse()
|
|
544
|
-
: comboUnavailable(comboId
|
|
607
|
+
: comboUnavailable(comboId, undefined,
|
|
608
|
+
comboExclusionReasons(config, comboId, target => meteredAllowed || !target.metered));
|
|
545
609
|
}
|
|
546
610
|
if (!(await recoverUnreadableEncryptedTask())) {
|
|
547
611
|
return options.abortSignal?.aborted
|
|
@@ -557,13 +621,16 @@ export async function executeComboResponses(
|
|
|
557
621
|
if (onlyReplayIncompatibleTargetsRemain()) return targetIncompatibleResponse();
|
|
558
622
|
return options.abortSignal?.aborted
|
|
559
623
|
? clientCancelledResponse()
|
|
560
|
-
: comboUnavailable(comboId);
|
|
624
|
+
: comboUnavailable(comboId, undefined, comboExclusionReasons(config, comboId, targetEligible));
|
|
561
625
|
}
|
|
562
626
|
let jevDecision: JevDecision | undefined;
|
|
563
627
|
if (combo.strategy === "jev") {
|
|
564
628
|
const choices = eligibleJevComboChoices(config, comboId, targetEligible, Date.now());
|
|
565
629
|
const first = choices[0];
|
|
566
|
-
if (!first)
|
|
630
|
+
if (!first) {
|
|
631
|
+
return comboUnavailable(comboId, undefined, comboExclusionReasons(config, comboId, target =>
|
|
632
|
+
targetEligible(target) && target.provider !== JEV_PROVIDER_ID && jevChoiceEfforts(config, target) !== undefined));
|
|
633
|
+
}
|
|
567
634
|
const resolvedFailOpenEffort = resolveEffortAtOrBelow(
|
|
568
635
|
"medium",
|
|
569
636
|
first.candidate.reasoningEfforts,
|
|
@@ -677,6 +744,9 @@ export async function executeComboResponses(
|
|
|
677
744
|
// is gone, so carry the loop's own classification decision instead of re-deriving a
|
|
678
745
|
// weaker one from the status alone (#4149).
|
|
679
746
|
let lastFailureClassifiesOverflow = false;
|
|
747
|
+
// Local input admission refusals (#1524), while every failed target so far was one: such a
|
|
748
|
+
// target sent nothing, so an exhausted ladder must not be reported as a provider rejection.
|
|
749
|
+
let localAdmissionRefusals: string[] | undefined = [];
|
|
680
750
|
// The first quota/rate-limit refusal of the ladder. A fallback that then refuses its own
|
|
681
751
|
// credential or plan (401/403, or a 400 the ladder hopped past) says nothing about why the
|
|
682
752
|
// request failed: the primary target ran out. Returning that fallback refusal told clients
|
|
@@ -712,12 +782,31 @@ export async function executeComboResponses(
|
|
|
712
782
|
retryAfter: String(Math.max(1, Math.ceil(remainingQuotaCooldownMs / 1000))),
|
|
713
783
|
}) : lastFailure;
|
|
714
784
|
};
|
|
785
|
+
// The caller's output ceiling, captured once so a floor applied to one target never
|
|
786
|
+
// leaks into the next. An established parent estimate wins; otherwise (combo dispatch
|
|
787
|
+
// precedes request preparation) the caller's own positive cap is reserved.
|
|
788
|
+
const callerBudget = isPlainObject(body) ? body.max_output_tokens : undefined;
|
|
789
|
+
const callerOutputCeilingTokens = logCtx.spendOutputCeilingTokens
|
|
790
|
+
?? (typeof callerBudget === "number" && Number.isFinite(callerBudget) && callerBudget > 0
|
|
791
|
+
? Math.trunc(callerBudget) : undefined);
|
|
715
792
|
while (pick) {
|
|
716
793
|
if (options.abortSignal?.aborted) return clientCancelledResponse();
|
|
717
794
|
const firstComboTarget = comboTargetsDispatched === 0;
|
|
718
795
|
const targetRoute = routeConcreteModel(config, `${pick.target.provider}/${pick.target.model}`);
|
|
719
796
|
// The inherited spend tracker observes this parent log, before the child has its own label.
|
|
720
797
|
logCtx.spendPoolId = targetRoute.providerName;
|
|
798
|
+
// Combo prepays before its child normalizes the route; the parent tracker must
|
|
799
|
+
// already see this target's effective cap rather than a previous target's cap.
|
|
800
|
+
const spendProvider = resolveWireProtocolOverride(targetRoute.providerName, targetRoute.modelId,
|
|
801
|
+
targetRoute.provider, options.inboundWire ?? "responses", targetRoute.staticPolicy);
|
|
802
|
+
const outputBudget = effectiveAzureOutputBudget(spendProvider, callerBudget);
|
|
803
|
+
if (outputBudget !== callerBudget && typeof outputBudget === "number" && outputBudget > 0) {
|
|
804
|
+
logCtx.spendOutputCeilingTokens = Math.trunc(outputBudget);
|
|
805
|
+
} else if (callerOutputCeilingTokens === undefined) {
|
|
806
|
+
delete logCtx.spendOutputCeilingTokens;
|
|
807
|
+
} else {
|
|
808
|
+
logCtx.spendOutputCeilingTokens = callerOutputCeilingTokens;
|
|
809
|
+
}
|
|
721
810
|
// Derive the target's allowance before booking its initial send; the booking occupies it.
|
|
722
811
|
const targetSendBudget = comboSendScope
|
|
723
812
|
? comboTargetSendBudget(comboSendScope, combo.targets.length - 1 - comboTargetsDispatched)
|
|
@@ -832,7 +921,8 @@ export async function executeComboResponses(
|
|
|
832
921
|
// The live config can change while the child is streaming. Never retain credentials.
|
|
833
922
|
const currentCombo = getCombo(config, comboId);
|
|
834
923
|
const provider = config.providers[completedTarget.provider];
|
|
835
|
-
if (!options.compactionRoutingOverride &&
|
|
924
|
+
if (!options.compactionRoutingOverride && !options.mainHardLockFallbackOverride
|
|
925
|
+
&& Object.hasOwn(config.providers, completedTarget.provider)
|
|
836
926
|
&& provider && provider.disabled !== true
|
|
837
927
|
&& currentCombo?.targets.some(target => targetKey(target) === targetKey(completedTarget))) {
|
|
838
928
|
rememberComboForLane(sessionLaneIdFromRequest(req.headers), comboId, completedTarget, model, writerGeneration);
|
|
@@ -1024,6 +1114,12 @@ export async function executeComboResponses(
|
|
|
1024
1114
|
Date.now() - started,
|
|
1025
1115
|
failure.usage,
|
|
1026
1116
|
);
|
|
1117
|
+
if (failure.upstreamCode === "input_admission_refused" && attempt.sendCount === 0) {
|
|
1118
|
+
attempt.errorCode = failure.upstreamCode;
|
|
1119
|
+
localAdmissionRefusals?.push(failure.upstreamMessage ?? "");
|
|
1120
|
+
} else {
|
|
1121
|
+
localAdmissionRefusals = undefined;
|
|
1122
|
+
}
|
|
1027
1123
|
attemptRetained = true;
|
|
1028
1124
|
lastFailure = failure.response;
|
|
1029
1125
|
if (!quotaFailure && (lastFailure.status === 429 || lastFailure.status === 402)) {
|
|
@@ -1107,12 +1203,16 @@ export async function executeComboResponses(
|
|
|
1107
1203
|
const failedTargetKey = targetKey(pick.target);
|
|
1108
1204
|
let failedTargetCooldownRecorded = false;
|
|
1109
1205
|
// The dispatch rewrote this to name the pool account that actually served the turn.
|
|
1110
|
-
const
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1206
|
+
const poolAccountLabel = poolAccountProviderLabel(childLog.provider, pick.target.provider);
|
|
1207
|
+
const failedAccount = failure.response.status === 402
|
|
1208
|
+
? creditRefusedAnthropicAccountLabel(
|
|
1209
|
+
config, pick.target.provider, pick.target.model, poolAccountLabel, failure.classificationText, failure.upstreamCode,
|
|
1210
|
+
failure.response,
|
|
1211
|
+
)
|
|
1212
|
+
: cooledPoolAccountLabel(config, pick.target.provider, pick.target.model, poolAccountLabel)
|
|
1213
|
+
?? (failure.response.status === 429 && failureCooldownScope === "target"
|
|
1214
|
+
? cooledGenericOAuthAccountLabel(config, pick.target.provider, pick.target.model, childLog.accountLogLabel, failure.retryAfter)
|
|
1215
|
+
: undefined);
|
|
1116
1216
|
// The target still cools on the pool's own refusal (the anti-hammer guard), but for the local
|
|
1117
1217
|
// fallback rather than the pool's restated per-account Retry-After.
|
|
1118
1218
|
const poolLocalRefusal = isAnthropicPoolLocalRefusal(
|
|
@@ -1204,9 +1304,14 @@ export async function executeComboResponses(
|
|
|
1204
1304
|
lastFailure?.status === 413
|
|
1205
1305
|
&& lastFailureClassifiesOverflow
|
|
1206
1306
|
) {
|
|
1307
|
+
if (localAdmissionRefusals?.length) logCtx.errorCode = "input_admission_refused";
|
|
1308
|
+
const localMessage = localAdmissionRefusals?.length
|
|
1309
|
+
? `opencodex refused this turn locally; nothing was sent upstream. No target of combo ${comboId} `
|
|
1310
|
+
+ `that was tried can fit its input: ${localAdmissionRefusals.join(" ")} Compact the conversation before retrying.`
|
|
1311
|
+
: undefined;
|
|
1207
1312
|
return (rawBody as { stream?: unknown } | null)?.stream === true
|
|
1208
|
-
? streamingContextOverflowResponse(requestedModel, options.translatorBudget)
|
|
1209
|
-
: jsonContextOverflowResponse();
|
|
1313
|
+
? streamingContextOverflowResponse(requestedModel, options.translatorBudget, localMessage)
|
|
1314
|
+
: jsonContextOverflowResponse(localMessage);
|
|
1210
1315
|
}
|
|
1211
1316
|
return failure;
|
|
1212
1317
|
}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
DecompressedBodyTooLargeError,
|
|
9
9
|
describeInboundBodyRefusal,
|
|
10
10
|
} from "../request-decompress";
|
|
11
|
-
import { comboCooldownRetryAfterSeconds } from "../../combos";
|
|
11
|
+
import { comboCooldownRetryAfterSeconds, NoAvailableComboTargetsError } from "../../combos";
|
|
12
12
|
import type { AgentTaskRecoveryFailureReason } from "./agent-task-recovery";
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -115,8 +115,8 @@ export function comboUnavailableResponse(
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
|
|
118
|
-
export function comboUnavailable(comboId: string, now = Date.now()): Response {
|
|
119
|
-
return comboUnavailableResponse(
|
|
118
|
+
export function comboUnavailable(comboId: string, now = Date.now(), reasons = ""): Response {
|
|
119
|
+
return comboUnavailableResponse(new NoAvailableComboTargetsError(comboId, reasons).message, {
|
|
120
120
|
retryAfter: comboCooldownRetryAfterSeconds(comboId, now),
|
|
121
121
|
});
|
|
122
122
|
}
|