@bitkyc08/opencodex 2.80.0 → 2.82.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 +3 -3
- package/bin/ocx.mjs +166 -73
- package/gui/dist/assets/App-Ds_qL7o8.js +54 -0
- package/gui/dist/assets/Tray-BqHF5Trd.js +1 -0
- package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
- package/gui/dist/assets/index-D3FBCNiP.js +94 -0
- package/gui/dist/assets/quota-summary-DTBC8n7-.js +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/claude-green.svg +1 -0
- package/package.json +3 -2
- package/src/adapters/anthropic-model-contract.ts +8 -2
- package/src/adapters/anthropic.ts +8 -3
- package/src/adapters/codebuddy/mcp-server.ts +2 -25
- package/src/adapters/codebuddy/tool-bridge.ts +1 -7
- package/src/adapters/coding-agent/mcp-server.ts +33 -0
- package/src/adapters/coding-agent/protocol.ts +122 -17
- package/src/adapters/coding-agent/tool-catalog.ts +75 -0
- package/src/adapters/coding-agent/turn.ts +101 -44
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +2 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -6
- package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
- package/src/adapters/openai-responses/passthrough.ts +9 -4
- package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
- package/src/adapters/physical-send.ts +8 -5
- package/src/adapters/qoder/adapter.ts +41 -6
- package/src/adapters/qoder/scaffold-guard.ts +2 -3
- package/src/adapters/zed.ts +16 -1
- package/src/claude/agents-inject.ts +14 -3
- package/src/claude/context-windows.ts +19 -17
- package/src/claude/desktop-3p.ts +88 -12
- package/src/claude/desktop-profile.ts +2 -1
- package/src/claude/gateway-cache.ts +18 -1
- package/src/claude/inbound-cache-stabilize.ts +4 -9
- package/src/claude/inbound.ts +78 -3
- package/src/claude/intercept/cli-catalog.ts +3 -0
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/claude/intercept/picker-ca-persistence.ts +7 -6
- package/src/claude/intercept/picker-listener.ts +71 -4
- package/src/claude/intercept/picker-models.ts +14 -6
- package/src/claude/intercept/settings.ts +3 -0
- package/src/claude/long-context.ts +66 -0
- package/src/claude/model-info.ts +19 -16
- package/src/claude/outbound.ts +94 -9
- package/src/claude/subagent-model.ts +40 -11
- package/src/cli/account-anthropic-threshold.ts +14 -5
- package/src/cli/account-api.ts +3 -1
- package/src/cli/account-extended.ts +13 -9
- package/src/cli/account-policy-dto.ts +1 -1
- package/src/cli/account-policy.ts +13 -4
- package/src/cli/account.ts +3 -3
- package/src/cli/agent.ts +9 -5
- package/src/cli/aside-profile-recovery.ts +33 -0
- package/src/cli/capabilities-accounts.ts +2 -2
- package/src/cli/capabilities-agents-routing.ts +24 -0
- package/src/cli/capabilities-base.ts +5 -5
- package/src/cli/capabilities-integrations.ts +18 -2
- package/src/cli/cli-path-diagnostics.ts +132 -0
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/desktop-runtime-guidance.ts +65 -0
- package/src/cli/dispatch.ts +15 -1
- package/src/cli/doctor.ts +16 -3
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +25 -14
- package/src/cli/integration-aside-sync.ts +2 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/integration-preview.ts +8 -1
- package/src/cli/integrations.ts +48 -6
- package/src/cli/message-args.ts +34 -0
- package/src/cli/message-command.ts +43 -0
- package/src/cli/message-runtime.ts +6 -0
- package/src/cli/observe.ts +3 -1
- package/src/cli/provider.ts +29 -6
- package/src/cli/registry.ts +24 -2
- package/src/cli/resolve.ts +25 -2
- package/src/cli/root.ts +4 -2
- package/src/cli/status.ts +64 -12
- package/src/cli/update-restart-child.ts +4 -2
- package/src/cli/update-restart-home.ts +28 -10
- package/src/cli/update-restart-service-record.ts +116 -0
- package/src/cli/update-restart-supervision.ts +96 -0
- package/src/cli/update-restart.ts +59 -13
- package/src/cli/v2.ts +27 -20
- package/src/cli/version-skew-notice.ts +77 -0
- package/src/clients/config-export/commandcode.ts +131 -0
- package/src/clients/config-export/contracts.ts +12 -0
- package/src/clients/config-export.ts +64 -1
- package/src/clients/lazycodex.ts +1 -1
- package/src/clients/omo-role-models.ts +32 -7
- package/src/codex/auth-api/account-list.ts +11 -3
- package/src/codex/auth-api/account-selection.ts +107 -0
- package/src/codex/auth-api/main-account-probe.ts +25 -8
- package/src/codex/auth-api/pool-mode-gate.ts +33 -9
- package/src/codex/auth-api/pool-quota-probe.ts +15 -1
- package/src/codex/auth-api/routes.ts +11 -49
- package/src/codex/auth-context.ts +20 -1
- package/src/codex/autostart-health.ts +12 -3
- package/src/codex/catalog/auto-review.ts +13 -6
- package/src/codex/catalog/build-entries.ts +33 -7
- package/src/codex/catalog/bundled.ts +155 -26
- package/src/codex/catalog/combo-member.ts +3 -2
- package/src/codex/catalog/control-plane.ts +45 -0
- package/src/codex/catalog/effort.ts +37 -6
- package/src/codex/catalog/gather-capture.ts +10 -3
- package/src/codex/catalog/model-hints.ts +15 -8
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/operator-tiers.ts +38 -0
- package/src/codex/catalog/parsing.ts +2 -1
- package/src/codex/catalog/provider-models.ts +58 -44
- package/src/codex/catalog/retained-sync.ts +7 -5
- package/src/codex/catalog/routed-gather.ts +15 -1
- package/src/codex/catalog-auto-refresh-sources.ts +3 -4
- package/src/codex/catalog-auto-refresh.ts +19 -0
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-home-owner.ts +3 -0
- package/src/codex/config-write-lock.ts +333 -0
- package/src/codex/control-plane-models.ts +6 -0
- package/src/codex/convergence.ts +2 -1
- package/src/codex/features.ts +257 -200
- package/src/codex/inject/config-write-section.ts +34 -0
- package/src/codex/inject/multi-agent-v2.ts +14 -6
- package/src/codex/inject/remove.ts +36 -11
- package/src/codex/inject/restore.ts +85 -20
- package/src/codex/inject-coordination.ts +15 -6
- package/src/codex/inject.ts +62 -20
- package/src/codex/journal.ts +62 -17
- package/src/codex/main-account-cache.ts +43 -0
- package/src/codex/paths.ts +2 -0
- package/src/codex/prepared-home.ts +18 -0
- package/src/codex/prompt-journal.ts +25 -12
- package/src/codex/prompt-layers.ts +49 -26
- package/src/codex/prompt-lock-claim.ts +134 -0
- package/src/codex/prompt-lock-evidence.ts +26 -0
- package/src/codex/prompt-lock-io.ts +16 -0
- package/src/codex/prompt-lock-owner.ts +130 -0
- package/src/codex/prompt-lock.ts +84 -53
- package/src/codex/routing/failure-window.ts +146 -0
- package/src/codex/routing/selection.ts +4 -1
- package/src/codex/routing.ts +13 -17
- package/src/codex/runtime.ts +106 -18
- package/src/codex/shim.ts +45 -19
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev-service-exchange.ts +281 -0
- package/src/combos/jev.ts +34 -219
- package/src/combos/types.ts +1 -1
- package/src/config/atomic-write.ts +1 -1
- package/src/config/codex-ws-reuse-setting.ts +10 -0
- package/src/config/diagnostics.ts +27 -16
- package/src/config/live-reconcile.ts +17 -0
- package/src/config/load-degrade.ts +42 -0
- package/src/config/rebase-provenance.ts +11 -0
- package/src/config/schema/anthropic-account-pool.ts +143 -0
- package/src/config/schema/config-schema.ts +11 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +566 -298
- package/src/generated/model-metadata.ts +7 -6
- package/src/grok/inject.ts +50 -4
- package/src/grok/sync.ts +2 -1
- package/src/images/loop.ts +30 -11
- package/src/integrations/aside-profiles.ts +10 -3
- package/src/integrations/catalog-refresh.ts +19 -3
- package/src/integrations/mutation-plan.ts +7 -0
- package/src/integrations/registry.ts +8 -0
- package/src/integrations/state.ts +9 -2
- package/src/integrations/writer.ts +17 -5
- package/src/lib/bun-path-runtime.d.mts +32 -0
- package/src/lib/bun-path-runtime.mjs +95 -0
- package/src/lib/bun-runtime-preflight.ts +140 -0
- package/src/lib/desktop-cli-handoff.d.mts +20 -0
- package/src/lib/desktop-cli-handoff.mjs +92 -0
- package/src/lib/desktop-cli-record.d.mts +29 -0
- package/src/lib/desktop-cli-record.mjs +102 -0
- package/src/lib/lab-live-route-production.ts +9 -1
- package/src/lib/request-execution-budget.ts +244 -58
- package/src/lib/spend-pool-alias-validation.ts +79 -0
- package/src/lib/spend-pool-continuity.ts +34 -0
- package/src/lib/spend-reservation-ledger.ts +412 -50
- package/src/lib/state-store-registrations.ts +20 -3
- package/src/lib/synced-state-location.ts +46 -6
- package/src/lib/test-home-guard.ts +87 -1
- package/src/lib/translator-budget.ts +17 -4
- package/src/lib/upstream-retry.ts +17 -5
- package/src/lib/windows-atomic-replace.ts +38 -16
- package/src/lib/windows-elevation.ts +6 -0
- package/src/lib/windows-secret-acl.ts +92 -25
- package/src/lib/winsw.ts +2 -3
- package/src/lib/workflow-budget.ts +53 -11
- package/src/messaging/budget.ts +45 -0
- package/src/messaging/discovery.ts +57 -0
- package/src/messaging/envelope.ts +49 -0
- package/src/messaging/input.ts +37 -0
- package/src/messaging/rpc.ts +196 -0
- package/src/messaging/send.ts +58 -0
- package/src/messaging/socket.ts +72 -0
- package/src/messaging/types.ts +29 -0
- package/src/oauth/anthropic-account-refusal.ts +90 -18
- package/src/oauth/anthropic-account-threshold.ts +8 -2
- package/src/oauth/anthropic-continuity.ts +15 -1
- package/src/oauth/anthropic-model-quota.ts +120 -48
- package/src/oauth/anthropic-model-routes.ts +11 -3
- package/src/oauth/anthropic-oauth-definitions.ts +98 -0
- package/src/oauth/anthropic-pool-config.ts +30 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
- package/src/oauth/anthropic-routing.ts +1202 -866
- package/src/oauth/anthropic-send-ownership.ts +41 -0
- package/src/oauth/anthropic.ts +4 -1
- package/src/oauth/chatgpt.ts +113 -41
- package/src/oauth/generic-account-failover.ts +3 -1
- package/src/oauth/health.ts +8 -6
- package/src/oauth/index.ts +96 -175
- package/src/oauth/kiro.ts +22 -9
- package/src/oauth/login-cli.ts +2 -0
- package/src/oauth/model-discovery-auth.ts +43 -0
- package/src/oauth/pool-kernel.ts +11 -5
- package/src/oauth/pool-settings-capability.ts +5 -3
- package/src/oauth/store-anthropic-instance.ts +87 -0
- package/src/oauth/store.ts +68 -10
- package/src/oauth/token-guardian.ts +2 -0
- package/src/oauth/types.ts +2 -0
- package/src/protocols/plan-snapshot.ts +5 -0
- package/src/protocols/settings.ts +20 -5
- package/src/providers/anthropic-instance-id.ts +47 -0
- package/src/providers/anthropic-instance.ts +48 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
- package/src/providers/azure-model-metadata.ts +115 -0
- package/src/providers/derive.ts +23 -0
- package/src/providers/kiro-models.ts +3 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/quota/account-cache.ts +37 -11
- package/src/providers/quota/anthropic-account-quota.ts +93 -0
- package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
- package/src/providers/quota/report-cache.ts +24 -1
- package/src/providers/quota/vendor-probes-key.ts +127 -17
- package/src/providers/quota/vendor-probes-oauth.ts +46 -14
- package/src/providers/quota.ts +23 -33
- package/src/providers/registry/entries-core.ts +35 -30
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +7 -4
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/registry.ts +3 -3
- package/src/responses/reasoning-envelope.ts +29 -1
- package/src/responses/reasoning-replay-cache.ts +17 -1
- package/src/responses/spill-store.ts +20 -3
- package/src/responses/state/metrics.ts +95 -0
- package/src/responses/state/snapshot-codec.ts +6 -0
- package/src/responses/state/spill-failure.ts +12 -0
- package/src/responses/state/spill-queue.ts +13 -5
- package/src/responses/state/unforced-store-false.ts +55 -0
- package/src/responses/state.ts +68 -76
- package/src/responses/task-input.ts +17 -0
- package/src/router.ts +28 -4
- package/src/routing/compatibility/assemble.ts +1 -1
- package/src/routing/quota.ts +7 -3
- package/src/server/auth-cors.ts +5 -0
- package/src/server/background-lifecycle.ts +27 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native.ts +64 -28
- package/src/server/claude-messages.ts +59 -8
- package/src/server/gui-static.ts +38 -4
- package/src/server/inbound-body-admission.ts +1 -0
- package/src/server/index/claude-intercept-lifecycle.ts +3 -1
- package/src/server/index/serve-options.ts +10 -2
- package/src/server/index/spend-ledger-lifecycle.ts +12 -5
- package/src/server/index/startup-warnings.ts +19 -0
- package/src/server/index.ts +5 -2
- package/src/server/inference/context.ts +1 -1
- package/src/server/lifecycle.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +123 -38
- package/src/server/management/anthropic-account-threshold.ts +12 -9
- package/src/server/management/anthropic-pool-settings.ts +72 -0
- package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
- package/src/server/management/codex-agent-role-routes.ts +4 -2
- package/src/server/management/codex-prompt-routes.ts +1 -0
- package/src/server/management/codex-role-auto-assign.ts +13 -2
- package/src/server/management/config-routes.ts +85 -14
- package/src/server/management/context.ts +2 -2
- package/src/server/management/decision-routes.ts +2 -1
- package/src/server/management/integration-routes.ts +25 -2
- package/src/server/management/oauth-account-routes.ts +79 -98
- package/src/server/management/provider-instance-ownership.ts +44 -0
- package/src/server/management/provider-routes.ts +21 -28
- package/src/server/management/route-registry.ts +3 -1
- package/src/server/management/routing-profile-routes.ts +4 -4
- package/src/server/management/shared.ts +3 -3
- package/src/server/management/system-restart.ts +44 -14
- package/src/server/management/system-routes.ts +24 -2
- package/src/server/management/vision-sidecar-options.ts +9 -6
- package/src/server/management/web-search-sidecar-options.ts +51 -2
- package/src/server/management-auth.ts +6 -0
- package/src/server/messages-native-eligibility.ts +3 -2
- package/src/server/messages-native-oauth.ts +91 -45
- package/src/server/messages-native-selector.ts +39 -0
- package/src/server/messages-native.ts +133 -38
- package/src/server/messages-response-headers.ts +14 -0
- package/src/server/request-log-conversation.ts +48 -5
- package/src/server/request-log.ts +36 -2
- package/src/server/responses/adapter-continuation.ts +31 -19
- package/src/server/responses/adapter-dispatch.ts +75 -24
- package/src/server/responses/antigravity-validation-refusal.ts +20 -6
- package/src/server/responses/codex-ws-exchange.ts +17 -2
- package/src/server/responses/codex-ws-pool.ts +100 -13
- package/src/server/responses/codex-ws-wire.ts +9 -0
- package/src/server/responses/combo-requested-effort.ts +3 -1
- package/src/server/responses/compact.ts +25 -8
- package/src/server/responses/compaction-recovery.ts +1 -1
- package/src/server/responses/core-codex-account.ts +22 -1
- package/src/server/responses/core-combo-native.ts +24 -4
- package/src/server/responses/core-combo.ts +38 -25
- package/src/server/responses/core-lifetime.ts +3 -36
- package/src/server/responses/core-normalize.ts +1 -0
- package/src/server/responses/core-options.ts +8 -1
- package/src/server/responses/core-replay.ts +78 -0
- package/src/server/responses/core.ts +3 -3
- package/src/server/responses/fetch-helpers.ts +26 -3
- package/src/server/responses/passthrough-delivery.ts +30 -4
- package/src/server/responses/passthrough-dispatch.ts +39 -9
- package/src/server/responses/request-prepare.ts +41 -5
- package/src/server/responses/request-send-budget.ts +150 -50
- package/src/server/responses/request-spend.ts +325 -33
- package/src/server/responses/request-transport.ts +132 -52
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +13 -3
- package/src/server/responses/sidecar-execution.ts +113 -22
- package/src/server/responses/sidecar-send-budget.ts +58 -0
- package/src/server/responses/ws-upstream.ts +19 -6
- package/src/server/responses-undeclared-tool-guard.ts +5 -1
- package/src/server/startup-health-cache.ts +2 -1
- package/src/server/workflow-refusal.ts +33 -0
- package/src/service/cli.ts +20 -4
- package/src/service/desktop-command-guard.ts +32 -0
- package/src/service/desktop-startup.ts +66 -40
- package/src/service/desktop-supervision.d.mts +26 -0
- package/src/service/desktop-supervision.mjs +140 -0
- package/src/service/managing-cli.ts +1 -0
- package/src/service/orchestration.ts +46 -20
- package/src/service/ownership-mutation-lease.d.mts +1 -1
- package/src/service/ownership-mutation-lease.mjs +5 -7
- package/src/service/repair.ts +25 -9
- package/src/service/state.ts +4 -3
- package/src/service/systemd.ts +17 -8
- package/src/service/windows-ops.ts +61 -22
- package/src/sidecar/anthropic-binding.ts +79 -0
- package/src/sidecar/auth.ts +76 -2
- package/src/storage/policy-job.ts +5 -1
- package/src/storage/policy-worker.ts +3 -1
- package/src/storage/policy.ts +9 -0
- package/src/types/anthropic-account-pool.ts +33 -0
- package/src/types/config.ts +33 -28
- package/src/types/provider.ts +5 -0
- package/src/types/request.ts +5 -0
- package/src/types/tools.ts +7 -0
- package/src/types/wire.ts +1 -0
- package/src/types.ts +4 -0
- package/src/update/index.ts +57 -9
- package/src/update/restart-ownership.ts +10 -3
- package/src/update/runtime-ownership.d.mts +6 -0
- package/src/update/runtime-ownership.mjs +23 -7
- package/src/usage/cost.ts +3 -2
- package/src/usage/expected-prices.ts +66 -14
- package/src/vision/anthropic-describe.ts +16 -3
- package/src/vision/index.ts +3 -0
- package/src/vision/plan.ts +19 -6
- package/src/web-search/alpha-search.ts +7 -1
- package/src/web-search/anthropic-executor.ts +16 -4
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +32 -7
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-MbDVfUeC.js +0 -54
- package/gui/dist/assets/Tray-CzkxK82A.js +0 -1
- package/gui/dist/assets/index-UJt6npXk.js +0 -94
- package/gui/dist/assets/quota-summary-DU3sD6Z-.js +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { rotateAnthropicAccountOnResponseForInstance } from "../../oauth/anthropic-account-refusal";
|
|
2
2
|
import type { ResponsesRequestContext } from "./core-options";
|
|
3
3
|
import type { PreparedResponsesRequest } from "./request-prepare";
|
|
4
4
|
import type { ResponsesTransport } from "./request-transport";
|
|
@@ -24,20 +24,22 @@ import {
|
|
|
24
24
|
isGenericOAuthFailoverEnabled,
|
|
25
25
|
rotateGenericOAuthAccountOn429,
|
|
26
26
|
rotateGenericOAuthAccountOnRefusal,
|
|
27
|
+
rotateAntigravityAccountOnAuthRefusal,
|
|
27
28
|
quarantineKiroSuspendedAccount,
|
|
28
29
|
failoverAccountSnapshot,
|
|
29
30
|
} from "../../oauth/generic-account-failover";
|
|
30
31
|
import { classifyKiroRefusal } from "../../adapters/kiro-refusal";
|
|
32
|
+
import { isNonReplayableResponse } from "../../lib/upstream-retry";
|
|
31
33
|
import { noteKiroMonthlyRefusal, noteKiroServedSuccess } from "../../providers/kiro-usage";
|
|
32
34
|
import { persistKiroAccountState } from "../../providers/kiro-account-state-disk";
|
|
33
35
|
import { readDisplaySafeErrorText } from "./core-errors";
|
|
34
36
|
import {
|
|
35
37
|
ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
|
|
36
|
-
|
|
38
|
+
anthropicRoutingFor,
|
|
37
39
|
formatAnthropicProviderForLog,
|
|
38
40
|
} from "../../oauth/anthropic-routing";
|
|
39
41
|
import { resolveWireProtocolOverride } from "../adapter-resolve";
|
|
40
|
-
import { bindRouteReasoningReplayScope, adapterNeedsForcedContinuation } from "./core-replay";
|
|
42
|
+
import { bindRouteReasoningReplayScope, adapterNeedsForcedContinuation, adapterNeedsToolCallContinuation } from "./core-replay";
|
|
41
43
|
import { namespacedToolName } from "../../types";
|
|
42
44
|
import { providerFetch } from "./fetch-helpers";
|
|
43
45
|
import type { AttemptRecoveryKind } from "../../usage/log";
|
|
@@ -45,6 +47,8 @@ import { recordAdapterReasoning, recordAdapterTier } from "../request-log";
|
|
|
45
47
|
import { normalizeLogConversationId } from "../request-log-conversation";
|
|
46
48
|
import { rememberResponseState } from "../../responses/state";
|
|
47
49
|
import { trackStreamLifetime } from "../lifecycle";
|
|
50
|
+
import { isAntigravityRawValidationRefusal } from "./antigravity-validation-refusal";
|
|
51
|
+
import { createSidecarSendBudget } from "./sidecar-send-budget";
|
|
48
52
|
|
|
49
53
|
/** One responsibility of the Responses request pipeline; state owners are explicit. */
|
|
50
54
|
export async function executeResponsesSidecars(
|
|
@@ -67,6 +71,8 @@ export async function executeResponsesSidecars(
|
|
|
67
71
|
| "genericFailoverLimit"
|
|
68
72
|
| "replayOAuthCredentialSnapshot"
|
|
69
73
|
| "applyFailoverSnapshot"
|
|
74
|
+
| "anthropicInstance"
|
|
75
|
+
| "currentAnthropicRouteDecision"
|
|
70
76
|
| "anthropicRouteDecision"
|
|
71
77
|
| "anthropicPoolAccountId"
|
|
72
78
|
| "anthropicPoolFailovers"
|
|
@@ -85,11 +91,15 @@ export async function executeResponsesSidecars(
|
|
|
85
91
|
| "notifyResponseComplete"
|
|
86
92
|
| "cancelResponseCompletion"
|
|
87
93
|
>,
|
|
88
|
-
sendBudgetState: Pick<ResponsesSendBudget, "reserveCredentialHop">,
|
|
94
|
+
sendBudgetState: Pick<ResponsesSendBudget, "reserveCredentialHop" | "refundableAdapterDispatchBudget" | "adapterDispatchBudget" | "noteInitialDispatch" | "noteAdapterPhysicalSend" | "noteAdapterRecoveryWithheld" | "pendingHopPermit">,
|
|
89
95
|
) {
|
|
90
96
|
const { config, options, logCtx } = requestContext;
|
|
97
|
+
const antigravityPoolActivated = requestState.route.providerName === "google-antigravity"
|
|
98
|
+
&& isGenericOAuthFailoverEnabled(config, requestState.route.providerName);
|
|
91
99
|
const {
|
|
92
100
|
applyFailoverSnapshot,
|
|
101
|
+
anthropicInstance,
|
|
102
|
+
currentAnthropicRouteDecision,
|
|
93
103
|
anthropicSessionKey,
|
|
94
104
|
commitResolvedOAuthSelection,
|
|
95
105
|
resolveSelectionAdapter,
|
|
@@ -105,6 +115,7 @@ export async function executeResponsesSidecars(
|
|
|
105
115
|
} = requestState;
|
|
106
116
|
const { routedCompaction, openAiSidecar } = sidecarState;
|
|
107
117
|
const { reserveCredentialHop } = sendBudgetState;
|
|
118
|
+
const sidecarBudget = createSidecarSendBudget(options, sendBudgetState, () => logCtx.usageLogInputTokens);
|
|
108
119
|
const {
|
|
109
120
|
commitReasoningReplayServingRoute,
|
|
110
121
|
continuationStateForResponse,
|
|
@@ -182,13 +193,29 @@ export async function executeResponsesSidecars(
|
|
|
182
193
|
|| (logCtx.activeAttempt?.deliverySummary?.semanticBytes ?? 0) > 0
|
|
183
194
|
|| (logCtx.activeAttempt?.deliverySummary?.sideEffectEvents ?? 0) > 0;
|
|
184
195
|
const noteSidecarOutput = () => { sidecarOutputStarted = true; options.onFirstOutput?.(); };
|
|
196
|
+
// A single request may rotate once for a verification refusal. Keep this separate from the
|
|
197
|
+
// broader OAuth failover count: a sidecar turn can also spend that count on rate-limit recovery.
|
|
198
|
+
let antigravityValidationRotationAttempted = false;
|
|
185
199
|
const rotateSidecarProviderOn429 = async (
|
|
186
200
|
retryAfter: string | null,
|
|
187
201
|
responseHeaders?: Headers,
|
|
188
202
|
retryParsed?: OcxParsedRequest,
|
|
189
203
|
originalResponse?: Response,
|
|
190
204
|
): Promise<{ adapter: ProviderAdapter; recoveryKind: AttemptRecoveryKind } | null> => {
|
|
191
|
-
|
|
205
|
+
const antigravityValidationResponse = canRunWebSearch && route.providerName === "google-antigravity"
|
|
206
|
+
&& route.provider.authMode === "oauth" && originalResponse?.status === 403;
|
|
207
|
+
if (route.providerName !== "kiro" && !(anthropicInstance && (originalResponse?.status === 403 || originalResponse?.status === 401))
|
|
208
|
+
&& !antigravityValidationResponse && originalResponse && originalResponse.status !== 429) return null;
|
|
209
|
+
let antigravityVerification = false;
|
|
210
|
+
if (antigravityValidationResponse) {
|
|
211
|
+
if (transportState.adapter.name !== "google" || !antigravityPoolActivated
|
|
212
|
+
|| options.abortSignal?.aborted || isNonReplayableResponse(originalResponse)
|
|
213
|
+
|| antigravityValidationRotationAttempted
|
|
214
|
+
|| transportState.genericFailovers >= transportState.genericFailoverLimit
|
|
215
|
+
|| sidecarHasCommittedOutput()) return null;
|
|
216
|
+
antigravityVerification = await isAntigravityRawValidationRefusal(originalResponse, options.abortSignal);
|
|
217
|
+
if (!antigravityVerification) return null;
|
|
218
|
+
}
|
|
192
219
|
const refusal = route.providerName === "kiro" && originalResponse
|
|
193
220
|
? classifyKiroRefusal(originalResponse.status,
|
|
194
221
|
await readDisplaySafeErrorText(originalResponse.clone(), options.abortSignal ?? new AbortController().signal, "")).kind
|
|
@@ -205,6 +232,43 @@ export async function executeResponsesSidecars(
|
|
|
205
232
|
// sidecar loops used to flatten all three to `key-429`, so an account rotation read as a key
|
|
206
233
|
// rotation in the attempt row and in the Logs UI.
|
|
207
234
|
let recoveryKind: AttemptRecoveryKind = "key-429";
|
|
235
|
+
if (antigravityVerification) {
|
|
236
|
+
const sent = transportState.replayOAuthCredentialSnapshot;
|
|
237
|
+
const failedAccountId = transportState.genericFailoverAccountId;
|
|
238
|
+
if (!sent || !failedAccountId || sent.accountId !== failedAccountId) return null;
|
|
239
|
+
if (options.abortSignal?.aborted || sidecarHasCommittedOutput()) return null;
|
|
240
|
+
|
|
241
|
+
const hop = reserveCredentialHop(
|
|
242
|
+
"auth-recovery",
|
|
243
|
+
`${route.providerName}|${route.modelId}|sidecar-oauth-verify`,
|
|
244
|
+
);
|
|
245
|
+
if (!hop.allowed) return null;
|
|
246
|
+
antigravityValidationRotationAttempted = true;
|
|
247
|
+
const nextAccountId = rotateAntigravityAccountOnAuthRefusal(
|
|
248
|
+
antigravityPoolActivated, sent.accountId, sent.generation, route.modelId,
|
|
249
|
+
);
|
|
250
|
+
if (!nextAccountId) {
|
|
251
|
+
hop.permit?.release();
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
// Keep the sibling's bearer and its account-matched project together; never inherit the
|
|
256
|
+
// failed account's identity or project.
|
|
257
|
+
const snapshot = await failoverAccountSnapshot(route.providerName, nextAccountId);
|
|
258
|
+
if (options.abortSignal?.aborted || !await applyFailoverSnapshot(snapshot, retryParsed)
|
|
259
|
+
|| transportState.replayOAuthCredentialSnapshot?.accountId !== nextAccountId) {
|
|
260
|
+
hop.permit?.release();
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
transportState.genericFailovers += 1;
|
|
264
|
+
} catch {
|
|
265
|
+
hop.permit?.release();
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
recoveryKind = "oauth-account-403";
|
|
269
|
+
if (!options.comboInitialSend) sendBudgetState.pendingHopPermit = hop.permit;
|
|
270
|
+
else sidecarBudget.ownCredentialHop(hop.permit);
|
|
271
|
+
}
|
|
208
272
|
const rotated = !originalResponse || originalResponse.status === 429
|
|
209
273
|
? rotateProviderTransportOn429(config, route.providerName, route.provider, {
|
|
210
274
|
retryAfter,
|
|
@@ -212,7 +276,9 @@ export async function executeResponsesSidecars(
|
|
|
212
276
|
attemptedKey: route.provider.apiKey,
|
|
213
277
|
promptCacheKey: parsed.options.promptCacheKey,
|
|
214
278
|
}) : null;
|
|
215
|
-
if (
|
|
279
|
+
if (antigravityVerification) {
|
|
280
|
+
// `applyFailoverSnapshot` has already rebound the route to the sibling identity/project.
|
|
281
|
+
} else if (rotated) {
|
|
216
282
|
route.provider = rotated;
|
|
217
283
|
} else if (
|
|
218
284
|
// A POSITIVE gate, not an early return. An early `return null` here made every later arm
|
|
@@ -225,7 +291,8 @@ export async function executeResponsesSidecars(
|
|
|
225
291
|
) {
|
|
226
292
|
// Intersection with the request's shared budget. The sidecar replay is dispatched by the
|
|
227
293
|
// web-search/image loop and never reaches `onSendsConsumed`, so this reservation is the
|
|
228
|
-
// charge
|
|
294
|
+
// charge for other sidecars. Antigravity web search hands its reservation to the adapter
|
|
295
|
+
// physical-send owner instead; a refusal preserves the real 429.
|
|
229
296
|
const hop = reserveCredentialHop(
|
|
230
297
|
"auth-recovery",
|
|
231
298
|
`${route.providerName}|${route.modelId}|sidecar-oauth-429`,
|
|
@@ -259,12 +326,14 @@ export async function executeResponsesSidecars(
|
|
|
259
326
|
return null;
|
|
260
327
|
}
|
|
261
328
|
recoveryKind = "oauth-account-429";
|
|
262
|
-
|
|
329
|
+
if (!options.comboInitialSend && canRunWebSearch && route.providerName === "google-antigravity")
|
|
330
|
+
sendBudgetState.pendingHopPermit = hop.permit;
|
|
331
|
+
else sidecarBudget.ownCredentialHop(hop.permit);
|
|
263
332
|
} else if (
|
|
264
333
|
// Anthropic's pool is excluded from generic failover, so without this arm a 429 inside a
|
|
265
334
|
// web-search or image-bridge turn was terminal even with the pool fully enabled -- while
|
|
266
335
|
// the very same 429 on the main response path rotated.
|
|
267
|
-
transportState.anthropicPoolAccountId
|
|
336
|
+
anthropicInstance && transportState.anthropicPoolAccountId
|
|
268
337
|
) {
|
|
269
338
|
// Same intersection for the Anthropic roster: its own per-request bound still applies,
|
|
270
339
|
// and the shared budget decides whether this request may spend another send at all.
|
|
@@ -272,10 +341,10 @@ export async function executeResponsesSidecars(
|
|
|
272
341
|
"auth-recovery",
|
|
273
342
|
`${route.providerName}|${route.modelId}|sidecar-anthropic-429`,
|
|
274
343
|
);
|
|
275
|
-
const nextAccountId = await
|
|
344
|
+
const nextAccountId = await rotateAnthropicAccountOnResponseForInstance(anthropicInstance,
|
|
276
345
|
originalResponse ?? new Response(null, { status: 429, headers: responseHeaders ?? (retryAfter ? { "retry-after": retryAfter } : undefined) }), {
|
|
277
346
|
config, accountId: transportState.anthropicPoolAccountId, sessionKey: anthropicSessionKey,
|
|
278
|
-
model: route.modelId, requestKey: transportState, decision: transportState.anthropicRouteDecision, signal: options.abortSignal,
|
|
347
|
+
model: route.modelId, requestKey: transportState, decision: transportState.anthropicRouteDecision, currentDecision: currentAnthropicRouteDecision, signal: options.abortSignal,
|
|
279
348
|
allow429Recovery: !sidecarHasCommittedOutput(), allowAccountRefusal: !sidecarHasCommittedOutput(),
|
|
280
349
|
canRetry: hop.allowed && transportState.anthropicPoolFailovers < ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
|
|
281
350
|
});
|
|
@@ -289,18 +358,18 @@ export async function executeResponsesSidecars(
|
|
|
289
358
|
// carries none, and getAnthropicPoolAccessToken is what enforces its fail-closed
|
|
290
359
|
// local-cli credential rule. Both existing Anthropic rotation sites apply the token the
|
|
291
360
|
// same way.
|
|
292
|
-
const admitted = await commitResolvedOAuthSelection(await getAnthropicPoolAccessSnapshot(nextAccountId));
|
|
361
|
+
const admitted = await commitResolvedOAuthSelection(await anthropicRoutingFor(anthropicInstance).getAnthropicPoolAccessSnapshot(nextAccountId));
|
|
293
362
|
if (!admitted) throw new Error("OAuth selection changed during recovery");
|
|
294
363
|
transportState.anthropicPoolAccountId = admitted.accountId;
|
|
295
364
|
transportState.anthropicPoolFailovers += 1;
|
|
296
365
|
route.provider = { ...route.provider, apiKey: admitted.accessToken };
|
|
297
|
-
logCtx.provider = formatAnthropicProviderForLog(
|
|
366
|
+
logCtx.provider = formatAnthropicProviderForLog(anthropicInstance, admitted.accountId, config);
|
|
298
367
|
} catch {
|
|
299
368
|
hop.permit?.release();
|
|
300
369
|
return null;
|
|
301
370
|
}
|
|
302
|
-
recoveryKind = "anthropic-oauth-429";
|
|
303
|
-
hop.permit
|
|
371
|
+
recoveryKind = originalResponse?.status === 401 ? "oauth-401" : "anthropic-oauth-429";
|
|
372
|
+
sidecarBudget.ownCredentialHop(hop.permit);
|
|
304
373
|
} else {
|
|
305
374
|
// No key pool, no generic OAuth roster, no Anthropic pool could produce a replacement
|
|
306
375
|
// credential. The 429 is terminal for this sidecar turn.
|
|
@@ -321,6 +390,7 @@ export async function executeResponsesSidecars(
|
|
|
321
390
|
providerName: route.providerName,
|
|
322
391
|
provider: route.provider,
|
|
323
392
|
adapterName: rotatedAdapter.name,
|
|
393
|
+
...(antigravityVerification ? { oauthCredentialSnapshot: transportState.replayOAuthCredentialSnapshot } : {}),
|
|
324
394
|
});
|
|
325
395
|
}
|
|
326
396
|
bindRouteReasoningReplayScope({
|
|
@@ -328,6 +398,7 @@ export async function executeResponsesSidecars(
|
|
|
328
398
|
providerName: route.providerName,
|
|
329
399
|
provider: route.provider,
|
|
330
400
|
adapterName: rotatedAdapter.name,
|
|
401
|
+
...(antigravityVerification ? { oauthCredentialSnapshot: transportState.replayOAuthCredentialSnapshot } : {}),
|
|
331
402
|
});
|
|
332
403
|
return { adapter: rotatedAdapter, recoveryKind };
|
|
333
404
|
};
|
|
@@ -384,11 +455,14 @@ export async function executeResponsesSidecars(
|
|
|
384
455
|
const imageProviderFetch = providerFetch(
|
|
385
456
|
route.provider,
|
|
386
457
|
options.codexWsRuntimeIdentity,
|
|
387
|
-
{ providerName: route.providerName, modelId: route.modelId },
|
|
458
|
+
{ providerName: route.providerName, modelId: route.modelId, ...sidecarBudget.fetchOptions(transportState.adapter) },
|
|
388
459
|
);
|
|
389
460
|
const imgResponse = await runWithImageBridge({
|
|
390
461
|
parsed, adapter: transportState.adapter,
|
|
391
|
-
incomingMeta: { headers: requestState.selectedForwardHeaders, providerName: route.providerName, abortSignal: options.abortSignal, translatorBudget },
|
|
462
|
+
incomingMeta: { headers: requestState.selectedForwardHeaders, providerName: route.providerName, abortSignal: options.abortSignal, translatorBudget, ...(options.comboInitialSend ? { providerFetch: imageProviderFetch } : {}), ...sidecarBudget.incomingMeta },
|
|
463
|
+
onProducerStart: sidecarBudget.takeProducerOwnership,
|
|
464
|
+
onProducerEnd: sidecarBudget.release,
|
|
465
|
+
onIterationEnd: sidecarBudget.releaseUnsentHop,
|
|
392
466
|
...(imgPlan ? { plan: imgPlan } : {}),
|
|
393
467
|
...(vidPlan ? { videoPlan: vidPlan } : {}),
|
|
394
468
|
forwardHeaders: requestState.selectedForwardHeaders,
|
|
@@ -404,10 +478,10 @@ export async function executeResponsesSidecars(
|
|
|
404
478
|
stallTimeoutSec: config.stallTimeoutSec,
|
|
405
479
|
waitForRequestSlot: imageProviderFetch.waitForPacing,
|
|
406
480
|
fetchImpl: imageProviderFetch.unpacedFetch ?? imageProviderFetch,
|
|
407
|
-
fetchForRequest: (request, iterParsed) => {
|
|
481
|
+
fetchForRequest: (request, iterParsed, adapter) => {
|
|
408
482
|
const fetch = providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
409
483
|
dispatchOverride: oauthDispatch(request, iterParsed),
|
|
410
|
-
providerName: route.providerName, modelId: route.modelId,
|
|
484
|
+
providerName: route.providerName, modelId: route.modelId, ...sidecarBudget.fetchOptions(adapter),
|
|
411
485
|
});
|
|
412
486
|
return fetch.unpacedFetch ?? fetch;
|
|
413
487
|
},
|
|
@@ -440,7 +514,10 @@ export async function executeResponsesSidecars(
|
|
|
440
514
|
parsed._rawBody,
|
|
441
515
|
response,
|
|
442
516
|
continuationStateForResponse(providerState),
|
|
443
|
-
|
|
517
|
+
{
|
|
518
|
+
...responseStateOptions(adapterNeedsForcedContinuation(transportState.adapter.name)),
|
|
519
|
+
retainForToolContinuation: adapterNeedsToolCallContinuation(transportState.adapter.name),
|
|
520
|
+
},
|
|
444
521
|
);
|
|
445
522
|
notifyResponseComplete(response);
|
|
446
523
|
},
|
|
@@ -471,12 +548,14 @@ export async function executeResponsesSidecars(
|
|
|
471
548
|
providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
472
549
|
providerName: route.providerName,
|
|
473
550
|
modelId: route.modelId,
|
|
551
|
+
...sidecarBudget.fetchOptions(transportState.adapter),
|
|
474
552
|
})(input, init)) as typeof globalThis.fetch;
|
|
475
553
|
const wsResponse = await runWithWebSearch({
|
|
476
554
|
parsed, adapter: transportState.adapter,
|
|
477
|
-
|
|
555
|
+
onIterationEnd: sidecarBudget.releaseUnsentHop,
|
|
556
|
+
fetchForRequest: (request, iterParsed, adapter) => providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
478
557
|
dispatchOverride: oauthDispatch(request, iterParsed),
|
|
479
|
-
providerName: route.providerName, modelId: route.modelId,
|
|
558
|
+
providerName: route.providerName, modelId: route.modelId, ...sidecarBudget.fetchOptions(adapter),
|
|
480
559
|
}),
|
|
481
560
|
incomingMeta: {
|
|
482
561
|
headers: requestState.selectedForwardHeaders,
|
|
@@ -484,6 +563,11 @@ export async function executeResponsesSidecars(
|
|
|
484
563
|
abortSignal: options.abortSignal,
|
|
485
564
|
translatorBudget,
|
|
486
565
|
providerFetch: routedProviderFetch,
|
|
566
|
+
...(!options.comboInitialSend && route.providerName === "google-antigravity" ? {
|
|
567
|
+
sendBudget: sendBudgetState.refundableAdapterDispatchBudget,
|
|
568
|
+
onPhysicalSend: (send: { ordinal: number; recovery?: AttemptRecoveryKind }) =>
|
|
569
|
+
sendBudgetState.noteAdapterPhysicalSend(logCtx.usageLogInputTokens, send),
|
|
570
|
+
} : sidecarBudget.incomingMeta),
|
|
487
571
|
},
|
|
488
572
|
backend: wsPlan.backend,
|
|
489
573
|
forwardProvider: wsPlan.forwardSidecar?.provider,
|
|
@@ -516,6 +600,13 @@ export async function executeResponsesSidecars(
|
|
|
516
600
|
stallTimeoutSec: wsPlan.stallTimeoutSec,
|
|
517
601
|
streamRoutedModelOutput: wsPlan.streamRoutedModelOutput,
|
|
518
602
|
on429: rotateSidecarProviderOn429,
|
|
603
|
+
...(!options.comboInitialSend && route.providerName === "google-antigravity" ? { onIterationEnd: () => {
|
|
604
|
+
// The next Antigravity adapter dispatch claims this permit. If request construction,
|
|
605
|
+
// admission or cancellation stopped it first, return the unused send allowance.
|
|
606
|
+
const permit = sendBudgetState.pendingHopPermit;
|
|
607
|
+
sendBudgetState.pendingHopPermit = undefined;
|
|
608
|
+
permit?.release();
|
|
609
|
+
} } : {}),
|
|
519
610
|
retryOn429Policy: route.providerName === "kiro" && isGenericOAuthFailoverEnabled(config, "kiro")
|
|
520
611
|
? null : rateLimitRetryPolicyFor(route.provider),
|
|
521
612
|
onCompletedResponse: response => {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { IncomingMeta, ProviderAdapter } from "../../adapters/base";
|
|
2
|
+
import type { HandleResponsesOptions } from "./core-options";
|
|
3
|
+
import type { ResponsesSendBudget } from "./request-send-budget";
|
|
4
|
+
import type { ProviderFetchOptions } from "./fetch-helpers";
|
|
5
|
+
import type { SingleUseDispatchPermit } from "../../lib/request-execution-budget";
|
|
6
|
+
|
|
7
|
+
/** Hosted inference consumes the same child-owned booking as ordinary dispatch. */
|
|
8
|
+
export function createSidecarSendBudget(
|
|
9
|
+
options: HandleResponsesOptions,
|
|
10
|
+
budget: Pick<ResponsesSendBudget, "adapterDispatchBudget" | "noteInitialDispatch" | "noteAdapterPhysicalSend" | "noteAdapterRecoveryWithheld" | "pendingHopPermit">,
|
|
11
|
+
inputTokens: () => number | undefined,
|
|
12
|
+
) {
|
|
13
|
+
const initial = options.comboInitialSend;
|
|
14
|
+
let hopPermit: SingleUseDispatchPermit | undefined;
|
|
15
|
+
let producerActive = false;
|
|
16
|
+
/** Detach this owner’s hop before refunding its still-unused reservation. */
|
|
17
|
+
const releaseHop = (): void => {
|
|
18
|
+
const permit = hopPermit;
|
|
19
|
+
hopPermit = undefined;
|
|
20
|
+
if (budget.pendingHopPermit === permit) budget.pendingHopPermit = undefined;
|
|
21
|
+
permit?.release();
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
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.
|
|
27
|
+
releaseHop();
|
|
28
|
+
hopPermit = permit;
|
|
29
|
+
budget.pendingHopPermit = permit;
|
|
30
|
+
},
|
|
31
|
+
// Adapter-owned transports settle through the live dispatch view, not an HTTP receipt too.
|
|
32
|
+
incomingMeta: initial ? {
|
|
33
|
+
comboAttempt: options.comboAttempt === true,
|
|
34
|
+
sendBudget: budget.adapterDispatchBudget,
|
|
35
|
+
onPhysicalSend: send => budget.noteAdapterPhysicalSend(inputTokens(), send),
|
|
36
|
+
onRecoveryWithheld: budget.noteAdapterRecoveryWithheld,
|
|
37
|
+
} satisfies Partial<IncomingMeta> : {},
|
|
38
|
+
/** Attach HTTP receipts only when the adapter does not own physical dispatch. */
|
|
39
|
+
fetchOptions(adapter: ProviderAdapter): Pick<ProviderFetchOptions, "onPhysicalDispatch"> {
|
|
40
|
+
return initial && !adapter.fetchResponse && !adapter.runTurn
|
|
41
|
+
? { onPhysicalDispatch: () => {
|
|
42
|
+
const prepaid = budget.pendingHopPermit;
|
|
43
|
+
budget.pendingHopPermit = undefined;
|
|
44
|
+
budget.noteInitialDispatch(prepaid);
|
|
45
|
+
} } : {};
|
|
46
|
+
},
|
|
47
|
+
/** Transfer cleanup to the asynchronous producer before it can outlive the response. */
|
|
48
|
+
takeProducerOwnership(): void {
|
|
49
|
+
if (initial) { initial.producerOwned = true; producerActive = true; }
|
|
50
|
+
},
|
|
51
|
+
/** Release unused bookings only after the owning producer has settled. */
|
|
52
|
+
release(): void {
|
|
53
|
+
initial?.permit.release(); producerActive = false; releaseHop();
|
|
54
|
+
},
|
|
55
|
+
/** An iteration may finish before runTurn; defer its refund while that producer is active. */
|
|
56
|
+
releaseUnsentHop(): void { if (!producerActive) releaseHop(); },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -15,6 +15,7 @@ import type { NativeResponseControl } from "./native-response-control";
|
|
|
15
15
|
// returned event frames as an SSE byte stream, so every downstream consumer
|
|
16
16
|
// (passthrough relay, adapter parsers, usage sniffing) is unchanged.
|
|
17
17
|
|
|
18
|
+
import { codexWsReuseAcrossTurnsEnabled } from "../../config/codex-ws-reuse-setting";
|
|
18
19
|
import { compareBunVersions } from "../../lib/bun-stream-caps";
|
|
19
20
|
import { resolveProxyRoute, socks5ProxyFromEnv } from "../../lib/proxy-env";
|
|
20
21
|
import type { CodexWsQuotaObserver } from "./codex-ws-metadata";
|
|
@@ -26,7 +27,7 @@ import { codexWsCreateFrameExceedsLimit } from "./codex-ws-wire";
|
|
|
26
27
|
import { isLoopbackUrl, rewriteWebSocketDial } from "../../plugins/upstream-hooks";
|
|
27
28
|
export { CODEX_WS_LIVENESS_PING_INTERVAL_MS, CODEX_WS_RESPONSE_PRELUDE_TIMEOUT_MS, MAX_CODEX_WS_FRAME_BYTES, MAX_CODEX_WS_QUEUE_BYTES,
|
|
28
29
|
MAX_CODEX_WS_CREATE_FRAME_BYTES, CODEX_WS_CREATE_FRAME_LIMIT_BYTES, codexWsCreateFrameExceedsLimit,
|
|
29
|
-
isCodexWsQuotaObservedResponse, isCodexWsUpstreamResponse } from "./codex-ws-wire";
|
|
30
|
+
isCodexWsQuotaObservedResponse, isCodexWsUpstreamResponse, isCodexWsPreludeProjection } from "./codex-ws-wire";
|
|
30
31
|
export const MIN_BOUNDED_CODEX_WS_BUN_VERSION = "1.4.0";
|
|
31
32
|
|
|
32
33
|
/**
|
|
@@ -99,8 +100,10 @@ export function planCodexWsDial(
|
|
|
99
100
|
// and is authoritative; a pooled socket's upgrade copy is intentionally ignored. A rewriter
|
|
100
101
|
// therefore cannot change them here, and the upgrade keeps the values the frame carries.
|
|
101
102
|
const dialHeaders = { ...rewritten.headers };
|
|
103
|
+
const reuseAcrossTurns = codexWsReuseAcrossTurnsEnabled();
|
|
102
104
|
for (const name of CODEX_WS_FRAME_HEADERS) {
|
|
103
|
-
if (
|
|
105
|
+
if (reuseAcrossTurns) delete dialHeaders[name];
|
|
106
|
+
else if (Object.hasOwn(headers, name)) dialHeaders[name] = headers[name]!;
|
|
104
107
|
else delete dialHeaders[name];
|
|
105
108
|
}
|
|
106
109
|
const dial = { ...rewritten, headers: dialHeaders };
|
|
@@ -148,7 +151,8 @@ export function shouldUseCodexWsUpstream(
|
|
|
148
151
|
}
|
|
149
152
|
}
|
|
150
153
|
|
|
151
|
-
|
|
154
|
+
/** Select the bounded WS lane or HTTP fallback, forwarding receipts at physical dispatch. */
|
|
155
|
+
export async function codexWsUpstreamFetch(
|
|
152
156
|
url: string,
|
|
153
157
|
init: RequestInit,
|
|
154
158
|
sseFallback: typeof globalThis.fetch,
|
|
@@ -157,6 +161,7 @@ export function codexWsUpstreamFetch(
|
|
|
157
161
|
beforeDispatch?: (headers: Headers) => void,
|
|
158
162
|
nativeControl?: NativeResponseControl,
|
|
159
163
|
beforeContinuation?: () => Promise<void>,
|
|
164
|
+
onPhysicalDispatch?: () => void,
|
|
160
165
|
): Promise<Response> {
|
|
161
166
|
const prepared = prepareCodexWsRequest(url, init);
|
|
162
167
|
if (!prepared) return sseFallback(url, prepareCodexHttpInit(url, init));
|
|
@@ -217,8 +222,16 @@ export function codexWsUpstreamFetch(
|
|
|
217
222
|
const dial = planCodexWsDial(wsUrl, headers, proxy);
|
|
218
223
|
if (!dial) return sseFallback(url, init);
|
|
219
224
|
const identity = control ? null : codexWsReuseIdentity(url, dial.headers, frameText, dial.proxy, dial.url);
|
|
220
|
-
|
|
221
|
-
|
|
225
|
+
const reuseAcrossTurns = identity !== null && codexWsReuseAcrossTurnsEnabled();
|
|
226
|
+
const pooled = identity
|
|
227
|
+
? reuseAcrossTurns
|
|
228
|
+
? await codexWsPool.acquireWaiting(identity, dial.url, dial.headers, dial.proxy, signal)
|
|
229
|
+
: codexWsPool.acquire(identity, dial.url, dial.headers, dial.proxy)
|
|
230
|
+
: null;
|
|
231
|
+
if (reuseAcrossTurns && signal?.aborted) {
|
|
232
|
+
return Promise.reject(signal.reason ?? new DOMException("The operation was aborted.", "AbortError"));
|
|
233
|
+
}
|
|
234
|
+
session = pooled ?? new CodexWsSession(dial.url, dial.headers, false, undefined, dial.proxy);
|
|
222
235
|
if (!session.busy && !session.reserve()) {
|
|
223
236
|
session.dispose();
|
|
224
237
|
return sseFallback(url, init);
|
|
@@ -229,7 +242,7 @@ export function codexWsUpstreamFetch(
|
|
|
229
242
|
return codexWsExchange({
|
|
230
243
|
session, url, init, prepared, sseFallback, onQuota, beforeDispatch,
|
|
231
244
|
nativeControl: control,
|
|
232
|
-
beforeContinuation,
|
|
245
|
+
beforeContinuation, onPhysicalDispatch,
|
|
233
246
|
bunVersion: typeof runtime === "string" ? runtime : runtime.version,
|
|
234
247
|
});
|
|
235
248
|
}
|
|
@@ -703,6 +703,7 @@ function failedBlocks(name: string, newline: string): readonly string[] {
|
|
|
703
703
|
* @param providerExecutedCallTypes - Call types executed by the provider.
|
|
704
704
|
* @param declaredBare - Explicitly declared bare tool names without namespace provenance.
|
|
705
705
|
* @param declaredCustom - Current bare custom declarations eligible for code-mode recovery.
|
|
706
|
+
* @param enforceDeclaredToolNames - Refuse undeclared calls; normalization remains active when false.
|
|
706
707
|
* @returns An SSE block rewrite function.
|
|
707
708
|
*/
|
|
708
709
|
export function createUndeclaredToolCallGuardBlockRewrite(
|
|
@@ -711,6 +712,7 @@ export function createUndeclaredToolCallGuardBlockRewrite(
|
|
|
711
712
|
providerExecutedCallTypes: ProviderExecutedCallTypes = EMPTY_PROVIDER_EXECUTED_CALL_TYPES,
|
|
712
713
|
declaredBare?: ReadonlySet<string>,
|
|
713
714
|
declaredCustom?: ReadonlySet<string>,
|
|
715
|
+
enforceDeclaredToolNames = true,
|
|
714
716
|
): SseBlockRewrite {
|
|
715
717
|
let tripped = false;
|
|
716
718
|
return (block: string) => {
|
|
@@ -723,7 +725,9 @@ export function createUndeclaredToolCallGuardBlockRewrite(
|
|
|
723
725
|
} catch {
|
|
724
726
|
return [block];
|
|
725
727
|
}
|
|
726
|
-
const name =
|
|
728
|
+
const name = enforceDeclaredToolNames
|
|
729
|
+
? undeclaredToolCallName(parsed, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare, declaredCustom)
|
|
730
|
+
: undefined;
|
|
727
731
|
if (name !== undefined) {
|
|
728
732
|
tripped = true;
|
|
729
733
|
return failedBlocks(name, block.includes("\r\n") ? "\r\n" : "\n");
|
|
@@ -88,7 +88,8 @@ export function markStartupHealthDiagnosticStale(value: StartupHealth): StartupH
|
|
|
88
88
|
// Mirror deriveStartupHealth's choice: an already-registered service is refreshed in
|
|
89
89
|
// place. Hardcoding installService here silently undid that for every stale-cache
|
|
90
90
|
// read, which is the path the dashboard hits while a probe is revalidating.
|
|
91
|
-
|
|
91
|
+
recommendedAction: value.desktop ? "Reopen OpenCodex and check Start at Login." : value.recommendedAction,
|
|
92
|
+
recommendedCommand: value.routingKind === "opencodex-local" && value.desktop !== undefined
|
|
92
93
|
? null
|
|
93
94
|
: value.routingKind === "custom-local" || value.routingKind === "unknown"
|
|
94
95
|
? value.commands.restoreNative
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sharedPoolContinuityDenial } from "../lib/spend-reservation-ledger";
|
|
1
2
|
/**
|
|
2
3
|
* The one place that knows how this proxy refuses a turn on its own workflow budget.
|
|
3
4
|
*
|
|
@@ -40,6 +41,8 @@ export interface WorkflowRefusalLog {
|
|
|
40
41
|
readonly requestId: string;
|
|
41
42
|
readonly start: number;
|
|
42
43
|
readonly logCtx: RequestLogContext;
|
|
44
|
+
/** Receiving HTTP route's notification after this refusal's final-log call. */
|
|
45
|
+
readonly onLogged?: () => void;
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
/**
|
|
@@ -83,6 +86,7 @@ export function workflowRefusalResponse(
|
|
|
83
86
|
addFinalRequestLog(refusalLog.requestId, refusalLog.start, refusalLog.logCtx, 429, {
|
|
84
87
|
closeReason: "terminal",
|
|
85
88
|
});
|
|
89
|
+
refusalLog.onLogged?.();
|
|
86
90
|
}
|
|
87
91
|
const refusal = formatErrorResponse(
|
|
88
92
|
429,
|
|
@@ -96,6 +100,22 @@ export function workflowRefusalResponse(
|
|
|
96
100
|
return refusal;
|
|
97
101
|
}
|
|
98
102
|
|
|
103
|
+
/** Render the approved conservative-overlay explanation after a dispatch reservation refuses. */
|
|
104
|
+
export function unboundPoolSpendRefusalMessage(
|
|
105
|
+
logCtx: Pick<RequestLogContext, "spendRefusalDetail">,
|
|
106
|
+
): string | undefined {
|
|
107
|
+
const detail = logCtx.spendRefusalDetail;
|
|
108
|
+
if (!detail?.includesUnboundPoolHistory) return undefined;
|
|
109
|
+
return workflowDenialSummary("workflow-spend-exhausted", detail).message;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** HTTP form for pre-commit send-budget catches; the tracker already recorded the event. */
|
|
113
|
+
export function unboundPoolSpendRefusalResponse(logCtx: RequestLogContext): Response | undefined {
|
|
114
|
+
const detail = logCtx.spendRefusalDetail;
|
|
115
|
+
if (!detail?.includesUnboundPoolHistory) return undefined;
|
|
116
|
+
return workflowRefusalResponse("workflow-spend-exhausted", logCtx, undefined, undefined, detail);
|
|
117
|
+
}
|
|
118
|
+
|
|
99
119
|
/**
|
|
100
120
|
* Admit one HTTP turn against its root workflow budget.
|
|
101
121
|
*
|
|
@@ -104,8 +124,20 @@ export function workflowRefusalResponse(
|
|
|
104
124
|
* children. A request that names a parent is treated as that fan-out; a top-level request is
|
|
105
125
|
* the conversation and may use the reserved slots.
|
|
106
126
|
*/
|
|
127
|
+
/** Read-only alias validation: keep replay corruption distinct from conflicting pool owners. */
|
|
128
|
+
export function poolContinuityRefusalReason(): WorkflowDenial | undefined {
|
|
129
|
+
const denial = sharedPoolContinuityDenial();
|
|
130
|
+
if (!denial) return undefined;
|
|
131
|
+
return denial.reason === "pool-history-unresolved" ? "workflow-pool-history-unresolved" : "workflow-spend-undurable";
|
|
132
|
+
}
|
|
133
|
+
|
|
107
134
|
export function admitHttpWorkflowTurn(headers: Headers): WorkflowDecision | undefined {
|
|
108
135
|
const rootId = headers.get("x-codex-parent-thread-id")?.trim() || undefined;
|
|
136
|
+
const continuityRefusal = poolContinuityRefusalReason();
|
|
137
|
+
if (continuityRefusal) {
|
|
138
|
+
recordWorkflowRefusalEvent(rootId, continuityRefusal);
|
|
139
|
+
return { admitted: false, reason: continuityRefusal, rootId: rootId ?? "" };
|
|
140
|
+
}
|
|
109
141
|
const threadId = headers.get("thread-id")?.trim() || undefined;
|
|
110
142
|
const lane: WorkflowLane = rootId !== undefined && threadId !== undefined && threadId !== rootId
|
|
111
143
|
? "worker"
|
|
@@ -132,6 +164,7 @@ export function workflowDecisionRefusalResponse(
|
|
|
132
164
|
scope: decision.spendScope,
|
|
133
165
|
limit: decision.spendLimit,
|
|
134
166
|
...(decision.spendProjected !== undefined ? { projected: decision.spendProjected } : {}),
|
|
167
|
+
...(decision.spendIncludesUnboundPoolHistory ? { includesUnboundPoolHistory: true } : {}),
|
|
135
168
|
}
|
|
136
169
|
: undefined;
|
|
137
170
|
return workflowRefusalResponse(decision.reason, logCtx, refusalLog, undefined, spend);
|
package/src/service/cli.ts
CHANGED
|
@@ -26,6 +26,7 @@ import type { WindowsSchedulerTaskProbe } from "./windows-scheduler";
|
|
|
26
26
|
import { win32 } from "node:path";
|
|
27
27
|
import { serviceDiagnosticsSummary } from "./diagnostics";
|
|
28
28
|
import { runServiceClaim } from "./claim";
|
|
29
|
+
import { createSupervisionLatch, desktopServiceCommandRefusal } from "./desktop-command-guard";
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* `restart` is NO LONGER folded into `repair`.
|
|
@@ -214,6 +215,7 @@ export function removeServiceTokenAfterUninstall(
|
|
|
214
215
|
/** Execute a service verb while preserving client-owned credentials during uninstall. */
|
|
215
216
|
export async function serviceCommand(...args: (string | undefined)[]): Promise<void> {
|
|
216
217
|
const filteredArgs = args.filter((a): a is string => Boolean(a));
|
|
218
|
+
const supervisionLatch = createSupervisionLatch();
|
|
217
219
|
const execute = async (): Promise<void> => {
|
|
218
220
|
// `claim` is not an install verb: it is deliberately outside planServiceCommand (whose
|
|
219
221
|
// backend/installation checks do not apply to an ownership write) and outside
|
|
@@ -231,6 +233,12 @@ export async function serviceCommand(...args: (string | undefined)[]): Promise<v
|
|
|
231
233
|
process.exit(1);
|
|
232
234
|
}
|
|
233
235
|
const { parsed, command } = plan;
|
|
236
|
+
const refusal = desktopServiceCommandRefusal(command, undefined, supervisionLatch);
|
|
237
|
+
if (refusal) {
|
|
238
|
+
console.error(refusal);
|
|
239
|
+
process.exitCode = 1;
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
234
242
|
if (command === "repair" || command === "restart") {
|
|
235
243
|
const verb: ServiceRepairVerb = command === "restart" ? "restart" : "repair";
|
|
236
244
|
assertServiceEnvironmentMatchesInstall();
|
|
@@ -249,7 +257,7 @@ export async function serviceCommand(...args: (string | undefined)[]): Promise<v
|
|
|
249
257
|
// existing registration had been restarted, not repaired (#4914).
|
|
250
258
|
let repairError: unknown;
|
|
251
259
|
try {
|
|
252
|
-
await repairService({ verb });
|
|
260
|
+
await repairService({ verb, supervisionLatch });
|
|
253
261
|
} catch (error) {
|
|
254
262
|
repairError = error;
|
|
255
263
|
process.exitCode = 1;
|
|
@@ -295,12 +303,12 @@ export async function serviceCommand(...args: (string | undefined)[]): Promise<v
|
|
|
295
303
|
throw new Error(`Task Scheduler state could not be verified before install: ${scheduler.detail}`);
|
|
296
304
|
}
|
|
297
305
|
if (scheduler.status === "absent") {
|
|
298
|
-
await installFreshWindowsSchedulerSafely();
|
|
306
|
+
await installFreshWindowsSchedulerSafely({ supervisionLatch });
|
|
299
307
|
} else {
|
|
300
|
-
await installServiceSafely(backend, ops.install);
|
|
308
|
+
await installServiceSafely(backend, ops.install, { supervisionLatch });
|
|
301
309
|
}
|
|
302
310
|
} else {
|
|
303
|
-
await installServiceSafely(backend, ops.install);
|
|
311
|
+
await installServiceSafely(backend, ops.install, { supervisionLatch });
|
|
304
312
|
}
|
|
305
313
|
} catch (error) {
|
|
306
314
|
console.error(`❌ Service install cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -474,6 +482,14 @@ export async function serviceCommand(...args: (string | undefined)[]): Promise<v
|
|
|
474
482
|
};
|
|
475
483
|
|
|
476
484
|
const preliminary = parseServiceArgs(filteredArgs);
|
|
485
|
+
if (preliminary.invalid.length === 0) {
|
|
486
|
+
const refusal = desktopServiceCommandRefusal(preliminary.sub, undefined, supervisionLatch);
|
|
487
|
+
if (refusal) {
|
|
488
|
+
console.error(refusal);
|
|
489
|
+
process.exitCode = 1;
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
477
493
|
const windowsMutation = process.platform === "win32"
|
|
478
494
|
&& preliminary.invalid.length === 0
|
|
479
495
|
&& preliminary.sub !== "status";
|