@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
|
@@ -16,6 +16,7 @@ import type { ExplicitOpenAiCallerAuth } from "../../providers/openai-sidecar";
|
|
|
16
16
|
import type { CallerDirectAuth } from "../../providers/caller-authorization";
|
|
17
17
|
import type { CompactionRoutingOverride } from "./compaction-routing";
|
|
18
18
|
import type { TranslatorBudget } from "../../lib/translator-budget";
|
|
19
|
+
import type { NativeReasoningOwner } from "../../responses/reasoning-replay-cache";
|
|
19
20
|
import type { TransientSendBudget } from "../../lib/upstream-retry";
|
|
20
21
|
import type { RequestLogContext } from "../request-log";
|
|
21
22
|
import type { UpstreamHostAdmissionLease } from "../../codex/upstream-host-health";
|
|
@@ -71,7 +72,7 @@ export interface HandleResponsesOptions {
|
|
|
71
72
|
compactionRecoveryKind?: "compaction-v1" | "compaction-v2";
|
|
72
73
|
onCompactionRecoveryRoute?: (route: RouteResult) => void;
|
|
73
74
|
onCompactionRecoveryAdapterEvent?: (event: AdapterEvent) => void;
|
|
74
|
-
/** Physical-send reports already delivered to the shared
|
|
75
|
+
/** Physical-send reports already delivered to the shared ledger, including exact booking settlement. */
|
|
75
76
|
onCompactionRecoverySendsReported?: (count: number) => void;
|
|
76
77
|
/** Private holder for the Kiro serving-account lease. */
|
|
77
78
|
accountLoad?: { lease: AccountLease | null; cancelled: boolean };
|
|
@@ -134,6 +135,8 @@ export interface HandleResponsesOptions {
|
|
|
134
135
|
* it. Omitted means a genuine Responses inbound.
|
|
135
136
|
*/
|
|
136
137
|
inboundWire?: InboundWire;
|
|
138
|
+
nativeReasoningReplay?: ReadonlyMap<string, string>;
|
|
139
|
+
nativeReasoningMint?: { owner?: NativeReasoningOwner };
|
|
137
140
|
/** Droid's per-request effort default; each concrete combo or policy target applies it only if its ladder allows it. */
|
|
138
141
|
droidDefaultEffort?: string;
|
|
139
142
|
/** PF-07: the Chat source a combo child may send natively; set only by the Chat ingress. */
|
|
@@ -158,6 +161,10 @@ export interface HandleResponsesOptions {
|
|
|
158
161
|
callerDirectAuth?: CallerDirectAuth | null;
|
|
159
162
|
/** Internal recursion guard; callers outside this module must not set it. */
|
|
160
163
|
comboAttempt?: boolean;
|
|
164
|
+
/** Child-owned prepaid initial send; capacity remains charged until dispatch or unsent release. */
|
|
165
|
+
comboInitialSend?: { permit: SingleUseDispatchPermit; producerOwned?: boolean };
|
|
166
|
+
/** Exact externally booked combo hop, used for its child's spend preflight and send reports. */
|
|
167
|
+
comboDispatchPermit?: SingleUseDispatchPermit;
|
|
161
168
|
/** Internal handoff: this combo was selected by shadow-call interception. */
|
|
162
169
|
shadowCallIntercepted?: boolean;
|
|
163
170
|
/** Internal handoff: the memory phase this turn belongs to, so combo children keep its routing. */
|
|
@@ -19,6 +19,9 @@ import {
|
|
|
19
19
|
reasoningReplayCodexCredentialIdentity,
|
|
20
20
|
reasoningReplayKeyCredentialIdentity,
|
|
21
21
|
durableReplayDestinationIdentity,
|
|
22
|
+
nativeReasoningTag,
|
|
23
|
+
nativeReasoningTagMatches,
|
|
24
|
+
type NativeReasoningOwner,
|
|
22
25
|
bindReasoningReplayScope,
|
|
23
26
|
reasoningReplayServingIdentityChanged,
|
|
24
27
|
reasoningReplayItemStoreChanged,
|
|
@@ -37,6 +40,11 @@ export function adapterNeedsForcedContinuation(name: string): boolean {
|
|
|
37
40
|
return name === "kiro" || name === "cursor";
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
/** Adapters opting into restricted store:false replay for pending client function calls. */
|
|
44
|
+
export function adapterNeedsToolCallContinuation(name: string): boolean {
|
|
45
|
+
return name === "qoder";
|
|
46
|
+
}
|
|
47
|
+
|
|
40
48
|
|
|
41
49
|
export type ContinuationOwnerRead =
|
|
42
50
|
| { kind: "missing" }
|
|
@@ -113,6 +121,16 @@ export function bindRouteReasoningReplayScope(args: {
|
|
|
113
121
|
...args,
|
|
114
122
|
modelId: parsed.modelId,
|
|
115
123
|
});
|
|
124
|
+
const owner = nativeReasoningOwnerForRoute(args);
|
|
125
|
+
if (owner) parsed._nativeReasoningBoundOwner = owner;
|
|
126
|
+
else delete parsed._nativeReasoningBoundOwner;
|
|
127
|
+
if (parsed._nativeReasoningReplay?.size) {
|
|
128
|
+
for (const [blob, tag] of parsed._nativeReasoningReplay) {
|
|
129
|
+
if (!nativeReasoningTagMatches(nativeReasoningTag(owner, blob), tag)) {
|
|
130
|
+
stripNativeReasoningBlob(parsed, blob);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
116
134
|
const continuationDestinationIdentity = providerContinuationDestinationIdentity(parsed, provider);
|
|
117
135
|
const continuationOwner = providerContinuationOwnerFromReplayIdentity(
|
|
118
136
|
replayIdentity && continuationDestinationIdentity
|
|
@@ -151,6 +169,66 @@ export function bindRouteReasoningReplayScope(args: {
|
|
|
151
169
|
bindProviderContinuationForRoute(parsed, continuationOwner);
|
|
152
170
|
}
|
|
153
171
|
|
|
172
|
+
function stripNativeReasoningBlob(parsed: OcxParsedRequest, blob: string): void {
|
|
173
|
+
const input = (parsed._rawBody as { input?: unknown } | undefined)?.input;
|
|
174
|
+
if (Array.isArray(input)) {
|
|
175
|
+
for (const item of input) {
|
|
176
|
+
if (item && typeof item === "object" && item.type === "reasoning" && item.encrypted_content === blob) {
|
|
177
|
+
delete item.encrypted_content;
|
|
178
|
+
delete item.id;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
for (const message of parsed.context.messages) {
|
|
183
|
+
if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
184
|
+
for (const part of message.content) {
|
|
185
|
+
if (part.type !== "thinking" || typeof part.signature !== "string") continue;
|
|
186
|
+
try {
|
|
187
|
+
const item: unknown = JSON.parse(part.signature);
|
|
188
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
189
|
+
const reasoning = item as Record<string, unknown>;
|
|
190
|
+
if (reasoning.type !== "reasoning" || reasoning.encrypted_content !== blob) continue;
|
|
191
|
+
delete reasoning.encrypted_content;
|
|
192
|
+
delete reasoning.id;
|
|
193
|
+
part.signature = JSON.stringify(reasoning);
|
|
194
|
+
delete part.itemId;
|
|
195
|
+
} catch { /* A provider signature is not the parser's JSON carrier. */ }
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function nativeReasoningOwnerForRoute(args: {
|
|
201
|
+
provider: OcxProviderConfig;
|
|
202
|
+
oauthCredentialSnapshot?: Pick<OAuthAccessSnapshot, "accountId" | "generation">;
|
|
203
|
+
codexAuthContext?: CodexAuthContext;
|
|
204
|
+
}): NativeReasoningOwner | undefined {
|
|
205
|
+
const { provider } = args;
|
|
206
|
+
const salt = thoughtSignatureReplaySalt();
|
|
207
|
+
if (!salt || salt.length < 16) return undefined;
|
|
208
|
+
const destination = durableReplayDestinationIdentity(JSON.stringify([
|
|
209
|
+
provider.baseUrl.trim().replace(/\/+$/, ""), provider.responsesPath ?? "",
|
|
210
|
+
]));
|
|
211
|
+
let credential: string | undefined;
|
|
212
|
+
if (provider.authMode === "oauth") {
|
|
213
|
+
const snapshot = args.oauthCredentialSnapshot;
|
|
214
|
+
if (snapshot?.accountId && snapshot.generation !== undefined) credential = durableReplayCredentialIdentity(
|
|
215
|
+
"oauth", `${snapshot.accountId}\0${snapshot.generation}`, provider.headers, salt,
|
|
216
|
+
);
|
|
217
|
+
} else if (provider.authMode === "forward") {
|
|
218
|
+
const context = args.codexAuthContext;
|
|
219
|
+
if (context && (context.kind === "pool" || context.kind === "main-pool")
|
|
220
|
+
&& context.accountId && context.writerGeneration !== undefined
|
|
221
|
+
&& (context.kind !== "pool" || context.generation !== undefined)) {
|
|
222
|
+
credential = durableReplayCredentialIdentity("codex", JSON.stringify([
|
|
223
|
+
context.accountId, context.kind === "pool" ? context.generation : null, context.writerGeneration,
|
|
224
|
+
]), provider.headers, salt);
|
|
225
|
+
}
|
|
226
|
+
} else if (provider.authMode !== "local") {
|
|
227
|
+
credential = durableReplayCredentialIdentity("key", nonEmptyProviderApiKey(provider), provider.headers, salt);
|
|
228
|
+
}
|
|
229
|
+
return destination && credential ? { destination, credential } : undefined;
|
|
230
|
+
}
|
|
231
|
+
|
|
154
232
|
|
|
155
233
|
function routeReasoningReplayIdentity(args: {
|
|
156
234
|
providerName: string;
|
|
@@ -29,8 +29,6 @@ import { releaseUpstreamHostAdmission } from "../../codex/upstream-host-health";
|
|
|
29
29
|
import { releaseCodexAuthContextProbeLease } from "../../codex/auth-context";
|
|
30
30
|
import { runWithCompactionRecovery } from "./compaction-recovery";
|
|
31
31
|
|
|
32
|
-
/** Public Responses entry and compatibility exports. Implementations live with their owners. */
|
|
33
|
-
|
|
34
32
|
/**
|
|
35
33
|
* Route one `/v1/responses` request through the adapter pipeline: recovery loop, passthrough
|
|
36
34
|
* wire, image/web-search bridges, and the terminal-guard continuation.
|
|
@@ -66,13 +64,15 @@ export async function handleResponses(
|
|
|
66
64
|
// Once at ingress, spend observer included: a combo child inherits the parent's holder.
|
|
67
65
|
sendBudget: options.sendBudget ?? createInferenceSendBudget(req, logCtx),
|
|
68
66
|
}, handleResponsesInner);
|
|
69
|
-
const finalResponse = ownsBudget ? finalizeOwnedTranslatorBudget(response, translatorBudget) : response;
|
|
67
|
+
const finalResponse = ownsBudget ? finalizeOwnedTranslatorBudget(response, translatorBudget, abortSignal) : response;
|
|
70
68
|
if (!accountLoad.lease) { release(); return finalResponse; }
|
|
71
69
|
return finalizeAccountLease(finalResponse, release);
|
|
72
70
|
} catch (error) {
|
|
73
71
|
release();
|
|
74
72
|
if (ownsBudget) translatorBudget.dispose();
|
|
75
73
|
throw error;
|
|
74
|
+
} finally {
|
|
75
|
+
if (!options.comboInitialSend?.producerOwned) options.comboInitialSend?.permit.release();
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
@@ -43,6 +43,10 @@ const pacingWebsocketDowngradeWarned = new Set<string>();
|
|
|
43
43
|
*/
|
|
44
44
|
const EGRESS_DECIDED = Symbol.for("opencodex.provider-egress.decided");
|
|
45
45
|
const UPSTREAM_REWRITTEN = Symbol.for("opencodex.plugins.upstream-rewritten");
|
|
46
|
+
// Survives queued credential rebuilds and executor replacement without importing budget owners.
|
|
47
|
+
const PHYSICAL_DISPATCH = Symbol.for("opencodex.physical-dispatch");
|
|
48
|
+
// One WS attempt and its HTTP fallback are one logical physical-send receipt.
|
|
49
|
+
const WS_FALLBACK_RECEIPT = Symbol.for("opencodex.ws-fallback-receipt");
|
|
46
50
|
|
|
47
51
|
/**
|
|
48
52
|
* Announce once, per provider, that an explicit egress route moved this provider off the
|
|
@@ -200,6 +204,7 @@ export function sendWithConnectionPolicy(
|
|
|
200
204
|
const largeCodexBody = typeof body === "string"
|
|
201
205
|
&& /^https:\/\/chatgpt\.com\/backend-api\/codex\/responses(?:\/compact)?$/.test(target)
|
|
202
206
|
&& Buffer.byteLength(body, "utf8") >= 1024 * 1024;
|
|
207
|
+
(init as Record<symbol, (() => void) | undefined> | undefined)?.[PHYSICAL_DISPATCH]?.();
|
|
203
208
|
return physicalFetch(input, {
|
|
204
209
|
...init,
|
|
205
210
|
...(largeCodexBody ? { body: Buffer.from(body, "utf8") } : {}),
|
|
@@ -233,10 +238,13 @@ export interface ProviderFetchOptions {
|
|
|
233
238
|
onCodexWsQuota?: CodexWsQuotaObserver;
|
|
234
239
|
/** Synchronous admission at actual credential dispatch, after pacing/backoff. */
|
|
235
240
|
beforeDispatch?: (headers: Headers) => void;
|
|
241
|
+
/** One-shot receipt after final local HTTP admission, immediately before executor entry. */
|
|
242
|
+
onPhysicalDispatch?: () => void;
|
|
236
243
|
/** Revalidate/rebuild a queued request at its physical send boundary, after pacing. */
|
|
237
244
|
dispatchOverride?: (input: Parameters<typeof globalThis.fetch>[0], init: RequestInit, execute: typeof globalThis.fetch) => Promise<Response>;
|
|
238
245
|
}
|
|
239
246
|
|
|
247
|
+
/** Compose provider pacing and transport admission; receipts run only at executor entry. */
|
|
240
248
|
export function providerFetch(
|
|
241
249
|
provider: OcxProviderConfig,
|
|
242
250
|
runtime: BunRuntimeGateInput = currentBunRuntimeIdentity(),
|
|
@@ -301,7 +309,14 @@ export function providerFetch(
|
|
|
301
309
|
options.beforeDispatch?.(new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined)));
|
|
302
310
|
// No proxy option is attached here: a `dispatchOverride` may rebuild this request against
|
|
303
311
|
// a different destination, so the route is decided at the physical send instead.
|
|
304
|
-
const
|
|
312
|
+
const sharedReceipt = (init as Record<symbol, (() => void) | undefined> | undefined)?.[WS_FALLBACK_RECEIPT];
|
|
313
|
+
let notified = false;
|
|
314
|
+
const dispatchInit = { ...withUpstreamHttpVersion(input, init, provider), timeout: 0,
|
|
315
|
+
...(options.onPhysicalDispatch ? { [PHYSICAL_DISPATCH]: () => {
|
|
316
|
+
if (sharedReceipt) sharedReceipt();
|
|
317
|
+
else if (!notified) { options.onPhysicalDispatch!(); notified = true; }
|
|
318
|
+
} } : {}),
|
|
319
|
+
};
|
|
305
320
|
return options.dispatchOverride
|
|
306
321
|
? options.dispatchOverride(input, dispatchInit, dispatch)
|
|
307
322
|
: dispatch(input, dispatchInit);
|
|
@@ -332,8 +347,16 @@ export function providerFetch(
|
|
|
332
347
|
// used, protocol pin included: a WS turn that falls back is serving the
|
|
333
348
|
// request over HTTP, and dropping the provider's `upstreamHttpVersion`
|
|
334
349
|
// there would silently negotiate a transport the operator ruled out.
|
|
335
|
-
|
|
336
|
-
|
|
350
|
+
let notified = false;
|
|
351
|
+
const notifyOnce = (): void => {
|
|
352
|
+
if (notified) return;
|
|
353
|
+
notified = true;
|
|
354
|
+
options.onPhysicalDispatch?.();
|
|
355
|
+
};
|
|
356
|
+
const fallback = ((url: Parameters<typeof globalThis.fetch>[0], fallbackInit?: RequestInit) =>
|
|
357
|
+
httpFetch(url, { ...fallbackInit, [WS_FALLBACK_RECEIPT]: notifyOnce } as RequestInit)) as typeof globalThis.fetch;
|
|
358
|
+
return codexWsUpstreamFetch(input, init, fallback, runtime, options.onCodexWsQuota, options.beforeDispatch, options.nativeControl,
|
|
359
|
+
async () => { (await waitForPacing(init.signal ?? undefined))?.release(); }, notifyOnce);
|
|
337
360
|
}
|
|
338
361
|
return httpFetch(input, init);
|
|
339
362
|
};
|
|
@@ -24,13 +24,16 @@ import { teeWithBoundedInspection } from "../inspection-tee";
|
|
|
24
24
|
import {
|
|
25
25
|
codexForwardTerminalOutcomeRecorder,
|
|
26
26
|
usesCodexForwardPoolAuth,
|
|
27
|
+
liveMainQuotaDispatch,
|
|
27
28
|
codexQuotaOutcomeMeta,
|
|
28
29
|
codexDenialOutcomeMeta,
|
|
29
30
|
isFixedCodexAccount,
|
|
30
31
|
shouldDeferCodexResetDerivedCooldown,
|
|
31
32
|
} from "./core-codex-account";
|
|
33
|
+
import { isMainQuotaDispatchLive, isMainQuotaDispatchWsClaimed } from "../../codex/main-account-cache";
|
|
34
|
+
import { MAIN_CODEX_ACCOUNT_ID } from "../../codex/account-id";
|
|
32
35
|
import type { ResponsesTerminalStatus } from "../../bridge";
|
|
33
|
-
import { isCodexWsQuotaObservedResponse, isCodexWsUpstreamResponse } from "./ws-upstream";
|
|
36
|
+
import { isCodexWsQuotaObservedResponse, isCodexWsUpstreamResponse, isCodexWsPreludeProjection } from "./ws-upstream";
|
|
34
37
|
import { recordSubagentQuotaFailureForThreadSpawn } from "../../codex/subagent-model-fallback";
|
|
35
38
|
import { recordCodexUpstreamOutcome } from "../../codex/routing";
|
|
36
39
|
import { codexProbeLeaseId, codexProbeQuotaScope, codexTransientProbeGrant, releaseCodexAuthContextProbeLease } from "../../codex/auth-context";
|
|
@@ -139,6 +142,7 @@ import {
|
|
|
139
142
|
httpStatusFromTerminalError,
|
|
140
143
|
inspectResponseLogJson,
|
|
141
144
|
inspectResponseLogSsePayloadParsed,
|
|
145
|
+
noteUpstreamRequestId,
|
|
142
146
|
} from "../request-log";
|
|
143
147
|
import { restoreRoutedCustomCallsInJson } from "../../responses/custom-tool-compat";
|
|
144
148
|
import { restoreRoutedToolSearchCallsInJson } from "../../responses/tool-search-compat";
|
|
@@ -364,6 +368,7 @@ export async function deliverPassthroughResponse(
|
|
|
364
368
|
| "subagentFallbackAccountId"
|
|
365
369
|
| "clientRequestedStream"
|
|
366
370
|
| "translatorBudget"
|
|
371
|
+
| "inboundWire"
|
|
367
372
|
>,
|
|
368
373
|
transportState: Pick<ResponsesTransport, "requestBindings">,
|
|
369
374
|
sidecarState: Pick<ResponsesSidecarAuth, "openAiSidecar">,
|
|
@@ -405,6 +410,9 @@ export async function deliverPassthroughResponse(
|
|
|
405
410
|
| "localUpstream"
|
|
406
411
|
>,
|
|
407
412
|
): Promise<Response> {
|
|
413
|
+
const { route } = requestState;
|
|
414
|
+
// The proof must belong to the response whose headers are published.
|
|
415
|
+
const arrivalMainDispatch = liveMainQuotaDispatch(admissionState.authCtx, route.provider);
|
|
408
416
|
const { logCtx, config, options, req } = requestContext;
|
|
409
417
|
const {
|
|
410
418
|
codexSafetyBufferingOptions,
|
|
@@ -428,7 +436,8 @@ export async function deliverPassthroughResponse(
|
|
|
428
436
|
normalizeFunctionCompletionJson,
|
|
429
437
|
} = nativeExchange;
|
|
430
438
|
const { commitReasoningReplayServingRoute, recordTerminalOutcomes } = responseEffects;
|
|
431
|
-
const { parsed,
|
|
439
|
+
const { parsed, subagentQuotaFailureModel, clientRequestedStream, translatorBudget, inboundWire } = requestState;
|
|
440
|
+
const enforceDeclaredToolNames = inboundWire !== "chat" && inboundWire !== "anthropic";
|
|
432
441
|
const { openAiSidecar } = sidecarState;
|
|
433
442
|
const { requestBindings } = transportState;
|
|
434
443
|
|
|
@@ -450,6 +459,7 @@ export async function deliverPassthroughResponse(
|
|
|
450
459
|
}
|
|
451
460
|
|
|
452
461
|
const headers = sanitizePassthroughHeaders(upstreamResponse.headers, codexSafetyBufferingOptions);
|
|
462
|
+
if (!upstreamResponse.ok) noteUpstreamRequestId(logCtx, headers);
|
|
453
463
|
const resolvedModel = headers.get("openai-model")?.trim();
|
|
454
464
|
if (resolvedModel) {
|
|
455
465
|
logCtx.servedModel = resolvedModel;
|
|
@@ -533,6 +543,20 @@ export async function deliverPassthroughResponse(
|
|
|
533
543
|
...(admissionState.authCtx.kind === "pool" ? { credentialGeneration: admissionState.authCtx.generation } : {}),
|
|
534
544
|
});
|
|
535
545
|
}
|
|
546
|
+
} else {
|
|
547
|
+
// The WS observer is the only plain-main publisher for a WebSocket exchange;
|
|
548
|
+
// a prelude projection carries the prelude snapshot, not fresh evidence.
|
|
549
|
+
// The dispatch claim is authoritative because downstream wrappers can replace the Response.
|
|
550
|
+
if (arrivalMainDispatch && !isMainQuotaDispatchWsClaimed(arrivalMainDispatch)
|
|
551
|
+
&& !(isCodexWsUpstreamResponse(upstreamResponse) || isCodexWsPreludeProjection(upstreamResponse))) {
|
|
552
|
+
const { applyAccountQuotaFromUpstreamHeaders } = await import("../../codex/auth-api");
|
|
553
|
+
// Import yields; same-account token replacement leaves the identity writer live.
|
|
554
|
+
// Re-check the credential fence with no await before publication.
|
|
555
|
+
if (isMainQuotaDispatchLive(arrivalMainDispatch)) {
|
|
556
|
+
applyAccountQuotaFromUpstreamHeaders(MAIN_CODEX_ACCOUNT_ID, upstreamResponse.headers,
|
|
557
|
+
arrivalMainDispatch.configGeneration, arrivalMainDispatch.writer, { modelId: route.modelId });
|
|
558
|
+
}
|
|
559
|
+
}
|
|
536
560
|
}
|
|
537
561
|
|
|
538
562
|
// Non-2xx passthrough failures must never reach Codex as an empty body —
|
|
@@ -637,6 +661,7 @@ export async function deliverPassthroughResponse(
|
|
|
637
661
|
route.provider.webSearchBridge?.backend,
|
|
638
662
|
config,
|
|
639
663
|
openAiSidecar,
|
|
664
|
+
route.providerName,
|
|
640
665
|
);
|
|
641
666
|
const webSearchBridgePlan = planPassthroughWebSearchBridge(parsed, route.provider, {
|
|
642
667
|
providerName: route.providerName,
|
|
@@ -836,6 +861,7 @@ export async function deliverPassthroughResponse(
|
|
|
836
861
|
providerExecutedCallTypes,
|
|
837
862
|
declaredBareWireToolNames,
|
|
838
863
|
recoverableBareCustomWireToolNames,
|
|
864
|
+
enforceDeclaredToolNames,
|
|
839
865
|
)
|
|
840
866
|
: undefined,
|
|
841
867
|
grokUpstreamEchoEnabled
|
|
@@ -1293,7 +1319,7 @@ export async function deliverPassthroughResponse(
|
|
|
1293
1319
|
if (grokUpstreamEchoEnabled) {
|
|
1294
1320
|
clientJson = stripGrokUpstreamEnvelopeEchoFromResponsesJson(clientJson);
|
|
1295
1321
|
}
|
|
1296
|
-
// #1700: same
|
|
1322
|
+
// #1700: same inbound-wire refusal policy as the SSE relay above. Both the plain JSON answer and
|
|
1297
1323
|
// the reframed-SSE branch below are built from this body, so one check covers them. This
|
|
1298
1324
|
// runs BEFORE the continuation cache write below: a refused turn must not become state a
|
|
1299
1325
|
// later `previous_response_id` replay can expand from.
|
|
@@ -1312,7 +1338,7 @@ export async function deliverPassthroughResponse(
|
|
|
1312
1338
|
return undefined;
|
|
1313
1339
|
}
|
|
1314
1340
|
})();
|
|
1315
|
-
if (undeclared !== undefined) {
|
|
1341
|
+
if (enforceDeclaredToolNames && undeclared !== undefined) {
|
|
1316
1342
|
return formatErrorResponse(502, "upstream_error", undeclaredToolCallMessage(undeclared));
|
|
1317
1343
|
}
|
|
1318
1344
|
clientJson = normalizeDefaultNamespaceInJson(
|
|
@@ -12,6 +12,7 @@ import type { ResponsesTransport } from "./request-transport";
|
|
|
12
12
|
import type { ResponsesEffects } from "./response-effects";
|
|
13
13
|
import type { ResponsesSendBudget } from "./request-send-budget";
|
|
14
14
|
import { transientSendCapFor } from "./request-send-budget";
|
|
15
|
+
import { renewMainQuotaDispatchForAttempt } from "../../codex/main-account-cache";
|
|
15
16
|
import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
16
17
|
import { isLocalUpstream } from "../../lib/local-upstream";
|
|
17
18
|
import { codexSafetyBufferingFilterOptions, terminalStatusFromParsed } from "../relay";
|
|
@@ -163,6 +164,7 @@ import { ambiguousResendAllowanceFor, selfContainedResponsesBody } from "./reset
|
|
|
163
164
|
import { upstreamErrorMessageFromPayload, ENCRYPTED_FUNCTION_OUTPUT_REJECTION } from "../../lib/errors";
|
|
164
165
|
import { isTransientConsoleGoUploadRejection } from "../../providers/opencode-zen-rate-limit";
|
|
165
166
|
import { planReasoningEffortDowngrade } from "../../providers/reasoning-metadata";
|
|
167
|
+
import { unboundPoolSpendRefusalResponse } from "../workflow-refusal";
|
|
166
168
|
|
|
167
169
|
/** Prepares and recovers one native Responses exchange before client commitment. */
|
|
168
170
|
export async function preparePassthroughExchange(
|
|
@@ -214,6 +216,7 @@ export async function preparePassthroughExchange(
|
|
|
214
216
|
ResponsesSendBudget,
|
|
215
217
|
| "remainingTransientSendBudget"
|
|
216
218
|
| "noteTransientSends"
|
|
219
|
+
| "transientSendReporter"
|
|
217
220
|
| "recoverySendAllowance"
|
|
218
221
|
| "recoveryClassFor"
|
|
219
222
|
| "sendBudgetExhausted"
|
|
@@ -223,6 +226,10 @@ export async function preparePassthroughExchange(
|
|
|
223
226
|
| "pendingHopPermit"
|
|
224
227
|
| "workflowRootId"
|
|
225
228
|
| "sendsUsed"
|
|
229
|
+
| "targetSendsUsed"
|
|
230
|
+
| "initialSendAllowance"
|
|
231
|
+
| "noteInitialDispatch"
|
|
232
|
+
| "adapterSendBudget"
|
|
226
233
|
>,
|
|
227
234
|
) {
|
|
228
235
|
const { config, logCtx, options, req } = requestContext;
|
|
@@ -249,6 +256,7 @@ export async function preparePassthroughExchange(
|
|
|
249
256
|
const {
|
|
250
257
|
remainingTransientSendBudget,
|
|
251
258
|
noteTransientSends,
|
|
259
|
+
transientSendReporter,
|
|
252
260
|
recoverySendAllowance,
|
|
253
261
|
recoveryClassFor,
|
|
254
262
|
sendBudgetExhausted,
|
|
@@ -750,7 +758,7 @@ export async function preparePassthroughExchange(
|
|
|
750
758
|
const transientSendPolicy = () => transientRetryPolicyFor(route.provider);
|
|
751
759
|
const transientSendAttempts = (): number => transientSendCapFor(
|
|
752
760
|
transientSendPolicy()?.attempts,
|
|
753
|
-
sendBudgetState.
|
|
761
|
+
sendBudgetState.targetSendsUsed,
|
|
754
762
|
);
|
|
755
763
|
const configuredTransientSendBudgetExhausted = (): boolean =>
|
|
756
764
|
transientSendPolicy() !== null && transientSendAttempts() === 0;
|
|
@@ -794,7 +802,13 @@ export async function preparePassthroughExchange(
|
|
|
794
802
|
*/
|
|
795
803
|
const sendAmbiguousReplacement = (
|
|
796
804
|
signal: AbortSignal = upstream.signal,
|
|
797
|
-
): Promise<Response> =>
|
|
805
|
+
): Promise<Response> => {
|
|
806
|
+
if (admissionState.authCtx.kind === "main" && admissionState.authCtx.mainQuotaDispatch) {
|
|
807
|
+
admissionState.authCtx.mainQuotaDispatch = renewMainQuotaDispatchForAttempt(admissionState.authCtx.mainQuotaDispatch);
|
|
808
|
+
}
|
|
809
|
+
const report = transientSendReporter();
|
|
810
|
+
let started = false;
|
|
811
|
+
const run = () => fetchWithHeaderTimeout(
|
|
798
812
|
request.url,
|
|
799
813
|
applyUpstreamRecoveryInit({
|
|
800
814
|
method: request.method,
|
|
@@ -829,11 +843,17 @@ export async function preparePassthroughExchange(
|
|
|
829
843
|
// Charged to the SAME request counter every other send goes through. The
|
|
830
844
|
// replacement is bought here rather than by a nested retry helper, so there is
|
|
831
845
|
// one charge for one send and no per-layer counter to reconcile.
|
|
832
|
-
|
|
846
|
+
if (report.beforeSend?.() === false) throw new SendBudgetExhaustedError(safeHostLabel(request.url));
|
|
847
|
+
started = true;
|
|
833
848
|
},
|
|
834
849
|
}),
|
|
835
850
|
route.provider.authMode === "forward",
|
|
836
851
|
);
|
|
852
|
+
return (report.execute ? report.execute(run) : run()).finally(() => {
|
|
853
|
+
try { if (started) report(1); }
|
|
854
|
+
finally { report.close?.(); }
|
|
855
|
+
});
|
|
856
|
+
};
|
|
837
857
|
/**
|
|
838
858
|
* Refuse a built body that exceeds the operator's configured ceiling, before it is sent.
|
|
839
859
|
*
|
|
@@ -891,7 +911,8 @@ export async function preparePassthroughExchange(
|
|
|
891
911
|
releaseUpstreamHostAdmission(nativeHostState.lease);
|
|
892
912
|
nativeHostState.lease = null;
|
|
893
913
|
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
894
|
-
return
|
|
914
|
+
return unboundPoolSpendRefusalResponse(logCtx)
|
|
915
|
+
?? formatErrorResponse(429, "request_send_budget_exhausted", err.message);
|
|
895
916
|
}
|
|
896
917
|
const refusal = unwrapUpstreamRetryEvidenceError(err);
|
|
897
918
|
// Pacing may outlive the selected account's admission. No fetch occurred, so do
|
|
@@ -955,6 +976,11 @@ export async function preparePassthroughExchange(
|
|
|
955
976
|
};
|
|
956
977
|
const initialBodyRefusal = refuseOversizedOutboundBody(request);
|
|
957
978
|
if (initialBodyRefusal) return initialBodyRefusal;
|
|
979
|
+
// A combo-owned first send is settled by the physical-dispatch receipt unless spend
|
|
980
|
+
// enforcement is on, where the shared reporter already owns the permit lifecycle.
|
|
981
|
+
// Stable per request: the Combo booking (`reserveDispatch`) already started and froze the spend
|
|
982
|
+
// policy, so `spendEnforced` cannot change before dispatch even though no reporter starts it here.
|
|
983
|
+
const receiptMode = !sendBudgetState.adapterSendBudget?.spendEnforced && Boolean(options.comboInitialSend);
|
|
958
984
|
try {
|
|
959
985
|
// Transient-5xx pre-stream retry (devlog/_plan/260716_claudecode_hardening/010):
|
|
960
986
|
// the ChatGPT backend emits transient 502/520s that an immediate retry absorbs.
|
|
@@ -981,6 +1007,7 @@ export async function preparePassthroughExchange(
|
|
|
981
1007
|
dispatchOverride: oauthDispatch(request),
|
|
982
1008
|
providerName: route.providerName,
|
|
983
1009
|
modelId: route.modelId,
|
|
1010
|
+
onPhysicalDispatch: receiptMode ? sendBudgetState.noteInitialDispatch : undefined,
|
|
984
1011
|
onCodexWsQuota: codexWsQuotaObserver(admissionState.authCtx, route.provider, route.modelId),
|
|
985
1012
|
beforeDispatch: isCanonicalOpenAiForwardProvider(route.provider)
|
|
986
1013
|
? createCodexAuthDispatchGuard(admissionState.authCtx, options.codexAuthPolicy ?? config, route.modelId, options.admission, options.visionDescribeTerminal === true) : undefined,
|
|
@@ -991,7 +1018,10 @@ export async function preparePassthroughExchange(
|
|
|
991
1018
|
.then(adoptObservedResponse);
|
|
992
1019
|
},
|
|
993
1020
|
{ abortSignal: upstream.signal, label: safeHostLabel(request.url),
|
|
994
|
-
attempts:
|
|
1021
|
+
attempts: sendBudgetState.initialSendAllowance(transientSendAttempts()),
|
|
1022
|
+
// Reporter path: direct requests and enforced spend. Receipt-mode sends are settled
|
|
1023
|
+
// by `onPhysicalDispatch`; a reporter as well would count them twice.
|
|
1024
|
+
...(receiptMode ? {} : { onSendsConsumed: transientSendReporter() }),
|
|
995
1025
|
claimAmbiguousResend: claimPreHeaderResend,
|
|
996
1026
|
},
|
|
997
1027
|
);
|
|
@@ -1094,7 +1124,7 @@ export async function preparePassthroughExchange(
|
|
|
1094
1124
|
.then(adoptObservedResponse);
|
|
1095
1125
|
},
|
|
1096
1126
|
{ abortSignal: upstream.signal, label: safeHostLabel(request.url), attempts: allowance.attempts,
|
|
1097
|
-
onSendsConsumed:
|
|
1127
|
+
onSendsConsumed: transientSendReporter(allowance.permit), claimAmbiguousResend: claimPreHeaderResend },
|
|
1098
1128
|
);
|
|
1099
1129
|
} catch (err) {
|
|
1100
1130
|
return { failed: transportFailureResponse(err) };
|
|
@@ -1226,7 +1256,7 @@ export async function preparePassthroughExchange(
|
|
|
1226
1256
|
},
|
|
1227
1257
|
{ abortSignal: upstream.signal, label: safeHostLabel(request.url),
|
|
1228
1258
|
attempts: remainingTransientSendBudget(transientSendAttempts()),
|
|
1229
|
-
onSendsConsumed:
|
|
1259
|
+
onSendsConsumed: transientSendReporter(), claimAmbiguousResend: claimPreHeaderResend },
|
|
1230
1260
|
);
|
|
1231
1261
|
} catch (err) {
|
|
1232
1262
|
return transportFailureResponse(err);
|
|
@@ -1354,7 +1384,7 @@ export async function preparePassthroughExchange(
|
|
|
1354
1384
|
.then(adoptObservedResponse);
|
|
1355
1385
|
},
|
|
1356
1386
|
{ abortSignal: upstream.signal, label: safeHostLabel(request.url), attempts: remainingTransientSendBudget(transientSendAttempts()),
|
|
1357
|
-
onSendsConsumed:
|
|
1387
|
+
onSendsConsumed: transientSendReporter(), claimAmbiguousResend: claimPreHeaderResend },
|
|
1358
1388
|
);
|
|
1359
1389
|
} catch (err) {
|
|
1360
1390
|
return transportFailureResponse(err);
|
|
@@ -1489,7 +1519,7 @@ export async function preparePassthroughExchange(
|
|
|
1489
1519
|
.then(adoptObservedResponse);
|
|
1490
1520
|
},
|
|
1491
1521
|
{ abortSignal: upstream.signal, label: safeHostLabel(request.url), attempts: remainingTransientSendBudget(transientSendAttempts()),
|
|
1492
|
-
onSendsConsumed:
|
|
1522
|
+
onSendsConsumed: transientSendReporter(), claimAmbiguousResend: claimPreHeaderResend },
|
|
1493
1523
|
);
|
|
1494
1524
|
} catch (err) {
|
|
1495
1525
|
return transportFailureResponse(err);
|
|
@@ -64,6 +64,7 @@ import type { OcxParsedRequest } from "../../types";
|
|
|
64
64
|
import { buildToolBridgeMaps } from "./collaboration";
|
|
65
65
|
import { parseRequest } from "../../responses/parser";
|
|
66
66
|
import { anthropicSessionKeyFromParts } from "../../oauth/anthropic-routing";
|
|
67
|
+
import { configuredAnthropicInstance } from "../../providers/anthropic-instance";
|
|
67
68
|
import { isTranslatorBudgetExceededError } from "../../lib/translator-budget";
|
|
68
69
|
import { bindTurnTerminationScope, rememberDeliveredFinalAnswer } from "../../responses/turn-termination";
|
|
69
70
|
import { observeCacheDiagnosticInbound, rebindCacheDiagnosticBody, requestLogSpeedLabel, readConfiguredCodexServiceTier } from "../request-log";
|
|
@@ -74,6 +75,7 @@ import {
|
|
|
74
75
|
routeCompactionModel,
|
|
75
76
|
routeModel,
|
|
76
77
|
NoEligiblePolicyCandidateError,
|
|
78
|
+
AnthropicSecondaryInstanceUnavailableError,
|
|
77
79
|
} from "../../router";
|
|
78
80
|
import { evidenceFromBody } from "../../routing/request-evidence";
|
|
79
81
|
import { OPENAI_CODEX_PROVIDER_ID, isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
@@ -379,6 +381,10 @@ export async function prepareResponsesRequest(
|
|
|
379
381
|
let toolBridgeMaps: ReturnType<typeof buildToolBridgeMaps>;
|
|
380
382
|
try {
|
|
381
383
|
parsed = parseRequest(body);
|
|
384
|
+
if (options.inboundWire === "anthropic") {
|
|
385
|
+
parsed._nativeReasoningReplay = options.nativeReasoningReplay;
|
|
386
|
+
parsed._nativeReasoningMint = options.nativeReasoningMint;
|
|
387
|
+
}
|
|
382
388
|
parsed._promptCacheKeyIsSharedCohort = options.promptCacheKeyIsSharedCohort;
|
|
383
389
|
// The body may have been rebuilt since the inbound observation (previous-response
|
|
384
390
|
// expansion); alias the parsed raw body to the same draft so the outbound
|
|
@@ -522,6 +528,23 @@ export async function prepareResponsesRequest(
|
|
|
522
528
|
// or a subagent fallback rewrites it, so a refusal names the client's own
|
|
523
529
|
// request rather than a destination it never asked for.
|
|
524
530
|
const inboundSelector = parsed.modelId;
|
|
531
|
+
const selectorTargetsSecondary = (selector: string): boolean => {
|
|
532
|
+
const slash = selector.indexOf("/");
|
|
533
|
+
if (slash <= 0) return false;
|
|
534
|
+
const qualifier = selector.slice(0, slash);
|
|
535
|
+
// Match router precedence: exact custom keys (including uppercase names) win over aliases.
|
|
536
|
+
if (Object.hasOwn(config.providers, qualifier)) return qualifier === "anthropic2";
|
|
537
|
+
return qualifier === "anthropic2"
|
|
538
|
+
|| config.providers.anthropic2?.alias?.trim().toLowerCase() === qualifier.toLowerCase();
|
|
539
|
+
};
|
|
540
|
+
const explicitlyRequestsSecondary = selectorTargetsSecondary(inboundSelector);
|
|
541
|
+
const secondaryProvider = config.providers.anthropic2;
|
|
542
|
+
// Reserve an explicit B selector before an absent row can default-route it to A.
|
|
543
|
+
// Combo parents have already dispatched above; concrete children retain their own target.
|
|
544
|
+
if (explicitlyRequestsSecondary && (!secondaryProvider || secondaryProvider.disabled === true
|
|
545
|
+
|| secondaryProvider.authMode === "oauth" && configuredAnthropicInstance(config, "anthropic2") !== "anthropic2")) {
|
|
546
|
+
return formatErrorResponse(401, "authentication_error", "Anthropic Pool 2 requires an enabled configured provider");
|
|
547
|
+
}
|
|
525
548
|
const admissionScope = resolveAdmissionModelScope(config, options.admission);
|
|
526
549
|
const captureInboundRoutePolicy = (candidate: RouteResult, captureRequestPolicy = true): RouteResult => {
|
|
527
550
|
// Every route this request path produces passes through here: the direct
|
|
@@ -553,11 +576,21 @@ export async function prepareResponsesRequest(
|
|
|
553
576
|
// no canonical OpenAI route for (#2901). Only the initial compaction route
|
|
554
577
|
// may fall back to the configured default provider; combo attempts and the
|
|
555
578
|
// later fallback/recovery re-routes keep the ordinary reservation.
|
|
556
|
-
const routeInbound = (modelId: string) =>
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
579
|
+
const routeInbound = (modelId: string) => {
|
|
580
|
+
const resolved = concreteSelection
|
|
581
|
+
? routeConcreteModel(config, modelId)
|
|
582
|
+
: parsed._compactionRequest === true
|
|
583
|
+
? routeCompactionModel(config, modelId, evidenceFromBody(parsed._rawBody))
|
|
584
|
+
: routeModel(config, modelId, evidenceFromBody(parsed._rawBody));
|
|
585
|
+
// A Pool 2 selector may leave Pool 2 only through an operator-configured cross-provider
|
|
586
|
+
// blocked-model redirect, which the router marks as a credential-domain rewrite. Any other
|
|
587
|
+
// destination means the selector fell through to default resolution, which stays refused.
|
|
588
|
+
const operatorRedirect = resolved.routeReason === "blocked-model-redirect" && resolved.credentialDomainRewrite === true;
|
|
589
|
+
if (selectorTargetsSecondary(modelId) && resolved.providerName !== "anthropic2" && !operatorRedirect) {
|
|
590
|
+
throw new Error("Anthropic Pool 2 selector cannot use the default provider");
|
|
591
|
+
}
|
|
592
|
+
return resolved;
|
|
593
|
+
};
|
|
561
594
|
const resolveRoute = (modelId: string) => captureInboundRoutePolicy(routeInbound(modelId));
|
|
562
595
|
// The phase's destination. Resolved through the admission-scoped resolver every other route
|
|
563
596
|
// uses, and it fails closed exactly like the shadow target: falling back to the native model
|
|
@@ -644,6 +677,9 @@ export async function prepareResponsesRequest(
|
|
|
644
677
|
logCtx.routeDecision = route.routeDecision;
|
|
645
678
|
logCtx.policyEligibility = route.policyEligibility;
|
|
646
679
|
} catch (err) {
|
|
680
|
+
if (err instanceof AnthropicSecondaryInstanceUnavailableError) {
|
|
681
|
+
return formatErrorResponse(401, "authentication_error", err.message);
|
|
682
|
+
}
|
|
647
683
|
const policyRefusal = policyCandidateRefusalResponse(err);
|
|
648
684
|
if (policyRefusal) return policyRefusal;
|
|
649
685
|
if (err instanceof AdmissionModelDeniedError) return admissionModelDeniedResponse(err);
|