@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,3 +1,5 @@
|
|
|
1
|
+
import { createPhysicalSendReporter } from "../lib/request-execution-budget";
|
|
2
|
+
import { createInferenceSendBudget } from "./inference/context";
|
|
1
3
|
/**
|
|
2
4
|
* Managed native Messages lane (PF-08, behind `protocols.rollout.managedMessagesNative`).
|
|
3
5
|
*
|
|
@@ -17,32 +19,36 @@
|
|
|
17
19
|
*
|
|
18
20
|
* Loaded lazily by `claude-messages.ts` only when the switch is on and a route is eligible.
|
|
19
21
|
*/
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
22
|
+
import { resolveAnthropicModelRouteForInstance } from "../oauth/anthropic-model-routes";
|
|
23
|
+
import { bindAnthropicRefusalCredentialForSend, rotateAnthropicAccountOnResponseForInstance } from "../oauth/anthropic-account-refusal";
|
|
24
|
+
import { captureAnthropicPhysicalSendOwnership, anthropicPhysicalSendOwnershipIsCurrent,
|
|
25
|
+
type AnthropicPhysicalSendOwnership } from "../oauth/anthropic-send-ownership";
|
|
26
|
+
import { anthropicModelQuotaFor } from "../oauth/anthropic-model-quota";
|
|
23
27
|
import { captureConfigGeneration } from "../lib/state-store-sweeper";
|
|
24
|
-
import {
|
|
28
|
+
import { recordAnthropicAccountQuotaFromHeadersForInstance } from "../providers/quota";
|
|
25
29
|
import { credentialGeneration, getAccountCredentialWithStatus } from "../oauth/store";
|
|
26
30
|
import { ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST } from "../oauth/anthropic-routing";
|
|
27
31
|
import { enforceAnthropicImageLimits } from "../adapters/anthropic-image-guard";
|
|
28
32
|
import { normalizeAnthropicImages } from "../adapters/anthropic-image-normalize";
|
|
29
|
-
import { formatAnthropicErrorBody } from "../adapters/anthropic";
|
|
33
|
+
import { formatAnthropicErrorBody, resolveAnthropicMessagesUrl } from "../adapters/anthropic";
|
|
30
34
|
import {
|
|
31
35
|
anthropicMessagesNativeWireBody,
|
|
32
36
|
buildAnthropicMessagesPassthroughRequest,
|
|
33
37
|
type AnthropicMessagesPassthroughRequest,
|
|
34
38
|
} from "../adapters/anthropic/passthrough";
|
|
35
|
-
import { resolveInboundModel } from "../claude/inbound";
|
|
36
|
-
import { anthropicErrorBody, anthropicErrorResponse, collectAnthropicMessage } from "../claude/outbound";
|
|
39
|
+
import { effortFromOutputConfig, resolveInboundModel } from "../claude/inbound";
|
|
40
|
+
import { anthropicErrorBody, anthropicErrorResponse, claudeOverflowSsePayload, claudePromptTooLongMessage, collectAnthropicMessage, isContextOverflowText, isThroughputLimitText } from "../claude/outbound";
|
|
37
41
|
import type { AdmissionLease } from "../lib/admission";
|
|
38
42
|
import { readBoundedResponseBody } from "../lib/bounded-body";
|
|
39
43
|
import { classifyError } from "../lib/errors";
|
|
44
|
+
import { relaySseWithPayloadRewrite } from "./sse-payload-rewrite";
|
|
40
45
|
import { redactSecretString } from "../lib/redact";
|
|
41
46
|
import { resolveClientRetryAfter } from "../lib/retry-after";
|
|
42
47
|
import { isTranslatorBudgetExceededError, type TranslatorBudget } from "../lib/translator-budget";
|
|
43
48
|
import {
|
|
44
49
|
applyReplayRefusalClientHeaders,
|
|
45
50
|
applyUpstreamRecoveryInit,
|
|
51
|
+
SendBudgetExhaustedError,
|
|
46
52
|
fetchWithResetRetry,
|
|
47
53
|
fetchWithTransientRetry,
|
|
48
54
|
isNonReplayableResponse,
|
|
@@ -67,12 +73,13 @@ import {
|
|
|
67
73
|
} from "../providers/key-failover";
|
|
68
74
|
import { stampApiKeyAccountLabel } from "../providers/label";
|
|
69
75
|
import { OAuthAccountPausedError, OAuthLoginRequiredError, publicOAuthAuthenticationErrorMessage } from "../oauth";
|
|
70
|
-
import { AnthropicAccountCooldownError, formatAnthropicProviderForLog,
|
|
76
|
+
import { AnthropicAccountCooldownError, formatAnthropicProviderForLog, anthropicRoutingFor } from "../oauth/anthropic-routing";
|
|
77
|
+
import { configuredAnthropicInstance } from "../providers/anthropic-instance";
|
|
71
78
|
import { resolveProtocolSettings } from "../protocols/settings";
|
|
72
79
|
import { addProtocolEntryReason, markProtocolBlocked } from "../protocols/trace";
|
|
73
80
|
import type { OcxProviderTransport } from "../providers/xai-transport";
|
|
74
81
|
import { preservesPhysicalComboProvider, resolveComboId } from "../combos";
|
|
75
|
-
import { captureRouteStaticPolicy, routeModel, type RouteResult } from "../router";
|
|
82
|
+
import { captureRouteStaticPolicy, routedProviderConfig, routeModel, type RouteResult } from "../router";
|
|
76
83
|
import { POLICY_NAMESPACE, resolvePolicyProfileId } from "../routing/profile";
|
|
77
84
|
import type { AnthropicClientIdentity } from "../adapters/anthropic/client-identity";
|
|
78
85
|
import type { OcxConfig, OcxProviderConfig, OcxUsage } from "../types";
|
|
@@ -88,11 +95,12 @@ import { beginInferenceAttempt } from "./inference/attempt";
|
|
|
88
95
|
import { createFinalRequestLog, type FinalRequestLogMeta } from "./inference/final-log";
|
|
89
96
|
import { registerTurn, unregisterTurn } from "./lifecycle";
|
|
90
97
|
import { nativeMessagesDeclineReason, type NativeMessagesSelector } from "./messages-native-eligibility";
|
|
98
|
+
import { messagesSecondaryInstanceUnavailable } from "./messages-native-selector";
|
|
91
99
|
import {
|
|
92
100
|
nativeOAuthBindingIsCurrent,
|
|
93
101
|
NativeOAuthSelectionChangedError,
|
|
94
102
|
NativeOAuthModelRouteError,
|
|
95
|
-
|
|
103
|
+
resolveNativeOAuthBindingForInstance,
|
|
96
104
|
restoreOAuthToolNamesInMessage,
|
|
97
105
|
restoreOAuthToolNamesInSse,
|
|
98
106
|
type NativeOAuthBinding,
|
|
@@ -100,6 +108,7 @@ import {
|
|
|
100
108
|
import {
|
|
101
109
|
noteProviderAttemptSend,
|
|
102
110
|
recordAttemptCredentialSource,
|
|
111
|
+
recordAttemptRequestedEffort,
|
|
103
112
|
recordFirstOutput,
|
|
104
113
|
recordKeyAttemptFailure,
|
|
105
114
|
recordKeyWireAttemptUsage,
|
|
@@ -112,6 +121,7 @@ import { workflowRefusalResponse } from "./workflow-refusal";
|
|
|
112
121
|
import { sseFieldValue } from "../lib/sse-decoder";
|
|
113
122
|
import { admissionModelDeniedResponse, type AdmissionModelScope } from "./admission-model-scope";
|
|
114
123
|
import { nativeMessagesToolScopeDenial } from "./messages-native-scope";
|
|
124
|
+
import { retainUpstreamMessagesRequestId } from "./messages-response-headers";
|
|
115
125
|
|
|
116
126
|
export {
|
|
117
127
|
isNativeMessagesRouteEligible,
|
|
@@ -300,14 +310,25 @@ async function prepareNativeBody(body: Rec, signal: AbortSignal): Promise<void>
|
|
|
300
310
|
*/
|
|
301
311
|
export async function handleNativeMessages(options: HandleNativeMessagesOptions): Promise<Response> {
|
|
302
312
|
const { req, config, logCtx, logIds, route, body, requestedModel, translatorBudget } = options;
|
|
313
|
+
const nativeInstance = configuredAnthropicInstance(config, route.providerName);
|
|
303
314
|
const requestedStream = body.stream === true;
|
|
304
315
|
logCtx.inboundProtocol = "messages";
|
|
305
316
|
logCtx.model = route.modelId;
|
|
306
317
|
logCtx.provider = route.providerName;
|
|
318
|
+
logCtx.spendPoolId = route.providerName;
|
|
307
319
|
logCtx.providerAdapter = route.provider.adapter;
|
|
308
320
|
logCtx.requestedModel = requestedModel;
|
|
309
321
|
if (route.routeReason === "model-alias" || route.modelId !== requestedModel) logCtx.requestedAlias = requestedModel;
|
|
310
322
|
logCtx.requestedServiceTier = typeof body.service_tier === "string" ? body.service_tier : undefined;
|
|
323
|
+
if (logCtx.requestedEffort === undefined) {
|
|
324
|
+
const effort = effortFromOutputConfig(body.output_config);
|
|
325
|
+
const thinking = isRec(body.thinking) ? body.thinking : undefined;
|
|
326
|
+
const budget = thinking?.budget_tokens;
|
|
327
|
+
if (effort !== undefined) logCtx.requestedEffort = effort;
|
|
328
|
+
else if (thinking?.type === "disabled") logCtx.requestedEffort = "none";
|
|
329
|
+
else if (thinking?.type === "enabled" && typeof budget === "number"
|
|
330
|
+
&& Number.isSafeInteger(budget) && budget > 0) logCtx.requestedEffort = `budget:${budget}`;
|
|
331
|
+
}
|
|
311
332
|
// Reserve spend the way native Chat does: an input estimate that never enters usage, and the
|
|
312
333
|
// caller's own output ceiling.
|
|
313
334
|
if (logCtx.usageLogInputTokens === undefined) {
|
|
@@ -323,6 +344,7 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
323
344
|
adapter: "anthropic",
|
|
324
345
|
});
|
|
325
346
|
attemptHandle.seal(logCtx.accountLogLabel);
|
|
347
|
+
recordAttemptRequestedEffort(logCtx);
|
|
326
348
|
const { attempt } = attemptHandle;
|
|
327
349
|
const finalLog = createFinalRequestLog(logIds, logCtx);
|
|
328
350
|
const finishLog: FinishLog = (status, message, meta = { closeReason: "non_stream" }) => {
|
|
@@ -353,11 +375,18 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
353
375
|
return null;
|
|
354
376
|
};
|
|
355
377
|
|
|
378
|
+
if (route.provider.authMode === "oauth" && !nativeInstance) {
|
|
379
|
+
return fail(401, "Configured Anthropic OAuth instance is unavailable", "authentication_error");
|
|
380
|
+
}
|
|
381
|
+
|
|
356
382
|
const toolScopeDenial = nativeMessagesToolScopeDenial(options.modelScope, route.providerName, requestedModel, body);
|
|
357
383
|
if (toolScopeDenial) {
|
|
358
384
|
finishLog(403, toolScopeDenial.message);
|
|
359
385
|
return admissionModelDeniedResponse(toolScopeDenial);
|
|
360
386
|
}
|
|
387
|
+
let routeTarget: string;
|
|
388
|
+
try { routeTarget = resolveAnthropicMessagesUrl(route.provider); }
|
|
389
|
+
catch { return fail(400, "Invalid Anthropic Messages destination", "invalid_request_error"); }
|
|
361
390
|
|
|
362
391
|
try {
|
|
363
392
|
await prepareNativeBody(body, req.signal);
|
|
@@ -391,7 +420,7 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
391
420
|
const oauthProvider = (binding: NativeOAuthBinding): OcxProviderConfig => ({ ...route.provider, apiKey: binding.snapshot.accessToken });
|
|
392
421
|
if (route.provider.authMode === "oauth") {
|
|
393
422
|
try {
|
|
394
|
-
oauthBinding = await
|
|
423
|
+
oauthBinding = await resolveNativeOAuthBindingForInstance(nativeInstance!, config, { routeTarget, sessionKey: options.sessionKey, model: route.modelId });
|
|
395
424
|
} catch (error) {
|
|
396
425
|
cleanupAbort();
|
|
397
426
|
upstream.abort();
|
|
@@ -411,6 +440,7 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
411
440
|
stampApiKeyAccountLabel(logCtx, route.providerName, activeProvider);
|
|
412
441
|
if (oauthBinding) logCtx.provider = formatAnthropicProviderForLog(route.providerName, oauthBinding.snapshot.accountId, config);
|
|
413
442
|
const spendTracker = attachRequestSpendTracker(req, logCtx);
|
|
443
|
+
const physicalBudget = createInferenceSendBudget(req, logCtx);
|
|
414
444
|
let activeRequest: AnthropicMessagesPassthroughRequest;
|
|
415
445
|
let retainedRequestBytes = 0;
|
|
416
446
|
const releaseRetainedRequest = () => {
|
|
@@ -473,8 +503,17 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
473
503
|
: Number.POSITIVE_INFINITY;
|
|
474
504
|
const transientSendAvailable = (): boolean => remainingTransientSends() > 0;
|
|
475
505
|
const selector: NativeMessagesSelector = options.selector ?? {};
|
|
506
|
+
const nativeOAuthRouteIsCurrent = (binding: NativeOAuthBinding): boolean => {
|
|
507
|
+
const provider = config.providers[binding.instance];
|
|
508
|
+
if (configuredAnthropicInstance(config, binding.instance) !== binding.instance || !provider) return false;
|
|
509
|
+
// Same normalization as the bound target (nativeOAuthRouteTarget): compare routed destinations.
|
|
510
|
+
let routed: OcxProviderConfig;
|
|
511
|
+
try { routed = routedProviderConfig(binding.instance, provider); } catch { return false; }
|
|
512
|
+
return resolveAnthropicMessagesUrl(routed) === routeTarget
|
|
513
|
+
&& nativeMessagesDeclineReason({ ...route, provider: routed }, body, config, selector) === undefined;
|
|
514
|
+
};
|
|
476
515
|
|
|
477
|
-
const send = async (recovery?: "rate-limit-429" | "oauth-account-403" | "key-429" | "key-401"): Promise<Response> => {
|
|
516
|
+
const send = async (recovery?: "rate-limit-429" | "oauth-401" | "oauth-account-403" | "key-429" | "key-401"): Promise<Response> => {
|
|
478
517
|
const remaining = remainingTransientSends();
|
|
479
518
|
if (requestTransientPolicy && remaining <= 0) {
|
|
480
519
|
throw new Error("native Messages transient send budget exhausted before recovery dispatch");
|
|
@@ -493,12 +532,13 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
493
532
|
modelId: route.modelId,
|
|
494
533
|
dispatchOverride: async (_input, init, execute) => {
|
|
495
534
|
if (oauthBinding) {
|
|
535
|
+
if (!nativeOAuthRouteIsCurrent(oauthBinding)) throw new NativeOAuthSelectionChangedError();
|
|
496
536
|
// The OAuth twin of the key check below: re-resolve through the same selection
|
|
497
537
|
// owner when the committed account or its credential moved since the build.
|
|
498
538
|
for (let attempt = 0; !nativeOAuthBindingIsCurrent(oauthBinding); attempt++) {
|
|
499
539
|
if (attempt >= 3) throw new NativeOAuthSelectionChangedError();
|
|
500
540
|
try {
|
|
501
|
-
oauthBinding = await
|
|
541
|
+
oauthBinding = await resolveNativeOAuthBindingForInstance(nativeInstance!, config, { routeTarget, sessionKey: options.sessionKey, model: route.modelId });
|
|
502
542
|
} catch (error) {
|
|
503
543
|
if (error instanceof OAuthAccountPausedError || error instanceof OAuthLoginRequiredError
|
|
504
544
|
|| error instanceof AnthropicAccountCooldownError || error instanceof NativeOAuthModelRouteError) throw error;
|
|
@@ -519,31 +559,58 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
519
559
|
const headers = new Headers(wire.headers);
|
|
520
560
|
const encoding = new Headers(init.headers).get("accept-encoding");
|
|
521
561
|
if (!headers.has("accept-encoding") && encoding) headers.set("accept-encoding", encoding);
|
|
522
|
-
const
|
|
523
|
-
const
|
|
562
|
+
const sendingBinding = oauthBinding;
|
|
563
|
+
const snapshot = sendingBinding?.snapshot;
|
|
564
|
+
const providerAccountUuid = sendingBinding?.providerAccountUuid;
|
|
524
565
|
const writerGeneration = snapshot ? captureConfigGeneration() : 0;
|
|
525
|
-
const ownsBearer = snapshot &&
|
|
526
|
-
|
|
566
|
+
const ownsBearer = snapshot && snapshot.provider === nativeInstance
|
|
567
|
+
&& headers.get("authorization") === `Bearer ${snapshot.accessToken}` && !headers.has("x-api-key");
|
|
568
|
+
if (oauthBinding && (!ownsBearer || wire.url !== oauthBinding.routeTarget
|
|
569
|
+
|| !nativeOAuthRouteIsCurrent(oauthBinding) || !nativeOAuthBindingIsCurrent(oauthBinding))) {
|
|
570
|
+
throw new NativeOAuthSelectionChangedError();
|
|
571
|
+
}
|
|
572
|
+
const releaseFamily = snapshot && oauthBinding ? anthropicModelQuotaFor(oauthBinding.instance).claimAnthropicFamilyRevalidation(snapshot.accountId, route.modelId) : () => {};
|
|
527
573
|
if (!releaseFamily) throw new AnthropicAccountCooldownError(1);
|
|
528
574
|
let dispatched: Response;
|
|
575
|
+
const spendReport = createPhysicalSendReporter(physicalBudget, () => ({
|
|
576
|
+
poolId: logCtx.spendPoolId ?? route.providerName, identityId: logCtx.accountLogLabel,
|
|
577
|
+
}));
|
|
578
|
+
let sendOwner: AnthropicPhysicalSendOwnership | null = null;
|
|
529
579
|
try {
|
|
530
580
|
if (init.signal?.aborted) throw init.signal.reason;
|
|
531
|
-
|
|
581
|
+
// Capture before either accounting path so this request retains its start policy.
|
|
582
|
+
if (spendReport.beforeSend && !spendReport.beforeSend()) {
|
|
583
|
+
if (spendTracker.refusals) throw new NativeMessagesSpendRefusal();
|
|
584
|
+
throw new SendBudgetExhaustedError();
|
|
585
|
+
}
|
|
586
|
+
if (!physicalBudget.spendEnforced && !spendTracker.charge()) throw new NativeMessagesSpendRefusal();
|
|
532
587
|
noteProviderAttemptSend(logCtx, route.providerName, activeProvider, logCtx.usageLogInputTokens, transportRecovery ?? recovery);
|
|
588
|
+
// Last synchronous ownership reservation before the physical fetch. Retain this
|
|
589
|
+
// exact incarnation across its await; a returned response can never reserve one.
|
|
590
|
+
sendOwner = snapshot ? captureAnthropicPhysicalSendOwnership(snapshot) : null;
|
|
591
|
+
if (snapshot && !sendOwner) throw new NativeOAuthSelectionChangedError();
|
|
533
592
|
dispatched = await sendWithConnectionPolicy(
|
|
534
593
|
(activeProvider as OcxProviderTransport).fetch ?? execute,
|
|
535
594
|
wire.url,
|
|
536
595
|
applyUpstreamRecoveryInit({ ...init, method: "POST", headers, body: wire.body }, transportRecovery),
|
|
537
596
|
{ providerName: route.providerName, provider: activeProvider },
|
|
538
597
|
);
|
|
539
|
-
} finally {
|
|
540
|
-
|
|
598
|
+
} finally {
|
|
599
|
+
try { if (physicalBudget.spendEnforced) spendReport(1); }
|
|
600
|
+
finally {
|
|
601
|
+
try { spendReport.close?.(); }
|
|
602
|
+
finally { releaseFamily(); }
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (ownsBearer && snapshot && sendOwner && sendingBinding) {
|
|
541
606
|
try {
|
|
542
|
-
const current = getAccountCredentialWithStatus(
|
|
543
|
-
if (
|
|
607
|
+
const current = getAccountCredentialWithStatus(snapshot.provider, snapshot.accountId);
|
|
608
|
+
if (anthropicPhysicalSendOwnershipIsCurrent(sendOwner) && nativeOAuthRouteIsCurrent(sendingBinding)
|
|
609
|
+
&& current && !current.needsReauth && current.credential.access === snapshot.accessToken
|
|
610
|
+
&& credentialGeneration(current.credential) === snapshot.generation
|
|
544
611
|
&& current.credential.accountId === providerAccountUuid) {
|
|
545
|
-
|
|
546
|
-
|
|
612
|
+
bindAnthropicRefusalCredentialForSend(dispatched, sendOwner, providerAccountUuid);
|
|
613
|
+
recordAnthropicAccountQuotaFromHeadersForInstance(nativeInstance!, snapshot.accountId, dispatched.headers, writerGeneration, dispatched.status, route.modelId);
|
|
547
614
|
}
|
|
548
615
|
} catch { /* Passive observation must not fail the response. */ }
|
|
549
616
|
}
|
|
@@ -618,16 +685,17 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
618
685
|
const oauthRetryKey = {};
|
|
619
686
|
const triedAccountIds = new Set<string>();
|
|
620
687
|
let oauthFailovers = 0;
|
|
621
|
-
while (oauthBinding && (response.status === 429 || response.status === 403)) {
|
|
688
|
+
while (oauthBinding && (response.status === 429 || response.status === 403 || response.status === 401)) {
|
|
622
689
|
const sendingBinding = oauthBinding;
|
|
623
690
|
const expectedRecoverySelection = sendingBinding.selection;
|
|
624
691
|
triedAccountIds.add(sendingBinding.snapshot.accountId);
|
|
625
|
-
const nextAccountId = await
|
|
692
|
+
const nextAccountId = await rotateAnthropicAccountOnResponseForInstance(sendingBinding.instance, response, {
|
|
626
693
|
config, accountId: sendingBinding.snapshot.accountId, model: route.modelId,
|
|
627
694
|
sessionKey: sendingBinding.sessionKey, decision: sendingBinding.routeDecision,
|
|
628
695
|
excludedAccountIds: triedAccountIds,
|
|
629
696
|
currentDecision: () => {
|
|
630
|
-
|
|
697
|
+
if (!nativeOAuthRouteIsCurrent(sendingBinding)) throw new NativeOAuthSelectionChangedError();
|
|
698
|
+
const resolved = resolveAnthropicModelRouteForInstance(sendingBinding.instance, config, route.modelId);
|
|
631
699
|
if (resolved.error) throw new NativeOAuthModelRouteError(`Invalid Anthropic model routes: ${resolved.error}`);
|
|
632
700
|
return resolved.decision;
|
|
633
701
|
},
|
|
@@ -635,9 +703,9 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
635
703
|
canRetry: transientSendAvailable() && oauthFailovers < ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
|
|
636
704
|
});
|
|
637
705
|
if (!nextAccountId) break;
|
|
638
|
-
const recovery = response.status === 403 ? "oauth-account-403" : "rate-limit-429";
|
|
706
|
+
const recovery = response.status === 401 ? "oauth-401" : response.status === 403 ? "oauth-account-403" : "rate-limit-429";
|
|
639
707
|
discard(response);
|
|
640
|
-
oauthBinding = await
|
|
708
|
+
oauthBinding = await resolveNativeOAuthBindingForInstance(nativeInstance!, config, { routeTarget,
|
|
641
709
|
sessionKey: options.sessionKey, model: route.modelId, candidateAccountId: nextAccountId,
|
|
642
710
|
expectedRecoverySelection, expectedRecoveryRouteDecision: sendingBinding.routeDecision,
|
|
643
711
|
});
|
|
@@ -652,6 +720,7 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
652
720
|
upstream.abort();
|
|
653
721
|
if (req.signal.aborted) return fail(499, "Client cancelled request", "api_error");
|
|
654
722
|
const sendError = error instanceof UpstreamRetryEvidenceError ? error.cause : error;
|
|
723
|
+
if (sendError instanceof SendBudgetExhaustedError) return fail(429, sendError.message, "send_budget_exhausted", "send_budget_exhausted");
|
|
655
724
|
if (sendError instanceof NativeMessagesSpendRefusal) {
|
|
656
725
|
const refusal = workflowRefusalResponse("workflow-spend-exhausted", logCtx);
|
|
657
726
|
finishLog(429);
|
|
@@ -683,7 +752,7 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
683
752
|
upstream.abort();
|
|
684
753
|
return fail(499, "Client cancelled request", "api_error");
|
|
685
754
|
}
|
|
686
|
-
return nativeMessagesErrorResponse(response, bodyText, finishLog);
|
|
755
|
+
return retainUpstreamMessagesRequestId(nativeMessagesErrorResponse(response, bodyText, finishLog), response.headers);
|
|
687
756
|
}
|
|
688
757
|
|
|
689
758
|
const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
|
|
@@ -708,10 +777,15 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
708
777
|
releaseStreamTurn();
|
|
709
778
|
}
|
|
710
779
|
}, bodyGuard);
|
|
711
|
-
|
|
780
|
+
// A configured Messages provider words an oversized-input refusal its own way; Claude Code
|
|
781
|
+
// compacts only on Anthropic's wording (devlog/_plan/261009_claude_1m_default/010). The
|
|
782
|
+
// rewrite buffers whole frames, so it sits after the tap: stall detection keeps timing raw
|
|
783
|
+
// upstream bytes. Real Anthropic pools refuse pre-stream in that wording and keep one relay.
|
|
784
|
+
const clientStream = nativeInstance ? relayed : relaySseWithPayloadRewrite(relayed, claudeOverflowSsePayload, translatorBudget);
|
|
785
|
+
return retainUpstreamMessagesRequestId(new Response(clientStream, {
|
|
712
786
|
status: 200,
|
|
713
787
|
headers: { "Content-Type": "text/event-stream; charset=utf-8", "Cache-Control": "no-cache", Connection: "keep-alive" },
|
|
714
|
-
});
|
|
788
|
+
}), response.headers);
|
|
715
789
|
}
|
|
716
790
|
// A non-streaming caller whose upstream streamed anyway: fold the stream into one message.
|
|
717
791
|
const tapState: { closeReason?: FinalRequestLogMeta["closeReason"]; meta?: FinalRequestLogMeta } = {};
|
|
@@ -733,10 +807,11 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
733
807
|
// as the streaming lane's row; a plain upstream error event keeps the non_stream row.
|
|
734
808
|
const tapMeta = tapState.meta;
|
|
735
809
|
if (tapMeta && (tapMeta.terminalStatus || tapMeta.closeReason !== "terminal")) finishLog(502, text, tapMeta);
|
|
810
|
+
if (isNativeOverflowError(error, text)) return fail(400, claudePromptTooLongMessage(text), "invalid_request_error");
|
|
736
811
|
return fail(502, text, "api_error");
|
|
737
812
|
}
|
|
738
813
|
finishLog(200);
|
|
739
|
-
return Response.json(message);
|
|
814
|
+
return retainUpstreamMessagesRequestId(Response.json(message), response.headers);
|
|
740
815
|
} catch (error) {
|
|
741
816
|
cleanupAbort();
|
|
742
817
|
upstream.abort();
|
|
@@ -794,12 +869,19 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
794
869
|
const serialized = JSON.stringify(message);
|
|
795
870
|
finishLog(200);
|
|
796
871
|
if (requestedStream) {
|
|
797
|
-
return new Response(messageAsSse(message), {
|
|
872
|
+
return retainUpstreamMessagesRequestId(new Response(messageAsSse(message), {
|
|
798
873
|
status: 200,
|
|
799
874
|
headers: { "Content-Type": "text/event-stream; charset=utf-8", "Cache-Control": "no-cache" },
|
|
800
|
-
});
|
|
875
|
+
}), response.headers);
|
|
801
876
|
}
|
|
802
|
-
return new Response(serialized, { status: 200, headers: { "Content-Type": "application/json" } });
|
|
877
|
+
return retainUpstreamMessagesRequestId(new Response(serialized, { status: 200, headers: { "Content-Type": "application/json" } }), response.headers);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/** A folded stream error that refuses an oversized input (same gate as the SSE rewrite). */
|
|
881
|
+
function isNativeOverflowError(error: Rec, message: string): boolean {
|
|
882
|
+
const sized = error.type === "invalid_request_error" || error.type === "request_too_large";
|
|
883
|
+
return sized && !isThroughputLimitText(message)
|
|
884
|
+
&& (error.code === "context_length_exceeded" || isContextOverflowText(message));
|
|
803
885
|
}
|
|
804
886
|
|
|
805
887
|
/**
|
|
@@ -809,11 +891,13 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
809
891
|
*/
|
|
810
892
|
function nativeMessagesErrorResponse(response: Response, bodyText: string, finishLog: FinishLog): Response {
|
|
811
893
|
let upstreamType: string | undefined;
|
|
894
|
+
let upstreamCode: string | undefined;
|
|
812
895
|
let upstreamMessage: string | undefined;
|
|
813
896
|
try {
|
|
814
897
|
const parsed = JSON.parse(bodyText) as Rec;
|
|
815
898
|
const details = isRec(parsed.error) ? parsed.error : parsed;
|
|
816
899
|
if (typeof details.type === "string") upstreamType = details.type;
|
|
900
|
+
if (typeof details.code === "string") upstreamCode = details.code;
|
|
817
901
|
if (typeof details.message === "string" && details.message.trim()) {
|
|
818
902
|
upstreamMessage = redactSecretString(details.message.trim());
|
|
819
903
|
}
|
|
@@ -842,9 +926,17 @@ function nativeMessagesErrorResponse(response: Response, bodyText: string, finis
|
|
|
842
926
|
if (retryAfter) headers.set("Retry-After", retryAfter);
|
|
843
927
|
else if (transient) headers.set("Retry-After", "2");
|
|
844
928
|
if (replayRefusal) applyReplayRefusalClientHeaders(headers);
|
|
929
|
+
// The native envelope keeps its shape (no added code); only an overflow's wording changes, so
|
|
930
|
+
// Claude Code recognizes it and compacts (010).
|
|
931
|
+
// Only a 400/413 refusal: the shared classifier also files a 429 "too many tokens per minute"
|
|
932
|
+
// under context_length_exceeded, and that one must stay a rate limit.
|
|
933
|
+
const overflow = !replayRefusal && (response.status === 400 || response.status === 413)
|
|
934
|
+
&& !isThroughputLimitText(safeMessage)
|
|
935
|
+
&& (classified.code === "context_length_exceeded" || upstreamCode === "context_length_exceeded"
|
|
936
|
+
|| isContextOverflowText(safeMessage));
|
|
845
937
|
const out = new Response(JSON.stringify(anthropicErrorBody(
|
|
846
938
|
status,
|
|
847
|
-
safeMessage,
|
|
939
|
+
overflow ? claudePromptTooLongMessage(safeMessage) : safeMessage,
|
|
848
940
|
transient ? "overloaded_error" : upstreamType,
|
|
849
941
|
replayRefusal ? UPSTREAM_RESET_REPLAY_REFUSED_CODE : undefined,
|
|
850
942
|
)), { status, headers });
|
|
@@ -865,6 +957,7 @@ export function nativeMessagesCountBody(
|
|
|
865
957
|
): Rec | undefined {
|
|
866
958
|
if (typeof body.model !== "string") return undefined;
|
|
867
959
|
try {
|
|
960
|
+
if (messagesSecondaryInstanceUnavailable(config, body.model, cc)) return undefined;
|
|
868
961
|
const selectorId = resolveInboundModel(body.model, cc);
|
|
869
962
|
// Combo and policy selectors are never eligible, and routing them would advance round-robin
|
|
870
963
|
// state or run the policy evaluator for a request that sends nothing.
|
|
@@ -875,11 +968,13 @@ export function nativeMessagesCountBody(
|
|
|
875
968
|
route.providerName, route.modelId, route.provider, route.staticPolicy.effectiveAlias, "anthropic",
|
|
876
969
|
);
|
|
877
970
|
route.provider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, "anthropic", route.staticPolicy);
|
|
971
|
+
const instance = configuredAnthropicInstance(config, route.providerName);
|
|
878
972
|
const selector: NativeMessagesSelector = {
|
|
879
973
|
...rows,
|
|
880
974
|
routeSelector: selectorId,
|
|
881
975
|
claudeCode: cc,
|
|
882
|
-
...(route.provider.authMode === "oauth"
|
|
976
|
+
...(route.provider.authMode === "oauth" && instance
|
|
977
|
+
? { oauthFailoverQuorum: anthropicRoutingFor(instance).hasAnthropicFailoverQuorum() } : {}),
|
|
883
978
|
};
|
|
884
979
|
if (nativeMessagesDeclineReason(route, body, config, selector) !== undefined) return undefined;
|
|
885
980
|
// The body without a credential: counting never resolves or refreshes an OAuth account.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Only an opaque Anthropic request id is carried; arbitrary upstream headers stay private. */
|
|
2
|
+
export function upstreamMessagesRequestIdHeaders(upstream: Headers): Record<string, string> {
|
|
3
|
+
const requestId = upstream.get("request-id");
|
|
4
|
+
return requestId && /^req_[A-Za-z0-9_-]{1,128}$/.test(requestId)
|
|
5
|
+
? { "request-id": requestId }
|
|
6
|
+
: {};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** The response is newly constructed by the Messages handler; preserve its body and markers. */
|
|
10
|
+
export function retainUpstreamMessagesRequestId(response: Response, upstream: Headers): Response {
|
|
11
|
+
const requestId = upstreamMessagesRequestIdHeaders(upstream)["request-id"];
|
|
12
|
+
if (requestId) response.headers.set("request-id", requestId);
|
|
13
|
+
return response;
|
|
14
|
+
}
|
|
@@ -37,6 +37,47 @@ export function normalizeLogConversationId(raw: string | undefined | null): stri
|
|
|
37
37
|
return createHash("sha256").update(trimmed).digest("hex").slice(0, LOG_CONVERSATION_ID_LEN);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
const CODEX_THREAD_LINK_PREFIX = "codex://threads/";
|
|
41
|
+
/** Real thread links stay well under this bound; larger pastes skip unwrapping so per-row matching stays cheap. */
|
|
42
|
+
const LOG_CONVERSATION_LINK_MAX = 512;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Unwrap a pasted `codex://threads/<id>` deep link to the bare thread id. Codex's
|
|
46
|
+
* "copy session link" clipboard carries that URI form while clients send the bare id
|
|
47
|
+
* on the wire, so Logs search accepts either paste without changing what is persisted.
|
|
48
|
+
* Query/fragment metadata (e.g. `?hostId=…`) belongs to the link, not the id, and is
|
|
49
|
+
* dropped. The scan is a bounded linear walk — no ambiguous regex — because this runs
|
|
50
|
+
* per log row on every filter evaluation.
|
|
51
|
+
*/
|
|
52
|
+
export function unwrapLogConversationQuery(query: string): string {
|
|
53
|
+
const trimmed = query.trim();
|
|
54
|
+
if (
|
|
55
|
+
trimmed.length > LOG_CONVERSATION_LINK_MAX ||
|
|
56
|
+
trimmed.length <= CODEX_THREAD_LINK_PREFIX.length ||
|
|
57
|
+
!trimmed.toLowerCase().startsWith(CODEX_THREAD_LINK_PREFIX)
|
|
58
|
+
) {
|
|
59
|
+
return trimmed;
|
|
60
|
+
}
|
|
61
|
+
const rest = trimmed.slice(CODEX_THREAD_LINK_PREFIX.length);
|
|
62
|
+
const metaIndex = rest.search(/[?#]/);
|
|
63
|
+
const segment = (metaIndex === -1 ? rest : rest.slice(0, metaIndex)).replace(/\/+$/, "");
|
|
64
|
+
// A thread id is a single path segment; deeper paths or embedded whitespace keep the
|
|
65
|
+
// whole paste as the query, so foreign `codex://` URIs still fail to match.
|
|
66
|
+
return segment !== "" && !/[/\s]/.test(segment) ? segment : trimmed;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The candidate ids a conversation query may legitimately match. Unwrapping a pasted
|
|
71
|
+
* `codex://threads/<id>` link must add a match path, not replace one: a client can
|
|
72
|
+
* literally send a `codex://threads/…` session id, which is hashed whole for storage,
|
|
73
|
+
* so the untouched paste stays a candidate too.
|
|
74
|
+
*/
|
|
75
|
+
export function logConversationQueryCandidates(query: string): string[] {
|
|
76
|
+
const trimmed = query.trim();
|
|
77
|
+
const unwrapped = unwrapLogConversationQuery(trimmed);
|
|
78
|
+
return unwrapped === trimmed ? [unwrapped] : [unwrapped, trimmed];
|
|
79
|
+
}
|
|
80
|
+
|
|
40
81
|
/**
|
|
41
82
|
* Filter match: accept either the persisted digest or the original preimage
|
|
42
83
|
* (so pasting from Logs detail or the client-facing session id both work).
|
|
@@ -46,11 +87,13 @@ export function matchesLogConversationId(
|
|
|
46
87
|
query: string | undefined | null,
|
|
47
88
|
): boolean {
|
|
48
89
|
if (!stored) return false;
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
90
|
+
for (const candidate of logConversationQueryCandidates(typeof query === "string" ? query : "")) {
|
|
91
|
+
if (!candidate) continue;
|
|
92
|
+
if (stored === candidate) return true;
|
|
93
|
+
const hashed = normalizeLogConversationId(candidate);
|
|
94
|
+
if (hashed !== undefined && stored === hashed) return true;
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
54
97
|
}
|
|
55
98
|
|
|
56
99
|
/**
|
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
type UsageStatus,
|
|
64
64
|
} from "../usage/log";
|
|
65
65
|
import type { RequestExecutionBudget } from "../lib/request-execution-budget";
|
|
66
|
+
import type { WorkflowSpendDenialDetail } from "../lib/workflow-budget";
|
|
66
67
|
import { attributeFinalRequest, attributeSealedAttempt } from "./request-log-failure-attribution";
|
|
67
68
|
import { debugAttemptDeliverySummary } from "../lib/debug";
|
|
68
69
|
import {
|
|
@@ -203,6 +204,10 @@ export interface RequestLogContext {
|
|
|
203
204
|
spendOutputCeilingTokens?: number;
|
|
204
205
|
/** Pre-send input estimate reserved for spend only; unlike usageLogInputTokens it never enters usage. */
|
|
205
206
|
spendInputEstimateTokens?: number;
|
|
207
|
+
/** Canonical provider-pool spend identity; independent of mutable, account-specific log labels. */
|
|
208
|
+
spendPoolId?: string;
|
|
209
|
+
/** Internal safe refusal detail for a request whose ceiling includes unidentified old pool spend. */
|
|
210
|
+
spendRefusalDetail?: WorkflowSpendDenialDetail;
|
|
206
211
|
/** Settles this request's durable spend entries from `addFinalRequestLog`. */
|
|
207
212
|
spendTracker?: RequestSpendSettlement;
|
|
208
213
|
attempts?: PersistedUsageAttempt[];
|
|
@@ -227,6 +232,8 @@ export interface RequestLogContext {
|
|
|
227
232
|
* message) extracted from a `response.failed` SSE payload or non-streaming error body, so the
|
|
228
233
|
* request log / GUI shows the actual upstream failure rather than only the HTTP-mapped code. */
|
|
229
234
|
upstreamError?: string;
|
|
235
|
+
upstreamErrorCode?: string;
|
|
236
|
+
upstreamRequestId?: string;
|
|
230
237
|
/** HTTP status derived from a terminal `response.failed` SSE payload (429/401/503/etc.). */
|
|
231
238
|
terminalHttpStatus?: number;
|
|
232
239
|
/** Recognized structured terminal code whose exact identity must survive status mapping. */
|
|
@@ -337,6 +344,8 @@ export interface RequestLogEntry {
|
|
|
337
344
|
closeReason?: "terminal" | "client_cancel" | "non_stream" | "body_stall" | "body_overflow";
|
|
338
345
|
/** Secret-redacted upstream error reason, surfaced in /api/logs and the GUI detail modal. */
|
|
339
346
|
upstreamError?: string;
|
|
347
|
+
upstreamErrorCode?: string;
|
|
348
|
+
upstreamRequestId?: string;
|
|
340
349
|
usageStatus: UsageStatus;
|
|
341
350
|
usage?: OcxUsage;
|
|
342
351
|
totalTokens?: number;
|
|
@@ -1090,6 +1099,24 @@ export function inspectResponseLogSsePayloadParsed(
|
|
|
1090
1099
|
* a non-streaming JSON error body. We keep the FIRST non-empty reason (the original failure) and
|
|
1091
1100
|
* run it through redactSecretString so secrets never reach /api/logs. Pure; safe on any text.
|
|
1092
1101
|
*/
|
|
1102
|
+
const UPSTREAM_DIAGNOSTIC_TOKEN = /^[A-Za-z0-9_.:-]{1,128}$/;
|
|
1103
|
+
|
|
1104
|
+
function noteBoundedDiagnostic(
|
|
1105
|
+
logCtx: RequestLogContext,
|
|
1106
|
+
field: "upstreamErrorCode" | "upstreamRequestId",
|
|
1107
|
+
value: unknown,
|
|
1108
|
+
): void {
|
|
1109
|
+
if (logCtx[field] !== undefined) return;
|
|
1110
|
+
if (typeof value !== "string" || !UPSTREAM_DIAGNOSTIC_TOKEN.test(value)) return;
|
|
1111
|
+
logCtx[field] = value;
|
|
1112
|
+
const status = logCtx.terminalHttpStatus;
|
|
1113
|
+
if (status === undefined || status >= 500) console.warn(`[opencodex] upstream failure${status ? ` status=${status}` : ""}${logCtx.upstreamErrorCode ? ` code=${logCtx.upstreamErrorCode}` : ""}${logCtx.upstreamRequestId ? ` request_id=${logCtx.upstreamRequestId}` : ""}`);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
export function noteUpstreamRequestId(logCtx: RequestLogContext, headers: Headers): void {
|
|
1117
|
+
noteBoundedDiagnostic(logCtx, "upstreamRequestId", headers.get("openai-request-id") ?? headers.get("x-request-id"));
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1093
1120
|
function captureUpstreamError(logCtx: RequestLogContext, text: string | null): void {
|
|
1094
1121
|
if (!text) return;
|
|
1095
1122
|
let parsed: unknown | undefined;
|
|
@@ -1109,14 +1136,19 @@ function captureUpstreamErrorParsed(
|
|
|
1109
1136
|
if (parsed !== undefined && parsed !== null) {
|
|
1110
1137
|
const json = parsed as {
|
|
1111
1138
|
type?: unknown;
|
|
1112
|
-
error?: { message?: unknown };
|
|
1113
|
-
last_error?: { message?: unknown };
|
|
1139
|
+
error?: { message?: unknown; code?: unknown };
|
|
1140
|
+
last_error?: { message?: unknown; code?: unknown };
|
|
1114
1141
|
response?: {
|
|
1115
1142
|
error?: { type?: unknown; code?: unknown; message?: unknown };
|
|
1116
1143
|
incomplete_details?: { reason?: unknown; message?: unknown };
|
|
1117
1144
|
};
|
|
1118
1145
|
};
|
|
1119
1146
|
captureTerminalHttpStatus(logCtx, json);
|
|
1147
|
+
noteBoundedDiagnostic(
|
|
1148
|
+
logCtx,
|
|
1149
|
+
"upstreamErrorCode",
|
|
1150
|
+
json.error?.code ?? json.response?.error?.code ?? json.last_error?.code,
|
|
1151
|
+
);
|
|
1120
1152
|
const reason = json?.response?.incomplete_details?.reason;
|
|
1121
1153
|
if (json.type === "response.incomplete"
|
|
1122
1154
|
&& logCtx.terminalIncompleteReason === undefined
|
|
@@ -1588,6 +1620,8 @@ export function addFinalRequestLog(
|
|
|
1588
1620
|
...(meta?.terminalStatus ? { terminalStatus: meta.terminalStatus } : {}),
|
|
1589
1621
|
...(closeReason ? { closeReason } : {}),
|
|
1590
1622
|
...(logCtx.upstreamError ? { upstreamError: logCtx.upstreamError } : {}),
|
|
1623
|
+
...(logCtx.upstreamErrorCode ? { upstreamErrorCode: logCtx.upstreamErrorCode } : {}),
|
|
1624
|
+
...(logCtx.upstreamRequestId ? { upstreamRequestId: logCtx.upstreamRequestId } : {}),
|
|
1591
1625
|
usageStatus,
|
|
1592
1626
|
...(loggedUsage ? { usage: loggedUsage } : {}),
|
|
1593
1627
|
...(totalTokens !== undefined ? { totalTokens } : {}),
|