@bitkyc08/opencodex 2.86.0 → 2.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/App-CHC89EXQ.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-B5bC7AWL.js} +1 -1
- package/gui/dist/assets/index-B5bsvxor.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-D8y2wTsB.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +9 -8
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/passthrough.ts +7 -23
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic-rejection-memo.ts +71 -0
- package/src/adapters/anthropic-replay.ts +135 -0
- package/src/adapters/anthropic-tool-catalog.ts +11 -0
- package/src/adapters/anthropic.ts +151 -33
- package/src/adapters/base.ts +2 -0
- package/src/adapters/claude-cli/adapter.ts +111 -23
- package/src/adapters/claude-cli/profiles.ts +2 -1
- package/src/adapters/claude-cli/stable-replay.ts +108 -0
- package/src/adapters/claude-cli/usage-admission.ts +230 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +18 -2
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-progress-history.ts +53 -0
- package/src/adapters/google.ts +52 -9
- package/src/adapters/openai-chat/messages.ts +4 -3
- package/src/adapters/openai-chat/passthrough.ts +8 -2
- package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
- package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
- package/src/adapters/openai-responses/passthrough.ts +57 -125
- package/src/adapters/openai-responses/request-strips.ts +25 -3
- package/src/adapters/openai-responses/transport.ts +81 -0
- package/src/adapters/qoder/adapter.ts +4 -2
- package/src/adapters/registry.ts +5 -0
- package/src/adapters/zed.ts +26 -1
- package/src/advisor/activity.ts +55 -0
- package/src/advisor/budget.ts +62 -0
- package/src/advisor/consult.ts +4 -1
- package/src/advisor/context.ts +16 -4
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +79 -31
- package/src/advisor/settings.ts +11 -2
- package/src/bridge/errors.ts +19 -1
- package/src/bridge/internal.ts +7 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +59 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/claude/desktop-3p-library.ts +32 -7
- package/src/claude/desktop-3p.ts +55 -13
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +77 -0
- package/src/claude/inbound.ts +33 -0
- package/src/claude/intercept/picker-ca-startup.ts +2 -1
- package/src/claude/intercept/picker-runtime.ts +6 -5
- package/src/claude/intercept/picker-trust-windows.ts +338 -0
- package/src/claude/intercept/picker-trust.ts +19 -0
- package/src/claude/intercept/port-policy.ts +30 -0
- package/src/claude/intercept/runtime.ts +97 -94
- package/src/claude/outbound.ts +46 -13
- package/src/cli/account-api.ts +1 -0
- package/src/cli/account-policy-dto.ts +6 -0
- package/src/cli/account-policy.ts +34 -5
- package/src/cli/account-preservation.ts +44 -0
- package/src/cli/account.ts +8 -2
- package/src/cli/advisor.ts +17 -3
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +5 -2
- package/src/cli/capabilities-base.ts +38 -10
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/codex-cli-update.ts +151 -41
- package/src/cli/codex-provider.ts +44 -0
- package/src/cli/combo-input.ts +10 -1
- package/src/cli/combo-result.ts +1 -1
- package/src/cli/combo.ts +2 -1
- package/src/cli/cross-home-owner.ts +33 -16
- package/src/cli/dispatch.ts +25 -4
- package/src/cli/doctor.ts +6 -1
- package/src/cli/ensure-desktop-cold-start.ts +60 -7
- package/src/cli/help.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/loopback-family.ts +31 -0
- package/src/cli/models-runtime-subcommands.ts +1 -0
- package/src/cli/models-runtime.ts +72 -8
- package/src/cli/observe-stream.ts +2 -0
- package/src/cli/observe.ts +16 -4
- package/src/cli/provider.ts +27 -5
- package/src/cli/registry.ts +28 -8
- package/src/cli/runtime-api.ts +5 -3
- package/src/cli/system-command.ts +40 -8
- package/src/cli/uninstall-client-state.ts +32 -0
- package/src/cli/usage-compare.ts +309 -0
- package/src/cli/usage-model-search.ts +4 -4
- package/src/cli/usage-report.ts +2 -2
- package/src/cli/v2.ts +1 -1
- package/src/client/machine-listener.ts +1 -3
- package/src/client/runtime.ts +1 -0
- package/src/clients/client-path.ts +51 -0
- package/src/clients/config-export/contracts.ts +2 -1
- package/src/clients/config-export.ts +18 -44
- package/src/clients/gentle-shell-path.ts +87 -0
- package/src/codex/account-auto-switch.ts +2 -0
- package/src/codex/account-identity.ts +29 -0
- package/src/codex/account-label.ts +1 -1
- package/src/codex/account-lifecycle.ts +21 -5
- package/src/codex/account-preservation.ts +28 -0
- package/src/codex/account-use-remaining.ts +23 -0
- package/src/codex/app-server-processes.ts +97 -7
- package/src/codex/app-server-restart-service.ts +55 -4
- package/src/codex/auth-api/account-list.ts +4 -0
- package/src/codex/auth-api/account-pause-group.ts +6 -50
- package/src/codex/auth-api/account-preservation.ts +57 -0
- package/src/codex/auth-api/routes.ts +8 -0
- package/src/codex/auth-api/use-remaining-route.ts +36 -0
- package/src/codex/auth-api.ts +0 -1
- package/src/codex/auth-context.ts +2 -2
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +9 -1
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +1 -1
- package/src/codex/catalog/gather-capture.ts +2 -0
- package/src/codex/catalog/metadata.ts +11 -9
- package/src/codex/catalog/model-hints.ts +21 -16
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/parsing.ts +30 -86
- package/src/codex/catalog/provider-models.ts +36 -34
- package/src/codex/catalog/routed-gather.ts +14 -13
- package/src/codex/cli-install-provenance.ts +105 -17
- package/src/codex/cli-update-plan.ts +545 -0
- package/src/codex/config-write-lock.ts +23 -2
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app-restart.ts +21 -0
- package/src/codex/desktop-compatibility/installed-build.ts +24 -6
- package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
- package/src/codex/desktop-compatibility/runtime.ts +12 -3
- package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
- package/src/codex/features.ts +85 -45
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/restore.ts +24 -2
- package/src/codex/inject/routing-classify.ts +4 -3
- package/src/codex/inject.ts +18 -27
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/native-profile-startup.ts +1 -1
- package/src/codex/ocx-compaction-history.ts +12 -10
- package/src/codex/paths.ts +86 -1
- package/src/codex/provider-routing.ts +174 -0
- package/src/codex/refresh.ts +2 -2
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/signed-in-identity.ts +43 -0
- package/src/codex/subagent-model-fallback.ts +1 -1
- package/src/codex/sync.ts +6 -1
- package/src/codex/user-identity.ts +16 -5
- package/src/codex/websocket-activity.ts +161 -0
- package/src/codex/websocket-registry.ts +25 -1
- package/src/codex/windows-local-appdata.ts +84 -0
- package/src/combos/failover.ts +6 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/resolve.ts +46 -10
- package/src/combos/types.ts +15 -0
- package/src/config/diagnostics.ts +33 -6
- package/src/config/live-reconcile.ts +38 -2
- package/src/config/load-degrade.ts +17 -0
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +53 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +22 -0
- package/src/config/schema/leaf-validators.ts +38 -9
- package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
- package/src/config/schema/native-reasoning-retention.ts +26 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +657 -297
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +116 -27
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +1 -0
- package/src/integrations/registry.ts +8 -0
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-upstream-tunnel.ts +18 -53
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +19 -3
- package/src/lib/gcp-adc.ts +96 -9
- package/src/lib/gcp-credential-input.ts +12 -0
- package/src/lib/lab-live-route-production.ts +5 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/redact.ts +2 -2
- package/src/lib/request-failure-model.ts +2 -0
- package/src/oauth/account-quota-rank.ts +69 -2
- package/src/oauth/anthropic-account-refusal.ts +20 -2
- package/src/oauth/anthropic-oauth-definitions.ts +3 -1
- package/src/oauth/anthropic-routing.ts +1 -21
- package/src/oauth/anthropic.ts +38 -7
- package/src/oauth/callback-server.ts +27 -1
- package/src/oauth/chatgpt-device.ts +1 -1
- package/src/oauth/chatgpt-shared.ts +83 -0
- package/src/oauth/chatgpt.ts +8 -72
- package/src/oauth/dsh.ts +203 -0
- package/src/oauth/generic-account-failover.ts +40 -11
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +184 -109
- package/src/oauth/local-token-detect.ts +21 -1
- package/src/oauth/login-cli-errors.ts +44 -0
- package/src/oauth/login-cli.ts +69 -7
- package/src/oauth/login-flow-state.ts +371 -23
- package/src/oauth/meta-muse-errors.ts +41 -0
- package/src/oauth/meta-muse-keychain.ts +52 -0
- package/src/oauth/meta-muse.ts +51 -84
- package/src/oauth/model-discovery-auth.ts +3 -0
- package/src/oauth/pool-settings-capability.ts +4 -2
- package/src/oauth/refresh-flight-retirement.ts +48 -0
- package/src/oauth/session-affinity-key.ts +23 -0
- package/src/oauth/store-anthropic-instance.ts +9 -2
- package/src/oauth/store.ts +12 -5
- package/src/protocols/encoders/adapter-events.ts +6 -5
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/provider-summary.ts +2 -1
- package/src/providers/api-key-cooldowns.ts +49 -0
- package/src/providers/api-key-resolve.ts +11 -6
- package/src/providers/api-key-response-ownership.ts +57 -0
- package/src/providers/api-key-selection.ts +31 -6
- package/src/providers/api-keys.ts +214 -3
- package/src/providers/claude-cli-usage.ts +96 -0
- package/src/providers/dsh-account-destination.ts +31 -0
- package/src/providers/dsh-account-models.ts +41 -0
- package/src/providers/gcp-credential-cleanup.ts +59 -0
- package/src/providers/key-failover.ts +54 -50
- package/src/providers/key-store.ts +7 -2
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/model-rename-migration.ts +2 -2
- package/src/providers/openai-tiers-destination.ts +11 -2
- package/src/providers/openai-tiers.ts +5 -12
- package/src/providers/opencode-go-transport.ts +3 -1
- package/src/providers/opencode-zen-rate-limit.ts +7 -2
- package/src/providers/quota/account-cache.ts +6 -5
- package/src/providers/quota/account-row.ts +34 -0
- package/src/providers/quota/antigravity.ts +28 -7
- package/src/providers/quota/dsh-account.ts +70 -0
- package/src/providers/quota/vendor-probes-oauth.ts +1 -93
- package/src/providers/quota-types.ts +2 -0
- package/src/providers/quota.ts +14 -20
- package/src/providers/registry/entries-core.ts +37 -8
- package/src/providers/registry/types.ts +1 -1
- package/src/providers/request-pacing.ts +77 -19
- package/src/providers/routed-config.ts +339 -0
- package/src/providers/service-tier.ts +2 -1
- package/src/quota/window-mapping.ts +5 -2
- package/src/responses/anthropic-progress.ts +34 -0
- package/src/responses/assistant-replay.ts +35 -0
- package/src/responses/compaction-history-framing.ts +65 -0
- package/src/responses/compaction.ts +6 -1
- package/src/responses/incomplete-reason.ts +31 -0
- package/src/responses/parser.ts +50 -5
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +74 -6
- package/src/responses/reasoning-retention.ts +173 -0
- package/src/responses/retained-compaction.ts +46 -0
- package/src/responses/schema.ts +23 -0
- package/src/router.ts +8 -321
- package/src/routing/analytics.ts +7 -15
- package/src/routing/health.ts +37 -22
- package/src/routing/history/indexer.ts +6 -5
- package/src/routing/history/ingest.ts +6 -2
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +17 -0
- package/src/server/auth-cors.ts +11 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native-reasoning.ts +123 -0
- package/src/server/chat-native-sse.ts +110 -7
- package/src/server/chat-native.ts +76 -17
- package/src/server/claude-messages.ts +56 -414
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/live-sideband.ts +3 -2
- package/src/server/index/model-alias-id.ts +36 -0
- package/src/server/index/optional-listeners.ts +2 -0
- package/src/server/index/serve-options.ts +20 -6
- package/src/server/index/startup-warnings.ts +2 -0
- package/src/server/index/websocket-handler.ts +18 -4
- package/src/server/index.ts +8 -8
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/lifecycle.ts +13 -1
- package/src/server/management/advisor-routes.ts +15 -4
- package/src/server/management/claude-desktop-picker-routes.ts +1 -1
- package/src/server/management/combo-routes.ts +176 -50
- package/src/server/management/companion-routes.ts +1 -1
- package/src/server/management/config-routes.ts +75 -4
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/model-routes.ts +11 -6
- package/src/server/management/model-rows.ts +14 -0
- package/src/server/management/native-integration-routes.ts +2 -7
- package/src/server/management/native-reasoning-retention-routes.ts +41 -0
- package/src/server/management/oauth-account-routes.ts +87 -14
- package/src/server/management/provider-gcp-credential-routes.ts +29 -0
- package/src/server/management/provider-key-pacing-routes.ts +31 -0
- package/src/server/management/provider-overwrite-carry.ts +7 -0
- package/src/server/management/provider-routes.ts +25 -30
- package/src/server/management/route-registry.ts +10 -1
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +117 -24
- package/src/server/messages-shared.ts +431 -0
- package/src/server/relay.ts +1 -1
- package/src/server/request-log-terminal-status.ts +1 -1
- package/src/server/request-log.ts +6 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +47 -19
- package/src/server/responses/adapter-delivery.ts +72 -12
- package/src/server/responses/adapter-dispatch.ts +46 -3
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +73 -41
- package/src/server/responses/anthropic-thinking-recovery.ts +153 -0
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/combo-stream-preflight.ts +3 -2
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +20 -10
- package/src/server/responses/compaction-retention.ts +61 -0
- package/src/server/responses/context-overflow.ts +11 -6
- package/src/server/responses/core-auth.ts +6 -0
- package/src/server/responses/core-codex-account.ts +11 -1
- package/src/server/responses/core-combo-failure.ts +2 -0
- package/src/server/responses/core-combo.ts +121 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +8 -4
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +7 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +23 -3
- package/src/server/responses/core.ts +2 -115
- package/src/server/responses/fetch-helpers.ts +73 -19
- package/src/server/responses/main-hard-lock-routing.ts +109 -0
- package/src/server/responses/native-response-control.ts +4 -2
- package/src/server/responses/passthrough-delivery.ts +3 -1
- package/src/server/responses/passthrough-dispatch.ts +15 -13
- package/src/server/responses/passthrough-execution.ts +3 -2
- package/src/server/responses/request-prepare.ts +77 -12
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +104 -27
- package/src/server/responses/response-effects.ts +2 -0
- package/src/server/responses/response-tool-ownership.ts +21 -0
- package/src/server/responses/run-turn-execution.ts +96 -30
- package/src/server/responses/sidecar-execution.ts +63 -9
- package/src/server/responses/sidecar-send-budget.ts +2 -2
- package/src/server/responses/terminal-guard.ts +2 -16
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/ws-bridge.ts +2 -0
- package/src/service/auth-preflight.ts +71 -0
- package/src/service/cli.ts +4 -2
- package/src/service/definition.ts +97 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +76 -57
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +72 -9
- package/src/service/repair.ts +2 -1
- package/src/service/state.ts +7 -8
- package/src/service/systemd.ts +1 -1
- package/src/service/windows-guarded-manager.ts +2 -1
- package/src/service/windows-ops.ts +1 -1
- package/src/service/windows-scheduler.ts +2 -7
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service/windows-wscript.ts +14 -0
- package/src/service.ts +4 -2
- package/src/sidecar/auth.ts +2 -0
- package/src/types/config.ts +64 -3
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +35 -0
- package/src/types/provider.ts +32 -6
- package/src/types/request.ts +31 -2
- package/src/types/wire.ts +31 -37
- package/src/types.ts +2 -0
- package/src/update/npm-invocation.mjs +12 -2
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +3 -0
- package/src/usage/ledger-retention.ts +2 -1
- package/src/usage/ledger-scanner.ts +6 -5
- package/src/usage/log.ts +17 -8
- package/src/usage/summary.ts +30 -11
- package/src/usage/telemetry-contract.ts +1 -0
- package/src/usage/user-cost-overlays.ts +14 -19
- package/src/usage/user-cost-validation.ts +38 -0
- package/src/vision/eligibility.ts +13 -0
- package/src/web-search/anthropic-history.ts +59 -0
- package/src/web-search/loop.ts +236 -73
- package/src/web-search/progress-stream.ts +15 -2
- package/src/web-search/run-turn-loop.ts +9 -1
- package/gui/dist/assets/App-BjlttVLy.js +0 -58
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-BMssP4k3.js +0 -94
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
|
@@ -276,6 +276,39 @@ export function isSelfIdentifiedOpaqueBlobRejection(bodyText: string): boolean {
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Opted-in native reasoning retention forwards ciphertext another model or account minted. ChatGPT
|
|
281
|
+
* can refuse it inside a 200 stream -- a Codex WebSocket `error` frame or `response.failed` --
|
|
282
|
+
* rather than as an HTTP 4xx, so the passthrough inspects the zero-output prelude of exactly these
|
|
283
|
+
* requests. Ordinary turns keep the function-output-only inspection.
|
|
284
|
+
*/
|
|
285
|
+
export function retainedReasoningStreamCandidate(parsed: OcxParsedRequest, outboundBody: string | undefined): boolean {
|
|
286
|
+
return parsed._nativeReasoningRetainedAcrossRoute === true
|
|
287
|
+
&& parsed._stripReasoningEncryptedContent !== true
|
|
288
|
+
&& outboundResponsesBodyCarriesOpaqueBlob(outboundBody);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Whether a stream-prelude payload is a self-identified refusal of retained ciphertext. A match marks
|
|
293
|
+
* the projected failure as the 4xx it is, so the shared single-shot trigger accepts it.
|
|
294
|
+
*/
|
|
295
|
+
export function retainedReasoningStreamRejection(
|
|
296
|
+
payload: unknown,
|
|
297
|
+
parsed: OcxParsedRequest,
|
|
298
|
+
outboundBody: string | undefined,
|
|
299
|
+
preflightLog: RequestLogContext,
|
|
300
|
+
): boolean {
|
|
301
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
|
|
302
|
+
if (!retainedReasoningStreamCandidate(parsed, outboundBody)) return false;
|
|
303
|
+
const { type, response } = payload as { type?: unknown; response?: { error?: unknown } };
|
|
304
|
+
if (type !== "error" && type !== "response.failed") return false;
|
|
305
|
+
const envelope = type === "error" ? payload : { error: response?.error };
|
|
306
|
+
if (!isSelfIdentifiedOpaqueBlobRejection(JSON.stringify(envelope))) return false;
|
|
307
|
+
preflightLog.terminalHttpStatus = 400;
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
279
312
|
/**
|
|
280
313
|
* Whether an upstream Responses 4xx authoritatively rejected opaque replay state.
|
|
281
314
|
*
|
|
@@ -411,6 +444,8 @@ export async function consoleGoUploadRejectionBody(
|
|
|
411
444
|
|
|
412
445
|
export function prepareOpaqueBlobRecovery(parsed: OcxParsedRequest): void {
|
|
413
446
|
parsed._stripReasoningEncryptedContent = true;
|
|
447
|
+
delete parsed._nativeReasoningRetainedAcrossRoute;
|
|
448
|
+
parsed._stripNativeCompactionEncryptedContent = true;
|
|
414
449
|
// The destination rejected state another serving identity minted. A reasoning item's id names
|
|
415
450
|
// an item in that identity's store, so it goes with the blob.
|
|
416
451
|
parsed._dropForeignReasoningItemIds = true;
|
|
@@ -15,6 +15,7 @@ import type { InboundWire } from "../../providers/registry";
|
|
|
15
15
|
import type { ExplicitOpenAiCallerAuth } from "../../providers/openai-sidecar";
|
|
16
16
|
import type { CallerDirectAuth } from "../../providers/caller-authorization";
|
|
17
17
|
import type { CompactionRoutingOverride } from "./compaction-routing";
|
|
18
|
+
import type { MainHardLockFallbackOverride } from "./main-hard-lock-routing";
|
|
18
19
|
import type { TranslatorBudget } from "../../lib/translator-budget";
|
|
19
20
|
import type { NativeReasoningOwner } from "../../responses/reasoning-replay-cache";
|
|
20
21
|
import type { TransientSendBudget } from "../../lib/upstream-retry";
|
|
@@ -174,6 +175,10 @@ export interface HandleResponsesOptions {
|
|
|
174
175
|
clientDeclaredEmptyToolCatalog?: boolean;
|
|
175
176
|
nativeReasoningReplay?: ReadonlyMap<string, string>;
|
|
176
177
|
nativeReasoningMint?: { owner?: NativeReasoningOwner };
|
|
178
|
+
/** The Claude client's typed `web_search_*` / `web_fetch_*` declarations; kept only for `inboundWire: "anthropic"`. */
|
|
179
|
+
anthropicServerTools?: readonly Record<string, unknown>[];
|
|
180
|
+
/** The Claude client's replayable server-tool exchanges (`replayedAnthropicServerBlocks`); same scope. */
|
|
181
|
+
anthropicServerReplay?: ReadonlyMap<string, readonly Record<string, unknown>[]>;
|
|
177
182
|
/** Droid's per-request effort default; each concrete combo or policy target applies it only if its ladder allows it. */
|
|
178
183
|
droidDefaultEffort?: string;
|
|
179
184
|
/** PF-07: the Chat source a combo child may send natively; set only by the Chat ingress. */
|
|
@@ -207,6 +212,8 @@ export interface HandleResponsesOptions {
|
|
|
207
212
|
/** Internal handoff: the memory phase this turn belongs to, so combo children keep its routing. */
|
|
208
213
|
memoryModelPhase?: "extract" | "consolidation";
|
|
209
214
|
compactionRoutingOverride?: CompactionRoutingOverride | null;
|
|
215
|
+
/** Internal handoff: the hard-lock fallback rewrote this turn; the log shows `sourceModel`. */
|
|
216
|
+
mainHardLockFallbackOverride?: MainHardLockFallbackOverride | null;
|
|
210
217
|
/** Internal combo handoff for one parent-validated continuation snapshot. */
|
|
211
218
|
comboReplaySnapshot?: {
|
|
212
219
|
sourceBody: unknown;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { supportsNativeResponsesCompactionTrigger } from "../../providers/openai-tiers";
|
|
2
|
+
import type { OcxConfig } from "../../types";
|
|
3
|
+
import type { RequestLogContext } from "../request-log";
|
|
4
|
+
import type {
|
|
5
|
+
HandleResponsesOptions,
|
|
6
|
+
ResponsesRequestContext,
|
|
7
|
+
ResponsesAdmissionState,
|
|
8
|
+
ResponsesDispatchers,
|
|
9
|
+
} from "./core-options";
|
|
10
|
+
import type { TranslatorBudget } from "../../lib/translator-budget";
|
|
11
|
+
import { prepareResponsesRequest } from "./request-prepare";
|
|
12
|
+
import { prepareResponsesTransport } from "./request-transport";
|
|
13
|
+
import { prepareResponsesSidecarAuth } from "./request-sidecar-auth";
|
|
14
|
+
import { createResponsesEffects } from "./response-effects";
|
|
15
|
+
import { createResponsesSendBudget } from "./request-send-budget";
|
|
16
|
+
import { executePassthroughResponse } from "./passthrough-execution";
|
|
17
|
+
import { executeResponsesSidecars } from "./sidecar-execution";
|
|
18
|
+
import { createResponsesCompletionPolicy } from "./completion-policy";
|
|
19
|
+
import { executeResponsesRunTurn } from "./run-turn-execution";
|
|
20
|
+
import { prepareAdapterExchange } from "./adapter-dispatch";
|
|
21
|
+
import { createAdapterContinuations } from "./adapter-continuation";
|
|
22
|
+
import { deliverAdapterResponse } from "./adapter-delivery";
|
|
23
|
+
import { releaseUpstreamHostAdmission } from "../../codex/upstream-host-health";
|
|
24
|
+
import { releaseCodexAuthContextProbeLease } from "../../codex/auth-context";
|
|
25
|
+
import { formatErrorResponse } from "../../bridge";
|
|
26
|
+
import { InvalidRetainedCompactionSummary, prepareCompactionRetention } from "./compaction-retention";
|
|
27
|
+
|
|
28
|
+
/** Compose request phases while retaining the original admission-finally ownership. */
|
|
29
|
+
export async function handleResponsesInner(
|
|
30
|
+
req: Request,
|
|
31
|
+
config: OcxConfig,
|
|
32
|
+
logCtx: RequestLogContext,
|
|
33
|
+
options: HandleResponsesOptions & { translatorBudget: TranslatorBudget },
|
|
34
|
+
requestDispatchers: ResponsesDispatchers,
|
|
35
|
+
): Promise<Response> {
|
|
36
|
+
const requestContext: ResponsesRequestContext = { req, config, logCtx, options };
|
|
37
|
+
const admissionState: ResponsesAdmissionState = {
|
|
38
|
+
pendingHostAdmissionLease: null,
|
|
39
|
+
authCtx: { kind: "main", accountId: null },
|
|
40
|
+
};
|
|
41
|
+
let retention: ReturnType<typeof prepareCompactionRetention>;
|
|
42
|
+
let releasePendingSend = () => {};
|
|
43
|
+
try {
|
|
44
|
+
const requestState = await prepareResponsesRequest(requestContext, admissionState, requestDispatchers);
|
|
45
|
+
if (requestState instanceof Response) return requestState;
|
|
46
|
+
const transportState = await prepareResponsesTransport(requestContext, admissionState, requestState);
|
|
47
|
+
if (transportState instanceof Response) return transportState;
|
|
48
|
+
options.onCompactionRecoveryRoute?.(requestState.route);
|
|
49
|
+
if (requestState.parsed._compactionRequest && options.compactionRecoveryKind !== "compaction-v1"
|
|
50
|
+
&& (requestState.parsed._portableCompaction || !supportsNativeResponsesCompactionTrigger(requestState.route.provider))) {
|
|
51
|
+
retention = prepareCompactionRetention(requestState.parsed, config,
|
|
52
|
+
requestState.route.staticPolicy.model.contextWindow ?? requestState.route.staticPolicy.model.maxInputTokens,
|
|
53
|
+
options.translatorBudget, options.abortSignal ?? req.signal);
|
|
54
|
+
}
|
|
55
|
+
const execute = async (): Promise<Response> => {
|
|
56
|
+
const sidecarState = await prepareResponsesSidecarAuth(requestContext, requestState, transportState);
|
|
57
|
+
if (sidecarState instanceof Response) return sidecarState;
|
|
58
|
+
const responseEffects = createResponsesEffects(
|
|
59
|
+
requestContext,
|
|
60
|
+
admissionState,
|
|
61
|
+
requestState,
|
|
62
|
+
sidecarState,
|
|
63
|
+
);
|
|
64
|
+
const sendBudgetState = createResponsesSendBudget(requestContext);
|
|
65
|
+
if (sendBudgetState instanceof Response) return sendBudgetState;
|
|
66
|
+
if ("passthrough" in transportState.adapter && transportState.adapter.passthrough && !sidecarState.routedCompaction) {
|
|
67
|
+
const passthroughResult = await executePassthroughResponse(
|
|
68
|
+
requestContext,
|
|
69
|
+
admissionState,
|
|
70
|
+
requestState,
|
|
71
|
+
transportState,
|
|
72
|
+
sidecarState,
|
|
73
|
+
responseEffects,
|
|
74
|
+
sendBudgetState,
|
|
75
|
+
);
|
|
76
|
+
if (passthroughResult instanceof Response) return passthroughResult;
|
|
77
|
+
const unclaimedHop = sendBudgetState.pendingHopPermit;
|
|
78
|
+
releasePendingSend = () => { if (sendBudgetState.pendingHopPermit === unclaimedHop) { unclaimedHop?.release(); sendBudgetState.pendingHopPermit = undefined; } };
|
|
79
|
+
}
|
|
80
|
+
const sidecarPlans = await executeResponsesSidecars(
|
|
81
|
+
requestContext,
|
|
82
|
+
requestState,
|
|
83
|
+
transportState,
|
|
84
|
+
sidecarState,
|
|
85
|
+
responseEffects,
|
|
86
|
+
sendBudgetState,
|
|
87
|
+
);
|
|
88
|
+
if (sidecarPlans instanceof Response) return sidecarPlans;
|
|
89
|
+
const completionPolicy = createResponsesCompletionPolicy(requestContext, sidecarState);
|
|
90
|
+
if (transportState.adapter.runTurn) return await executeResponsesRunTurn(
|
|
91
|
+
requestContext,
|
|
92
|
+
admissionState,
|
|
93
|
+
requestState,
|
|
94
|
+
transportState,
|
|
95
|
+
sidecarState,
|
|
96
|
+
responseEffects,
|
|
97
|
+
sendBudgetState,
|
|
98
|
+
completionPolicy,
|
|
99
|
+
);
|
|
100
|
+
const adapterExchange = await prepareAdapterExchange(
|
|
101
|
+
requestContext,
|
|
102
|
+
admissionState,
|
|
103
|
+
requestState,
|
|
104
|
+
transportState,
|
|
105
|
+
responseEffects,
|
|
106
|
+
sendBudgetState,
|
|
107
|
+
);
|
|
108
|
+
if (adapterExchange instanceof Response) return adapterExchange;
|
|
109
|
+
const continuationState = createAdapterContinuations(
|
|
110
|
+
requestContext,
|
|
111
|
+
requestState,
|
|
112
|
+
transportState,
|
|
113
|
+
sidecarState,
|
|
114
|
+
sendBudgetState,
|
|
115
|
+
adapterExchange,
|
|
116
|
+
);
|
|
117
|
+
return await deliverAdapterResponse(
|
|
118
|
+
requestContext,
|
|
119
|
+
requestState,
|
|
120
|
+
transportState,
|
|
121
|
+
sidecarState,
|
|
122
|
+
responseEffects,
|
|
123
|
+
completionPolicy,
|
|
124
|
+
adapterExchange,
|
|
125
|
+
continuationState,
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
const response = await execute();
|
|
129
|
+
return retention ? retention.deliver(response) : response;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
retention?.dispose();
|
|
132
|
+
if (error instanceof InvalidRetainedCompactionSummary) return formatErrorResponse(502, "invalid_compaction_summary", error.message);
|
|
133
|
+
throw error;
|
|
134
|
+
} finally {
|
|
135
|
+
releasePendingSend();
|
|
136
|
+
if (admissionState.pendingHostAdmissionLease) {
|
|
137
|
+
releaseUpstreamHostAdmission(admissionState.pendingHostAdmissionLease);
|
|
138
|
+
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { bindAnthropicRejectionPolicy } from "../../adapters/anthropic-rejection-memo";
|
|
2
|
+
import { getReasoningReplayOpaqueBlobRejection } from "../../responses/reasoning-replay-cache";
|
|
1
3
|
import type {
|
|
2
4
|
OcxProviderContinuationOwner,
|
|
3
5
|
OcxProviderContinuationState,
|
|
@@ -24,6 +26,7 @@ import {
|
|
|
24
26
|
type NativeReasoningOwner,
|
|
25
27
|
bindReasoningReplayScope,
|
|
26
28
|
reasoningReplayServingIdentityChanged,
|
|
29
|
+
reasoningReplayServingIdentityChange,
|
|
27
30
|
reasoningReplayItemStoreChanged,
|
|
28
31
|
reasoningReplayOpaqueBlobRejectionMemoized,
|
|
29
32
|
} from "../../responses/reasoning-replay-cache";
|
|
@@ -32,6 +35,7 @@ import type { CodexAuthContext } from "../../codex/auth-context";
|
|
|
32
35
|
import { thoughtSignatureReplaySalt } from "../../responses/thought-signature-replay";
|
|
33
36
|
import { randomUUID } from "node:crypto";
|
|
34
37
|
import { requiresPlaintextReasoningReplay } from "../../adapters/openai-responses/passthrough";
|
|
38
|
+
import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
35
39
|
|
|
36
40
|
/**
|
|
37
41
|
* Adapters whose continuation state must survive Codex's store:false requests.
|
|
@@ -156,18 +160,34 @@ export function bindRouteReasoningReplayScope(args: {
|
|
|
156
160
|
);
|
|
157
161
|
// Keep this sticky for the whole outbound request: a later auth/key rebind may compare equal
|
|
158
162
|
// after the first mismatch, but it cannot make history minted by the prior route decodable.
|
|
159
|
-
|
|
160
|
-
|
|
163
|
+
const identityChange = reasoningReplayServingIdentityChange(parsed._reasoningReplayScope);
|
|
164
|
+
if (identityChange) {
|
|
165
|
+
// Reasoning retention does not establish portability of native compacted history.
|
|
166
|
+
parsed._stripNativeCompactionEncryptedContent = true;
|
|
167
|
+
const retention = parsed._nativeReasoningRetention;
|
|
168
|
+
const mayRetainReasoning = isCanonicalOpenAiForwardProvider(provider)
|
|
169
|
+
&& !identityChange.providerChanged
|
|
170
|
+
&& !identityChange.destinationChanged
|
|
171
|
+
&& !identityChange.adapterChanged
|
|
172
|
+
&& (!identityChange.modelChanged || retention?.modelSwitch === true)
|
|
173
|
+
&& (!identityChange.credentialChanged || retention?.accountSwitch === true);
|
|
174
|
+
if (!mayRetainReasoning) parsed._stripReasoningEncryptedContent = true;
|
|
175
|
+
else if (parsed._stripReasoningEncryptedContent !== true) parsed._nativeReasoningRetainedAcrossRoute = true;
|
|
161
176
|
// Only a different destination or credential makes the replayed item ids unresolvable; a
|
|
162
177
|
// model change on the same store keeps them.
|
|
163
178
|
if (reasoningReplayItemStoreChanged(parsed._reasoningReplayScope)) {
|
|
164
179
|
parsed._dropForeignReasoningItemIds = true;
|
|
165
180
|
}
|
|
166
181
|
}
|
|
182
|
+
const rejectionMemo = getReasoningReplayOpaqueBlobRejection(parsed._reasoningReplayScope);
|
|
183
|
+
bindAnthropicRejectionPolicy(parsed, adapterName === "anthropic" ? rejectionMemo : undefined);
|
|
167
184
|
if (reasoningReplayOpaqueBlobRejectionMemoized(parsed._reasoningReplayScope)) {
|
|
168
|
-
parsed._stripReasoningEncryptedContent = true;
|
|
185
|
+
if (adapterName !== "anthropic" || rejectionMemo?.mode !== "selective") parsed._stripReasoningEncryptedContent = true;
|
|
186
|
+
parsed._stripNativeCompactionEncryptedContent = true;
|
|
169
187
|
parsed._dropForeignReasoningItemIds = true;
|
|
170
188
|
}
|
|
189
|
+
// Stripping is authoritative: nothing retained remains to be refused.
|
|
190
|
+
if (parsed._stripReasoningEncryptedContent === true) delete parsed._nativeReasoningRetainedAcrossRoute;
|
|
171
191
|
bindProviderContinuationForRoute(parsed, continuationOwner);
|
|
172
192
|
}
|
|
173
193
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import { handleResponsesInner } from "./core-pipeline";
|
|
1
2
|
import type { OcxConfig } from "../../types";
|
|
2
3
|
import type { RequestLogContext } from "../request-log";
|
|
3
4
|
import type {
|
|
4
5
|
HandleResponsesOptions,
|
|
5
|
-
ResponsesRequestContext,
|
|
6
|
-
ResponsesAdmissionState,
|
|
7
6
|
ResponsesDispatchers,
|
|
8
7
|
} from "./core-options";
|
|
9
8
|
import { createTranslatorBudget } from "../../lib/translator-budget";
|
|
@@ -13,20 +12,6 @@ import { createInferenceSendBudget } from "../inference/context";
|
|
|
13
12
|
import { finalizeOwnedTranslatorBudget, finalizeAccountLease } from "./core-lifetime";
|
|
14
13
|
import type { TranslatorBudget } from "../../lib/translator-budget";
|
|
15
14
|
import { executeComboResponses } from "./core-combo";
|
|
16
|
-
import { prepareResponsesRequest } from "./request-prepare";
|
|
17
|
-
import { prepareResponsesTransport } from "./request-transport";
|
|
18
|
-
import { prepareResponsesSidecarAuth } from "./request-sidecar-auth";
|
|
19
|
-
import { createResponsesEffects } from "./response-effects";
|
|
20
|
-
import { createResponsesSendBudget } from "./request-send-budget";
|
|
21
|
-
import { executePassthroughResponse } from "./passthrough-execution";
|
|
22
|
-
import { executeResponsesSidecars } from "./sidecar-execution";
|
|
23
|
-
import { createResponsesCompletionPolicy } from "./completion-policy";
|
|
24
|
-
import { executeResponsesRunTurn } from "./run-turn-execution";
|
|
25
|
-
import { prepareAdapterExchange } from "./adapter-dispatch";
|
|
26
|
-
import { createAdapterContinuations } from "./adapter-continuation";
|
|
27
|
-
import { deliverAdapterResponse } from "./adapter-delivery";
|
|
28
|
-
import { releaseUpstreamHostAdmission } from "../../codex/upstream-host-health";
|
|
29
|
-
import { releaseCodexAuthContextProbeLease } from "../../codex/auth-context";
|
|
30
15
|
import { runWithCompactionRecovery } from "./compaction-recovery";
|
|
31
16
|
/**
|
|
32
17
|
* Route one `/v1/responses` request through the adapter pipeline: recovery loop, passthrough
|
|
@@ -62,7 +47,7 @@ export async function handleResponses(
|
|
|
62
47
|
accountLoad,
|
|
63
48
|
// Once at ingress, spend observer included: a combo child inherits the parent's holder.
|
|
64
49
|
sendBudget: options.sendBudget ?? createInferenceSendBudget(req, logCtx),
|
|
65
|
-
}, handleResponsesInner);
|
|
50
|
+
}, (req, config, logCtx, options) => handleResponsesInner(req, config, logCtx, options, requestDispatchers));
|
|
66
51
|
const finalResponse = ownsBudget ? finalizeOwnedTranslatorBudget(response, translatorBudget, abortSignal) : response;
|
|
67
52
|
if (!accountLoad.lease) { release(); return finalResponse; }
|
|
68
53
|
return finalizeAccountLease(finalResponse, release);
|
|
@@ -92,104 +77,6 @@ export async function handleComboResponses(
|
|
|
92
77
|
requestDispatchers,
|
|
93
78
|
);
|
|
94
79
|
}
|
|
95
|
-
/** Compose request phases while retaining the original admission-finally ownership. */
|
|
96
|
-
async function handleResponsesInner(
|
|
97
|
-
req: Request,
|
|
98
|
-
config: OcxConfig,
|
|
99
|
-
logCtx: RequestLogContext,
|
|
100
|
-
options: HandleResponsesOptions & { translatorBudget: TranslatorBudget },
|
|
101
|
-
): Promise<Response> {
|
|
102
|
-
const requestContext: ResponsesRequestContext = { req, config, logCtx, options };
|
|
103
|
-
const admissionState: ResponsesAdmissionState = {
|
|
104
|
-
pendingHostAdmissionLease: null,
|
|
105
|
-
authCtx: { kind: "main", accountId: null },
|
|
106
|
-
};
|
|
107
|
-
let releasePendingSend = () => {};
|
|
108
|
-
try {
|
|
109
|
-
const requestState = await prepareResponsesRequest(requestContext, admissionState, requestDispatchers);
|
|
110
|
-
if (requestState instanceof Response) return requestState;
|
|
111
|
-
const transportState = await prepareResponsesTransport(requestContext, admissionState, requestState);
|
|
112
|
-
if (transportState instanceof Response) return transportState;
|
|
113
|
-
options.onCompactionRecoveryRoute?.(requestState.route);
|
|
114
|
-
const sidecarState = await prepareResponsesSidecarAuth(requestContext, requestState, transportState);
|
|
115
|
-
if (sidecarState instanceof Response) return sidecarState;
|
|
116
|
-
const responseEffects = createResponsesEffects(
|
|
117
|
-
requestContext,
|
|
118
|
-
admissionState,
|
|
119
|
-
requestState,
|
|
120
|
-
sidecarState,
|
|
121
|
-
);
|
|
122
|
-
const sendBudgetState = createResponsesSendBudget(requestContext);
|
|
123
|
-
if (sendBudgetState instanceof Response) return sendBudgetState;
|
|
124
|
-
if ("passthrough" in transportState.adapter && transportState.adapter.passthrough && !sidecarState.routedCompaction) {
|
|
125
|
-
const passthroughResult = await executePassthroughResponse(
|
|
126
|
-
requestContext,
|
|
127
|
-
admissionState,
|
|
128
|
-
requestState,
|
|
129
|
-
transportState,
|
|
130
|
-
sidecarState,
|
|
131
|
-
responseEffects,
|
|
132
|
-
sendBudgetState,
|
|
133
|
-
);
|
|
134
|
-
if (passthroughResult instanceof Response) return passthroughResult;
|
|
135
|
-
const unclaimedHop = sendBudgetState.pendingHopPermit;
|
|
136
|
-
releasePendingSend = () => { if (sendBudgetState.pendingHopPermit === unclaimedHop) { unclaimedHop?.release(); sendBudgetState.pendingHopPermit = undefined; } };
|
|
137
|
-
}
|
|
138
|
-
const sidecarPlans = await executeResponsesSidecars(
|
|
139
|
-
requestContext,
|
|
140
|
-
requestState,
|
|
141
|
-
transportState,
|
|
142
|
-
sidecarState,
|
|
143
|
-
responseEffects,
|
|
144
|
-
sendBudgetState,
|
|
145
|
-
);
|
|
146
|
-
if (sidecarPlans instanceof Response) return sidecarPlans;
|
|
147
|
-
const completionPolicy = createResponsesCompletionPolicy(requestContext, sidecarState);
|
|
148
|
-
if (transportState.adapter.runTurn) return await executeResponsesRunTurn(
|
|
149
|
-
requestContext,
|
|
150
|
-
admissionState,
|
|
151
|
-
requestState,
|
|
152
|
-
transportState,
|
|
153
|
-
sidecarState,
|
|
154
|
-
responseEffects,
|
|
155
|
-
sendBudgetState,
|
|
156
|
-
completionPolicy,
|
|
157
|
-
);
|
|
158
|
-
const adapterExchange = await prepareAdapterExchange(
|
|
159
|
-
requestContext,
|
|
160
|
-
admissionState,
|
|
161
|
-
requestState,
|
|
162
|
-
transportState,
|
|
163
|
-
responseEffects,
|
|
164
|
-
sendBudgetState,
|
|
165
|
-
);
|
|
166
|
-
if (adapterExchange instanceof Response) return adapterExchange;
|
|
167
|
-
const continuationState = createAdapterContinuations(
|
|
168
|
-
requestContext,
|
|
169
|
-
requestState,
|
|
170
|
-
transportState,
|
|
171
|
-
sidecarState,
|
|
172
|
-
sendBudgetState,
|
|
173
|
-
adapterExchange,
|
|
174
|
-
);
|
|
175
|
-
return await deliverAdapterResponse(
|
|
176
|
-
requestContext,
|
|
177
|
-
requestState,
|
|
178
|
-
transportState,
|
|
179
|
-
sidecarState,
|
|
180
|
-
responseEffects,
|
|
181
|
-
completionPolicy,
|
|
182
|
-
adapterExchange,
|
|
183
|
-
continuationState,
|
|
184
|
-
);
|
|
185
|
-
} finally {
|
|
186
|
-
releasePendingSend();
|
|
187
|
-
if (admissionState.pendingHostAdmissionLease) {
|
|
188
|
-
releaseUpstreamHostAdmission(admissionState.pendingHostAdmissionLease);
|
|
189
|
-
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
80
|
const requestDispatchers: ResponsesDispatchers = { handleResponses, handleComboResponses };
|
|
194
81
|
export { adapterNeedsForcedContinuation } from "./core-replay";
|
|
195
82
|
export {
|
|
@@ -12,6 +12,7 @@ import type { WsData } from "../ws-bridge";
|
|
|
12
12
|
import {
|
|
13
13
|
requestPacingMaxConcurrentRequests,
|
|
14
14
|
sendTrackingRequestSlot,
|
|
15
|
+
trackProviderRequestSlotBody,
|
|
15
16
|
waitForProviderRequestSlot,
|
|
16
17
|
type ProviderRequestSlot,
|
|
17
18
|
} from "../../providers/request-pacing";
|
|
@@ -49,6 +50,9 @@ const UPSTREAM_REWRITTEN = Symbol.for("opencodex.plugins.upstream-rewritten");
|
|
|
49
50
|
const PHYSICAL_DISPATCH = Symbol.for("opencodex.physical-dispatch");
|
|
50
51
|
// One WS attempt and its HTTP fallback are one logical physical-send receipt.
|
|
51
52
|
const WS_FALLBACK_RECEIPT = Symbol.for("opencodex.ws-fallback-receipt");
|
|
53
|
+
// Replacement admission waits on the client signal, outside the response-header deadline.
|
|
54
|
+
const HEADER_DEADLINE = Symbol("header-deadline");
|
|
55
|
+
type HeaderDeadline = { clientSignal: AbortSignal; pause(): void; resume(): void };
|
|
52
56
|
|
|
53
57
|
/**
|
|
54
58
|
* Announce once, per provider, that an explicit egress route moved this provider off the
|
|
@@ -139,6 +143,7 @@ export function wantsFreshConnection(
|
|
|
139
143
|
export interface PaceAwareFetch {
|
|
140
144
|
waitForPacing?: (signal?: AbortSignal) => Promise<ProviderRequestSlot | undefined>;
|
|
141
145
|
unpacedFetch?: typeof globalThis.fetch;
|
|
146
|
+
fetchWithPacingSlot?: (slot: ProviderRequestSlot | undefined, input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => Promise<Response>;
|
|
142
147
|
}
|
|
143
148
|
|
|
144
149
|
export type ProviderFetch = typeof globalThis.fetch & PaceAwareFetch;
|
|
@@ -252,7 +257,8 @@ export interface ProviderFetchOptions {
|
|
|
252
257
|
*/
|
|
253
258
|
preOutputRefusalHold?: CodexWsPreOutputRefusalHold;
|
|
254
259
|
/** Revalidate/rebuild a queued request at its physical send boundary, after pacing. */
|
|
255
|
-
dispatchOverride?: (input: Parameters<typeof globalThis.fetch>[0], init: RequestInit, execute: typeof globalThis.fetch
|
|
260
|
+
dispatchOverride?: (input: Parameters<typeof globalThis.fetch>[0], init: RequestInit, execute: typeof globalThis.fetch,
|
|
261
|
+
reacquirePacing?: (provider: OcxProviderConfig, modelId?: string) => Promise<void>) => Promise<Response>;
|
|
256
262
|
}
|
|
257
263
|
|
|
258
264
|
/** Compose provider pacing and transport admission; receipts run only at executor entry. */
|
|
@@ -301,7 +307,8 @@ export function providerFetch(
|
|
|
301
307
|
{ preconnect },
|
|
302
308
|
) as typeof globalThis.fetch);
|
|
303
309
|
const httpFetch = Object.assign(
|
|
304
|
-
async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit
|
|
310
|
+
async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit,
|
|
311
|
+
reacquirePacing?: (provider: OcxProviderConfig, modelId?: string) => Promise<void>) => {
|
|
305
312
|
// Refuse before any dispatch side effect where that is sound. `beforeDispatch` commits
|
|
306
313
|
// attempt accounting and consumes admission state, so a refusal firing after it would
|
|
307
314
|
// charge an attempt for a send that never happens, and a throwing hook would mask the
|
|
@@ -329,30 +336,33 @@ export function providerFetch(
|
|
|
329
336
|
} } : {}),
|
|
330
337
|
};
|
|
331
338
|
return options.dispatchOverride
|
|
332
|
-
? options.dispatchOverride(input, dispatchInit, dispatch)
|
|
339
|
+
? options.dispatchOverride(input, dispatchInit, dispatch, reacquirePacing)
|
|
333
340
|
: dispatch(input, dispatchInit);
|
|
334
341
|
},
|
|
335
342
|
{ preconnect },
|
|
336
|
-
)
|
|
343
|
+
);
|
|
337
344
|
// ChatGPT Codex backend: streaming turns ride the responses_websockets
|
|
338
345
|
// transport (measured ~3s faster TTFT than the SSE POST queue); everything
|
|
339
346
|
// else keeps the provider's HTTP fetch. See ws-upstream.ts for the details.
|
|
340
|
-
const unpaced = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit
|
|
347
|
+
const unpaced = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit,
|
|
348
|
+
reacquirePacing?: (provider: OcxProviderConfig, modelId?: string) => Promise<void>) => {
|
|
341
349
|
const upstreamWebsocket = provider.upstreamWebsocket;
|
|
342
350
|
if (!options.httpOnly && typeof input === "string" && init
|
|
343
351
|
&& shouldUseCodexWsUpstream(input, init, runtime, upstreamWebsocket)) {
|
|
344
|
-
|
|
352
|
+
const keyedPacing = provider.requestPacing?.enabled
|
|
353
|
+
&& (provider.requestPacing.keys || provider.requestPacing.keySelection === "round-robin");
|
|
354
|
+
if (keyedPacing || (options.providerName && requestPacingMaxConcurrentRequests(provider, options.modelId) > 0)) {
|
|
345
355
|
if (!pacingWebsocketDowngradeWarned.has(providerName)
|
|
346
356
|
&& pacingWebsocketDowngradeWarned.size < EGRESS_DOWNGRADE_NOTICE_LIMIT) {
|
|
347
357
|
pacingWebsocketDowngradeWarned.add(providerName);
|
|
348
|
-
console.warn(`[opencodex] provider ${JSON.stringify(redactSecretString(providerName))}
|
|
358
|
+
console.warn(`[opencodex] provider ${JSON.stringify(redactSecretString(providerName))} requires guarded request pacing; serving over HTTP/SSE.`);
|
|
349
359
|
}
|
|
350
|
-
return httpFetch(input, init);
|
|
360
|
+
return httpFetch(input, init, reacquirePacing);
|
|
351
361
|
}
|
|
352
362
|
const egress = egressFor(input);
|
|
353
363
|
if (providerEgressIsExplicit(egress)) {
|
|
354
364
|
warnEgressWebsocketDowngradeOnce(providerName, describeProviderEgressForLog(egress));
|
|
355
|
-
return httpFetch(input, init);
|
|
365
|
+
return httpFetch(input, init, reacquirePacing);
|
|
356
366
|
}
|
|
357
367
|
// The fallback has to be the same HTTP fetch the non-WS branch would have
|
|
358
368
|
// used, protocol pin included: a WS turn that falls back is serving the
|
|
@@ -365,11 +375,11 @@ export function providerFetch(
|
|
|
365
375
|
options.onPhysicalDispatch?.();
|
|
366
376
|
};
|
|
367
377
|
const fallback = ((url: Parameters<typeof globalThis.fetch>[0], fallbackInit?: RequestInit) =>
|
|
368
|
-
httpFetch(url, { ...fallbackInit, [WS_FALLBACK_RECEIPT]: notifyOnce } as RequestInit)) as typeof globalThis.fetch;
|
|
378
|
+
httpFetch(url, { ...fallbackInit, [WS_FALLBACK_RECEIPT]: notifyOnce } as RequestInit, reacquirePacing)) as typeof globalThis.fetch;
|
|
369
379
|
return codexWsUpstreamFetch(input, init, fallback, runtime, options.onCodexWsQuota, options.beforeDispatch, options.nativeControl,
|
|
370
380
|
async () => { (await waitForPacing(init.signal ?? undefined))?.release(); }, notifyOnce, options.preOutputRefusalHold);
|
|
371
381
|
}
|
|
372
|
-
return httpFetch(input, init);
|
|
382
|
+
return httpFetch(input, init, reacquirePacing);
|
|
373
383
|
};
|
|
374
384
|
let pacingSlotAcquired = options.pacingSlotAcquired === true;
|
|
375
385
|
const waitForPacing = async (signal?: AbortSignal): Promise<ProviderRequestSlot | undefined> => {
|
|
@@ -392,15 +402,35 @@ export function providerFetch(
|
|
|
392
402
|
throw new Error("A provider name is required for request concurrency pacing");
|
|
393
403
|
}
|
|
394
404
|
};
|
|
395
|
-
const
|
|
396
|
-
|
|
405
|
+
const fetchWithPacingSlot = async (slot: ProviderRequestSlot | undefined, input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
|
|
406
|
+
const reacquirePacing = async (refreshed: OcxProviderConfig, modelId?: string) => {
|
|
407
|
+
if (options.turnScopedPacing) throw new Error("Cannot replace a turn-scoped pacing lease during dispatch");
|
|
408
|
+
if (!options.providerName) throw new Error("Refreshed dispatch requires a named pacing owner");
|
|
409
|
+
slot?.release();
|
|
410
|
+
slot = undefined;
|
|
411
|
+
const deadline = (init as Record<symbol, HeaderDeadline> | undefined)?.[HEADER_DEADLINE];
|
|
412
|
+
deadline?.pause();
|
|
413
|
+
try {
|
|
414
|
+
slot = await waitForProviderRequestSlot(providerName, refreshed, modelId, deadline?.clientSignal ?? init?.signal ?? undefined);
|
|
415
|
+
} finally { deadline?.resume(); }
|
|
416
|
+
};
|
|
417
|
+
try {
|
|
418
|
+
const response = await unpaced(input, init, reacquirePacing);
|
|
419
|
+
return trackProviderRequestSlotBody(slot, response);
|
|
420
|
+
} catch (error) {
|
|
421
|
+
slot?.release();
|
|
422
|
+
throw error;
|
|
423
|
+
}
|
|
397
424
|
};
|
|
425
|
+
const wrapped = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) =>
|
|
426
|
+
fetchWithPacingSlot(await waitForPacing(init?.signal ?? undefined), input, init);
|
|
398
427
|
// The returned wrapper forwards its init down to `dispatch`, which applies the route at the
|
|
399
428
|
// physical send. Adapters that hand this executor back as `provider.fetch` (Cursor does)
|
|
400
429
|
// therefore still carry a per-provider route instead of being refused as opaque.
|
|
401
430
|
const paceAware = Object.assign(wrapped, {
|
|
402
431
|
preconnect,
|
|
403
432
|
waitForPacing,
|
|
433
|
+
fetchWithPacingSlot,
|
|
404
434
|
unpacedFetch: Object.assign(unpaced, { preconnect }),
|
|
405
435
|
});
|
|
406
436
|
markEgressTransparentExecutor(paceAware as unknown as typeof globalThis.fetch);
|
|
@@ -432,6 +462,9 @@ export function storedPoolReplayDispatchNotifier(
|
|
|
432
462
|
notified = true;
|
|
433
463
|
onDispatch();
|
|
434
464
|
};
|
|
465
|
+
const notifyWithSlot = (slot: ProviderRequestSlot | undefined): void => {
|
|
466
|
+
try { notifyOnce(); } catch (error) { slot?.release(); throw error; }
|
|
467
|
+
};
|
|
435
468
|
const unpacedSource = executor.unpacedFetch ?? executor;
|
|
436
469
|
const unpaced = Object.assign(
|
|
437
470
|
(input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
|
|
@@ -441,12 +474,21 @@ export function storedPoolReplayDispatchNotifier(
|
|
|
441
474
|
{ preconnect: unpacedSource.preconnect },
|
|
442
475
|
) as ProviderFetch["unpacedFetch"];
|
|
443
476
|
const wrapped = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
|
|
477
|
+
if (executor.fetchWithPacingSlot) {
|
|
478
|
+
const slot = await executor.waitForPacing?.(init?.signal ?? undefined);
|
|
479
|
+
notifyWithSlot(slot);
|
|
480
|
+
return executor.fetchWithPacingSlot(slot, input, init);
|
|
481
|
+
}
|
|
444
482
|
return sendTrackingRequestSlot(await executor.waitForPacing?.(init?.signal ?? undefined), () => unpaced!(input, init));
|
|
445
483
|
};
|
|
446
484
|
return Object.assign(wrapped, {
|
|
447
485
|
preconnect: executor.preconnect,
|
|
448
486
|
waitForPacing: executor.waitForPacing,
|
|
449
487
|
unpacedFetch: unpaced,
|
|
488
|
+
...(executor.fetchWithPacingSlot ? { fetchWithPacingSlot: (slot: ProviderRequestSlot | undefined, input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
|
|
489
|
+
notifyWithSlot(slot);
|
|
490
|
+
return executor.fetchWithPacingSlot!(slot, input, init);
|
|
491
|
+
} } : {}),
|
|
450
492
|
}) as ProviderFetch;
|
|
451
493
|
}
|
|
452
494
|
|
|
@@ -470,24 +512,36 @@ export async function fetchWithHeaderTimeout(
|
|
|
470
512
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
471
513
|
try {
|
|
472
514
|
const timeout = new AbortController();
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
515
|
+
const deadline: HeaderDeadline = {
|
|
516
|
+
clientSignal: abortSignal,
|
|
517
|
+
pause() { if (timer) clearTimeout(timer); timer = undefined; },
|
|
518
|
+
resume() {
|
|
519
|
+
this.pause();
|
|
520
|
+
timer = setTimeout(() => {
|
|
521
|
+
if (!timeout.signal.aborted) timeout.abort(new DOMException("Timeout elapsed", "TimeoutError"));
|
|
522
|
+
}, timeoutMs);
|
|
523
|
+
},
|
|
524
|
+
};
|
|
525
|
+
deadline.resume();
|
|
476
526
|
const headers = new Headers(init.headers);
|
|
477
527
|
// Compressed SSE can be held until the decompressor has a complete block. Streaming calls
|
|
478
528
|
// default to identity for low-latency frame delivery, while an explicit caller choice wins.
|
|
479
529
|
if (preferIdentityEncoding && !headers.has("accept-encoding")) {
|
|
480
530
|
headers.set("accept-encoding", "identity");
|
|
481
531
|
}
|
|
482
|
-
|
|
532
|
+
const dispatchInit = {
|
|
483
533
|
...init,
|
|
484
534
|
headers,
|
|
485
535
|
// Never replay provider credentials or request bodies to a redirect destination.
|
|
486
536
|
// Preserve the 3xx for the owner's existing response/health policy (#914, #1471).
|
|
487
|
-
redirect: "manual",
|
|
537
|
+
redirect: "manual" as const,
|
|
488
538
|
signal: AbortSignal.any([abortSignal, timeout.signal]),
|
|
539
|
+
[HEADER_DEADLINE]: deadline,
|
|
489
540
|
timeout: 0,
|
|
490
|
-
}
|
|
541
|
+
};
|
|
542
|
+
return pacing.fetchWithPacingSlot
|
|
543
|
+
? await pacing.fetchWithPacingSlot(slot, url, dispatchInit)
|
|
544
|
+
: await sendTrackingRequestSlot(slot, () => fetchExecutor(url, dispatchInit));
|
|
491
545
|
} finally {
|
|
492
546
|
if (timer) clearTimeout(timer);
|
|
493
547
|
if (!slot?.bodyTracked) slot?.release();
|