@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
|
@@ -3,11 +3,16 @@ import { persistCommittedDesktopGateway } from "../../claude/desktop-gateway-sta
|
|
|
3
3
|
import { captureDesktopAppliedMarker, commitDesktopAppliedMarker } from "../../claude/desktop-applied-marker";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { readFileSync } from "node:fs";
|
|
6
|
+
import { dirname } from "node:path";
|
|
7
|
+
import { prepareCodexHome } from "../../codex/prepared-home";
|
|
8
|
+
import { codexHomeIsAbsent } from "../../codex/codex-home-owner";
|
|
6
9
|
import type { CatalogModel } from "../../codex/catalog";
|
|
7
10
|
import { catalogModelSlug, filterCatalogVisibleModels, invalidateCodexModelsCache, nativeContextLimits, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
|
|
8
11
|
import { mergeModelPinnedEfforts, modelPinnedEffortsConfigError } from "../../config/provider-validation";
|
|
9
12
|
import { MULTI_AGENT_SURFACE_ADVISORY_VERSION, multiAgentSurfaceAdvisory, resolveMultiAgentMode } from "../../config/multi-agent-surface";
|
|
10
13
|
import { captureConfigTopLevelRollback, parsedConfigRebaseDeletionKeys, projectConfigRebaseProvenance } from "../../config/rebase-provenance";
|
|
14
|
+
import { anthropicSidecarPatchError } from "../../config/schema/anthropic-account-pool";
|
|
15
|
+
import { isAnthropicInstanceId, type AnthropicInstanceId } from "../../providers/anthropic-instance-id";
|
|
11
16
|
import {
|
|
12
17
|
adoptPersistedClaudeCode,
|
|
13
18
|
DEFAULT_SUBAGENT_MODELS,
|
|
@@ -71,9 +76,13 @@ import {
|
|
|
71
76
|
webSearchCandidateRows,
|
|
72
77
|
webSearchModelIsRejected,
|
|
73
78
|
webSearchModelRejection,
|
|
79
|
+
sidecarAnthropicPoolOptions,
|
|
80
|
+
sidecarSettingsAfterPatch,
|
|
81
|
+
sidecarOptionsAuth,
|
|
74
82
|
type WebSearchBackend,
|
|
75
83
|
} from "./web-search-sidecar-options";
|
|
76
84
|
import { drainAndShutdown } from "../lifecycle";
|
|
85
|
+
import { inheritedAnthropicInstance } from "../../sidecar/auth";
|
|
77
86
|
import { filterRequestLogs, getRequestLogEntries, type RequestLogEntry } from "../request-log";
|
|
78
87
|
import { estimateComboCost, estimateRequestCost, normalizeCostTokens, tokensPerSecond } from "../../usage/cost";
|
|
79
88
|
import type { PersistedUsageAttempt } from "../../usage/log";
|
|
@@ -394,6 +403,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
394
403
|
getAgentsEnabled, getAgentsMaxDepth, getSubagentDeveloperInstructions,
|
|
395
404
|
getMultiAgentModeHintText, probeCodexSupportsModeHint, setAgentsEnabled, setAgentsMaxDepth,
|
|
396
405
|
setSubagentDeveloperInstructions, setMultiAgentModeHintText, MODE_HINT_UNSUPPORTED_ERROR,
|
|
406
|
+
activeCodexConfigPath,
|
|
397
407
|
} = await import("../../codex/features");
|
|
398
408
|
// Probe the capability before any combined-request mutation. The scalar writer
|
|
399
409
|
// repeats this check, but doing it here prevents an earlier flag/mode/agents
|
|
@@ -413,17 +423,37 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
413
423
|
const requestedFlag = wantsFlag
|
|
414
424
|
? body.enabled as boolean
|
|
415
425
|
: modeFlag ?? (wantsKeepNative && hybridPinActive ? false : undefined);
|
|
426
|
+
/*
|
|
427
|
+
* One held section for every config.toml write this PUT makes — the
|
|
428
|
+
* feature transition plus each scalar edit. Individually-locked writers
|
|
429
|
+
* would let a foreign write land between them and leave a half-applied
|
|
430
|
+
* combination on disk; the lock is released before the post-write readers
|
|
431
|
+
* and catalog converge below.
|
|
432
|
+
*/
|
|
433
|
+
const needsCodexConfigWrites = requestedFlag !== undefined || wantsThreads
|
|
434
|
+
|| wantsAgentsEnabled || wantsMaxDepth || wantsSubagentInstructions || wantsModeHintText;
|
|
435
|
+
if (needsCodexConfigWrites && codexHomeIsAbsent(dirname(activeCodexConfigPath()))) {
|
|
436
|
+
return jsonResponse({ error: `config.toml not readable at ${activeCodexConfigPath()}` }, 502);
|
|
437
|
+
}
|
|
438
|
+
const { acquireConfigWriteLock, releaseConfigWriteLock, configWriteLockFailureMessage } = await import("../../codex/config-write-lock");
|
|
439
|
+
const configWriteLock = needsCodexConfigWrites ? await acquireConfigWriteLock(activeCodexConfigPath()) : null;
|
|
440
|
+
if (configWriteLock !== null && !configWriteLock.ok) {
|
|
441
|
+
return jsonResponse({ error: configWriteLockFailureMessage(configWriteLock), retryable: configWriteLock.error === "locked" }, 502);
|
|
442
|
+
}
|
|
443
|
+
const heldWriteLock = configWriteLock !== null && configWriteLock.ok ? configWriteLock.handle : undefined;
|
|
444
|
+
try {
|
|
416
445
|
if (requestedFlag !== undefined || wantsThreads) {
|
|
417
446
|
const targetFlag = requestedFlag ?? isMultiAgentV2Enabled();
|
|
418
447
|
let toggle = deps.toggleCodexMultiAgentV2;
|
|
419
448
|
if (!toggle) {
|
|
420
449
|
const { runCodexFeaturesCommand } = await import("../../cli/v2");
|
|
421
|
-
toggle = (enabled
|
|
450
|
+
toggle = (enabled, env, validate) => runCodexFeaturesCommand(enabled ? "enable" : "disable", "multi_agent_v2", env, validate);
|
|
422
451
|
}
|
|
423
452
|
const result = transitionMultiAgentV2(targetFlag, toggle, {
|
|
424
453
|
...(wantsThreads ? { threadLimit: body.maxConcurrentThreadsPerSession as number } : {}),
|
|
454
|
+
heldConfigWriteLock: heldWriteLock,
|
|
425
455
|
});
|
|
426
|
-
if (!result.ok) return jsonResponse({ error: `multi_agent_v2 transition failed: ${result.error}
|
|
456
|
+
if (!result.ok) return jsonResponse({ error: `multi_agent_v2 transition failed: ${result.error}`, ...(result.retryable === false ? { retryable: false } : {}) }, 502);
|
|
427
457
|
if (result.changed && result.threadLimit !== null) warnings.push(`Thread limit ${result.threadLimit} preserved for ${targetFlag ? "v2" : "v1"}.`);
|
|
428
458
|
}
|
|
429
459
|
if (wantsMode) {
|
|
@@ -459,10 +489,10 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
459
489
|
// asserts this route file contains no direct write primitive, and matches on the
|
|
460
490
|
// symbol name even inside a comment.
|
|
461
491
|
const scalarWrites: Array<{ field: string; run: () => { ok: true; changed: boolean } | { ok: false; error: string } }> = [];
|
|
462
|
-
if (wantsAgentsEnabled) scalarWrites.push({ field: "agentsEnabled", run: () => setAgentsEnabled(body.agentsEnabled as boolean | null) });
|
|
463
|
-
if (wantsMaxDepth) scalarWrites.push({ field: "agentsMaxDepth", run: () => setAgentsMaxDepth(body.agentsMaxDepth as number | null) });
|
|
464
|
-
if (wantsSubagentInstructions) scalarWrites.push({ field: "subagentDeveloperInstructions", run: () => setSubagentDeveloperInstructions(body.subagentDeveloperInstructions as string | null) });
|
|
465
|
-
if (wantsModeHintText) scalarWrites.push({ field: "multiAgentModeHintText", run: () => setMultiAgentModeHintText(body.multiAgentModeHintText as string | null) });
|
|
492
|
+
if (wantsAgentsEnabled) scalarWrites.push({ field: "agentsEnabled", run: () => setAgentsEnabled(body.agentsEnabled as boolean | null, undefined, heldWriteLock) });
|
|
493
|
+
if (wantsMaxDepth) scalarWrites.push({ field: "agentsMaxDepth", run: () => setAgentsMaxDepth(body.agentsMaxDepth as number | null, undefined, heldWriteLock) });
|
|
494
|
+
if (wantsSubagentInstructions) scalarWrites.push({ field: "subagentDeveloperInstructions", run: () => setSubagentDeveloperInstructions(body.subagentDeveloperInstructions as string | null, undefined, heldWriteLock) });
|
|
495
|
+
if (wantsModeHintText) scalarWrites.push({ field: "multiAgentModeHintText", run: () => setMultiAgentModeHintText(body.multiAgentModeHintText as string | null, undefined, heldWriteLock) });
|
|
466
496
|
const landed: string[] = [];
|
|
467
497
|
for (const write of scalarWrites) {
|
|
468
498
|
try {
|
|
@@ -476,6 +506,9 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
476
506
|
return jsonResponse({ error: `writing ${write.field} failed: ${message}${landed.length > 0 ? ` (already applied: ${landed.join(", ")})` : ""}` }, 502);
|
|
477
507
|
}
|
|
478
508
|
}
|
|
509
|
+
} finally {
|
|
510
|
+
if (heldWriteLock) releaseConfigWriteLock(heldWriteLock);
|
|
511
|
+
}
|
|
479
512
|
// Derived from fresh post-write readers (readConfigText is uncached): upstream
|
|
480
513
|
// lets an enabled multi_agent_v2 feature override [agents].enabled = false, so
|
|
481
514
|
// warn rather than reject — silently accepting would imply multi-agent is off.
|
|
@@ -527,35 +560,59 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
527
560
|
}
|
|
528
561
|
const body = parsedBody as { enabled?: unknown };
|
|
529
562
|
if (typeof body.enabled !== "boolean") return jsonResponse({ error: "body.enabled must be a boolean" }, 400);
|
|
530
|
-
const { isDefaultModeRequestUserInputEnabled, DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY } = await import("../../codex/features");
|
|
563
|
+
const { isDefaultModeRequestUserInputEnabled, DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY, activeCodexConfigPath } = await import("../../codex/features");
|
|
531
564
|
const before = isDefaultModeRequestUserInputEnabled();
|
|
532
565
|
let toggle = deps.toggleDefaultModeRequestUserInput;
|
|
533
566
|
if (!toggle) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const raw = err.stderr;
|
|
543
|
-
const stderrText = typeof raw === "string"
|
|
544
|
-
? raw.trim()
|
|
545
|
-
: raw instanceof Uint8Array ? new TextDecoder().decode(raw).trim() : "";
|
|
546
|
-
toggleError = stderrText || (err.message ?? String(error));
|
|
547
|
-
}
|
|
548
|
-
const enabled = isDefaultModeRequestUserInputEnabled();
|
|
549
|
-
if (toggleError !== null || enabled !== body.enabled) {
|
|
550
|
-
const reason = toggleError
|
|
551
|
-
?? `postcondition failed - the installed Codex build may not know the ${DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY} flag yet`;
|
|
552
|
-
return jsonResponse({ error: `default_mode_request_user_input toggle failed: ${reason}` }, 502);
|
|
567
|
+
try {
|
|
568
|
+
const { codexFeaturesInvocation, runCodexFeaturesCommand } = await import("../../cli/v2");
|
|
569
|
+
const action = body.enabled ? "enable" : "disable";
|
|
570
|
+
const invocation = codexFeaturesInvocation(action, DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY, process.platform, { requireAvailable: true });
|
|
571
|
+
toggle = (_enabled, env, validate) => runCodexFeaturesCommand(action, DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY, env, validate, invocation);
|
|
572
|
+
} catch (error) {
|
|
573
|
+
return jsonResponse({ error: `default_mode_request_user_input toggle failed: ${error instanceof Error ? error.message : String(error)}` }, 502);
|
|
574
|
+
}
|
|
553
575
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
576
|
+
// The `codex features` subprocess rewrites config.toml itself — run it
|
|
577
|
+
// under the shared write lock so it cannot interleave with an opencodex
|
|
578
|
+
// scalar edit or injection mid-write on either side.
|
|
579
|
+
const { acquireConfigWriteLock, releaseConfigWriteLock, configWriteLockFailureMessage, runConfigWriteChild, ConfigWriteDestinationChanged } = await import("../../codex/config-write-lock");
|
|
580
|
+
const configPath = activeCodexConfigPath();
|
|
581
|
+
// Preserve native home creation only after executable resolution succeeds.
|
|
582
|
+
prepareCodexHome(dirname(configPath));
|
|
583
|
+
{
|
|
584
|
+
const configLock = await acquireConfigWriteLock(configPath);
|
|
585
|
+
if (!configLock.ok) {
|
|
586
|
+
return jsonResponse({ error: configWriteLockFailureMessage(configLock), retryable: configLock.error === "locked" }, 502);
|
|
587
|
+
}
|
|
588
|
+
let toggleError: string | null = null;
|
|
589
|
+
let destinationChanged = false;
|
|
590
|
+
try {
|
|
591
|
+
runConfigWriteChild(configPath, configLock.handle, (env, validate) => toggle!(body.enabled as boolean, env, validate));
|
|
592
|
+
} catch (error) {
|
|
593
|
+
destinationChanged = error instanceof ConfigWriteDestinationChanged;
|
|
594
|
+
const err = error as { stderr?: unknown; message?: string };
|
|
595
|
+
const raw = err.stderr;
|
|
596
|
+
const stderrText = typeof raw === "string"
|
|
597
|
+
? raw.trim()
|
|
598
|
+
: raw instanceof Uint8Array ? new TextDecoder().decode(raw).trim() : "";
|
|
599
|
+
toggleError = stderrText || (err.message ?? String(error));
|
|
600
|
+
} finally {
|
|
601
|
+
releaseConfigWriteLock(configLock.handle);
|
|
602
|
+
}
|
|
603
|
+
if (destinationChanged) return jsonResponse({ error: toggleError, retryable: false }, 502);
|
|
604
|
+
const enabled = isDefaultModeRequestUserInputEnabled();
|
|
605
|
+
if (toggleError !== null || enabled !== body.enabled) {
|
|
606
|
+
const reason = toggleError
|
|
607
|
+
?? `postcondition failed - the installed Codex build may not know the ${DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY} flag yet`;
|
|
608
|
+
return jsonResponse({ error: `default_mode_request_user_input toggle failed: ${reason}` }, 502);
|
|
609
|
+
}
|
|
610
|
+
const warnings: string[] = [];
|
|
611
|
+
if (enabled !== before) {
|
|
612
|
+
warnings.push("Applies to new sessions; restart the Codex app or wait out its picker cache to see the change.");
|
|
613
|
+
}
|
|
614
|
+
return jsonResponse({ ok: true, enabled, changed: enabled !== before, warnings });
|
|
557
615
|
}
|
|
558
|
-
return jsonResponse({ ok: true, enabled, changed: enabled !== before, warnings });
|
|
559
616
|
}
|
|
560
617
|
|
|
561
618
|
// Subagent prompt injection model: single native or routed model whose info is
|
|
@@ -1368,6 +1425,12 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1368
1425
|
);
|
|
1369
1426
|
const webSearchOverride = config.claudeCode?.webSearchSidecar;
|
|
1370
1427
|
const visionOverride = config.claudeCode?.visionSidecar;
|
|
1428
|
+
const helperParent = config.claudeCode?.model?.includes("/") ? config.claudeCode.model.split("/")[0] : undefined;
|
|
1429
|
+
const helperVision = { ...config.visionSidecar, ...visionOverride };
|
|
1430
|
+
// Same pool the runtime picks (vision/plan.ts preferredVisionBackend): explicit, else inherited
|
|
1431
|
+
// from the Claude model's instance, so a usable inherited Pool 2 reports the Anthropic backend.
|
|
1432
|
+
const helperVisionInstance = helperVision.anthropicInstance ?? inheritedAnthropicInstance(config, helperParent);
|
|
1433
|
+
const helperVisionBackend = helperVision.backend ?? (sidecarOptionsAuth(config, helperVisionInstance).isAnthropicAuth ? "anthropic" : "openai");
|
|
1371
1434
|
const { firstPartyDesired, readFirstPartyProxyStatus } = await import("../../claude/first-party-settings");
|
|
1372
1435
|
const { observeClaudeDesktopMode } = await import("../../claude/desktop-first-party");
|
|
1373
1436
|
const { claudeInterceptEnabled, getClaudeInterceptState } = await import("../../claude/intercept/runtime");
|
|
@@ -1409,13 +1472,18 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1409
1472
|
alwaysEnableEffort: config.claudeCode?.alwaysEnableEffort === true,
|
|
1410
1473
|
autoContext: config.claudeCode?.autoContext !== false,
|
|
1411
1474
|
autoCompactWindow: config.claudeCode?.autoCompactWindow ?? null,
|
|
1475
|
+
contextAccounting: config.claudeCode?.contextAccounting === "200k" ? "200k" : "1m",
|
|
1412
1476
|
blockedSkills: config.claudeCode?.blockedSkills ?? null,
|
|
1413
1477
|
injectAgents: config.claudeCode?.injectAgents !== false,
|
|
1478
|
+
sidecarPools: {
|
|
1479
|
+
webSearchSidecar: sidecarAnthropicPoolOptions(config, { ...config.webSearchSidecar, ...webSearchOverride }, helperParent),
|
|
1480
|
+
visionSidecar: sidecarAnthropicPoolOptions(config, { ...helperVision, backend: helperVisionBackend }, helperParent),
|
|
1481
|
+
},
|
|
1414
1482
|
...(webSearchOverride && Object.keys(webSearchOverride).length > 0
|
|
1415
|
-
? { webSearchSidecar: { backend: webSearchOverride.backend, model: webSearchOverride.model } }
|
|
1483
|
+
? { webSearchSidecar: { backend: webSearchOverride.backend, model: webSearchOverride.model, ...(webSearchOverride.anthropicInstance ? { anthropicInstance: webSearchOverride.anthropicInstance } : {}) } }
|
|
1416
1484
|
: {}),
|
|
1417
1485
|
...(visionOverride && Object.keys(visionOverride).length > 0
|
|
1418
|
-
? { visionSidecar: { backend: visionOverride.backend, model: visionOverride.model } }
|
|
1486
|
+
? { visionSidecar: { backend: visionOverride.backend, model: visionOverride.model, ...(visionOverride.anthropicInstance ? { anthropicInstance: visionOverride.anthropicInstance } : {}) } }
|
|
1419
1487
|
: {}),
|
|
1420
1488
|
fastMode: config.fastMode,
|
|
1421
1489
|
forceAvailable: [...visibleNativeSlugs(config), ...filterCatalogVisibleModels(models, config).map(catalogModelSlug)],
|
|
@@ -1441,7 +1509,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1441
1509
|
return prototype === Object.prototype || prototype === null;
|
|
1442
1510
|
};
|
|
1443
1511
|
if (!isPlainObject(parsedBody)) return jsonResponse({ error: "body must be an object" }, 400);
|
|
1444
|
-
const body = parsedBody as { enabled?: unknown; cliFirstParty?: unknown; authMode?: unknown; model?: unknown; smallFastModel?: unknown; modelMap?: unknown; classifierModel?: unknown; classifierFallbacks?: unknown; systemEnv?: unknown; fastMode?: unknown; maxContextTokens?: unknown; alwaysEnableEffort?: unknown; tierModels?: unknown; autoContext?: unknown; autoCompactWindow?: unknown; blockedSkills?: unknown; injectAgents?: unknown; webSearchSidecar?: unknown; visionSidecar?: unknown };
|
|
1512
|
+
const body = parsedBody as { enabled?: unknown; cliFirstParty?: unknown; authMode?: unknown; model?: unknown; smallFastModel?: unknown; modelMap?: unknown; classifierModel?: unknown; classifierFallbacks?: unknown; systemEnv?: unknown; fastMode?: unknown; maxContextTokens?: unknown; alwaysEnableEffort?: unknown; tierModels?: unknown; autoContext?: unknown; autoCompactWindow?: unknown; contextAccounting?: unknown; blockedSkills?: unknown; injectAgents?: unknown; webSearchSidecar?: unknown; visionSidecar?: unknown };
|
|
1445
1513
|
if (body.cliFirstParty !== undefined && typeof body.cliFirstParty !== "boolean")
|
|
1446
1514
|
return jsonResponse({ error: "cliFirstParty must be a boolean" }, 400);
|
|
1447
1515
|
if (body.cliFirstParty !== undefined && Object.keys(body).length !== 1)
|
|
@@ -1563,6 +1631,8 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1563
1631
|
...(residual ? ["settings_residual"] : []),
|
|
1564
1632
|
] });
|
|
1565
1633
|
}
|
|
1634
|
+
const instanceError = anthropicSidecarPatchError(config, body, true);
|
|
1635
|
+
if (instanceError) return jsonResponse({ error: instanceError }, 400);
|
|
1566
1636
|
for (const field of ["webSearchSidecar", "visionSidecar"] as const) {
|
|
1567
1637
|
const section = body[field];
|
|
1568
1638
|
if (section === undefined || section === null) continue;
|
|
@@ -1587,7 +1657,9 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1587
1657
|
// gates cannot drift.
|
|
1588
1658
|
if (field === "visionSidecar" && typeof section.model === "string" && section.model !== "") {
|
|
1589
1659
|
const requested = section.model;
|
|
1590
|
-
const candidates = await visionCandidateRows(config
|
|
1660
|
+
const candidates = await visionCandidateRows({ ...config, visionSidecar: {
|
|
1661
|
+
...config.visionSidecar, ...sidecarSettingsAfterPatch(config.claudeCode?.visionSidecar, section),
|
|
1662
|
+
} });
|
|
1591
1663
|
const hint = section.backend === "anthropic" || section.backend === "openai"
|
|
1592
1664
|
|| section.backend === "routed"
|
|
1593
1665
|
? section.backend
|
|
@@ -1610,7 +1682,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1610
1682
|
// module as /api/sidecar-settings — a gate on one route and a stale copy on
|
|
1611
1683
|
// the other is no gate at all.
|
|
1612
1684
|
if (field === "webSearchSidecar"
|
|
1613
|
-
&& (section.model !== undefined || section.backend !== undefined)) {
|
|
1685
|
+
&& (section.model !== undefined || section.backend !== undefined || section.anthropicInstance !== undefined)) {
|
|
1614
1686
|
const stored = config.claudeCode?.webSearchSidecar;
|
|
1615
1687
|
// Validate against the SUBMITTED backend across the whole union, not
|
|
1616
1688
|
// just openai/anthropic (#2457). allowedBackends above already refused
|
|
@@ -1629,7 +1701,9 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1629
1701
|
: typeof section.model === "string"
|
|
1630
1702
|
? section.model
|
|
1631
1703
|
: stored?.model ?? config.webSearchSidecar?.model;
|
|
1632
|
-
const candidates = await webSearchCandidateRows(config
|
|
1704
|
+
const candidates = await webSearchCandidateRows({ ...config, webSearchSidecar: {
|
|
1705
|
+
...config.webSearchSidecar, ...sidecarSettingsAfterPatch(config.claudeCode?.webSearchSidecar, section),
|
|
1706
|
+
} });
|
|
1633
1707
|
if (effectiveModel && webSearchModelIsRejected(effectiveBackend, effectiveModel, candidates)) {
|
|
1634
1708
|
return jsonResponse(webSearchModelRejection(
|
|
1635
1709
|
"webSearchSidecar.model",
|
|
@@ -1650,8 +1724,10 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1650
1724
|
}
|
|
1651
1725
|
// The per-field validation above guarantees vision only ever carries the two-member
|
|
1652
1726
|
// union; the cast is the loop's shared-shape compromise, not a wider write path.
|
|
1653
|
-
const requested = section as { backend?: "openai" | "anthropic" | "xai" | "gemini" | "exa" | null; model?: string };
|
|
1727
|
+
const requested = section as { backend?: "openai" | "anthropic" | "xai" | "gemini" | "exa" | null; model?: string; anthropicInstance?: AnthropicInstanceId | null };
|
|
1654
1728
|
const override = { ...next[field] } as NonNullable<OcxClaudeCodeConfig[typeof field]>;
|
|
1729
|
+
if (requested.anthropicInstance === null) delete override.anthropicInstance;
|
|
1730
|
+
else if (isAnthropicInstanceId(requested.anthropicInstance)) override.anthropicInstance = requested.anthropicInstance;
|
|
1655
1731
|
if (requested.backend === null) delete override.backend;
|
|
1656
1732
|
else if (requested.backend !== undefined) override.backend = requested.backend as never;
|
|
1657
1733
|
if (requested.model === "") delete override.model;
|
|
@@ -1703,6 +1779,15 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1703
1779
|
if (body.autoContext) delete next.autoContext;
|
|
1704
1780
|
else next.autoContext = false;
|
|
1705
1781
|
}
|
|
1782
|
+
if (body.contextAccounting !== undefined) {
|
|
1783
|
+
// Sparse like autoContext (devlog/_plan/261009_claude_1m_default/030): "1m" is the default
|
|
1784
|
+
// and drops the key; "200k" is the only stored value.
|
|
1785
|
+
if (body.contextAccounting !== "1m" && body.contextAccounting !== "200k") {
|
|
1786
|
+
return jsonResponse({ error: "contextAccounting must be \"1m\" or \"200k\"" }, 400);
|
|
1787
|
+
}
|
|
1788
|
+
if (body.contextAccounting === "200k") next.contextAccounting = "200k";
|
|
1789
|
+
else delete next.contextAccounting;
|
|
1790
|
+
}
|
|
1706
1791
|
if (body.injectAgents !== undefined) {
|
|
1707
1792
|
// Default-on boolean (devlog 260712 070): true = drop the key, false = store.
|
|
1708
1793
|
if (typeof body.injectAgents !== "boolean") return jsonResponse({ error: "injectAgents must be a boolean" }, 400);
|
|
@@ -1817,7 +1902,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1817
1902
|
// levers feed the same injection, so a changed or cleared slot must not linger in
|
|
1818
1903
|
// launchd until the next restart.
|
|
1819
1904
|
const systemEnvInputs = ["systemEnv", "authMode", "model", "smallFastModel", "tierModels",
|
|
1820
|
-
"maxContextTokens", "alwaysEnableEffort", "autoContext", "autoCompactWindow"] as const;
|
|
1905
|
+
"maxContextTokens", "alwaysEnableEffort", "autoContext", "autoCompactWindow", "contextAccounting"] as const;
|
|
1821
1906
|
if (systemEnvInputs.some(field => body[field] !== undefined)) {
|
|
1822
1907
|
try {
|
|
1823
1908
|
await applySystemEnvToggle(config, config.port);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { isAnthropicOAuthInstance, configuredAnthropicInstance } from "../../providers/anthropic-instance";
|
|
1
2
|
import type { OcxConfig } from "../../types";
|
|
2
|
-
import {
|
|
3
|
+
import { effectiveAnthropicAccountThresholdForInstance, parseAnthropicAccountThreshold } from "../../oauth/anthropic-account-threshold";
|
|
3
4
|
import { OAUTH_PROVIDERS } from "../../oauth";
|
|
4
|
-
import {
|
|
5
|
+
import { setAnthropicAccountThresholdForInstance } from "../../oauth/store";
|
|
5
6
|
import { jsonResponse } from "../auth-cors";
|
|
6
7
|
import { readManagementJsonBodyOr } from "./body";
|
|
7
8
|
|
|
@@ -10,18 +11,20 @@ export async function handleAnthropicAccountThreshold(req: Request, config: OcxC
|
|
|
10
11
|
const body = await readManagementJsonBodyOr(req, {});
|
|
11
12
|
if (!body || typeof body !== "object" || Array.isArray(body)) return jsonResponse({ error: "body must be an object" }, 400);
|
|
12
13
|
const fields = body as Record<string, unknown>;
|
|
13
|
-
const
|
|
14
|
-
|
|
14
|
+
const provider = fields.provider;
|
|
15
|
+
if (typeof provider !== "string" || !isAnthropicOAuthInstance(provider)) return jsonResponse({ error: "account threshold requires Anthropic OAuth" }, 400);
|
|
16
|
+
const definition = OAUTH_PROVIDERS[provider];
|
|
17
|
+
const effectiveProvider = config.providers[provider]
|
|
15
18
|
?? definition?.resolveProviderConfig?.(config) ?? definition?.providerConfig;
|
|
16
|
-
if (
|
|
19
|
+
if (effectiveProvider?.authMode !== "oauth" || (provider === "anthropic2" && !configuredAnthropicInstance(config, provider))) {
|
|
17
20
|
return jsonResponse({ error: "account threshold requires Anthropic OAuth" }, 400);
|
|
18
21
|
}
|
|
19
22
|
if (typeof fields.accountId !== "string" || !fields.accountId.trim()) return jsonResponse({ error: "missing accountId" }, 400);
|
|
20
23
|
const threshold = parseAnthropicAccountThreshold(fields.threshold);
|
|
21
24
|
if (fields.threshold !== null && threshold === null) return jsonResponse({ error: "threshold must be an integer 0-100 or null" }, 400);
|
|
22
|
-
if (!await
|
|
23
|
-
return jsonResponse({ ok: true, provider
|
|
25
|
+
if (!await setAnthropicAccountThresholdForInstance(provider, fields.accountId, threshold)) return jsonResponse({ error: "account not found" }, 404);
|
|
26
|
+
return jsonResponse({ ok: true, provider, accountId: fields.accountId,
|
|
24
27
|
autoSwitchThresholdOverride: threshold,
|
|
25
|
-
effectiveAutoSwitchThreshold:
|
|
26
|
-
autoSwitchThreshold:
|
|
28
|
+
effectiveAutoSwitchThreshold: effectiveAnthropicAccountThresholdForInstance(provider, config, { autoSwitchThresholdOverride: threshold ?? undefined }),
|
|
29
|
+
autoSwitchThreshold: effectiveAnthropicAccountThresholdForInstance(provider, config) });
|
|
27
30
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { OcxConfig } from "../../types";
|
|
2
|
+
import type { AnthropicAccountPoolConfig } from "../../types/anthropic-account-pool";
|
|
3
|
+
import { configuredAnthropicInstance, type AnthropicInstanceId } from "../../providers/anthropic-instance";
|
|
4
|
+
import { rawAnthropicAccountPool, resolveAnthropicAccountPoolConfig } from "../../oauth/anthropic-pool-config";
|
|
5
|
+
import { readConfigFileSnapshot, validateConfigCandidate } from "../../config/diagnostics";
|
|
6
|
+
import { ConfigWritePublishedError } from "../../config/persist-unlocked";
|
|
7
|
+
import { mutatePersistedConfig } from "../../config";
|
|
8
|
+
import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
|
|
9
|
+
import { jsonResponse } from "../auth-cors";
|
|
10
|
+
|
|
11
|
+
/** Required instance; never synthesizes a deleted B provider row. */
|
|
12
|
+
export function writeAnthropicPoolSettings(config: OcxConfig, instance: AnthropicInstanceId, value: AnthropicAccountPoolConfig): void {
|
|
13
|
+
if (instance === "anthropic") config.anthropicAccountPool = value;
|
|
14
|
+
else {
|
|
15
|
+
const row = config.providers[instance];
|
|
16
|
+
if (!configuredAnthropicInstance(config, instance)) throw new Error("Anthropic pool instance is no longer configured");
|
|
17
|
+
row!.anthropicAccountPool = value;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Both Anthropic writers share publication-aware recovery from the atomic mutation owner.
|
|
23
|
+
* Recovery covers only the durable write. Once the file holds the new value, a stale live row
|
|
24
|
+
* or a failed reconcile is reported as saved with the fixed bookkeeping warning, never a 500.
|
|
25
|
+
*/
|
|
26
|
+
export function persistAnthropicPoolPatch(
|
|
27
|
+
config: OcxConfig,
|
|
28
|
+
instance: AnthropicInstanceId,
|
|
29
|
+
patch: (target: OcxConfig) => { changed: boolean; value: NonNullable<OcxConfig["anthropicAccountPool"]> },
|
|
30
|
+
): { status: "saved"; warning?: "config_bookkeeping_failed" } | { status: "failed"; response: Response } {
|
|
31
|
+
const before = readConfigFileSnapshot();
|
|
32
|
+
const unknown = () => ({ status: "failed" as const, response: jsonResponse({
|
|
33
|
+
error: "Pool settings save state is unknown; reload settings before editing again",
|
|
34
|
+
code: "config_save_state_unknown",
|
|
35
|
+
}, 409) });
|
|
36
|
+
const publish = (value: AnthropicAccountPoolConfig, warned: boolean): { status: "saved"; warning?: "config_bookkeeping_failed" } => {
|
|
37
|
+
let warning = warned;
|
|
38
|
+
try { writeAnthropicPoolSettings(config, instance, value); } catch { warning = true; }
|
|
39
|
+
try { reconcileLiveStateStores(); } catch { warning = true; }
|
|
40
|
+
return warning ? { status: "saved", warning: "config_bookkeeping_failed" } : { status: "saved" };
|
|
41
|
+
};
|
|
42
|
+
let durable: AnthropicAccountPoolConfig | undefined;
|
|
43
|
+
try {
|
|
44
|
+
const saved = mutatePersistedConfig(target => {
|
|
45
|
+
if (instance === "anthropic2" && !configuredAnthropicInstance(target, instance)) {
|
|
46
|
+
throw new Error("Anthropic pool instance is no longer configured");
|
|
47
|
+
}
|
|
48
|
+
return patch(target);
|
|
49
|
+
});
|
|
50
|
+
if (saved.status === "unavailable") return unknown();
|
|
51
|
+
durable = saved.value;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
const persisted = readConfigFileSnapshot();
|
|
54
|
+
if (persisted.diagnostics.source !== "file" || persisted.raw === undefined) return unknown();
|
|
55
|
+
// File reads may salvage hand edits; recovery success requires a strict, authoritative document.
|
|
56
|
+
let validated: ReturnType<typeof validateConfigCandidate>;
|
|
57
|
+
try { validated = validateConfigCandidate(JSON.parse(persisted.raw.replace(/^\uFEFF/, ""))); }
|
|
58
|
+
catch { return unknown(); }
|
|
59
|
+
if (!validated.ok) return unknown();
|
|
60
|
+
const current = validated.config;
|
|
61
|
+
if (instance === "anthropic2" && !configuredAnthropicInstance(current, instance)) return unknown();
|
|
62
|
+
let expected: AnthropicAccountPoolConfig;
|
|
63
|
+
try { expected = patch(structuredClone(current)).value; } catch { return unknown(); }
|
|
64
|
+
const matches = JSON.stringify(rawAnthropicAccountPool(current, instance)) === JSON.stringify(expected);
|
|
65
|
+
// A failed live write or reconcile must not turn confirmed publication back into a rollback.
|
|
66
|
+
if (matches) return publish(resolveAnthropicAccountPoolConfig(current, instance), true);
|
|
67
|
+
if (error instanceof ConfigWritePublishedError || persisted.raw !== before.raw) return unknown();
|
|
68
|
+
return { status: "failed", response: jsonResponse({ error: "Pool settings could not be saved" }, 500) };
|
|
69
|
+
}
|
|
70
|
+
if (durable === undefined) return unknown();
|
|
71
|
+
return publish(durable, false);
|
|
72
|
+
}
|