@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
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
rotateGenericOAuthAccountOn429,
|
|
31
31
|
failoverAccountSnapshot,
|
|
32
32
|
} from "../../oauth/generic-account-failover";
|
|
33
|
-
import { OAuthAccountPausedError, publicOAuthAuthenticationErrorMessage, type OAuthAccessSnapshot } from "../../oauth/index";
|
|
33
|
+
import { OAuthAccountPausedError, OAuthTokenRefreshBusyError, publicOAuthAuthenticationErrorMessage, type OAuthAccessSnapshot } from "../../oauth/index";
|
|
34
34
|
import { tryAlternateAfterTerminalRefresh } from "../../oauth/kiro-terminal-failover";
|
|
35
35
|
import { resolveWireProtocolOverride } from "../adapter-resolve";
|
|
36
36
|
import { formatErrorResponse, bridgeToResponsesSSE, buildResponseJSON } from "../../bridge";
|
|
@@ -49,12 +49,14 @@ import { rememberResponseState } from "../../responses/state";
|
|
|
49
49
|
import { trackStreamLifetime } from "../lifecycle";
|
|
50
50
|
import { awaitThoughtSignatureDurability } from "../../responses/thought-signature-replay";
|
|
51
51
|
import { undeclaredToolCallMessage } from "../responses-undeclared-tool-guard";
|
|
52
|
+
import { emptyToolCatalogRefusal, guardEmptyToolCatalogEvents, guardEmptyToolCatalogStream } from "../../lib/empty-tool-catalog";
|
|
52
53
|
// LOCAL PATCH (runturn-websearch)
|
|
53
54
|
import { planWebSearch } from "../../web-search";
|
|
54
55
|
import { runTurnWebSearchInitialParsed, runTurnWebSearchLoop } from "../../web-search/run-turn-loop";
|
|
55
56
|
import { WEB_SEARCH_TOOL_NAME } from "../../web-search/synthetic-tool";
|
|
56
57
|
import { orderDevinMessagesOutput } from "../../claude/devin-output-order";
|
|
57
58
|
import { unboundPoolSpendRefusalMessage } from "../workflow-refusal";
|
|
59
|
+
import { claudeUsagePreflight } from "../../adapters/claude-cli/usage-admission";
|
|
58
60
|
|
|
59
61
|
// LOCAL PATCH (runturn-websearch): top-level fields route binding or the
|
|
60
62
|
// adapter itself may write during a turn. Iteration-local `turnParsed` objects
|
|
@@ -71,13 +73,16 @@ const RUNTURN_WS_ROUTE_STATE_KEYS = [
|
|
|
71
73
|
"_providerContinuationOwner",
|
|
72
74
|
"_providerContinuationCandidate",
|
|
73
75
|
"_stripReasoningEncryptedContent",
|
|
76
|
+
"_stripNativeCompactionEncryptedContent",
|
|
77
|
+
"_nativeReasoningRetention",
|
|
78
|
+
"_nativeReasoningRetainedAcrossRoute",
|
|
74
79
|
"_dropForeignReasoningItemIds",
|
|
75
80
|
"_reasoningReplayScope",
|
|
76
81
|
] as const;
|
|
77
82
|
|
|
78
83
|
/** One responsibility of the Responses request pipeline; state owners are explicit. */
|
|
79
84
|
export async function executeResponsesRunTurn(
|
|
80
|
-
requestContext: Pick<ResponsesRequestContext, "options" | "logCtx" | "config">,
|
|
85
|
+
requestContext: Pick<ResponsesRequestContext, "req" | "options" | "logCtx" | "config">,
|
|
81
86
|
admissionState: ResponsesAdmissionState,
|
|
82
87
|
requestState: Pick<
|
|
83
88
|
PreparedResponsesRequest,
|
|
@@ -87,6 +92,7 @@ export async function executeResponsesRunTurn(
|
|
|
87
92
|
| "translatorBudget"
|
|
88
93
|
| "inboundWire"
|
|
89
94
|
| "toolBridgeMaps"
|
|
95
|
+
| "clientToolCatalogRestriction"
|
|
90
96
|
| "rememberKiroDeliveredFinalAnswer"
|
|
91
97
|
| "responseStateOptions"
|
|
92
98
|
>,
|
|
@@ -127,7 +133,7 @@ export async function executeResponsesRunTurn(
|
|
|
127
133
|
>,
|
|
128
134
|
completionPolicy: Pick<ResponsesCompletionPolicy, "emptyCompletionGuardEnabled">,
|
|
129
135
|
): Promise<Response> {
|
|
130
|
-
const { options, logCtx, config } = requestContext;
|
|
136
|
+
const { req, options, logCtx, config } = requestContext;
|
|
131
137
|
const {
|
|
132
138
|
selectionIsCurrent,
|
|
133
139
|
adapterBindings,
|
|
@@ -169,6 +175,33 @@ export async function executeResponsesRunTurn(
|
|
|
169
175
|
const releaseSearchProbeLease = (): void => {
|
|
170
176
|
sidecarState.openAiSidecar?.releaseProbeLease?.();
|
|
171
177
|
};
|
|
178
|
+
// Publish the known subscription pause before headers or a CLI turn, preserving its reset
|
|
179
|
+
// across both Responses and translated Chat instead of presenting a retryable generic 502.
|
|
180
|
+
if (transportState.runTurnAdapter.name === "claude-cli") {
|
|
181
|
+
let usage: Awaited<ReturnType<typeof claudeUsagePreflight>>;
|
|
182
|
+
try {
|
|
183
|
+
usage = await claudeUsagePreflight(parsed.modelId);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
// The outer cleanup does not own the sidecar's probe lease: hand it back before rethrowing.
|
|
186
|
+
releaseSearchProbeLease();
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
if (usage.state === "exhausted") {
|
|
190
|
+
cancelResponseCompletion();
|
|
191
|
+
releaseSearchProbeLease();
|
|
192
|
+
const refusal = new Response(JSON.stringify({ error: {
|
|
193
|
+
type: "rate_limit_error", code: "claude_subscription_cooldown", param: null,
|
|
194
|
+
message: usage.message ?? "Claude subscription usage is exhausted.",
|
|
195
|
+
} }), {
|
|
196
|
+
status: 429,
|
|
197
|
+
headers: { "Content-Type": "application/json",
|
|
198
|
+
...(usage.resetAt ? { "Retry-After": String(Math.max(1, Math.ceil((usage.resetAt - Date.now()) / 1000))) } : {}),
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
markResponseNonReplayable(refusal);
|
|
202
|
+
return refusal;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
172
205
|
// When Codex declared hosted web_search and a sidecar plan resolves, drive
|
|
173
206
|
// the routed model through the same search interception the fetch-path loop
|
|
174
207
|
// runs — injected as a function tool, calls intercepted, results appended to
|
|
@@ -186,7 +219,9 @@ export async function executeResponsesRunTurn(
|
|
|
186
219
|
}
|
|
187
220
|
|
|
188
221
|
const runTurnAbort = new AbortController();
|
|
189
|
-
const
|
|
222
|
+
const cleanupRequestAbort = linkAbortSignal(runTurnAbort, req.signal);
|
|
223
|
+
const cleanupOptionAbort = linkAbortSignal(runTurnAbort, options.abortSignal);
|
|
224
|
+
const cleanupRunTurnAbort = (): void => { cleanupRequestAbort(); cleanupOptionAbort(); };
|
|
190
225
|
const devinProducers = new Set<AbortController>();
|
|
191
226
|
const orderMessagesOutput = (source: AsyncIterable<AdapterEvent>): AsyncIterable<AdapterEvent> =>
|
|
192
227
|
inboundWire === "anthropic" && transportState.runTurnAdapter.name === "devin" && !routedCompaction
|
|
@@ -197,12 +232,13 @@ export async function executeResponsesRunTurn(
|
|
|
197
232
|
const queue = createAdapterEventQueue({
|
|
198
233
|
onBacklogExceeded: () => runTurnAbort.abort(),
|
|
199
234
|
});
|
|
200
|
-
const refreshRunTurnSelection = async (): Promise<
|
|
201
|
-
if (selectionIsCurrent(adapterBindings.get(transportState.runTurnAdapter))) return;
|
|
235
|
+
const refreshRunTurnSelection = async (): Promise<boolean> => {
|
|
236
|
+
if (selectionIsCurrent(adapterBindings.get(transportState.runTurnAdapter))) return false;
|
|
202
237
|
await refreshRunTurnAdapter(parsed);
|
|
203
238
|
bindRouteReasoningReplayScope({ parsed, providerName: route.providerName, provider: route.provider,
|
|
204
239
|
adapterName: transportState.runTurnAdapter.name, oauthCredentialSnapshot: transportState.replayOAuthCredentialSnapshot });
|
|
205
240
|
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, transportState.runTurnAdapter.name, logCtx.accountLogLabel);
|
|
241
|
+
return true;
|
|
206
242
|
};
|
|
207
243
|
// Initial admission must settle before the streaming Response commits HTTP 200.
|
|
208
244
|
// Let the outer Responses facade preserve the local retryable-429 contract.
|
|
@@ -256,13 +292,18 @@ export async function executeResponsesRunTurn(
|
|
|
256
292
|
if (!pacingSlotAcquired) {
|
|
257
293
|
pacingSlot = await waitForProviderRequestSlot(route.providerName, route.provider, route.modelId, runTurnAbort.signal);
|
|
258
294
|
}
|
|
259
|
-
await refreshRunTurnSelection();
|
|
295
|
+
const selectionChanged = await refreshRunTurnSelection();
|
|
260
296
|
const selectedBinding = adapterBindings.get(transportState.runTurnAdapter);
|
|
261
297
|
if (lastKeyFailure && selectedBinding?.kind === "api-key"
|
|
262
298
|
&& failedKeys.has(selectedBinding.provider.apiKey ?? "")) {
|
|
263
299
|
emit(lastKeyFailure);
|
|
264
300
|
return;
|
|
265
301
|
}
|
|
302
|
+
if (selectionChanged) {
|
|
303
|
+
releaseProviderRequestSlot(pacingSlot);
|
|
304
|
+
pacingSlot = undefined;
|
|
305
|
+
pacingSlot = await waitForProviderRequestSlot(route.providerName, route.provider, route.modelId, runTurnAbort.signal);
|
|
306
|
+
}
|
|
266
307
|
// LOCAL PATCH (runturn-websearch): refreshRunTurnSelection binds route
|
|
267
308
|
// state onto `parsed`; mirror it onto the iteration-local copy the
|
|
268
309
|
// adapter actually receives.
|
|
@@ -436,7 +477,19 @@ export async function executeResponsesRunTurn(
|
|
|
436
477
|
// selected; the turn then moves to a surviving stored account, or, with none, the client
|
|
437
478
|
// gets the login instruction instead of an opaque upstream 401.
|
|
438
479
|
let oauth401ReplayAttempted = false;
|
|
439
|
-
let
|
|
480
|
+
let authRecoveryRefusal: Response | undefined;
|
|
481
|
+
const refuseLocalAuthRecovery = (error: Extract<AdapterEvent, { type: "error" }>, cause: unknown): boolean => {
|
|
482
|
+
const cancelled = runTurnAbort.signal.aborted;
|
|
483
|
+
if (!cancelled && !(cause instanceof OAuthTokenRefreshBusyError) && !(cause instanceof OAuthAccountPausedError)) return false;
|
|
484
|
+
const status = cancelled ? 499 : cause instanceof OAuthTokenRefreshBusyError ? 503 : 403;
|
|
485
|
+
const errorType = cancelled ? "client_cancelled" : status === 503 ? "server_error" : "permission_error";
|
|
486
|
+
const message = cancelled ? "Client cancelled request" : publicOAuthAuthenticationErrorMessage(cause);
|
|
487
|
+
// If a readiness heartbeat already committed SSE, the mapped event remains retryable
|
|
488
|
+
// capacity rather than retaining the rejected credential's authentication error/code.
|
|
489
|
+
Object.assign(error, { status, errorType, message, code: undefined, retryable: status === 503 });
|
|
490
|
+
authRecoveryRefusal = formatErrorResponse(status, errorType, message, status === 503 ? { retryAfter: "1" } : undefined);
|
|
491
|
+
return true;
|
|
492
|
+
};
|
|
440
493
|
const recoverRunTurnAdapterOnPreflight401 = async (
|
|
441
494
|
error: Extract<AdapterEvent, { type: "error" }>,
|
|
442
495
|
): Promise<boolean> => {
|
|
@@ -450,9 +503,7 @@ export async function executeResponsesRunTurn(
|
|
|
450
503
|
try {
|
|
451
504
|
admitted = await applyFailoverSnapshot(await refreshResolvedOAuthSelection(sent));
|
|
452
505
|
} catch (err) {
|
|
453
|
-
if (err
|
|
454
|
-
pausedAuthRecovery = true;
|
|
455
|
-
Object.assign(error, { status: 403, errorType: "permission_error", message: publicOAuthAuthenticationErrorMessage(err) });
|
|
506
|
+
if (refuseLocalAuthRecovery(error, err)) {
|
|
456
507
|
hop.permit?.release();
|
|
457
508
|
return false;
|
|
458
509
|
}
|
|
@@ -475,7 +526,9 @@ export async function executeResponsesRunTurn(
|
|
|
475
526
|
} catch (err) {
|
|
476
527
|
// Applying the alternate can still fail, e.g. when a newer manual selection points back
|
|
477
528
|
// at the flagged account; the client then needs the login instruction, not the raw 401.
|
|
478
|
-
|
|
529
|
+
if (!refuseLocalAuthRecovery(error, err)) {
|
|
530
|
+
Object.assign(error, { errorType: "authentication_error", message: publicOAuthAuthenticationErrorMessage(err) });
|
|
531
|
+
}
|
|
479
532
|
hop.permit?.release();
|
|
480
533
|
return false;
|
|
481
534
|
}
|
|
@@ -699,14 +752,19 @@ export async function executeResponsesRunTurn(
|
|
|
699
752
|
return orderMessagesOutput(retryQueue.stream());
|
|
700
753
|
};
|
|
701
754
|
|
|
702
|
-
const
|
|
755
|
+
const clientToolCatalog = requestState.clientToolCatalogRestriction && route.provider.authMode !== "forward"
|
|
756
|
+
? requestState.clientToolCatalogRestriction : undefined;
|
|
757
|
+
const { toolNsMap, declaredToolNames, toolParameterSchemas, freeformToolNames, bareCustomToolNames, toolSearchToolNames } = clientToolCatalog
|
|
758
|
+
? { ...toolBridgeMaps, ...clientToolCatalog } : toolBridgeMaps;
|
|
703
759
|
const enforceDeclaredToolNames = inboundWire !== "chat" && inboundWire !== "anthropic";
|
|
704
760
|
const classifyUndeclaredFirstTool = (
|
|
705
761
|
event: AdapterEvent,
|
|
706
762
|
): Extract<AdapterEvent, { type: "error" }> | undefined => {
|
|
707
|
-
if (!enforceDeclaredToolNames || event.type !== "tool_call_start") return undefined;
|
|
708
763
|
// This tool is declared to the adapter by the private search loop.
|
|
709
|
-
if (wsPlan && event.name === WEB_SEARCH_TOOL_NAME) return undefined;
|
|
764
|
+
if (wsPlan && event.type === "tool_call_start" && event.name === WEB_SEARCH_TOOL_NAME) return undefined;
|
|
765
|
+
const emptyCatalogRefusal = emptyToolCatalogRefusal(event, clientToolCatalog);
|
|
766
|
+
if (emptyCatalogRefusal) return emptyCatalogRefusal;
|
|
767
|
+
if (!enforceDeclaredToolNames || event.type !== "tool_call_start") return undefined;
|
|
710
768
|
const effectiveName = normalizeDeclaredToolName(event.name, declaredToolNames, undefined, bareCustomToolNames);
|
|
711
769
|
if (declaredToolNames.has(effectiveName)) return undefined;
|
|
712
770
|
return {
|
|
@@ -752,13 +810,13 @@ export async function executeResponsesRunTurn(
|
|
|
752
810
|
// Preflight holds only heartbeats and the first meaningful event. A first-event 429 can be
|
|
753
811
|
// replayed transparently; after any output reaches the bridge, a later error stays terminal.
|
|
754
812
|
eventSource = await preflightRunTurnFailover(eventSource, wsFirstParsed, preflightDeadlineAt);
|
|
755
|
-
if (
|
|
813
|
+
if (authRecoveryRefusal) {
|
|
756
814
|
cancelResponseCompletion();
|
|
757
815
|
runTurnAbort.abort();
|
|
758
816
|
queue.close();
|
|
759
817
|
cleanupRunTurnAbort();
|
|
760
818
|
releaseSearchProbeLease();
|
|
761
|
-
return
|
|
819
|
+
return authRecoveryRefusal;
|
|
762
820
|
}
|
|
763
821
|
}
|
|
764
822
|
if (grokDevinPreflight) {
|
|
@@ -776,9 +834,13 @@ export async function executeResponsesRunTurn(
|
|
|
776
834
|
if (preflight.error || preflight.empty) {
|
|
777
835
|
runTurnAbort.abort();
|
|
778
836
|
queue.close();
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
837
|
+
const classified = adapterFailureFromEvent(preflight.error ?? {
|
|
838
|
+
type: "error", status: 502, message: "Adapter ended before producing a response",
|
|
839
|
+
});
|
|
840
|
+
const storageRefusal = preflight.error?.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE;
|
|
841
|
+
const failure = formatErrorResponse(storageRefusal ? 503 : classified.httpStatus,
|
|
842
|
+
storageRefusal ? SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE : classified.error.type,
|
|
843
|
+
redactSecretString(classified.error.message), { code: classified.error.code, classified: true });
|
|
782
844
|
// A replay-unsafe heartbeat means the adapter already ran a local side effect, so the
|
|
783
845
|
// combo must not send this turn to another target: the failure stays with this child.
|
|
784
846
|
if (preflight.replayUnsafe) markResponseNonReplayable(failure);
|
|
@@ -824,7 +886,7 @@ export async function executeResponsesRunTurn(
|
|
|
824
886
|
console.warn(emptyCompletionNotice(route.providerName, route.modelId));
|
|
825
887
|
});
|
|
826
888
|
const sseStream = bridgeToResponsesSSE(
|
|
827
|
-
guardedSource, parsed._responseModelId ?? parsed.modelId, toolNsMap, freeformToolNames, toolSearchToolNames,
|
|
889
|
+
guardEmptyToolCatalogStream(guardedSource, clientToolCatalog, translatorBudget), parsed._responseModelId ?? parsed.modelId, toolNsMap, freeformToolNames, toolSearchToolNames,
|
|
828
890
|
() => {
|
|
829
891
|
cancelResponseCompletion();
|
|
830
892
|
runTurnAbort.abort();
|
|
@@ -840,9 +902,11 @@ export async function executeResponsesRunTurn(
|
|
|
840
902
|
declaredToolNames,
|
|
841
903
|
bareCustomToolNames,
|
|
842
904
|
enforceDeclaredToolNames,
|
|
905
|
+
// Same wire split: only a Responses client (Codex) needs the refusal as a failure.
|
|
906
|
+
terminalRefusalAsFailed: enforceDeclaredToolNames,
|
|
843
907
|
toolParameterSchemas,
|
|
844
908
|
...(options.onFirstOutput ? { onFirstOutput: options.onFirstOutput } : {}),
|
|
845
|
-
...(routedCompaction ? { compaction: true } : {}),
|
|
909
|
+
...(routedCompaction ? { compaction: true, compactionRetention: parsed._compactionRetention } : {}),
|
|
846
910
|
// grok-build's strict decoder dies on the typed response.heartbeat frame; its
|
|
847
911
|
// eventsource layer tolerates comment keep-alives. Codex needs the opposite.
|
|
848
912
|
...(logCtx.surface === "grok" ? { heartbeatStyle: "comment" as const } : {}),
|
|
@@ -897,13 +961,13 @@ export async function executeResponsesRunTurn(
|
|
|
897
961
|
for await (const event of await preflightRunTurnFailover(
|
|
898
962
|
(async function* () { yield* firstAttemptEvents; })(),
|
|
899
963
|
)) runTurnEvents.push(event);
|
|
900
|
-
if (
|
|
964
|
+
if (authRecoveryRefusal) {
|
|
901
965
|
cancelResponseCompletion();
|
|
902
966
|
runTurnAbort.abort();
|
|
903
967
|
queue.close();
|
|
904
968
|
cleanupRunTurnAbort();
|
|
905
969
|
releaseSearchProbeLease();
|
|
906
|
-
return
|
|
970
|
+
return authRecoveryRefusal;
|
|
907
971
|
}
|
|
908
972
|
}
|
|
909
973
|
if (grokDevinPreflight) {
|
|
@@ -961,6 +1025,7 @@ export async function executeResponsesRunTurn(
|
|
|
961
1025
|
}
|
|
962
1026
|
events = searched;
|
|
963
1027
|
}
|
|
1028
|
+
events = guardEmptyToolCatalogEvents(events, clientToolCatalog, translatorBudget);
|
|
964
1029
|
if (options.comboAttempt) {
|
|
965
1030
|
const firstMeaningfulIndex = events.findIndex(event => event.type !== "heartbeat");
|
|
966
1031
|
const firstMeaningful = firstMeaningfulIndex === -1 ? undefined : events[firstMeaningfulIndex];
|
|
@@ -974,12 +1039,13 @@ export async function executeResponsesRunTurn(
|
|
|
974
1039
|
? classifyUndeclaredFirstTool(firstMeaningful)
|
|
975
1040
|
: undefined;
|
|
976
1041
|
if (!firstMeaningful || firstMeaningful.type === "error" || classifiedError) {
|
|
977
|
-
const
|
|
978
|
-
? firstMeaningful
|
|
979
|
-
: "Adapter ended before producing a response");
|
|
1042
|
+
const classified = adapterFailureFromEvent(classifiedError ?? (firstMeaningful?.type === "error"
|
|
1043
|
+
? firstMeaningful
|
|
1044
|
+
: { type: "error", status: 502, message: "Adapter ended before producing a response" }));
|
|
980
1045
|
const storageRefusal = firstMeaningful?.type === "error" && firstMeaningful.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE;
|
|
981
|
-
const failure = formatErrorResponse(storageRefusal ? 503 :
|
|
982
|
-
storageRefusal ? SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE :
|
|
1046
|
+
const failure = formatErrorResponse(storageRefusal ? 503 : classified.httpStatus,
|
|
1047
|
+
storageRefusal ? SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE : classified.error.type,
|
|
1048
|
+
redactSecretString(classified.error.message), { code: classified.error.code, classified: true });
|
|
983
1049
|
if (replayUnsafe) markResponseNonReplayable(failure);
|
|
984
1050
|
return failure;
|
|
985
1051
|
}
|
|
@@ -997,7 +1063,7 @@ export async function executeResponsesRunTurn(
|
|
|
997
1063
|
freeformToolNames,
|
|
998
1064
|
bareCustomToolNames,
|
|
999
1065
|
toolSearchToolNames,
|
|
1000
|
-
...(routedCompaction ? { compaction: true } : {}),
|
|
1066
|
+
...(routedCompaction ? { compaction: true, compactionRetention: parsed._compactionRetention } : {}),
|
|
1001
1067
|
onProviderState: state => { providerState = state; },
|
|
1002
1068
|
onUsage: usage => {
|
|
1003
1069
|
transportState.bindKeyUsageFromBridge(usage);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createAnthropicThinkingRecovery } from "./anthropic-thinking-recovery";
|
|
1
2
|
import { rotateAnthropicAccountOnResponseForInstance } from "../../oauth/anthropic-account-refusal";
|
|
2
3
|
import type { ResponsesRequestContext } from "./core-options";
|
|
3
4
|
import type { PreparedResponsesRequest } from "./request-prepare";
|
|
@@ -20,8 +21,8 @@ import {
|
|
|
20
21
|
runWithImageBridge,
|
|
21
22
|
clampImageMaxRounds,
|
|
22
23
|
} from "../../images";
|
|
23
|
-
import type { ProviderAdapter } from "../../adapters/base";
|
|
24
|
-
import type { OcxParsedRequest } from "../../types";
|
|
24
|
+
import type { AdapterRequest, ProviderAdapter } from "../../adapters/base";
|
|
25
|
+
import type { OcxParsedRequest, OcxUsage } from "../../types";
|
|
25
26
|
import { rotateProviderTransportOn429, rateLimitRetryPolicyFor } from "../../providers/key-failover";
|
|
26
27
|
import {
|
|
27
28
|
isGenericOAuthFailoverEnabled,
|
|
@@ -32,7 +33,7 @@ import {
|
|
|
32
33
|
failoverAccountSnapshot,
|
|
33
34
|
} from "../../oauth/generic-account-failover";
|
|
34
35
|
import { classifyKiroRefusal } from "../../adapters/kiro-refusal";
|
|
35
|
-
import { isNonReplayableResponse } from "../../lib/upstream-retry";
|
|
36
|
+
import { isNonReplayableResponse, SendBudgetExhaustedError } from "../../lib/upstream-retry";
|
|
36
37
|
import { unwrapUpstreamRetryEvidenceError } from "../../codex/auth-context";
|
|
37
38
|
import { AdmissionModelDeniedError, admissionModelDeniedResponse } from "../admission-model-scope";
|
|
38
39
|
import { noteKiroMonthlyRefusal, noteKiroServedSuccess } from "../../providers/kiro-usage";
|
|
@@ -50,6 +51,7 @@ import { providerFetch } from "./fetch-helpers";
|
|
|
50
51
|
import type { AttemptRecoveryKind } from "../../usage/log";
|
|
51
52
|
import { recordAdapterReasoning, recordAdapterTier } from "../request-log";
|
|
52
53
|
import { normalizeLogConversationId } from "../request-log-conversation";
|
|
54
|
+
import { attemptAccountChanged } from "../request-log-account-rotation";
|
|
53
55
|
import { rememberResponseState } from "../../responses/state";
|
|
54
56
|
import { trackStreamLifetime } from "../lifecycle";
|
|
55
57
|
import { isAntigravityRawValidationRefusal } from "./antigravity-validation-refusal";
|
|
@@ -65,6 +67,7 @@ export async function executeResponsesSidecars(
|
|
|
65
67
|
| "inboundWire"
|
|
66
68
|
| "selectedForwardHeaders"
|
|
67
69
|
| "translatorBudget"
|
|
70
|
+
| "clientToolCatalogRestriction"
|
|
68
71
|
| "toolBridgeMaps"
|
|
69
72
|
| "rememberKiroDeliveredFinalAnswer"
|
|
70
73
|
| "responseStateOptions"
|
|
@@ -98,7 +101,7 @@ export async function executeResponsesSidecars(
|
|
|
98
101
|
| "notifyResponseComplete"
|
|
99
102
|
| "cancelResponseCompletion"
|
|
100
103
|
>,
|
|
101
|
-
sendBudgetState: Pick<ResponsesSendBudget, "reserveCredentialHop" | "refundableAdapterDispatchBudget" | "adapterDispatchBudget" | "noteInitialDispatch" | "noteAdapterPhysicalSend" | "noteAdapterRecoveryWithheld" | "pendingHopPermit">,
|
|
104
|
+
sendBudgetState: Pick<ResponsesSendBudget, "sendBudgetExhausted" | "reserveCredentialHop" | "refundableAdapterDispatchBudget" | "adapterDispatchBudget" | "noteInitialDispatch" | "noteAdapterPhysicalSend" | "noteAdapterRecoveryWithheld" | "pendingHopPermit">,
|
|
102
105
|
) {
|
|
103
106
|
const { config, options, logCtx } = requestContext;
|
|
104
107
|
const antigravityPoolActivated = requestState.route.providerName === "google-antigravity"
|
|
@@ -377,7 +380,7 @@ export async function executeResponsesSidecars(
|
|
|
377
380
|
recoveryKind = "oauth-account-429";
|
|
378
381
|
if (!options.comboInitialSend && canRunWebSearch && route.providerName === "google-antigravity")
|
|
379
382
|
sendBudgetState.pendingHopPermit = hop.permit;
|
|
380
|
-
else sidecarBudget.ownCredentialHop(hop.permit);
|
|
383
|
+
else sidecarBudget.ownCredentialHop(hop.permit, (canRunWebSearch || !!imgPlan || !!vidPlan) && transportState.adapter.name === "anthropic");
|
|
381
384
|
} else if (
|
|
382
385
|
// Anthropic's pool is excluded from generic failover, so without this arm a 429 inside a
|
|
383
386
|
// web-search or image-bridge turn was terminal even with the pool fully enabled -- while
|
|
@@ -417,8 +420,9 @@ export async function executeResponsesSidecars(
|
|
|
417
420
|
hop.permit?.release();
|
|
418
421
|
return null;
|
|
419
422
|
}
|
|
420
|
-
recoveryKind = originalResponse?.status === 401 ? "oauth-401"
|
|
421
|
-
|
|
423
|
+
recoveryKind = originalResponse?.status === 401 ? "oauth-401"
|
|
424
|
+
: originalResponse?.status === 403 ? "oauth-account-403" : "anthropic-oauth-429";
|
|
425
|
+
sidecarBudget.ownCredentialHop(hop.permit, (canRunWebSearch || !!imgPlan || !!vidPlan) && transportState.adapter.name === "anthropic");
|
|
422
426
|
} else {
|
|
423
427
|
// No key pool, no generic OAuth roster, no Anthropic pool could produce a replacement
|
|
424
428
|
// credential. The 429 is terminal for this sidecar turn.
|
|
@@ -451,6 +455,44 @@ export async function executeResponsesSidecars(
|
|
|
451
455
|
});
|
|
452
456
|
return { adapter: rotatedAdapter, recoveryKind };
|
|
453
457
|
};
|
|
458
|
+
// One guard follows the active sidecar across all iterations. Enforced spend has no
|
|
459
|
+
// authoritative receipt for earlier legacy sends, so recovery cannot claim headroom there.
|
|
460
|
+
const thinkingRecovery = createAnthropicThinkingRecovery(parsed,
|
|
461
|
+
options.abortSignal ?? new AbortController().signal, { attempted: false },
|
|
462
|
+
() => !sendBudgetState.sendBudgetExhausted()
|
|
463
|
+
&& !!sendBudgetState.adapterDispatchBudget
|
|
464
|
+
&& sendBudgetState.adapterDispatchBudget.spendEnforced !== true);
|
|
465
|
+
const thinkingReceipt = (request: AdapterRequest, adapter: ProviderAdapter, recovery?: AttemptRecoveryKind) =>
|
|
466
|
+
adapter.name !== "anthropic" ? {} : { onPhysicalDispatch: () => {
|
|
467
|
+
if (recovery === "opaque-blob-rejection") {
|
|
468
|
+
const decision = sendBudgetState.adapterDispatchBudget?.reserveDispatch({
|
|
469
|
+
sendClass: "repair", targetKey: `${route.providerName}|${request.url}|${route.modelId}`,
|
|
470
|
+
});
|
|
471
|
+
if (sendBudgetState.adapterDispatchBudget?.spendEnforced || !decision?.allowed) {
|
|
472
|
+
if (decision?.allowed) decision.permit.release();
|
|
473
|
+
throw new SendBudgetExhaustedError("sidecar thinking recovery");
|
|
474
|
+
}
|
|
475
|
+
try { sendBudgetState.noteInitialDispatch(decision.permit); }
|
|
476
|
+
finally { decision.permit.release(); }
|
|
477
|
+
} else {
|
|
478
|
+
const existing = sidecarBudget.fetchOptions(adapter).onPhysicalDispatch;
|
|
479
|
+
if (existing) existing();
|
|
480
|
+
else {
|
|
481
|
+
const prepaid = sendBudgetState.pendingHopPermit;
|
|
482
|
+
sendBudgetState.pendingHopPermit = undefined;
|
|
483
|
+
sendBudgetState.noteInitialDispatch(prepaid);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
} };
|
|
487
|
+
// A loop's hidden rounds before a rotation were spent by the account it left. Bill them to that
|
|
488
|
+
// account's attempt while it is still open; the next send seals it. Declined when that send
|
|
489
|
+
// keeps the account (no new attempt), so the loop's final total still carries them.
|
|
490
|
+
const billRotatedUsage = (usage: OcxUsage): boolean => {
|
|
491
|
+
const attempt = logCtx.activeAttempt;
|
|
492
|
+
if (!attempt || !attemptAccountChanged(attempt.accountLogLabel, logCtx.accountLogLabel, attempt.provider, logCtx.provider)) return false;
|
|
493
|
+
transportState.bindKeyUsageFromBridge(usage);
|
|
494
|
+
return true;
|
|
495
|
+
};
|
|
454
496
|
if ((imgPlan || vidPlan) && wsPlan) {
|
|
455
497
|
// Web search takes priority when both are active — the media bridge cannot run
|
|
456
498
|
// alongside runWithWebSearch. Surface a runtime signal so the user knows their
|
|
@@ -508,6 +550,8 @@ export async function executeResponsesSidecars(
|
|
|
508
550
|
);
|
|
509
551
|
const imgResponse = await runWithImageBridge({
|
|
510
552
|
parsed, adapter: transportState.adapter,
|
|
553
|
+
...(transportState.adapter.name === "anthropic" ? { thinkingRecovery } : {}),
|
|
554
|
+
clientToolCatalogRestriction: route.provider.authMode === "forward" ? undefined : requestState.clientToolCatalogRestriction,
|
|
511
555
|
incomingMeta: { headers: requestState.selectedForwardHeaders, providerName: route.providerName, abortSignal: options.abortSignal, translatorBudget, ...(options.comboInitialSend ? { providerFetch: imageProviderFetch } : {}), ...sidecarBudget.incomingMeta },
|
|
512
556
|
onProducerStart: sidecarBudget.takeProducerOwnership,
|
|
513
557
|
onProducerEnd: sidecarBudget.release,
|
|
@@ -527,10 +571,11 @@ export async function executeResponsesSidecars(
|
|
|
527
571
|
stallTimeoutSec: config.stallTimeoutSec,
|
|
528
572
|
waitForRequestSlot: imageProviderFetch.waitForPacing,
|
|
529
573
|
fetchImpl: imageProviderFetch.unpacedFetch ?? imageProviderFetch,
|
|
530
|
-
fetchForRequest: (request, iterParsed, adapter) => {
|
|
574
|
+
fetchForRequest: (request, iterParsed, adapter, recovery) => {
|
|
531
575
|
const fetch = providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
532
576
|
dispatchOverride: oauthDispatch(request, iterParsed),
|
|
533
577
|
providerName: route.providerName, modelId: route.modelId, ...sidecarBudget.fetchOptions(adapter),
|
|
578
|
+
...thinkingReceipt(request, adapter, recovery),
|
|
534
579
|
});
|
|
535
580
|
return fetch.unpacedFetch ?? fetch;
|
|
536
581
|
},
|
|
@@ -549,11 +594,14 @@ export async function executeResponsesSidecars(
|
|
|
549
594
|
transportState.bindKeyUsageFromBridge(usage);
|
|
550
595
|
},
|
|
551
596
|
on429: rotateSidecarProviderOn429,
|
|
597
|
+
onRotatedUsage: billRotatedUsage,
|
|
552
598
|
retryOn429Policy: route.providerName === "kiro" && isGenericOAuthFailoverEnabled(config, "kiro")
|
|
553
599
|
? null : rateLimitRetryPolicyFor(route.provider),
|
|
554
600
|
onFirstOutput: noteSidecarOutput,
|
|
555
601
|
...(options.forceEmptyResponseId ? { forceEmptyResponseId: true } : {}),
|
|
602
|
+
terminalRefusalAsFailed: inboundWire !== "chat" && inboundWire !== "anthropic",
|
|
556
603
|
onCompletedResponse: (response, providerState) => {
|
|
604
|
+
if (!options.abortSignal?.aborted) thinkingRecovery.rememberSuccess(response);
|
|
557
605
|
const served = transportState.replayOAuthCredentialSnapshot;
|
|
558
606
|
if (route.providerName === "kiro" && response.status === "completed" && served
|
|
559
607
|
&& noteKiroServedSuccess(served.accountId, served.generation)) persistKiroAccountState();
|
|
@@ -603,10 +651,13 @@ export async function executeResponsesSidecars(
|
|
|
603
651
|
})(input, init)) as typeof globalThis.fetch;
|
|
604
652
|
const loopResponse = await runWithWebSearch({
|
|
605
653
|
parsed, adapter: transportState.adapter,
|
|
654
|
+
...(transportState.adapter.name === "anthropic" ? { thinkingRecovery } : {}),
|
|
655
|
+
clientToolCatalogRestriction: route.provider.authMode === "forward" ? undefined : requestState.clientToolCatalogRestriction,
|
|
606
656
|
onIterationEnd: sidecarBudget.releaseUnsentHop,
|
|
607
|
-
fetchForRequest: (request, iterParsed, adapter) => providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
657
|
+
fetchForRequest: (request, iterParsed, adapter, recovery) => providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
608
658
|
dispatchOverride: oauthDispatch(request, iterParsed),
|
|
609
659
|
providerName: route.providerName, modelId: route.modelId, ...sidecarBudget.fetchOptions(adapter),
|
|
660
|
+
...thinkingReceipt(request, adapter, recovery),
|
|
610
661
|
}),
|
|
611
662
|
incomingMeta: {
|
|
612
663
|
headers: requestState.selectedForwardHeaders,
|
|
@@ -633,6 +684,7 @@ export async function executeResponsesSidecars(
|
|
|
633
684
|
settings: wsPlan.settings,
|
|
634
685
|
maxSearches: wsPlan.maxSearches,
|
|
635
686
|
forceEmptyResponseId: true,
|
|
687
|
+
terminalRefusalAsFailed: inboundWire !== "chat" && inboundWire !== "anthropic",
|
|
636
688
|
abortSignal: options.abortSignal,
|
|
637
689
|
onFirstOutput: noteSidecarOutput,
|
|
638
690
|
onRequestBuilt: request => {
|
|
@@ -651,6 +703,7 @@ export async function executeResponsesSidecars(
|
|
|
651
703
|
stallTimeoutSec: wsPlan.stallTimeoutSec,
|
|
652
704
|
streamRoutedModelOutput: wsPlan.streamRoutedModelOutput,
|
|
653
705
|
on429: rotateSidecarProviderOn429,
|
|
706
|
+
onRotatedUsage: billRotatedUsage,
|
|
654
707
|
...(!options.comboInitialSend && route.providerName === "google-antigravity" ? { onIterationEnd: () => {
|
|
655
708
|
// The next Antigravity adapter dispatch claims this permit. If request construction,
|
|
656
709
|
// admission or cancellation stopped it first, return the unused send allowance.
|
|
@@ -665,6 +718,7 @@ export async function executeResponsesSidecars(
|
|
|
665
718
|
if (route.providerName === "kiro" && response.status === "completed" && served
|
|
666
719
|
&& noteKiroServedSuccess(served.accountId, served.generation)) persistKiroAccountState();
|
|
667
720
|
commitReasoningReplayServingRoute();
|
|
721
|
+
if (!options.abortSignal?.aborted) thinkingRecovery.rememberSuccess(response);
|
|
668
722
|
notifyResponseComplete(response);
|
|
669
723
|
},
|
|
670
724
|
});
|
|
@@ -22,8 +22,8 @@ export function createSidecarSendBudget(
|
|
|
22
22
|
};
|
|
23
23
|
return {
|
|
24
24
|
/** Retain a Combo hop until dispatch; direct requests keep eager settlement. */
|
|
25
|
-
ownCredentialHop(permit?: SingleUseDispatchPermit): void {
|
|
26
|
-
if (!initial) { permit?.use(); return; } // Preserve direct callers' reporting contract.
|
|
25
|
+
ownCredentialHop(permit?: SingleUseDispatchPermit, retainUntilDispatch = false): void {
|
|
26
|
+
if (!initial && !retainUntilDispatch) { permit?.use(); return; } // Preserve direct callers' reporting contract.
|
|
27
27
|
releaseHop();
|
|
28
28
|
hopPermit = permit;
|
|
29
29
|
budget.pendingHopPermit = permit;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { assistantReplayContent } from "../../responses/assistant-replay";
|
|
1
2
|
import type {
|
|
2
3
|
AdapterEvent,
|
|
3
|
-
OcxAssistantContentPart,
|
|
4
4
|
OcxAssistantMessage,
|
|
5
5
|
OcxParsedRequest,
|
|
6
6
|
OcxUsage,
|
|
@@ -121,21 +121,7 @@ export function analyzeTerminalTurn(parsed: OcxParsedRequest, events: readonly A
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
function assistantMessageFromEvents(events: readonly AdapterEvent[], timestamp: number): OcxAssistantMessage | undefined {
|
|
124
|
-
|
|
125
|
-
let thinking = "";
|
|
126
|
-
let signature: string | undefined;
|
|
127
|
-
const redacted: string[] = [];
|
|
128
|
-
for (const event of events) {
|
|
129
|
-
if (event.type === "text_delta") text += event.text;
|
|
130
|
-
else if (event.type === "thinking_delta") thinking += event.thinking;
|
|
131
|
-
else if (event.type === "thinking_signature") signature = event.signature;
|
|
132
|
-
else if (event.type === "redacted_thinking") redacted.push(event.data);
|
|
133
|
-
}
|
|
134
|
-
const content: OcxAssistantContentPart[] = [];
|
|
135
|
-
if (thinking || signature || redacted.length > 0) {
|
|
136
|
-
content.push({ type: "thinking", thinking, ...(signature ? { signature } : {}), ...(redacted.length > 0 ? { redacted } : {}) });
|
|
137
|
-
}
|
|
138
|
-
if (text) content.push({ type: "text", text });
|
|
124
|
+
const content = assistantReplayContent(events);
|
|
139
125
|
if (content.length === 0) return undefined;
|
|
140
126
|
return { role: "assistant", content, timestamp };
|
|
141
127
|
}
|
|
@@ -298,7 +298,7 @@ export function collectProviderExecutedCallTypes(body: unknown): Set<ProviderExe
|
|
|
298
298
|
return callTypes;
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
function isAuthorizedProviderExecutedCall(
|
|
301
|
+
export function isAuthorizedProviderExecutedCall(
|
|
302
302
|
item: Record<string, unknown>,
|
|
303
303
|
callTypes: ProviderExecutedCallTypes,
|
|
304
304
|
): boolean {
|
package/src/server/ws-bridge.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { BoundedSseFrameBuffer } from "./sse-frame-buffer";
|
|
|
11
11
|
import { sseDataPayload as parseSseBlock } from "./sse-payload-rewrite";
|
|
12
12
|
import { safeResponseHeaders } from "./safe-response-headers";
|
|
13
13
|
import type { AudioSocketTarget } from "./audio-dictation";
|
|
14
|
+
import type { CodexWebSocketActivity } from "../codex/websocket-activity";
|
|
14
15
|
|
|
15
16
|
export { safeResponseHeaders } from "./safe-response-headers";
|
|
16
17
|
|
|
@@ -19,6 +20,7 @@ type ResponsesTerminalReporter = (status: ResponsesTerminalStatus) => void;
|
|
|
19
20
|
type ResponsesPayloadObserver = (payload: string) => void;
|
|
20
21
|
|
|
21
22
|
export interface WsData {
|
|
23
|
+
responsesActivity?: CodexWebSocketActivity;
|
|
22
24
|
nativeControl?: NativeResponseControl;
|
|
23
25
|
/** Content-free per-turn explanation; never a model capability assertion. */
|
|
24
26
|
nativeSteeringUnavailable?: string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The install/repair preflight over the data-plane credential, plus the retry command it
|
|
3
|
+
* names. Split out of `guards.ts` so the guard module stops importing `diagnostics`.
|
|
4
|
+
*
|
|
5
|
+
* Direction of the remaining edge is deliberate: this module asks `guards` for
|
|
6
|
+
* `assertNotAdminToken` and `diagnostics` for the install verdict, and neither imports it back.
|
|
7
|
+
*/
|
|
8
|
+
import { loadConfig } from "../config";
|
|
9
|
+
import { isLoopbackHostname } from "../codex/loopback-target";
|
|
10
|
+
import { readServiceApiTokenState, serviceApiTokenFilePath } from "../lib/service-secrets";
|
|
11
|
+
import { diagnoseService } from "./diagnostics";
|
|
12
|
+
import type { ServiceDiagnostic } from "./diagnostics";
|
|
13
|
+
import { assertNotAdminToken } from "./guards";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The `ocx` command a user should rerun for the service state they actually have.
|
|
17
|
+
*
|
|
18
|
+
* `installed` alone is not enough: `repairService()` refuses a Task-Scheduler-plus-WinSW
|
|
19
|
+
* conflict outright, so recommending repair there names a command guaranteed to fail.
|
|
20
|
+
* Install IS the valid conflict recovery, because `installWindows` removes the native
|
|
21
|
+
* backend first. Exported so the guard tests the real selector rather than a copy of it.
|
|
22
|
+
*/
|
|
23
|
+
export function serviceRetryCommand(
|
|
24
|
+
diag: Pick<ServiceDiagnostic, "installed" | "conflict"> = diagnoseService(),
|
|
25
|
+
): string {
|
|
26
|
+
return diag.installed && !diag.conflict ? "ocx service repair" : "ocx service install";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Preflight for `service install` / `service repair` on the data-plane credential.
|
|
31
|
+
*
|
|
32
|
+
* It used to DEMAND `OPENCODEX_API_AUTH_TOKEN` for a non-loopback hostname, and it threw
|
|
33
|
+
* even when `~/.opencodex/service-api-token` already held a perfectly good token. That is
|
|
34
|
+
* the defect behind the incident this unit exists to close (#4236): an operator exported the
|
|
35
|
+
* ADMIN token as OPENCODEX_API_AUTH_TOKEN because `install` asked for a token, the hub then
|
|
36
|
+
* crash-looped on `assertNotAdminToken`, and `service repair` asked for the same env var
|
|
37
|
+
* again — so the only remembered way to make the command proceed was the thing that broke it.
|
|
38
|
+
*
|
|
39
|
+
* Nobody should have to export a token by hand to run a hub. {@link writeServiceApiTokenFile}
|
|
40
|
+
* provisions one, so the only conditions left that install cannot fix are an admin-token
|
|
41
|
+
* collision in the environment and a token file that exists but cannot be used.
|
|
42
|
+
*/
|
|
43
|
+
export function assertServiceAuthEnvironment(): void {
|
|
44
|
+
const config = loadConfig();
|
|
45
|
+
// Both collision checks come BEFORE the loopback short-circuit, because the launch wrapper
|
|
46
|
+
// exports the token file unconditionally (`buildServiceShellCommand` cats it whenever it
|
|
47
|
+
// exists, whatever the hostname): a management token in either source fences the whole
|
|
48
|
+
// management plane closed at boot, even on a loopback install that needs no admission
|
|
49
|
+
// secret. Returning early is what let that broken state through.
|
|
50
|
+
const present = process.env.OPENCODEX_API_AUTH_TOKEN?.trim();
|
|
51
|
+
if (present) assertNotAdminToken(present);
|
|
52
|
+
const state = readServiceApiTokenState();
|
|
53
|
+
// An existing FILE holding the admin token is the incident shape itself, and the first round
|
|
54
|
+
// only checked the env var — so install/repair reused it and the hub crash-looped at boot.
|
|
55
|
+
// On a machine connected to a hub this same file holds that hub's issued client key, which
|
|
56
|
+
// is never a management token, so the check is a no-op there.
|
|
57
|
+
if (state.kind === "present") assertNotAdminToken(state.token, process.env, "file");
|
|
58
|
+
if (isLoopbackHostname(config.hostname)) return;
|
|
59
|
+
if (present) return;
|
|
60
|
+
// Absent is fine — install/repair generates one below. `unsafe` is not: the writer refuses
|
|
61
|
+
// to replace a path it cannot vouch for, so say so here, where the operator can still act,
|
|
62
|
+
// instead of failing mid-install. Reached from `service repair` as well as `install`, so
|
|
63
|
+
// name a command that can actually succeed (see serviceRetryCommand).
|
|
64
|
+
if (state.kind !== "unsafe") return;
|
|
65
|
+
const diag = diagnoseService();
|
|
66
|
+
throw new Error(
|
|
67
|
+
`The data-plane token file cannot be used (${state.reason}): ${serviceApiTokenFilePath()}. `
|
|
68
|
+
+ `Move it aside, then rerun \`${serviceRetryCommand(diag)}\`; the service provisions a `
|
|
69
|
+
+ "fresh owner-only token and needs nothing from the environment.",
|
|
70
|
+
);
|
|
71
|
+
}
|