@bitkyc08/opencodex 2.83.0 → 2.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -4
- package/bin/ocx.mjs +61 -3
- package/gui/dist/assets/App-BjlttVLy.js +58 -0
- package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
- package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
- package/gui/dist/assets/index-BMssP4k3.js +94 -0
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +2 -3
- package/src/adapters/anthropic/beta-allowlist.ts +87 -8
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +33 -4
- package/src/adapters/azure.ts +19 -1
- package/src/adapters/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +15 -8
- package/src/adapters/coding-agent/turn.ts +22 -9
- package/src/adapters/cursor/arg-codec.ts +17 -5
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/freeform-args.ts +75 -0
- package/src/adapters/cursor/protobuf-events.ts +36 -23
- package/src/adapters/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +2 -2
- package/src/adapters/ollama-native.ts +29 -1
- package/src/adapters/openai-chat/messages.ts +8 -4
- package/src/adapters/openai-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- package/src/adapters/openai-responses/empty-assistant.ts +32 -0
- package/src/adapters/openai-responses/passthrough.ts +3 -0
- package/src/adapters/openai-responses/request-strips.ts +10 -2
- package/src/adapters/qoder/adapter.ts +56 -15
- package/src/advisor/consult.ts +220 -0
- package/src/advisor/context.ts +226 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/runtime.ts +305 -0
- package/src/advisor/settings.ts +135 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -0
- package/src/bridge.ts +1 -1
- package/src/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/context-windows.ts +54 -1
- package/src/claude/desktop-3p-library.ts +43 -3
- package/src/claude/desktop-3p.ts +22 -8
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/inbound.ts +121 -50
- package/src/claude/intercept/connect-proxy.ts +10 -3
- package/src/claude/intercept/local-ca.ts +8 -1
- package/src/claude/intercept/picker-ca-persistence.ts +3 -1
- package/src/claude/intercept/picker-listener.ts +49 -1
- package/src/claude/intercept/settings-no-proxy.ts +146 -0
- package/src/claude/intercept/settings.ts +101 -9
- package/src/claude/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +13 -6
- package/src/cli/account-api.ts +65 -0
- package/src/cli/account-auth.ts +26 -2
- package/src/cli/account.ts +41 -5
- package/src/cli/advisor.ts +151 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-access-remote.ts +8 -0
- package/src/cli/capabilities-agents-routing.ts +18 -0
- package/src/cli/capabilities-base.ts +14 -2
- package/src/cli/catalog-command-result.ts +2 -1
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +60 -11
- package/src/cli/config-command.ts +8 -2
- package/src/cli/debug.ts +8 -0
- package/src/cli/dispatch.ts +21 -4
- package/src/cli/doctor.ts +9 -0
- package/src/cli/ensure-desktop-cold-start.ts +329 -0
- package/src/cli/export-command.ts +15 -2
- package/src/cli/help.ts +2 -1
- package/src/cli/hub-disable.ts +99 -0
- package/src/cli/hub.ts +4 -2
- package/src/cli/index.ts +26 -8
- package/src/cli/integration-plan-dto.ts +2 -0
- package/src/cli/link-child.ts +104 -0
- package/src/cli/link-enrollment.ts +96 -0
- package/src/cli/link.ts +39 -24
- package/src/cli/observe-stream.ts +6 -1
- package/src/cli/observe.ts +10 -2
- package/src/cli/provider.ts +12 -0
- package/src/cli/registry.ts +20 -7
- package/src/cli/restart-scope.ts +3 -1
- package/src/cli/runtime-api.ts +27 -2
- package/src/cli/status.ts +104 -20
- package/src/client/connect.ts +39 -4
- package/src/client/hub-client.ts +14 -7
- package/src/client/hub-state.ts +25 -14
- package/src/client/link-home.ts +248 -0
- package/src/client/link-management-relay.ts +61 -0
- package/src/client/link-relay.ts +6 -1
- package/src/client/link-status.ts +14 -0
- package/src/client/machine-api.ts +14 -7
- package/src/client/machine-listener.ts +29 -4
- package/src/clients/config-export/contracts.ts +1 -0
- package/src/clients/config-export/model-metadata.ts +3 -2
- package/src/clients/config-export/qoder.ts +93 -0
- package/src/clients/config-export.ts +59 -16
- package/src/clients/omo-role-models.ts +6 -3
- package/src/codex/agent-role-models.ts +26 -8
- package/src/codex/auth-api/account-list.ts +17 -2
- package/src/codex/auth-api/account-selection.ts +17 -7
- package/src/codex/auth-api/pool-mode-gate.ts +5 -2
- package/src/codex/auth-api/routes.ts +5 -1
- package/src/codex/auth-context.ts +79 -5
- package/src/codex/autostart-health.ts +7 -1
- package/src/codex/catalog/build-entries.ts +11 -2
- package/src/codex/catalog/bundled.ts +5 -1
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +6 -3
- package/src/codex/catalog/metadata.ts +4 -2
- package/src/codex/catalog/model-hints.ts +21 -3
- package/src/codex/catalog/native-models.ts +16 -2
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +5 -0
- package/src/codex/catalog/provider-models.ts +4 -0
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +22 -4
- package/src/codex/catalog/vendor-context.ts +82 -0
- package/src/codex/config-write-lock.ts +40 -2
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desktop-app/types.ts +2 -0
- package/src/codex/desktop-app/windows.ts +29 -16
- package/src/codex/desktop-app-restart.ts +4 -2
- package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
- package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
- package/src/codex/desktop-compatibility/connection-store.ts +87 -0
- package/src/codex/desktop-compatibility/installed-build.ts +55 -0
- package/src/codex/desktop-compatibility/json-body.ts +29 -0
- package/src/codex/desktop-compatibility/native-identity.ts +62 -0
- package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
- package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
- package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
- package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
- package/src/codex/desktop-compatibility/runtime.ts +173 -0
- package/src/codex/desktop-compatibility/service.ts +30 -0
- package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
- package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
- package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
- package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
- package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
- package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
- package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
- package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
- package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
- package/src/codex/desktop-compatibility/windows-key-protection.ts +59 -0
- package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
- package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
- package/src/codex/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/remove.ts +18 -6
- package/src/codex/journal.ts +16 -1
- package/src/codex/main-account.ts +99 -15
- package/src/codex/native-main-refresh-mode.ts +217 -0
- package/src/codex/native-profile-startup.ts +4 -1
- package/src/codex/prompt-journal.ts +20 -2
- package/src/codex/prompt-layers/encoding.ts +49 -0
- package/src/codex/prompt-layers/import-source.ts +30 -0
- package/src/codex/prompt-layers/toml-edit.ts +269 -37
- package/src/codex/prompt-layers/toml-read.ts +52 -4
- package/src/codex/prompt-layers.ts +383 -20
- package/src/codex/prompt-lock-claim.ts +13 -3
- package/src/codex/prompt-lock-evidence.ts +3 -3
- package/src/codex/prompt-lock-owner.ts +14 -6
- package/src/codex/prompt-text-probe.ts +34 -5
- package/src/codex/quota.ts +13 -6
- package/src/codex/role-auto-assign.ts +2 -5
- package/src/codex/routing/active-account.ts +21 -0
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing.ts +1 -1
- package/src/codex/runtime.ts +196 -8
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +15 -6
- package/src/codex/shim.ts +1 -1
- package/src/combos/failover.ts +3 -0
- package/src/config/api-key-reconcile.ts +34 -0
- package/src/config/diagnostics.ts +18 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/live-reconcile.ts +56 -29
- package/src/config/live-topology.ts +29 -0
- package/src/config/load-degrade.ts +8 -2
- package/src/config/paths.ts +13 -3
- package/src/config/salvage.ts +3 -1
- package/src/config/schema/config-schema.ts +19 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +1 -0
- package/src/generated/compatibility-version.json +713 -285
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/grpc-web.ts +23 -1
- package/src/grok/reset-coupon-ledger.ts +75 -5
- package/src/grok/reset-coupons.ts +4 -2
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/config-io.ts +20 -2
- package/src/integrations/journal.ts +15 -1
- package/src/integrations/mutation-plan.ts +14 -7
- package/src/integrations/registry.ts +52 -11
- package/src/integrations/state.ts +19 -7
- package/src/integrations/store.ts +3 -0
- package/src/integrations/target.ts +42 -5
- package/src/integrations/writer.ts +24 -7
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/app-owned-memory-stores.ts +4 -0
- package/src/lib/app-owned-memory.ts +11 -0
- package/src/lib/background-work.ts +65 -0
- package/src/lib/bounded-file-read.ts +58 -0
- package/src/lib/bun-path-runtime.d.mts +10 -1
- package/src/lib/bun-path-runtime.mjs +62 -3
- package/src/lib/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +107 -0
- package/src/lib/errors.ts +7 -0
- package/src/lib/json-byte-size.ts +31 -0
- package/src/lib/local-internal-call-capability.ts +55 -0
- package/src/lib/optional-shutdown-hooks.ts +35 -0
- package/src/lib/outbound-credential-registry.ts +210 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/passive-read.ts +35 -0
- package/src/lib/shadow-call.ts +44 -5
- package/src/lib/socks5-fetch.ts +48 -71
- package/src/lib/socks5-handshake.ts +112 -0
- package/src/lib/translator-budget.ts +7 -1
- package/src/link/codex-settings.ts +155 -0
- package/src/link/management-routes.ts +89 -0
- package/src/link/remote-probe.ts +38 -0
- package/src/link/ssh-argv.ts +26 -5
- package/src/link/ssh-runner.ts +58 -27
- package/src/link/status-projection.ts +22 -4
- package/src/link/supervisor.ts +144 -49
- package/src/link/tunnel-state.ts +11 -4
- package/src/oauth/anthropic-continuity.ts +4 -3
- package/src/oauth/anthropic-identity.ts +13 -2
- package/src/oauth/anthropic-model-quota.ts +13 -1
- package/src/oauth/anthropic-routing.ts +230 -23
- package/src/oauth/anthropic.ts +18 -4
- package/src/oauth/generic-account-failover.ts +29 -5
- package/src/oauth/google-antigravity.ts +24 -7
- package/src/oauth/index.ts +28 -17
- package/src/oauth/kiro-account-identity.ts +106 -0
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +55 -6
- package/src/oauth/plan.ts +29 -0
- package/src/oauth/store.ts +40 -3
- package/src/oauth/types.ts +2 -0
- package/src/plugins/loader.ts +11 -0
- package/src/protocols/codecs/chat-ir.ts +467 -0
- package/src/protocols/codecs/chat.ts +10 -1
- package/src/protocols/codecs/messages-ir.ts +500 -0
- package/src/protocols/features.ts +10 -2
- package/src/protocols/path.ts +11 -1
- package/src/protocols/plan-snapshot.ts +39 -16
- package/src/protocols/plan.ts +9 -1
- package/src/protocols/settings.ts +8 -0
- package/src/providers/anthropic-billing-ownership.ts +74 -0
- package/src/providers/anthropic-billing-recovery.ts +31 -0
- package/src/providers/anthropic-billing-refusal.ts +55 -0
- package/src/providers/anthropic-reset-admission.ts +62 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- package/src/providers/api-key-resolve.ts +17 -2
- package/src/providers/key-failover.ts +52 -5
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/model-rename-migration.ts +5 -3
- package/src/providers/model-rename-startup.ts +5 -3
- package/src/providers/quota/account-cache.ts +11 -0
- package/src/providers/quota/anthropic-account-quota.ts +4 -2
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +3 -3
- package/src/providers/quota.ts +4 -1
- package/src/providers/registry/entries-core.ts +21 -20
- package/src/providers/registry/entries-extended.ts +19 -1
- package/src/providers/registry/model-seeds.ts +17 -0
- package/src/providers/request-pacing.ts +9 -0
- package/src/providers/stale-no-vision-seed-migration.ts +72 -0
- package/src/responses/compaction.ts +14 -0
- package/src/responses/parser.ts +37 -20
- package/src/responses/state.ts +32 -29
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +248 -216
- package/src/routing/history/ingest.ts +238 -0
- package/src/routing/history/rebuild-job.ts +111 -0
- package/src/routing/history/rebuild-protocol.ts +15 -0
- package/src/routing/history/rebuild-worker.ts +34 -0
- package/src/server/admission-model-scope.ts +4 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +46 -0
- package/src/server/chat-completions.ts +75 -9
- package/src/server/claude-messages.ts +110 -20
- package/src/server/configured-credentials.ts +194 -0
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- package/src/server/index/link-management.ts +58 -0
- package/src/server/index/optional-listeners.ts +5 -1
- package/src/server/index/serve-options.ts +15 -2
- package/src/server/index/startup-warnings.ts +9 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +13 -10
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/lifecycle.ts +2 -1
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +191 -0
- package/src/server/management/agent-settings-routes.ts +50 -14
- package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
- package/src/server/management/codex-agent-role-routes.ts +19 -6
- package/src/server/management/codex-prompt-routes.ts +136 -5
- package/src/server/management/codex-role-auto-assign.ts +1 -1
- package/src/server/management/companion-routes.ts +4 -0
- package/src/server/management/config-routes.ts +2 -2
- package/src/server/management/context.ts +4 -0
- package/src/server/management/desktop-compatibility-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
- package/src/server/management/grok-coupon-routes.ts +78 -27
- package/src/server/management/link-enrollment.ts +192 -0
- package/src/server/management/link-route-state.ts +185 -0
- package/src/server/management/link-routes.ts +82 -402
- package/src/server/management/model-routes.ts +2 -2
- package/src/server/management/model-rows.ts +3 -1
- package/src/server/management/protocol-settings-patch.ts +1 -0
- package/src/server/management/provider-overwrite-carry.ts +10 -0
- package/src/server/management/provider-routes.ts +1 -0
- package/src/server/management/route-registry.ts +16 -3
- package/src/server/management/shared.ts +3 -0
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management-api.ts +9 -1
- package/src/server/messages-antigravity-direct.ts +195 -0
- package/src/server/messages-native.ts +73 -43
- package/src/server/relay-eager.ts +2 -0
- package/src/server/request-log-filter.ts +6 -1
- package/src/server/request-log-terminal-status.ts +63 -6
- package/src/server/request-log.ts +7 -0
- package/src/server/responses/adapter-continuation.ts +35 -18
- package/src/server/responses/adapter-delivery.ts +24 -2
- package/src/server/responses/adapter-dispatch.ts +261 -81
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-slot.ts +336 -0
- package/src/server/responses/code-mode-collab-surface.ts +313 -0
- package/src/server/responses/codex-ws-exchange.ts +147 -6
- package/src/server/responses/codex-ws-wire.ts +203 -0
- package/src/server/responses/collaboration.ts +36 -7
- package/src/server/responses/compact.ts +126 -48
- package/src/server/responses/context-overflow.ts +13 -1
- package/src/server/responses/core-codex-account.ts +105 -1
- package/src/server/responses/core-combo-failure.ts +24 -1
- package/src/server/responses/core-combo.ts +28 -2
- package/src/server/responses/core-errors.ts +29 -1
- package/src/server/responses/core-lifetime.ts +2 -0
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-options.ts +48 -0
- package/src/server/responses/core-replay.ts +7 -4
- package/src/server/responses/core.ts +1 -1
- package/src/server/responses/deferred-collab-surface.ts +158 -0
- package/src/server/responses/deferred-send-reporter.ts +25 -0
- package/src/server/responses/fetch-helpers.ts +12 -1
- package/src/server/responses/passthrough-delivery.ts +20 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -7
- package/src/server/responses/request-prepare.ts +74 -9
- package/src/server/responses/request-transport.ts +22 -8
- package/src/server/responses/run-turn-execution.ts +70 -8
- package/src/server/responses/sidecar-execution.ts +89 -5
- package/src/server/responses/terminal-guard.ts +2 -1
- package/src/server/responses/ws-upstream.ts +9 -1
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +38 -4
- package/src/server/startup-health-cache.ts +5 -0
- package/src/server/system-env-shell.ts +7 -2
- package/src/server/system-env.ts +11 -1
- package/src/service/cli.ts +5 -1
- package/src/service/desktop-startup.ts +161 -34
- package/src/service/desktop-supervision.d.mts +27 -1
- package/src/service/desktop-supervision.mjs +111 -5
- package/src/service/health-sentinel-launch.ts +176 -0
- package/src/service/health-sentinel-main.ts +466 -0
- package/src/service/health-sentinel-policy.ts +241 -0
- package/src/service/health-sentinel-probe.ts +71 -0
- package/src/service/health-sentinel-windows.ts +87 -0
- package/src/service/linux-linger.ts +20 -0
- package/src/service/logging.ts +72 -0
- package/src/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +68 -6
- package/src/types/provider.ts +7 -0
- package/src/types/request.ts +15 -0
- package/src/types/tools.ts +2 -0
- package/src/update/async-check.ts +1 -1
- package/src/update/badge.ts +2 -2
- package/src/update/index.ts +88 -15
- package/src/update/job-supervised-restart.ts +107 -0
- package/src/update/job.ts +32 -32
- package/src/update/notify.ts +1 -1
- package/src/update/refresh-scheduler.ts +2 -2
- package/src/update/supervised-recovery.d.mts +27 -0
- package/src/update/supervised-recovery.mjs +157 -0
- package/src/usage/cache-diagnostic.ts +2 -0
- package/src/usage/expected-prices.ts +16 -0
- package/src/usage/log.ts +19 -1
- package/src/web-search/gemini-executor.ts +2 -2
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-Ba8uG696.js +0 -54
- package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
- package/gui/dist/assets/index-BHRZdJuv.js +0 -94
- package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
|
@@ -22,6 +22,8 @@ import type { RequestLogContext } from "../request-log";
|
|
|
22
22
|
import type { UpstreamHostAdmissionLease } from "../../codex/upstream-host-health";
|
|
23
23
|
import type { AccountLease } from "../../oauth/kiro-account-load";
|
|
24
24
|
import type { PolicyRequestScope } from "./policy-request-scope";
|
|
25
|
+
import type { OcxParsedRequest } from "../../types";
|
|
26
|
+
import type { PolicyRequestEvidence } from "../../routing/evaluator";
|
|
25
27
|
|
|
26
28
|
export interface ConsumedComboFailure {
|
|
27
29
|
response: Response;
|
|
@@ -30,6 +32,8 @@ export interface ConsumedComboFailure {
|
|
|
30
32
|
codexModelRefusal?: CodexAccountModelRefusal;
|
|
31
33
|
/** Structured upstream `error.code` when present in the failure body. */
|
|
32
34
|
upstreamCode?: string;
|
|
35
|
+
/** Sanitized structured `error.message`; compared only against proxy-owned constants, never emitted. */
|
|
36
|
+
upstreamMessage?: string;
|
|
33
37
|
/** Complete structured provider type, retained for conservative recovery classification. */
|
|
34
38
|
upstreamType?: string;
|
|
35
39
|
/** Valid numeric/date value used only for cooldown calculation. */
|
|
@@ -59,6 +63,32 @@ export interface ClientEncoderOption {
|
|
|
59
63
|
stream: boolean;
|
|
60
64
|
model: string;
|
|
61
65
|
inputTokenFloor?: number;
|
|
66
|
+
/**
|
|
67
|
+
* `direct` when the request side also skipped the internal Responses body (the Antigravity
|
|
68
|
+
* direct lane), so the attempt's request path is traced as `[inbound, ir, upstream]`.
|
|
69
|
+
*/
|
|
70
|
+
requestLane?: "bridge" | "direct";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The Antigravity direct lane (`protocols.rollout.antigravityDirect`). The ingress sends the core
|
|
75
|
+
* a Responses body that carries only the request settings; the conversation is decoded straight
|
|
76
|
+
* from the client body. Request preparation applies `decode` at the parse seam, reads routing
|
|
77
|
+
* evidence from `evidence`, and calls `restoreBridgeConversation` if the final route is not the
|
|
78
|
+
* one the lane was decided for, so every `_rawBody` reader of another adapter sees the full
|
|
79
|
+
* bridge body.
|
|
80
|
+
*/
|
|
81
|
+
export interface DirectRequestOption {
|
|
82
|
+
protocol: "chat" | "messages";
|
|
83
|
+
/** The concrete route the ingress decided the lane for. */
|
|
84
|
+
providerName: string;
|
|
85
|
+
modelId: string;
|
|
86
|
+
/** Policy-routing evidence from the client body; the settings-only body has no `input`. */
|
|
87
|
+
evidence: PolicyRequestEvidence;
|
|
88
|
+
/** The settings-only parse with the directly decoded conversation in place of its empty one. */
|
|
89
|
+
decode(base: OcxParsedRequest): OcxParsedRequest;
|
|
90
|
+
/** The bridge projection's conversation fields (`input`, `instructions`, `tools`). */
|
|
91
|
+
bridgeConversation(): Record<string, unknown>;
|
|
62
92
|
}
|
|
63
93
|
|
|
64
94
|
export interface HandleResponsesOptions {
|
|
@@ -135,6 +165,13 @@ export interface HandleResponsesOptions {
|
|
|
135
165
|
* it. Omitted means a genuine Responses inbound.
|
|
136
166
|
*/
|
|
137
167
|
inboundWire?: InboundWire;
|
|
168
|
+
/**
|
|
169
|
+
* Set by a translating ingress (Chat / Anthropic) when the client sent an explicit empty
|
|
170
|
+
* tool catalog. Those wires may defer tool delivery, so their translated body carries no
|
|
171
|
+
* readable catalog boundary; an explicit `[]` is still a deny-all declaration and the
|
|
172
|
+
* undeclared-tool guard enforces it. Absent catalogs and partial catalogs stay deferred.
|
|
173
|
+
*/
|
|
174
|
+
clientDeclaredEmptyToolCatalog?: boolean;
|
|
138
175
|
nativeReasoningReplay?: ReadonlyMap<string, string>;
|
|
139
176
|
nativeReasoningMint?: { owner?: NativeReasoningOwner };
|
|
140
177
|
/** Droid's per-request effort default; each concrete combo or policy target applies it only if its ladder allows it. */
|
|
@@ -200,6 +237,15 @@ export interface HandleResponsesOptions {
|
|
|
200
237
|
* rebuilds headers and carries the fact through this flag.
|
|
201
238
|
*/
|
|
202
239
|
visionDescribeTerminal?: boolean;
|
|
240
|
+
/**
|
|
241
|
+
* Terminal advisor marker: true when the inbound request IS the advisor sidecar's own loopback
|
|
242
|
+
* consultation. The advisor planner then never plans a consultation — a depth cap of 1 that
|
|
243
|
+
* keeps Worker → Advisor → Advisor recursion impossible (same structure as
|
|
244
|
+
* {@linkcode visionDescribeTerminal}). The Chat surface detects the raw
|
|
245
|
+
* `x-opencodex-advisor-internal` header before its bridge rebuilds headers and carries the
|
|
246
|
+
* fact through this flag.
|
|
247
|
+
*/
|
|
248
|
+
advisorInternal?: boolean;
|
|
203
249
|
/**
|
|
204
250
|
* Set only by the Chat and Messages ingresses when `protocols.rollout.directEncoders` is on and
|
|
205
251
|
* the settled route is one non-Responses target. Adapter delivery then encodes the adapter
|
|
@@ -208,6 +254,8 @@ export interface HandleResponsesOptions {
|
|
|
208
254
|
* returning a Responses body, which the ingress converts as before.
|
|
209
255
|
*/
|
|
210
256
|
clientEncoder?: ClientEncoderOption;
|
|
257
|
+
/** Set only by the Chat or Messages ingress on an eligible Antigravity route; see {@link DirectRequestOption}. */
|
|
258
|
+
directRequest?: DirectRequestOption;
|
|
211
259
|
}
|
|
212
260
|
|
|
213
261
|
/** Values shared by the call, not a bag of mutable pipeline state. */
|
|
@@ -125,11 +125,13 @@ export function bindRouteReasoningReplayScope(args: {
|
|
|
125
125
|
if (owner) parsed._nativeReasoningBoundOwner = owner;
|
|
126
126
|
else delete parsed._nativeReasoningBoundOwner;
|
|
127
127
|
if (parsed._nativeReasoningReplay?.size) {
|
|
128
|
+
const rejected = new Set<string>();
|
|
128
129
|
for (const [blob, tag] of parsed._nativeReasoningReplay) {
|
|
129
130
|
if (!nativeReasoningTagMatches(nativeReasoningTag(owner, blob), tag)) {
|
|
130
|
-
|
|
131
|
+
rejected.add(blob);
|
|
131
132
|
}
|
|
132
133
|
}
|
|
134
|
+
if (rejected.size) stripNativeReasoningBlobs(parsed, rejected);
|
|
133
135
|
}
|
|
134
136
|
const continuationDestinationIdentity = providerContinuationDestinationIdentity(parsed, provider);
|
|
135
137
|
const continuationOwner = providerContinuationOwnerFromReplayIdentity(
|
|
@@ -169,11 +171,11 @@ export function bindRouteReasoningReplayScope(args: {
|
|
|
169
171
|
bindProviderContinuationForRoute(parsed, continuationOwner);
|
|
170
172
|
}
|
|
171
173
|
|
|
172
|
-
function
|
|
174
|
+
function stripNativeReasoningBlobs(parsed: OcxParsedRequest, rejected: ReadonlySet<string>): void {
|
|
173
175
|
const input = (parsed._rawBody as { input?: unknown } | undefined)?.input;
|
|
174
176
|
if (Array.isArray(input)) {
|
|
175
177
|
for (const item of input) {
|
|
176
|
-
if (item && typeof item === "object" && item.type === "reasoning" && item.encrypted_content
|
|
178
|
+
if (item && typeof item === "object" && item.type === "reasoning" && rejected.has(item.encrypted_content)) {
|
|
177
179
|
delete item.encrypted_content;
|
|
178
180
|
delete item.id;
|
|
179
181
|
}
|
|
@@ -187,7 +189,8 @@ function stripNativeReasoningBlob(parsed: OcxParsedRequest, blob: string): void
|
|
|
187
189
|
const item: unknown = JSON.parse(part.signature);
|
|
188
190
|
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
189
191
|
const reasoning = item as Record<string, unknown>;
|
|
190
|
-
if (reasoning.type !== "reasoning" || reasoning.encrypted_content !==
|
|
192
|
+
if (reasoning.type !== "reasoning" || typeof reasoning.encrypted_content !== "string"
|
|
193
|
+
|| !rejected.has(reasoning.encrypted_content)) continue;
|
|
191
194
|
delete reasoning.encrypted_content;
|
|
192
195
|
delete reasoning.id;
|
|
193
196
|
part.signature = JSON.stringify(reasoning);
|
|
@@ -48,7 +48,7 @@ export async function handleResponses(
|
|
|
48
48
|
abortSignal.addEventListener("abort", cancel, { once: true });
|
|
49
49
|
try {
|
|
50
50
|
const response = await runWithCompactionRecovery(req, config, logCtx, {
|
|
51
|
-
...options,
|
|
51
|
+
...options, abortSignal,
|
|
52
52
|
openAiSidecarAuth: options.openAiSidecarAuth === undefined
|
|
53
53
|
? captureExplicitOpenAiCallerAuth(req.headers, config) : options.openAiSidecarAuth,
|
|
54
54
|
nativeCallerAuth: options.nativeCallerAuth === undefined
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deferred V1 collaboration evidence from Codex's `<tools>` world-state fragment (#6844).
|
|
3
|
+
*
|
|
4
|
+
* When the model supports tool search (every OpenCodex catalog row), codex-rs registers the V1
|
|
5
|
+
* collaboration tools as DEFERRED (core/src/tools/spec_plan.rs, add_collaboration_tools) under the
|
|
6
|
+
* fixed namespace `multi_agent_v1` (core/src/tools/handlers/multi_agents_spec.rs). Neither the wire
|
|
7
|
+
* tools nor the code-mode exec description then mention them. With the deferred_tool_world_state
|
|
8
|
+
* feature on (default off upstream; Codex Desktop sessions were observed receiving it), codex-rs
|
|
9
|
+
* instead tells the model which namespaces it can load, in a developer fragment
|
|
10
|
+
* (core/src/context/world_state/tools.rs):
|
|
11
|
+
*
|
|
12
|
+
* <tools>
|
|
13
|
+
* Deferred tool namespaces:
|
|
14
|
+
* - multi_agent_v1: Tools for spawning and managing sub-agents.
|
|
15
|
+
* </tools>
|
|
16
|
+
*
|
|
17
|
+
* Later turns carry only diffs ("Added deferred tool namespaces:", "Removed deferred tool
|
|
18
|
+
* namespaces:", "No deferred tool namespaces remain."), so membership is replayed in input order.
|
|
19
|
+
* The fragment is capped at 4 KiB: rows may be name-only, and whole rows may be replaced by
|
|
20
|
+
* "... <n> additional namespaces omitted." (tools_budget.rs).
|
|
21
|
+
*
|
|
22
|
+
* The evidence is positive-only and conservative:
|
|
23
|
+
* - only developer messages are read, each text part on its own, and only a part that is exactly
|
|
24
|
+
* one fragment after trimming; text that merely mentions the tag is ignored;
|
|
25
|
+
* - a fragment that starts like one but breaks the line grammar clears what was carried, because a
|
|
26
|
+
* lost removal could have been multi_agent_v1; omitted removals clear for the same reason, and
|
|
27
|
+
* omitted rows never establish a namespace;
|
|
28
|
+
* - it counts only beside a host that can load deferred tools: a genuine code-mode exec or a
|
|
29
|
+
* structured tool_search tool. A caller's own exec or a function merely named tool_search is not
|
|
30
|
+
* one.
|
|
31
|
+
*
|
|
32
|
+
* Capability boundary: the row proves that some deferred multi_agent_v1 tool is registered, not
|
|
33
|
+
* that spawn_agent is. Upstream can split them only through an embedder-supplied ToolPolicy
|
|
34
|
+
* allowlist; its one production allowlist (the guardian reviewer) admits no V1 tool at all, and
|
|
35
|
+
* the V1 depth limit disables the surface as a unit, so Codex CLI and Desktop never send a
|
|
36
|
+
* management-only V1 namespace. A developer item can also be impersonated by whoever writes the
|
|
37
|
+
* conversation. Both cases receive advisory guidance they cannot act on; nothing is enforced here.
|
|
38
|
+
*/
|
|
39
|
+
import type { OcxMessage, OcxParsedRequest, OcxTool } from "../../types";
|
|
40
|
+
import type { CodeModeExecAnalysis } from "./code-mode-collab-surface";
|
|
41
|
+
|
|
42
|
+
const MULTI_AGENT_V1_NAMESPACE = "multi_agent_v1";
|
|
43
|
+
const OPEN_TAG = "<tools>";
|
|
44
|
+
const CLOSE_TAG = "</tools>";
|
|
45
|
+
const SNAPSHOT_HEADER = "Deferred tool namespaces:";
|
|
46
|
+
const ADDED_HEADER = "Added deferred tool namespaces:";
|
|
47
|
+
const REMOVED_HEADER = "Removed deferred tool namespaces:";
|
|
48
|
+
const EMPTY_STATE = "No deferred tool namespaces remain.";
|
|
49
|
+
// A description is opaque: upstream copies its first line verbatim, which may hold any character
|
|
50
|
+
// except \n, including U+2028/U+2029 that `.` would refuse.
|
|
51
|
+
const ROW = /^- ([^:\s]+)(?:: [\s\S]*)?$/;
|
|
52
|
+
const OMISSION = /^\.\.\. \d+ additional namespaces omitted\.$/;
|
|
53
|
+
|
|
54
|
+
type Fragment =
|
|
55
|
+
| { kind: "malformed" }
|
|
56
|
+
| { kind: "snapshot"; rows: Set<string>; empty: boolean }
|
|
57
|
+
| { kind: "diff"; added: Set<string>; removed: Set<string>; removedOmitted: boolean; empty: boolean };
|
|
58
|
+
|
|
59
|
+
/** One standalone fragment, a malformed one, or nothing (text that is not a fragment). */
|
|
60
|
+
function parseFragment(text: string): Fragment | null {
|
|
61
|
+
const trimmed = text.trim();
|
|
62
|
+
if (!trimmed.startsWith(OPEN_TAG)) return null;
|
|
63
|
+
if (!trimmed.endsWith(CLOSE_TAG) || trimmed.length < OPEN_TAG.length + CLOSE_TAG.length) return { kind: "malformed" };
|
|
64
|
+
const body = trimmed.slice(OPEN_TAG.length, trimmed.length - CLOSE_TAG.length);
|
|
65
|
+
let group: "snapshot" | "added" | "removed" | undefined;
|
|
66
|
+
// Upstream renders each group at most once, Added before Removed, and skips empty groups.
|
|
67
|
+
const seen = new Set<"snapshot" | "added" | "removed">();
|
|
68
|
+
let groupHasEntries = true;
|
|
69
|
+
let empty = false;
|
|
70
|
+
const rows = { snapshot: new Set<string>(), added: new Set<string>(), removed: new Set<string>() };
|
|
71
|
+
let removedOmitted = false;
|
|
72
|
+
const open = (next: "snapshot" | "added" | "removed"): boolean => {
|
|
73
|
+
if (seen.has(next) || !groupHasEntries) return false;
|
|
74
|
+
if (next === "added" && seen.has("removed")) return false;
|
|
75
|
+
seen.add(next);
|
|
76
|
+
group = next;
|
|
77
|
+
groupHasEntries = false;
|
|
78
|
+
return true;
|
|
79
|
+
};
|
|
80
|
+
for (const raw of body.split("\n")) {
|
|
81
|
+
const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
|
|
82
|
+
if (line === "") continue;
|
|
83
|
+
// The empty-state notice closes the fragment; nothing may follow it.
|
|
84
|
+
if (empty) return { kind: "malformed" };
|
|
85
|
+
if (line === SNAPSHOT_HEADER) { if (!open("snapshot")) return { kind: "malformed" }; continue; }
|
|
86
|
+
if (line === ADDED_HEADER) { if (!open("added")) return { kind: "malformed" }; continue; }
|
|
87
|
+
if (line === REMOVED_HEADER) { if (!open("removed")) return { kind: "malformed" }; continue; }
|
|
88
|
+
if (line === EMPTY_STATE) {
|
|
89
|
+
if (!groupHasEntries) return { kind: "malformed" };
|
|
90
|
+
empty = true;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (group === undefined) return { kind: "malformed" };
|
|
94
|
+
groupHasEntries = true;
|
|
95
|
+
if (OMISSION.test(line)) {
|
|
96
|
+
if (group === "removed") removedOmitted = true;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const row = ROW.exec(line);
|
|
100
|
+
if (!row) return { kind: "malformed" };
|
|
101
|
+
rows[group].add(row[1]!);
|
|
102
|
+
}
|
|
103
|
+
if (!groupHasEntries) return { kind: "malformed" };
|
|
104
|
+
const sawSnapshot = seen.has("snapshot");
|
|
105
|
+
const sawDiff = seen.has("added") || seen.has("removed");
|
|
106
|
+
if (sawSnapshot && sawDiff) return { kind: "malformed" };
|
|
107
|
+
if (sawSnapshot) return { kind: "snapshot", rows: rows.snapshot, empty };
|
|
108
|
+
if (!sawDiff && !empty) return { kind: "malformed" };
|
|
109
|
+
for (const name of rows.added) if (rows.removed.has(name)) return { kind: "malformed" };
|
|
110
|
+
return { kind: "diff", added: rows.added, removed: rows.removed, removedOmitted, empty };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function developerTexts(message: OcxMessage): string[] {
|
|
114
|
+
if (message.role !== "developer") return [];
|
|
115
|
+
if (typeof message.content === "string") return [message.content];
|
|
116
|
+
return message.content.flatMap(part => (part.type === "text" ? [part.text] : []));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Replays every fragment in input order; true when multi_agent_v1 is currently known deferred. */
|
|
120
|
+
function deferredNamespacesIncludeV1(messages: readonly OcxMessage[]): boolean {
|
|
121
|
+
const current = new Set<string>();
|
|
122
|
+
for (const message of messages) {
|
|
123
|
+
for (const text of developerTexts(message)) {
|
|
124
|
+
const fragment = parseFragment(text);
|
|
125
|
+
if (fragment === null) continue;
|
|
126
|
+
switch (fragment.kind) {
|
|
127
|
+
case "malformed":
|
|
128
|
+
current.clear();
|
|
129
|
+
break;
|
|
130
|
+
case "snapshot":
|
|
131
|
+
current.clear();
|
|
132
|
+
for (const name of fragment.rows) current.add(name);
|
|
133
|
+
break;
|
|
134
|
+
case "diff":
|
|
135
|
+
if (fragment.removedOmitted) current.clear();
|
|
136
|
+
for (const name of fragment.added) current.add(name);
|
|
137
|
+
for (const name of fragment.removed) current.delete(name);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
if (fragment.kind !== "malformed" && fragment.empty) current.clear();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return current.has(MULTI_AGENT_V1_NAMESPACE);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function declaresStructuredToolSearch(tools: readonly OcxTool[] | undefined): boolean {
|
|
147
|
+
return (tools ?? []).some(tool => !tool.namespace && tool.toolSearch === true);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* True when the request's own developer history says V1 collaboration tools are deferred and the
|
|
152
|
+
* request comes from a host that can load deferred tools.
|
|
153
|
+
*/
|
|
154
|
+
export function deferredV1Evidence(parsed: OcxParsedRequest, codeMode: CodeModeExecAnalysis): boolean {
|
|
155
|
+
if (!codeMode.genuineExec && !declaresStructuredToolSearch(parsed.context.tools)) return false;
|
|
156
|
+
return deferredNamespacesIncludeV1(parsed.context.messages);
|
|
157
|
+
}
|
|
158
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PhysicalSendReporter } from "../../lib/request-execution-budget";
|
|
2
|
+
|
|
3
|
+
/** Retry thunks can fail in pacing; settle only starts admitted at the physical executor. */
|
|
4
|
+
export function deferPhysicalSendReporter(physical: PhysicalSendReporter) {
|
|
5
|
+
let dispatched = 0;
|
|
6
|
+
let reported = 0;
|
|
7
|
+
const report: PhysicalSendReporter = () => {
|
|
8
|
+
const count = dispatched - reported;
|
|
9
|
+
if (count <= 0) return;
|
|
10
|
+
physical(count);
|
|
11
|
+
reported += count;
|
|
12
|
+
};
|
|
13
|
+
// The retry helper owns attempts, while the physical callback owns spend admission.
|
|
14
|
+
report.beforeSend = () => true;
|
|
15
|
+
report.execute = physical.execute;
|
|
16
|
+
report.close = () => { report(0); physical.close?.(); };
|
|
17
|
+
return {
|
|
18
|
+
report,
|
|
19
|
+
beforeDispatch(): boolean {
|
|
20
|
+
if (physical.beforeSend && !physical.beforeSend()) return false;
|
|
21
|
+
dispatched++;
|
|
22
|
+
return true;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { NativeResponseControl } from "./native-response-control";
|
|
2
|
+
import type { CodexWsPreOutputRefusalHold } from "./codex-ws-wire";
|
|
2
3
|
import type { Server } from "bun";
|
|
3
4
|
import {
|
|
4
5
|
codexWsUpstreamFetch,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
type ProviderEgressBinding,
|
|
29
30
|
} from "../../lib/provider-egress";
|
|
30
31
|
import { redactSecretString } from "../../lib/redact";
|
|
32
|
+
import { outboundCredentials } from "../../lib/outbound-credential-registry";
|
|
31
33
|
|
|
32
34
|
export { withUpstreamHttpVersion };
|
|
33
35
|
|
|
@@ -205,6 +207,9 @@ export function sendWithConnectionPolicy(
|
|
|
205
207
|
&& /^https:\/\/chatgpt\.com\/backend-api\/codex\/responses(?:\/compact)?$/.test(target)
|
|
206
208
|
&& Buffer.byteLength(body, "utf8") >= 1024 * 1024;
|
|
207
209
|
(init as Record<symbol, (() => void) | undefined> | undefined)?.[PHYSICAL_DISPATCH]?.();
|
|
210
|
+
// Final headers, after plugin rewrites: an upstream can only echo what it received here, so the
|
|
211
|
+
// request log refuses any diagnostic that repeats one of these values (see request-log-terminal-status).
|
|
212
|
+
outboundCredentials().remember(headers);
|
|
208
213
|
return physicalFetch(input, {
|
|
209
214
|
...init,
|
|
210
215
|
...(largeCodexBody ? { body: Buffer.from(body, "utf8") } : {}),
|
|
@@ -240,6 +245,12 @@ export interface ProviderFetchOptions {
|
|
|
240
245
|
beforeDispatch?: (headers: Headers) => void;
|
|
241
246
|
/** One-shot receipt after final local HTTP admission, immediately before executor entry. */
|
|
242
247
|
onPhysicalDispatch?: () => void;
|
|
248
|
+
/**
|
|
249
|
+
* Opt-in (#6969): let a Codex WebSocket send hold empty lifecycle frames so an authoritative
|
|
250
|
+
* upstream refusal before any output settles as a replayable gateway status. Only a caller that
|
|
251
|
+
* restores the projection afterwards (restoreCodexWsPreOutputRefusal) may set it.
|
|
252
|
+
*/
|
|
253
|
+
preOutputRefusalHold?: CodexWsPreOutputRefusalHold;
|
|
243
254
|
/** Revalidate/rebuild a queued request at its physical send boundary, after pacing. */
|
|
244
255
|
dispatchOverride?: (input: Parameters<typeof globalThis.fetch>[0], init: RequestInit, execute: typeof globalThis.fetch) => Promise<Response>;
|
|
245
256
|
}
|
|
@@ -356,7 +367,7 @@ export function providerFetch(
|
|
|
356
367
|
const fallback = ((url: Parameters<typeof globalThis.fetch>[0], fallbackInit?: RequestInit) =>
|
|
357
368
|
httpFetch(url, { ...fallbackInit, [WS_FALLBACK_RECEIPT]: notifyOnce } as RequestInit)) as typeof globalThis.fetch;
|
|
358
369
|
return codexWsUpstreamFetch(input, init, fallback, runtime, options.onCodexWsQuota, options.beforeDispatch, options.nativeControl,
|
|
359
|
-
async () => { (await waitForPacing(init.signal ?? undefined))?.release(); }, notifyOnce);
|
|
370
|
+
async () => { (await waitForPacing(init.signal ?? undefined))?.release(); }, notifyOnce, options.preOutputRefusalHold);
|
|
360
371
|
}
|
|
361
372
|
return httpFetch(input, init);
|
|
362
373
|
};
|
|
@@ -108,6 +108,7 @@ import { createResponsesFunctionToolRepairBlockRewrite } from "../responses-func
|
|
|
108
108
|
import {
|
|
109
109
|
createUndeclaredToolCallGuardBlockRewrite,
|
|
110
110
|
currentTurnWireToolCatalogBody,
|
|
111
|
+
UNDECLARED_TOOL_CALL_ERROR_CODE,
|
|
111
112
|
undeclaredToolCallNameInResponse,
|
|
112
113
|
undeclaredToolCallMessage,
|
|
113
114
|
normalizeDefaultNamespaceInJson,
|
|
@@ -408,6 +409,7 @@ export async function deliverPassthroughResponse(
|
|
|
408
409
|
| "noteInspectedPayload"
|
|
409
410
|
| "normalizeFunctionCompletionJson"
|
|
410
411
|
| "localUpstream"
|
|
412
|
+
| "terminalOutcomeRecorded"
|
|
411
413
|
>,
|
|
412
414
|
): Promise<Response> {
|
|
413
415
|
const { route } = requestState;
|
|
@@ -436,8 +438,7 @@ export async function deliverPassthroughResponse(
|
|
|
436
438
|
normalizeFunctionCompletionJson,
|
|
437
439
|
} = nativeExchange;
|
|
438
440
|
const { commitReasoningReplayServingRoute, recordTerminalOutcomes } = responseEffects;
|
|
439
|
-
const { parsed, subagentQuotaFailureModel, clientRequestedStream, translatorBudget
|
|
440
|
-
const enforceDeclaredToolNames = inboundWire !== "chat" && inboundWire !== "anthropic";
|
|
441
|
+
const { parsed, subagentQuotaFailureModel, clientRequestedStream, translatorBudget } = requestState;
|
|
441
442
|
const { openAiSidecar } = sidecarState;
|
|
442
443
|
const { requestBindings } = transportState;
|
|
443
444
|
|
|
@@ -486,7 +487,9 @@ export async function deliverPassthroughResponse(
|
|
|
486
487
|
? (status: ResponsesTerminalStatus, httpStatusOverride?: number): void => {
|
|
487
488
|
if (terminalOutcomeRecorded) return;
|
|
488
489
|
terminalOutcomeRecorded = true;
|
|
489
|
-
|
|
490
|
+
// A restored pre-output refusal whose account outcome dispatch already wrote (#6969) keeps
|
|
491
|
+
// every other terminal callback but must not write the same refusal a second time.
|
|
492
|
+
if (!nativeExchange.terminalOutcomeRecorded) recordTerminalOutcome(status, httpStatusOverride);
|
|
490
493
|
}
|
|
491
494
|
: undefined;
|
|
492
495
|
const terminalBodyWillRecord = !!terminalRecorder && upstreamResponse.ok && isEventStream;
|
|
@@ -854,14 +857,17 @@ export async function deliverPassthroughResponse(
|
|
|
854
857
|
: undefined,
|
|
855
858
|
// Last: every rewrite above can still rename or reshape a call item, so the guard must
|
|
856
859
|
// compare the names the client will actually receive against the declared catalog.
|
|
857
|
-
|
|
860
|
+
// Deferred-catalog wires keep the rewrite for its normalization half only: a provider's
|
|
861
|
+
// `default.X` call still collapses to the declared `X`, but an undeclared name is the
|
|
862
|
+
// client's own deferred registry's business, not this proxy's verdict.
|
|
863
|
+
nativeExchange.undeclaredToolGuardActive || declaredWireToolNames.size > 0
|
|
858
864
|
? createUndeclaredToolCallGuardBlockRewrite(
|
|
859
865
|
declaredWireToolNames,
|
|
860
866
|
declaredNamelessClientCallTypes,
|
|
861
867
|
providerExecutedCallTypes,
|
|
862
868
|
declaredBareWireToolNames,
|
|
863
869
|
recoverableBareCustomWireToolNames,
|
|
864
|
-
|
|
870
|
+
{ refuseUndeclared: nativeExchange.undeclaredToolGuardActive },
|
|
865
871
|
)
|
|
866
872
|
: undefined,
|
|
867
873
|
grokUpstreamEchoEnabled
|
|
@@ -1319,10 +1325,7 @@ export async function deliverPassthroughResponse(
|
|
|
1319
1325
|
if (grokUpstreamEchoEnabled) {
|
|
1320
1326
|
clientJson = stripGrokUpstreamEnvelopeEchoFromResponsesJson(clientJson);
|
|
1321
1327
|
}
|
|
1322
|
-
//
|
|
1323
|
-
// the reframed-SSE branch below are built from this body, so one check covers them. This
|
|
1324
|
-
// runs BEFORE the continuation cache write below: a refused turn must not become state a
|
|
1325
|
-
// later `previous_response_id` replay can expand from.
|
|
1328
|
+
// Both response formats use this body, before a refused turn can enter continuation state.
|
|
1326
1329
|
if (nativeExchange.undeclaredToolGuardActive) {
|
|
1327
1330
|
const undeclared = (() => {
|
|
1328
1331
|
try {
|
|
@@ -1338,9 +1341,15 @@ export async function deliverPassthroughResponse(
|
|
|
1338
1341
|
return undefined;
|
|
1339
1342
|
}
|
|
1340
1343
|
})();
|
|
1341
|
-
if (
|
|
1342
|
-
return formatErrorResponse(502, "upstream_error", undeclaredToolCallMessage(undeclared)
|
|
1344
|
+
if (undeclared !== undefined) {
|
|
1345
|
+
return formatErrorResponse(502, "upstream_error", undeclaredToolCallMessage(undeclared), {
|
|
1346
|
+
code: UNDECLARED_TOOL_CALL_ERROR_CODE,
|
|
1347
|
+
});
|
|
1343
1348
|
}
|
|
1349
|
+
}
|
|
1350
|
+
// Same split as the SSE rewrite: normalization applies whenever a declared name exists,
|
|
1351
|
+
// even on wires whose deferred catalog stands the refusal down.
|
|
1352
|
+
if (declaredWireToolNames.size > 0) {
|
|
1344
1353
|
clientJson = normalizeDefaultNamespaceInJson(
|
|
1345
1354
|
clientJson,
|
|
1346
1355
|
declaredWireToolNames,
|
|
@@ -21,6 +21,7 @@ import { imageGenToolCallAliases } from "../responses-image-gen-repair";
|
|
|
21
21
|
import { rememberResponseState, isBodyNonPersistable } from "../../responses/state";
|
|
22
22
|
import {
|
|
23
23
|
currentTurnWireToolCatalogBody,
|
|
24
|
+
hasExplicitEmptyToolCatalog,
|
|
24
25
|
hasExplicitWireToolCatalog,
|
|
25
26
|
collectDeclaredWireToolNames,
|
|
26
27
|
collectDeclaredBareWireToolNames,
|
|
@@ -91,7 +92,7 @@ import {
|
|
|
91
92
|
safeHostLabel,
|
|
92
93
|
} from "./fetch-helpers";
|
|
93
94
|
import { classifyPoolRecoveryDispatch } from "../../routing/probe-lease";
|
|
94
|
-
import { clientCancelledResponse } from "./core-errors";
|
|
95
|
+
import { anthropicPoolCooldownResponse, clientCancelledResponse } from "./core-errors";
|
|
95
96
|
import {
|
|
96
97
|
upstreamHostCircuitOpenResponse,
|
|
97
98
|
usesCodexForwardPoolAuth,
|
|
@@ -101,6 +102,8 @@ import {
|
|
|
101
102
|
shouldRetryCodexPoolAccountQuota,
|
|
102
103
|
shouldRetryCodexPoolAccountTransient,
|
|
103
104
|
retryCodexPoolOnAlternateAccount,
|
|
105
|
+
PoolPreOutputRefusals,
|
|
106
|
+
poolPreOutputRefusalHold,
|
|
104
107
|
} from "./core-codex-account";
|
|
105
108
|
import {
|
|
106
109
|
clearCodexModelDenialEvidence,
|
|
@@ -185,6 +188,7 @@ export async function preparePassthroughExchange(
|
|
|
185
188
|
| "selectedForwardHeaders"
|
|
186
189
|
| "clientRequestedStream"
|
|
187
190
|
| "inboundWire"
|
|
191
|
+
| "clientDeclaredEmptyToolCatalog"
|
|
188
192
|
| "substituteMainCredential"
|
|
189
193
|
| "callerAuthHeaders"
|
|
190
194
|
| "subagentFallbackAccountId"
|
|
@@ -251,6 +255,7 @@ export async function preparePassthroughExchange(
|
|
|
251
255
|
responseStateOptions,
|
|
252
256
|
clientRequestedStream,
|
|
253
257
|
inboundWire,
|
|
258
|
+
clientDeclaredEmptyToolCatalog,
|
|
254
259
|
substituteMainCredential,
|
|
255
260
|
callerAuthHeaders,
|
|
256
261
|
} = requestState;
|
|
@@ -529,10 +534,18 @@ export async function preparePassthroughExchange(
|
|
|
529
534
|
declaredWireToolNames.add(name);
|
|
530
535
|
}
|
|
531
536
|
}
|
|
537
|
+
// Chat and Anthropic inbounds may deliver tools deferred/partially: their client registry
|
|
538
|
+
// owns the full catalog, so this guard cannot read the translated body as the authorization
|
|
539
|
+
// boundary. An explicit empty catalog — flagged by the translating ingress or present
|
|
540
|
+
// verbatim in a replayed body — is still a deny-all the caller declared; everything else
|
|
541
|
+
// stays deferred to the caller's own tool handling.
|
|
532
542
|
undeclaredToolGuardActive = (
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
543
|
+
inboundWire === "responses"
|
|
544
|
+
? declaredWireToolNames.size > 0
|
|
545
|
+
|| clientDeclaredNamelessCallTypes.size > 0
|
|
546
|
+
|| clientExplicitWireToolCatalog
|
|
547
|
+
: clientDeclaredEmptyToolCatalog
|
|
548
|
+
|| hasExplicitEmptyToolCatalog(clientToolAuthorizationBody)
|
|
536
549
|
) && route.provider.authMode !== "forward";
|
|
537
550
|
options.nativeControl?.configureToolAuthorization?.(
|
|
538
551
|
undeclaredToolGuardActive,
|
|
@@ -949,8 +962,7 @@ export async function preparePassthroughExchange(
|
|
|
949
962
|
return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(refusal));
|
|
950
963
|
}
|
|
951
964
|
if (refusal instanceof AnthropicAccountCooldownError) {
|
|
952
|
-
return
|
|
953
|
-
refusal.retryAfterSeconds === null ? undefined : { retryAfter: String(refusal.retryAfterSeconds) });
|
|
965
|
+
return anthropicPoolCooldownResponse(refusal, { comboAttempt: options.comboAttempt, logCtx });
|
|
954
966
|
}
|
|
955
967
|
return formatErrorResponse(403, "permission_error", publicOAuthAuthenticationErrorMessage(refusal));
|
|
956
968
|
}
|
|
@@ -1005,6 +1017,9 @@ export async function preparePassthroughExchange(
|
|
|
1005
1017
|
// Stable per request: the Combo booking (`reserveDispatch`) already started and froze the spend
|
|
1006
1018
|
// policy, so `spendEnforced` cannot change before dispatch even though no reporter starts it here.
|
|
1007
1019
|
const receiptMode = !sendBudgetState.adapterSendBudget?.spendEnforced && Boolean(options.comboInitialSend);
|
|
1020
|
+
// #6969: Pool sends hold empty lifecycle frames so a pre-output refusal reaches the ladder below.
|
|
1021
|
+
const preOutputRefusals = new PoolPreOutputRefusals();
|
|
1022
|
+
const preOutputRefusalHold = () => poolPreOutputRefusalHold(admissionState.authCtx, route.provider, connectMs, options.comboAttempt);
|
|
1008
1023
|
try {
|
|
1009
1024
|
// Transient-5xx pre-stream retry (devlog/_plan/260716_claudecode_hardening/010):
|
|
1010
1025
|
// the ChatGPT backend emits transient 502/520s that an immediate retry absorbs.
|
|
@@ -1035,11 +1050,12 @@ export async function preparePassthroughExchange(
|
|
|
1035
1050
|
onCodexWsQuota: codexWsQuotaObserver(admissionState.authCtx, route.provider, route.modelId),
|
|
1036
1051
|
beforeDispatch: isCanonicalOpenAiForwardProvider(route.provider)
|
|
1037
1052
|
? createCodexAuthDispatchGuard(admissionState.authCtx, options.codexAuthPolicy ?? config, route.modelId, options.admission, options.visionDescribeTerminal === true) : undefined,
|
|
1053
|
+
preOutputRefusalHold: preOutputRefusalHold(),
|
|
1038
1054
|
}),
|
|
1039
1055
|
route.provider.authMode === "forward")
|
|
1040
1056
|
// Every real attempt response — including an intermediate 5xx the
|
|
1041
1057
|
// retry wrapper replaces — proves the host was reached (#914 review).
|
|
1042
|
-
.then(adoptObservedResponse);
|
|
1058
|
+
.then(adoptObservedResponse).then(preOutputRefusals.observe);
|
|
1043
1059
|
},
|
|
1044
1060
|
{ abortSignal: upstream.signal, label: safeHostLabel(request.url),
|
|
1045
1061
|
attempts: sendBudgetState.initialSendAllowance(transientSendAttempts()),
|
|
@@ -1050,6 +1066,8 @@ export async function preparePassthroughExchange(
|
|
|
1050
1066
|
},
|
|
1051
1067
|
);
|
|
1052
1068
|
} catch (err) {
|
|
1069
|
+
preOutputRefusals.recordOnLocalExit(err, options.abortSignal?.aborted === true || req.signal.aborted,
|
|
1070
|
+
config, admissionState.authCtx, route.provider, route.modelId);
|
|
1053
1071
|
return transportFailureResponse(err);
|
|
1054
1072
|
} finally {
|
|
1055
1073
|
request.releaseBodyObservation?.();
|
|
@@ -1676,6 +1694,7 @@ export async function preparePassthroughExchange(
|
|
|
1676
1694
|
passthroughEstimate,
|
|
1677
1695
|
stream: upstreamRequestsStream,
|
|
1678
1696
|
httpOnly: canonicalBufferedJson,
|
|
1697
|
+
preOutputRefusalHold: preOutputRefusalHold(),
|
|
1679
1698
|
onResponse: (response, retryAuthCtx, retryRequest) => {
|
|
1680
1699
|
adoptCodexWsStage(response);
|
|
1681
1700
|
captureAffinityResponse(
|
|
@@ -1702,6 +1721,8 @@ export async function preparePassthroughExchange(
|
|
|
1702
1721
|
}
|
|
1703
1722
|
}
|
|
1704
1723
|
}
|
|
1724
|
+
// #6969: a projection nothing above recovered goes on as the original stream, before any reader.
|
|
1725
|
+
upstreamResponse = preOutputRefusals.restore(upstreamResponse);
|
|
1705
1726
|
// The deterministic route record cannot classify history it never observed (restart, expiry,
|
|
1706
1727
|
// eviction, or an older transcript). Inspect only a bounded clone of a 4xx whose exact outbound
|
|
1707
1728
|
// Responses body still carries opaque state, then rebuild once through the ordinary adapter
|
|
@@ -1969,6 +1990,7 @@ export async function preparePassthroughExchange(
|
|
|
1969
1990
|
connectMs,
|
|
1970
1991
|
localUpstream,
|
|
1971
1992
|
upstreamResponse,
|
|
1993
|
+
terminalOutcomeRecorded: preOutputRefusals.outcomeRecorded,
|
|
1972
1994
|
};
|
|
1973
1995
|
}
|
|
1974
1996
|
|