@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
|
import { buildOpenAIChatPassthroughRequest, createOpenAIChatAdapter } from "../adapters/openai-chat";
|
|
2
4
|
import type { AdapterRequest, ProviderAdapter } from "../adapters/base";
|
|
3
5
|
import { isNativeChatRouteEligible } from "./chat-native-eligibility";
|
|
@@ -17,7 +19,7 @@ import {
|
|
|
17
19
|
isCyberPolicyMessage,
|
|
18
20
|
SEND_BUDGET_EXHAUSTED_CODE,
|
|
19
21
|
} from "../lib/errors";
|
|
20
|
-
import type
|
|
22
|
+
import { reportDispatchSends, type RequestExecutionBudget, type SingleUseDispatchPermit } from "../lib/request-execution-budget";
|
|
21
23
|
import type { AdmissionLease } from "../lib/admission";
|
|
22
24
|
import { readBoundedResponseBody } from "../lib/bounded-body";
|
|
23
25
|
import { redactSecretString } from "../lib/redact";
|
|
@@ -198,6 +200,7 @@ export interface NativeChatExecution extends HandleNativeChatOptions {
|
|
|
198
200
|
* the parent row, replace the one the final log settles.
|
|
199
201
|
*/
|
|
200
202
|
sendBudget?: RequestExecutionBudget;
|
|
203
|
+
comboDispatchPermit?: SingleUseDispatchPermit;
|
|
201
204
|
/** Replaces the request-relative first-output mark; a combo child records its own. */
|
|
202
205
|
onFirstOutput?: () => void;
|
|
203
206
|
/** The lease a streamed body holds; defaults to `logIds.turnAdmissionLease`. */
|
|
@@ -254,6 +257,7 @@ export function createNativeChatComboSource(input: {
|
|
|
254
257
|
translatorBudget: input.translatorBudget,
|
|
255
258
|
finishLog: child.finishLog,
|
|
256
259
|
sendBudget: child.sendBudget,
|
|
260
|
+
comboDispatchPermit: child.comboDispatchPermit,
|
|
257
261
|
onFirstOutput: child.onFirstOutput,
|
|
258
262
|
...(child.turnAdmissionLease ? { turnAdmissionLease: child.turnAdmissionLease } : {}),
|
|
259
263
|
}, child.attemptHandle),
|
|
@@ -314,6 +318,7 @@ export async function runNativeChatAttempt(
|
|
|
314
318
|
let activeProvider: OcxProviderConfig = route.provider;
|
|
315
319
|
stampApiKeyAccountLabel(logCtx, route.providerName, activeProvider);
|
|
316
320
|
const spendTracker = sendBudget ? undefined : attachRequestSpendTracker(req, logCtx);
|
|
321
|
+
const physicalBudget = sendBudget ?? createInferenceSendBudget(req, logCtx);
|
|
317
322
|
let activeAdapter: ProviderAdapter = createOpenAIChatAdapter(activeProvider);
|
|
318
323
|
let activeRequest: AdapterRequest;
|
|
319
324
|
let retainedRequestBytes = 0;
|
|
@@ -356,14 +361,16 @@ export async function runNativeChatAttempt(
|
|
|
356
361
|
// key rotation so recovery cannot replace the ceiling along with the active credential.
|
|
357
362
|
const requestTransientPolicy = transientRetryPolicyFor(activeProvider);
|
|
358
363
|
let transientSendsUsed = 0;
|
|
359
|
-
// A
|
|
360
|
-
//
|
|
364
|
+
// A Combo child's initial send is prepaid, not exempt from the shared ceiling. Only its
|
|
365
|
+
// owning permit exposes that one booking in addition to the still-unspent allowance, and only
|
|
366
|
+
// until this child's first physical send has started and settled the booking.
|
|
361
367
|
const sharedSendCap = requestTransientPolicy?.attempts ?? TRANSIENT_RETRY_MAX_ATTEMPTS;
|
|
362
368
|
let physicalSends = 0;
|
|
369
|
+
let initialSendStarted = false;
|
|
363
370
|
const remainingSharedSends = (): number => {
|
|
364
371
|
if (!sendBudget) return Number.POSITIVE_INFINITY;
|
|
365
372
|
const remaining = sendBudget.remainingBaseSends(sharedSendCap);
|
|
366
|
-
return
|
|
373
|
+
return Math.min(sharedSendCap, remaining + (!initialSendStarted && execution.comboDispatchPermit ? 1 : 0));
|
|
367
374
|
};
|
|
368
375
|
const remainingTransientSends = (): number => Math.min(
|
|
369
376
|
requestTransientPolicy
|
|
@@ -399,6 +406,21 @@ export async function runNativeChatAttempt(
|
|
|
399
406
|
claimAmbiguousResend,
|
|
400
407
|
);
|
|
401
408
|
|
|
409
|
+
// Non-enforced accounting for one physical send. Enforced spend is owned by the reporter.
|
|
410
|
+
const comboReceipt = Boolean(sendBudget && execution.comboDispatchPermit);
|
|
411
|
+
const settleNonEnforcedSend = (url: string): void => {
|
|
412
|
+
if (sendBudget) {
|
|
413
|
+
// Backstop for sends the helper cannot see coming (a reset replay). The first
|
|
414
|
+
// report settles the combo's booking; each later one is charged and booked.
|
|
415
|
+
if (physicalSends > 0 && sendBudget.remainingBaseSends(sharedSendCap) <= 0) {
|
|
416
|
+
throw new SendBudgetExhaustedError(safeHostLabel(url));
|
|
417
|
+
}
|
|
418
|
+
physicalSends += 1;
|
|
419
|
+
reportDispatchSends(sendBudget, 1, execution.comboDispatchPermit);
|
|
420
|
+
initialSendStarted = true;
|
|
421
|
+
} else if (!spendTracker?.charge()) throw new NativeChatSpendRefusal();
|
|
422
|
+
};
|
|
423
|
+
|
|
402
424
|
const send = async (
|
|
403
425
|
request: AdapterRequest,
|
|
404
426
|
recovery?: "rate-limit-429" | "key-429" | UpstreamSendRecovery,
|
|
@@ -428,6 +450,9 @@ export async function runNativeChatAttempt(
|
|
|
428
450
|
providerFetch(activeProvider, undefined, {
|
|
429
451
|
providerName: route.providerName,
|
|
430
452
|
modelId: route.modelId,
|
|
453
|
+
onPhysicalDispatch: comboReceipt
|
|
454
|
+
? () => { if (!physicalBudget.spendEnforced) settleNonEnforcedSend(request.url); }
|
|
455
|
+
: undefined,
|
|
431
456
|
dispatchOverride: async (_input, init, execute) => {
|
|
432
457
|
if (!providerApiKeySelectionIsCurrent(config, route.providerName, activeProvider)) {
|
|
433
458
|
const current = resolveCurrentProviderApiKeyTransport(config, route.providerName, activeProvider);
|
|
@@ -450,30 +475,41 @@ export async function runNativeChatAttempt(
|
|
|
450
475
|
const encoding = new Headers(init.headers).get("accept-encoding");
|
|
451
476
|
if (!headers.has("accept-encoding") && encoding) headers.set("accept-encoding", encoding);
|
|
452
477
|
if (init.signal?.aborted) throw init.signal.reason;
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
478
|
+
const spendReport = createPhysicalSendReporter(physicalBudget, () => ({
|
|
479
|
+
poolId: logCtx.spendPoolId ?? route.providerName, identityId: logCtx.accountLogLabel,
|
|
480
|
+
}), execution.comboDispatchPermit);
|
|
481
|
+
let dispatched: Response;
|
|
482
|
+
try {
|
|
483
|
+
// Capture this request's spend policy at its first physical boundary, including
|
|
484
|
+
// observe-only starts; later configuration changes belong to later requests.
|
|
485
|
+
if (spendReport.beforeSend && !spendReport.beforeSend()) {
|
|
486
|
+
if ((logCtx.spendTracker as { refusals?: number } | undefined)?.refusals) throw new NativeChatSpendRefusal();
|
|
487
|
+
throw new SendBudgetExhaustedError();
|
|
458
488
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
489
|
+
// Enforced spend started this send at `beforeSend`. A Combo child's non-enforced
|
|
490
|
+
// send is booked by the physical-dispatch receipt instead, so a local refusal
|
|
491
|
+
// before the wire refunds the booking rather than charging it.
|
|
492
|
+
if (physicalBudget.spendEnforced) initialSendStarted = true;
|
|
493
|
+
else if (!comboReceipt) settleNonEnforcedSend(request.url);
|
|
494
|
+
noteProviderAttemptSend(logCtx, route.providerName, activeProvider, logCtx.usageLogInputTokens, transportRecovery ?? recovery);
|
|
495
|
+
// A reselected provider transport is still a physical send: the connection policy
|
|
496
|
+
// and manual-redirect ownership wrap the selected implementation (#4992).
|
|
497
|
+
dispatched = await sendWithConnectionPolicy(
|
|
498
|
+
(activeProvider as OcxProviderTransport).fetch ?? execute,
|
|
499
|
+
request.url,
|
|
500
|
+
applyUpstreamRecoveryInit({
|
|
501
|
+
...init, method: request.method, headers, body: request.body,
|
|
502
|
+
}, transportRecovery),
|
|
503
|
+
// Reselection can replace the provider transport and the wire shape, so the
|
|
504
|
+
// egress route is bound to the provider this send actually uses. Omitting it
|
|
505
|
+
// here would let a provider transport bypass its configured route entirely,
|
|
506
|
+
// because that transport wins over the executor that carries the binding.
|
|
507
|
+
{ providerName: route.providerName, provider: activeProvider },
|
|
508
|
+
);
|
|
509
|
+
} finally {
|
|
510
|
+
try { if (physicalBudget.spendEnforced) spendReport(1); }
|
|
511
|
+
finally { spendReport.close?.(); }
|
|
512
|
+
}
|
|
477
513
|
if (!dispatched.ok) await recordKeyAttemptFailure(logCtx, dispatched, init.signal ?? upstream.signal);
|
|
478
514
|
return dispatched;
|
|
479
515
|
},
|
|
@@ -558,7 +594,7 @@ export async function runNativeChatAttempt(
|
|
|
558
594
|
upstream.abort();
|
|
559
595
|
if (req.signal.aborted) return fail(499, "Client cancelled request", "client_cancelled");
|
|
560
596
|
const sendError = error instanceof UpstreamRetryEvidenceError ? error.cause : error;
|
|
561
|
-
if (
|
|
597
|
+
if (sendError instanceof SendBudgetExhaustedError) {
|
|
562
598
|
// A decision this process made, answered as the Responses path answers it: 429, not 502.
|
|
563
599
|
return fail(429, sendError.message, SEND_BUDGET_EXHAUSTED_CODE, SEND_BUDGET_EXHAUSTED_CODE);
|
|
564
600
|
}
|
|
@@ -21,7 +21,8 @@ import { normalizeAnthropicImages } from "../adapters/anthropic-image-normalize"
|
|
|
21
21
|
import { createToolCallIdAllocator } from "../adapters/tool-call-id";
|
|
22
22
|
import { openAIChatSerializesThinking } from "../adapters/openai-chat/messages";
|
|
23
23
|
import { messagesToResponsesTranslation } from "../protocols/codecs/messages";
|
|
24
|
-
import { AnthropicRequestError, DesktopModelMappingUnavailableError, extractOcxEffortDirective, extractOcxRouteDirective, resolveInboundModel, type ClaudeCacheKeySource } from "../claude/inbound";
|
|
24
|
+
import { AnthropicRequestError, DesktopModelMappingUnavailableError, extractOcxEffortDirective, extractOcxRouteDirective, nativeAnthropicProjection, resolveInboundModel, type ClaudeCacheKeySource } from "../claude/inbound";
|
|
25
|
+
import { nativeReasoningTag, type NativeReasoningOwner } from "../responses/reasoning-replay-cache";
|
|
25
26
|
import { isKnownDesktop3pModelId, resolveDesktop3pAlias } from "../claude/desktop-3p";
|
|
26
27
|
import { resolveAlias, claudeCodeNativeAlias, legacyAliasForNative } from "../claude/alias";
|
|
27
28
|
import { recordDesktopRequest } from "../claude/desktop-health";
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
} from "../lib/upstream-retry";
|
|
42
43
|
import { resolveClientRetryAfter } from "../lib/retry-after";
|
|
43
44
|
import { anthropicRateLimitHeaders } from "./anthropic-rate-limit-headers";
|
|
45
|
+
import { upstreamMessagesRequestIdHeaders } from "./messages-response-headers";
|
|
44
46
|
import {
|
|
45
47
|
anthropicErrorBody,
|
|
46
48
|
anthropicErrorResponse,
|
|
@@ -80,7 +82,9 @@ import { upstreamWireForAdapter } from "../protocols/contract";
|
|
|
80
82
|
import { createProtocolEnvelope, type ProtocolEnvelope } from "../protocols/envelope";
|
|
81
83
|
import { featuresFromMessagesBody, type ProtocolFeature } from "../protocols/features";
|
|
82
84
|
import { credentialDomainFor, messagesBodyHasOpaqueState } from "../protocols/opaque-state";
|
|
83
|
-
import {
|
|
85
|
+
import { anthropicRoutingFor, anthropicSessionKeyFromParts } from "../oauth/anthropic-routing";
|
|
86
|
+
import { configuredAnthropicInstance } from "../providers/anthropic-instance";
|
|
87
|
+
import { messagesSelectorTargetsSecondaryInstance, messagesSecondaryInstanceUnavailable } from "./messages-native-selector";
|
|
84
88
|
import { checkRepresentable, unrepresentableMessage } from "../protocols/guard";
|
|
85
89
|
import { requestPathForLane } from "../protocols/path";
|
|
86
90
|
import { resolveApiSurfaceSettings, resolveProtocolSettings } from "../protocols/settings";
|
|
@@ -244,6 +248,7 @@ function wantsNativePassthrough(
|
|
|
244
248
|
): model is string {
|
|
245
249
|
if (cc?.nativePassthrough === false) return false;
|
|
246
250
|
if (typeof model !== "string" || !/^(claude|anthropic)/i.test(model)) return false;
|
|
251
|
+
if (messagesSelectorTargetsSecondaryInstance(config, model, cc)) return false;
|
|
247
252
|
// Authorization and x-api-key both belong to the upstream on this branch. An exposed listener
|
|
248
253
|
// therefore requires the dedicated admission header even though the routed Messages surface
|
|
249
254
|
// keeps accepting all three legacy admission forms.
|
|
@@ -631,7 +636,10 @@ async function anthropicNativePassthrough(
|
|
|
631
636
|
const upstream = result.upstream;
|
|
632
637
|
|
|
633
638
|
const contentType = upstream.headers.get("content-type") ?? "application/json";
|
|
634
|
-
const rateLimitHeaders =
|
|
639
|
+
const rateLimitHeaders = {
|
|
640
|
+
...anthropicRateLimitHeaders(upstream.headers),
|
|
641
|
+
...(pathname === "/v1/messages" ? upstreamMessagesRequestIdHeaders(upstream.headers) : {}),
|
|
642
|
+
};
|
|
635
643
|
const bodyGuard = resolvePassthroughBodyGuard(config, req.signal);
|
|
636
644
|
if (upstream.ok && contentType.includes("text/event-stream") && upstream.body) {
|
|
637
645
|
return new Response(tapAnthropicSseForLog(upstream.body, logCtx, finalize, bodyGuard), {
|
|
@@ -834,6 +842,7 @@ export async function handleClaudeMessages(
|
|
|
834
842
|
return finalizeTranslatorBudgetResponse(
|
|
835
843
|
await handleClaudeMessagesWithBudget(req, config, logCtx, translatorBudget, logIds, requestPolicy, ingress),
|
|
836
844
|
translatorBudget,
|
|
845
|
+
req.signal,
|
|
837
846
|
);
|
|
838
847
|
} catch (error) {
|
|
839
848
|
translatorBudget.dispose();
|
|
@@ -869,6 +878,9 @@ async function handleClaudeMessagesWithBudget(
|
|
|
869
878
|
let anthropicBody: unknown;
|
|
870
879
|
let internalBody: Rec;
|
|
871
880
|
let cacheKeySource: ClaudeCacheKeySource = null;
|
|
881
|
+
let nativeReasoningReplay: ReadonlyMap<string, string> | undefined;
|
|
882
|
+
const nativeReasoningMint: { owner?: NativeReasoningOwner } = {};
|
|
883
|
+
let nativeProjectionOnlyError: AnthropicRequestError | undefined;
|
|
872
884
|
let effortOverride: string | null = null;
|
|
873
885
|
let effortRow: ParsedEffortRowId | null = null;
|
|
874
886
|
let fastRow: ParsedFastRowId | null = null;
|
|
@@ -959,7 +971,20 @@ async function handleClaudeMessagesWithBudget(
|
|
|
959
971
|
if (!effortRow && !fastRow && isRec(anthropicBody) && wantsNativePassthrough(req, config, requestPolicy, anthropicBody.model, cc)) {
|
|
960
972
|
markProtocolEntry(logCtx, { inbound: "messages", lane: "native", features: messagesFeatures });
|
|
961
973
|
recordProtocolShadowPlan(logCtx, config, { inbound: "messages", model: requestedModel });
|
|
962
|
-
|
|
974
|
+
let projectedBody: Rec;
|
|
975
|
+
try {
|
|
976
|
+
projectedBody = nativeAnthropicProjection(anthropicBody, translatorBudget);
|
|
977
|
+
} catch (err) {
|
|
978
|
+
if (!isTranslatorBudgetExceededError(err)) throw err;
|
|
979
|
+
if (logIds) addFinalRequestLog(logIds.requestId, logIds.start, logCtx, 413, { closeReason: "non_stream" });
|
|
980
|
+
return anthropicErrorResponse(413, "request translation buffer exceeded the safe limit", "request_too_large", "translation_buffer_limit");
|
|
981
|
+
}
|
|
982
|
+
return await anthropicNativePassthrough(req, config, logCtx, logIds, projectedBody, "/v1/messages");
|
|
983
|
+
}
|
|
984
|
+
if (isRec(anthropicBody) && typeof anthropicBody.model === "string"
|
|
985
|
+
&& messagesSecondaryInstanceUnavailable(config, anthropicBody.model, cc)) {
|
|
986
|
+
if (logIds) addFinalRequestLog(logIds.requestId, logIds.start, logCtx, 401, { closeReason: "non_stream" });
|
|
987
|
+
return anthropicErrorResponse(401, "Configured Anthropic OAuth instance is unavailable", "authentication_error");
|
|
963
988
|
}
|
|
964
989
|
// Only Anthropic holds message-thread state; the error makes Claude Code resend the full turn.
|
|
965
990
|
if (carriesMessageThread(anthropicBody)) {
|
|
@@ -1009,7 +1034,19 @@ async function handleClaudeMessagesWithBudget(
|
|
|
1009
1034
|
};
|
|
1010
1035
|
delete anthropicBody.thinking;
|
|
1011
1036
|
}
|
|
1012
|
-
|
|
1037
|
+
let translation;
|
|
1038
|
+
try {
|
|
1039
|
+
translation = messagesToResponsesTranslation(anthropicBody, cc, translatorBudget);
|
|
1040
|
+
} catch (err) {
|
|
1041
|
+
// A native Messages route can discard an undecodable proxy envelope. Keep the
|
|
1042
|
+
// original error for any route that ultimately translates to Responses.
|
|
1043
|
+
if (!(err instanceof AnthropicRequestError) || err.message !== "malformed ocxr1 reasoning signature"
|
|
1044
|
+
|| !isRec(anthropicBody)) throw err;
|
|
1045
|
+
const projected = nativeAnthropicProjection(anthropicBody, translatorBudget);
|
|
1046
|
+
if (projected === anthropicBody) throw err;
|
|
1047
|
+
translation = messagesToResponsesTranslation(projected, cc, translatorBudget);
|
|
1048
|
+
nativeProjectionOnlyError = err;
|
|
1049
|
+
}
|
|
1013
1050
|
internalBody = translation.body;
|
|
1014
1051
|
// The Anthropic translator builds its body from model/input/store/stream plus sampling
|
|
1015
1052
|
// fields only, so the caller intent is applied to the TRANSLATED body rather than the
|
|
@@ -1017,6 +1054,7 @@ async function handleClaudeMessagesWithBudget(
|
|
|
1017
1054
|
if (fastRow) internalBody.service_tier = "priority";
|
|
1018
1055
|
translatorBudget.chargeRetained(jsonUtf8Bytes(internalBody), { kind: "request_copies" });
|
|
1019
1056
|
cacheKeySource = translation.cacheKeySource;
|
|
1057
|
+
nativeReasoningReplay = translation.nativeReasoningReplay;
|
|
1020
1058
|
} catch (err) {
|
|
1021
1059
|
const overflow = isTranslatorBudgetExceededError(err);
|
|
1022
1060
|
const unavailable = err instanceof DesktopModelMappingUnavailableError;
|
|
@@ -1148,15 +1186,25 @@ async function handleClaudeMessagesWithBudget(
|
|
|
1148
1186
|
|
|
1149
1187
|
// PF-08: a managed-key Anthropic route sends its Messages body natively. The caller-forward
|
|
1150
1188
|
// passthrough above was decided on the caller's own credential and never reaches this point.
|
|
1189
|
+
const settledInstance = configuredAnthropicInstance(config, settledRoute?.providerName);
|
|
1190
|
+
if (settledRoute?.providerName === "anthropic2" && settledRoute.provider.authMode === "oauth" && !settledInstance) {
|
|
1191
|
+
if (logIds) addFinalRequestLog(logIds.requestId, logIds.start, logCtx, 401, { closeReason: "non_stream" });
|
|
1192
|
+
return anthropicErrorResponse(401, "Configured Anthropic OAuth instance is unavailable", "authentication_error");
|
|
1193
|
+
}
|
|
1151
1194
|
const nativeSelector: NativeMessagesSelector = {
|
|
1152
1195
|
effortRow: !!effortRow, fastRow: !!fastRow, routeSelector: String(internalBody.model ?? ""), claudeCode: cc,
|
|
1153
1196
|
// PF-10: a stored second OAuth account turns on the bridge's rotation, so it stays there.
|
|
1154
|
-
...(settledRoute?.provider.authMode === "oauth"
|
|
1197
|
+
...(settledRoute?.provider.authMode === "oauth" && settledInstance
|
|
1198
|
+
? { oauthFailoverQuorum: anthropicRoutingFor(settledInstance).hasAnthropicFailoverQuorum() } : {}),
|
|
1155
1199
|
};
|
|
1156
1200
|
const nativeDecline = settledRoute && isRec(anthropicBody)
|
|
1157
1201
|
? nativeMessagesDeclineReason(settledRoute, anthropicBody, config, nativeSelector)
|
|
1158
1202
|
: "rollout-disabled";
|
|
1159
1203
|
const nativeMessagesRoute = settledRoute && nativeDecline === undefined ? settledRoute : undefined;
|
|
1204
|
+
if (nativeProjectionOnlyError && !nativeMessagesRoute) {
|
|
1205
|
+
if (logIds) addFinalRequestLog(logIds.requestId, logIds.start, logCtx, 400, { closeReason: "non_stream" });
|
|
1206
|
+
return anthropicErrorResponse(400, nativeProjectionOnlyError.message, "invalid_request_error");
|
|
1207
|
+
}
|
|
1160
1208
|
// With the switch on, a declined route says why on its bridge mark, as native Chat does.
|
|
1161
1209
|
if (nativeDecline && nativeDecline !== "rollout-disabled") {
|
|
1162
1210
|
markProtocolEntry(logCtx, {
|
|
@@ -1195,7 +1243,7 @@ async function handleClaudeMessagesWithBudget(
|
|
|
1195
1243
|
let nativeBody: Rec;
|
|
1196
1244
|
try {
|
|
1197
1245
|
// Built from the source envelope when there is one, after the managed-client steps above.
|
|
1198
|
-
nativeBody = envelope ? envelope.freshBody() : anthropicBody as Rec;
|
|
1246
|
+
nativeBody = nativeAnthropicProjection(envelope ? envelope.freshBody() : anthropicBody as Rec, translatorBudget);
|
|
1199
1247
|
} catch (err) {
|
|
1200
1248
|
if (!isTranslatorBudgetExceededError(err)) throw err;
|
|
1201
1249
|
if (logIds) addFinalRequestLog(logIds.requestId, logIds.start, logCtx, 413, { closeReason: "non_stream" });
|
|
@@ -1300,6 +1348,8 @@ async function handleClaudeMessagesWithBudget(
|
|
|
1300
1348
|
// Without this the replay would look native and a Responses-scoped wire default
|
|
1301
1349
|
// would fire, disagreeing with the pre-flight decision above.
|
|
1302
1350
|
inboundWire: "anthropic",
|
|
1351
|
+
nativeReasoningReplay,
|
|
1352
|
+
nativeReasoningMint,
|
|
1303
1353
|
claudeGoAffinity: { sessionLane: claudeGoSessionLane },
|
|
1304
1354
|
claudeNativeSessionId,
|
|
1305
1355
|
stripClaudeMainAuthForNoncanonicalForward: true,
|
|
@@ -1381,6 +1431,7 @@ async function handleClaudeMessagesWithBudget(
|
|
|
1381
1431
|
if (contentType.includes("text/event-stream") && response.body) {
|
|
1382
1432
|
const anthropicSse = responsesSseToAnthropicSse(response.body, requestedModel, {
|
|
1383
1433
|
translatorBudget,
|
|
1434
|
+
nativeReasoningTagFor: blob => nativeReasoningTag(nativeReasoningMint.owner, blob),
|
|
1384
1435
|
// Only a floor, and only for the first frame: an upstream that reports usage early wins
|
|
1385
1436
|
// over it inside the translator, and the terminal `message_delta` carries the
|
|
1386
1437
|
// authoritative count either way (#4857).
|
|
@@ -1450,7 +1501,7 @@ async function handleClaudeMessagesWithBudget(
|
|
|
1450
1501
|
}
|
|
1451
1502
|
let message: Rec;
|
|
1452
1503
|
try {
|
|
1453
|
-
message = responsesJsonToAnthropicMessage(json, requestedModel, translatorBudget);
|
|
1504
|
+
message = responsesJsonToAnthropicMessage(json, requestedModel, translatorBudget, blob => nativeReasoningTag(nativeReasoningMint.owner, blob));
|
|
1454
1505
|
} catch (err) {
|
|
1455
1506
|
if (!isTranslatorBudgetExceededError(err)) throw err;
|
|
1456
1507
|
return anthropicErrorResponse(413, "upstream translation buffer exceeded the safe limit", "request_too_large", "translation_buffer_limit");
|
package/src/server/gui-static.ts
CHANGED
|
@@ -20,12 +20,46 @@ const MIME_TYPES: Record<string, string> = {
|
|
|
20
20
|
*/
|
|
21
21
|
const HASHED_ASSET_PATTERN = /-[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9]+$/;
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/** `productName` in desktop/src-tauri/tauri.conf.json; Linux packages install resources under `lib/<productName>`. */
|
|
24
|
+
export const DESKTOP_PRODUCT_NAME = "OpenCodex";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Where a compiled `ocx` looks for `gui/dist`, given the directory holding the executable.
|
|
28
|
+
*
|
|
29
|
+
* Beside the binary covers a release archive and the Windows desktop install. The desktop shell
|
|
30
|
+
* ships `ocx` as a sidecar and its resources where Tauri's `resource_dir` puts them: `../Resources`
|
|
31
|
+
* inside a macOS app bundle, `../lib/<productName>` for a Linux package (`/usr/bin`, `/usr/local/bin`,
|
|
32
|
+
* an AppImage's `usr/bin`). The shell passes that directory as OPENCODEX_GUI_DIST only to the sidecar
|
|
33
|
+
* it starts itself; `ocx ensure` from the Codex shim and the login service start the same binary
|
|
34
|
+
* without the variable, so they find the dashboard here.
|
|
35
|
+
*/
|
|
36
|
+
export function standaloneGuiDistCandidates(executableDir: string, platform: NodeJS.Platform = process.platform): string[] {
|
|
37
|
+
const candidates = [join(executableDir, "gui", "dist")];
|
|
38
|
+
if (platform === "darwin") candidates.push(join(executableDir, "..", "Resources", "gui", "dist"));
|
|
39
|
+
if (platform === "linux") candidates.push(join(executableDir, "..", "lib", DESKTOP_PRODUCT_NAME, "gui", "dist"));
|
|
40
|
+
return candidates;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Overrides for tests; production callers pass nothing and get this process's own layout. */
|
|
44
|
+
export interface GuiDistLookup {
|
|
45
|
+
/** Directory holding a compiled `ocx`, or null when running from source. */
|
|
46
|
+
standaloneDir?: string | null;
|
|
47
|
+
platform?: NodeJS.Platform;
|
|
48
|
+
/** Directory of this module; a source checkout's `gui/dist` is resolved from it. */
|
|
49
|
+
moduleDir?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Lookup only: checks for `index.html` and never creates, copies or builds anything. */
|
|
53
|
+
export function findGuiDist(lookup: GuiDistLookup = {}): string | null {
|
|
54
|
+
const standaloneDir = lookup.standaloneDir === undefined
|
|
55
|
+
? (isStandaloneBinary() ? standaloneRoot() : null)
|
|
56
|
+
: lookup.standaloneDir;
|
|
57
|
+
const moduleDir = lookup.moduleDir ?? import.meta.dir;
|
|
24
58
|
const candidates = [
|
|
25
59
|
process.env.OPENCODEX_GUI_DIST,
|
|
26
|
-
...(
|
|
27
|
-
join(
|
|
28
|
-
join(
|
|
60
|
+
...(standaloneDir === null ? [] : standaloneGuiDistCandidates(standaloneDir, lookup.platform)),
|
|
61
|
+
join(moduleDir, "..", "..", "gui", "dist"),
|
|
62
|
+
join(moduleDir, "..", "..", "..", "gui", "dist"),
|
|
29
63
|
].filter((candidate): candidate is string => Boolean(candidate));
|
|
30
64
|
for (const c of candidates) {
|
|
31
65
|
if (existsSync(join(c, "index.html"))) return c;
|
|
@@ -181,6 +181,7 @@ export function runAdmittedBodyWork(
|
|
|
181
181
|
addFinalRequestLog(refusalLog.requestId, refusalLog.start, refusalLog.logCtx, refused.status, {
|
|
182
182
|
closeReason: "terminal",
|
|
183
183
|
});
|
|
184
|
+
refusalLog.onLogged?.();
|
|
184
185
|
}
|
|
185
186
|
return withCors(refused, req, policy);
|
|
186
187
|
});
|
|
@@ -41,10 +41,11 @@ export function buildInterceptDesiredClients(config: OcxConfig, observed: Claude
|
|
|
41
41
|
* imports keep the catalog and discovery off the synchronous startup path.
|
|
42
42
|
*/
|
|
43
43
|
export async function loadPickerRoutesFromCatalog(): Promise<PickerRouteInput> {
|
|
44
|
-
const [{ loadConfig }, { fetchAllModels }, catalog] = await Promise.all([
|
|
44
|
+
const [{ loadConfig }, { fetchAllModels }, catalog, { resolveAutoContext }] = await Promise.all([
|
|
45
45
|
import("../../config"),
|
|
46
46
|
import("../management-api"),
|
|
47
47
|
import("../../codex/catalog"),
|
|
48
|
+
import("../../claude/context-windows"),
|
|
48
49
|
]);
|
|
49
50
|
const config = loadConfig();
|
|
50
51
|
const models = await fetchAllModels(config);
|
|
@@ -54,6 +55,7 @@ export async function loadPickerRoutesFromCatalog(): Promise<PickerRouteInput> {
|
|
|
54
55
|
.map(model => ({ provider: model.provider, id: model.id, contextWindow: model.contextWindow })),
|
|
55
56
|
...(config.claudeCode?.desktopProfile ? { profile: config.claudeCode.desktopProfile } : {}),
|
|
56
57
|
nativeContextCap: catalog.nativeContextLimits(config),
|
|
58
|
+
auto: resolveAutoContext(config.claudeCode),
|
|
57
59
|
};
|
|
58
60
|
}
|
|
59
61
|
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from "./live-sideband";
|
|
22
22
|
import {
|
|
23
23
|
withRequestLogId,
|
|
24
|
+
withMessagesRequestLogId,
|
|
24
25
|
} from "./startup-warnings";
|
|
25
26
|
|
|
26
27
|
import { remoteWorkspaceEnabled } from "../../remote-control/workspace-activation";
|
|
@@ -118,6 +119,7 @@ import { handleChatCompletions } from "../chat-completions";
|
|
|
118
119
|
import { anthropicErrorResponse } from "../../claude/outbound";
|
|
119
120
|
import {
|
|
120
121
|
buildDesktop3pRegistry,
|
|
122
|
+
desktop3pModelOptions,
|
|
121
123
|
generateDesktop3pModels,
|
|
122
124
|
} from "../../claude/desktop-3p";
|
|
123
125
|
import { buildDesktopDiscoveryInputs } from "../../claude/desktop-discovery-inputs";
|
|
@@ -1024,6 +1026,7 @@ export function createServeOptions(ctx: ServeOptionsContext) {
|
|
|
1024
1026
|
const models = config.claudeCode?.enabled === false ? [] : generateDesktop3pModels(
|
|
1025
1027
|
desktopInputs.nativeSlugs, desktopInputs.routedModels,
|
|
1026
1028
|
config.claudeCode?.desktopProfile, desktopInputs.nativeContextCap,
|
|
1029
|
+
desktop3pModelOptions(config.claudeCode),
|
|
1027
1030
|
);
|
|
1028
1031
|
const response = jsonResponse({ version: 1, models }, 200, req, policy);
|
|
1029
1032
|
response.headers.set("Cache-Control", "no-store");
|
|
@@ -1556,11 +1559,16 @@ export function createServeOptions(ctx: ServeOptionsContext) {
|
|
|
1556
1559
|
// pre-translation stream + native passthrough callbacks) — do not re-wrap the
|
|
1557
1560
|
// translated Anthropic stream here.
|
|
1558
1561
|
const sessionReq = withCallerSessionIdentity(req, admission);
|
|
1559
|
-
|
|
1562
|
+
// Entering the handler, or a logged refusal, means the final request log owns requestId.
|
|
1563
|
+
let ownsRequestLog = false;
|
|
1564
|
+
const claimRequestLog = (work: (lease: ActiveTurnLease) => Promise<Response>) =>
|
|
1565
|
+
(lease: ActiveTurnLease) => { ownsRequestLog = true; return work(lease); };
|
|
1566
|
+
const response = await runAdmittedHttpTurn(sessionReq, policy, claimRequestLog(async turnAdmissionLease => withCors(
|
|
1560
1567
|
await handleClaudeMessages(sessionReq, config, logCtx, { requestId, start, turnAdmissionLease, admission }, policy, { claudeIntercept: ingress === "claude-intercept" }),
|
|
1561
1568
|
req,
|
|
1562
1569
|
policy,
|
|
1563
|
-
), { requestId, start, logCtx });
|
|
1570
|
+
)), { requestId, start, logCtx, onLogged: () => { ownsRequestLog = true; } });
|
|
1571
|
+
return ownsRequestLog ? withMessagesRequestLogId(response, requestId) : response;
|
|
1564
1572
|
}
|
|
1565
1573
|
|
|
1566
1574
|
// OpenAI Chat Completions inbound (GitHub Copilot App / OpenAI-compatible clients).
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
configureSharedSpendLedger,
|
|
8
8
|
spendPolicyFromConfig,
|
|
9
|
+
waitForSharedSpendReporterDrain,
|
|
9
10
|
} from "../../lib/spend-reservation-ledger";
|
|
10
11
|
import { warnIfSyncedStateDirectory } from "../../lib/synced-state-location";
|
|
11
12
|
|
|
@@ -26,7 +27,7 @@ export function waitForFailedStartRollback(error: unknown): Promise<void> {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export interface SpendLedgerServerLifecycle {
|
|
29
|
-
configure(spend: OcxSpendConfig | undefined): void;
|
|
30
|
+
configure(spend: OcxSpendConfig | undefined, poolAliases?: unknown, providerIds?: readonly string[]): void;
|
|
30
31
|
track<T extends { stop(closeActiveConnections?: boolean): void | Promise<void> }>(server: T): T;
|
|
31
32
|
release(): void;
|
|
32
33
|
releaseAfterFailedStart(): Promise<void>;
|
|
@@ -36,7 +37,8 @@ export interface SpendLedgerServerLifecycle {
|
|
|
36
37
|
export function acquireSpendLedgerServerLifecycle(configDir: string): SpendLedgerServerLifecycle {
|
|
37
38
|
const owner: SpendLedgerOwnerLease = acquireSpendLedgerOwner(configDir);
|
|
38
39
|
// Advisory: a synced state directory makes the journal's hard-link guard refuse intermittently
|
|
39
|
-
// (#6314)
|
|
40
|
+
// (#6314), including Google Drive syncing the native Desktop or Documents folder. Said once
|
|
41
|
+
// at startup instead of being discovered from a 502. The nlink guard itself is unchanged.
|
|
40
42
|
warnIfSyncedStateDirectory(configDir);
|
|
41
43
|
// Each entry returns whatever the listener's own stop returned. Typed as void-or-promise
|
|
42
44
|
// because the rollback below has to WAIT on it: declaring it `() => void` let the call site
|
|
@@ -49,13 +51,18 @@ export function acquireSpendLedgerServerLifecycle(configDir: string): SpendLedge
|
|
|
49
51
|
owner.release();
|
|
50
52
|
};
|
|
51
53
|
return {
|
|
52
|
-
configure(spend): void {
|
|
53
|
-
configureSharedSpendLedger(spendPolicyFromConfig(spend));
|
|
54
|
+
configure(spend, poolAliases, providerIds): void {
|
|
55
|
+
configureSharedSpendLedger(spendPolicyFromConfig(spend, poolAliases, providerIds));
|
|
54
56
|
},
|
|
55
57
|
track<T extends { stop(closeActiveConnections?: boolean): void | Promise<void> }>(server: T): T {
|
|
56
58
|
// Capture the raw stop before startServer replaces the public method with full teardown.
|
|
57
59
|
const stop = server.stop.bind(server);
|
|
58
|
-
|
|
60
|
+
const drainedStop = async (closeActiveConnections?: boolean): Promise<void> => {
|
|
61
|
+
await stop(closeActiveConnections);
|
|
62
|
+
await waitForSharedSpendReporterDrain();
|
|
63
|
+
};
|
|
64
|
+
Object.defineProperty(server, "stop", { configurable: true, value: drainedStop });
|
|
65
|
+
failedStartStops.push(() => drainedStop(true));
|
|
59
66
|
return server;
|
|
60
67
|
},
|
|
61
68
|
release,
|
|
@@ -100,6 +100,25 @@ import {
|
|
|
100
100
|
|
|
101
101
|
const REQUEST_LOG_ID_RESPONSE_HEADER = "x-opencodex-request-id";
|
|
102
102
|
|
|
103
|
+
/** Messages clients persist request-id; the upstream diagnostic id has a separate namespace. */
|
|
104
|
+
export function withMessagesRequestLogId(response: Response, requestId: string): Response {
|
|
105
|
+
const upstreamId = response.headers.get("request-id");
|
|
106
|
+
const correlated = withRequestLogId(response, requestId);
|
|
107
|
+
correlated.headers.delete("x-opencodex-upstream-request-id");
|
|
108
|
+
if (upstreamId && /^req_[A-Za-z0-9_-]{1,128}$/.test(upstreamId)) {
|
|
109
|
+
correlated.headers.set("x-opencodex-upstream-request-id", upstreamId);
|
|
110
|
+
}
|
|
111
|
+
correlated.headers.set("request-id", requestId);
|
|
112
|
+
const exposed = correlated.headers.get("Access-Control-Expose-Headers") ?? "";
|
|
113
|
+
const names = exposed.split(",").map(name => name.trim().toLowerCase());
|
|
114
|
+
const additions = ["request-id", ...(correlated.headers.has("x-opencodex-upstream-request-id")
|
|
115
|
+
? ["x-opencodex-upstream-request-id"] : [])].filter(name => !names.includes(name));
|
|
116
|
+
if (additions.length) {
|
|
117
|
+
correlated.headers.set("Access-Control-Expose-Headers", [exposed, ...additions].filter(Boolean).join(", "));
|
|
118
|
+
}
|
|
119
|
+
return correlated;
|
|
120
|
+
}
|
|
121
|
+
|
|
103
122
|
export function withRequestLogId(response: Response, requestId: string): Response {
|
|
104
123
|
const headers = new Headers(response.headers);
|
|
105
124
|
headers.set(REQUEST_LOG_ID_RESPONSE_HEADER, requestId);
|
package/src/server/index.ts
CHANGED
|
@@ -302,7 +302,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
302
302
|
configureAppOwnedMemoryBudget(resolveAppOwnedMemoryBudgetBytes(config.appOwnedMemoryBudgetMb));
|
|
303
303
|
enforceAppOwnedMemoryBudget();
|
|
304
304
|
// Observe-only mode still journals physical sends, so every server owns before configuring.
|
|
305
|
-
spendLedgerLifecycle.configure(config.spend);
|
|
305
|
+
spendLedgerLifecycle.configure(config.spend, config.spendPoolAliases, Object.keys(config.providers));
|
|
306
306
|
// After ownership: a second server on the same home is refused above, so the process running
|
|
307
307
|
// this line is the only one appending to usage.jsonl and the only one that may compact it.
|
|
308
308
|
setUsageLedgerRetention(config.usageLedgerMaxBytes);
|
|
@@ -505,7 +505,9 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
505
505
|
const lease = tryAdmitTurn(sessionLaneIdFromRequest(req.headers));
|
|
506
506
|
if (!lease) return serverBusyResponse(req, "active turns", policy);
|
|
507
507
|
// Root, lane, and the refusal that follows from them, all live in ./workflow-refusal.
|
|
508
|
-
|
|
508
|
+
let workflow: ReturnType<typeof admitHttpWorkflowTurn>;
|
|
509
|
+
try { workflow = admitHttpWorkflowTurn(req.headers); }
|
|
510
|
+
catch (error) { lease.release(); throw error; }
|
|
509
511
|
if (workflow && !workflow.admitted) {
|
|
510
512
|
lease.release();
|
|
511
513
|
// withCors, because without Access-Control-Allow-Origin the exposed refusal header is
|
|
@@ -748,6 +750,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
748
750
|
Object.defineProperty(server, "stop", {
|
|
749
751
|
configurable: true,
|
|
750
752
|
value: async (closeActiveConnections?: boolean): Promise<void> => {
|
|
753
|
+
backgroundLifecycle?.revokeClientFanout();
|
|
751
754
|
remoteWorkspaceStopping = true;
|
|
752
755
|
liveCallBindings.clear();
|
|
753
756
|
// Disarm the package-tree restart timer before teardown can schedule another restart.
|
|
@@ -34,7 +34,7 @@ export function createInferenceSendBudget(
|
|
|
34
34
|
export function expandInferenceOAuthSendBudget(budget: TransientSendBudget | undefined, accounts: number): void {
|
|
35
35
|
if (!budget) return;
|
|
36
36
|
const policy = ingressPolicies.get(budget);
|
|
37
|
-
if (!policy || budget.used !== 0) return;
|
|
37
|
+
if (!policy || budget.used !== 0 || ("physicalStarted" in budget && Number(budget.physicalStarted) > 0)) return;
|
|
38
38
|
ingressPolicies.delete(budget);
|
|
39
39
|
if (accounts < 2 || !Number.isSafeInteger(accounts)) return;
|
|
40
40
|
const sends = Math.min(accounts, GENERIC_OAUTH_MAX_ACCOUNTS_PER_REQUEST) * TRANSIENT_RETRY_MAX_ATTEMPTS;
|
package/src/server/lifecycle.ts
CHANGED
|
@@ -479,7 +479,7 @@ export async function drainAndShutdown(
|
|
|
479
479
|
beginBackgroundShellShutdown();
|
|
480
480
|
try {
|
|
481
481
|
while (admittedTurns.size > 0 && Date.now() < deadline) {
|
|
482
|
-
await Bun.sleep(100);
|
|
482
|
+
await Bun.sleep(Math.min(100, Math.max(0, deadline - Date.now())));
|
|
483
483
|
}
|
|
484
484
|
if (admittedTurns.size > 0) {
|
|
485
485
|
console.warn(`⚠️ Aborting ${admittedTurns.size} in-flight turn(s) after ${timeoutMs}ms deadline`);
|