@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
|
@@ -318,6 +318,25 @@ function invalidClientResponse(ctx: ManagementContext): Response {
|
|
|
318
318
|
}, 400, ctx.req, ctx.config);
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
+
/** Bind an explicitly scoped restore before profile delegation or mutation. */
|
|
322
|
+
function restoreClientScopeResponse(ctx: ManagementContext, opId: string, expectedClientId: unknown, pathClientId?: IntegrationClientId): Response | null {
|
|
323
|
+
if (pathClientId !== undefined) {
|
|
324
|
+
if (expectedClientId !== undefined && (typeof expectedClientId !== "string" || !isIntegrationClientId(expectedClientId))) return invalidClientResponse(ctx);
|
|
325
|
+
if (expectedClientId !== undefined && expectedClientId !== pathClientId) return jsonResponse({ error: "restore identity does not match the client path", code: "integration_client_mismatch" }, 409, ctx.req, ctx.config);
|
|
326
|
+
expectedClientId = pathClientId;
|
|
327
|
+
}
|
|
328
|
+
if (expectedClientId === undefined) return null;
|
|
329
|
+
if (typeof expectedClientId !== "string" || !isIntegrationClientId(expectedClientId)) return invalidClientResponse(ctx);
|
|
330
|
+
try {
|
|
331
|
+
const operation = integrationStore().findOperation(opId);
|
|
332
|
+
if (!operation) return jsonResponse({ error: "integration operation not found", code: "integration_operation_not_found", opId }, 404, ctx.req, ctx.config);
|
|
333
|
+
if (operation.clientId !== expectedClientId) return jsonResponse({ error: "integration operation belongs to a different client", code: "integration_client_mismatch" }, 409, ctx.req, ctx.config);
|
|
334
|
+
return null;
|
|
335
|
+
} catch (error) {
|
|
336
|
+
return internalErrorResponse(error, ctx);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
321
340
|
function isStringRecord(value: unknown): value is Record<string, string> {
|
|
322
341
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
323
342
|
&& Object.values(value).every(item => typeof item === "string");
|
|
@@ -775,7 +794,7 @@ export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<R
|
|
|
775
794
|
}
|
|
776
795
|
}
|
|
777
796
|
|
|
778
|
-
if (url.pathname === "/api/client-integrations/restore/preview") {
|
|
797
|
+
if (url.pathname === "/api/client-integrations/restore/preview" || url.pathname === "/api/client-integrations/commandcode/restore/preview") {
|
|
779
798
|
if (req.method !== "POST") return null;
|
|
780
799
|
const parsed = await readJsonBody(ctx);
|
|
781
800
|
if (parsed instanceof Response) return parsed;
|
|
@@ -786,6 +805,8 @@ export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<R
|
|
|
786
805
|
return jsonResponse({ error: "confirmDrift must be a boolean", code: "invalid_confirm_drift" }, 400, req, ctx.config);
|
|
787
806
|
}
|
|
788
807
|
const opId = parsed.opId.trim();
|
|
808
|
+
const scopeResponse = restoreClientScopeResponse(ctx, opId, parsed.expectedClientId, url.pathname === "/api/client-integrations/commandcode/restore/preview" ? "commandcode" : undefined);
|
|
809
|
+
if (scopeResponse) return scopeResponse;
|
|
789
810
|
try {
|
|
790
811
|
const store = integrationStore();
|
|
791
812
|
const operation = store.findOperation(opId);
|
|
@@ -819,7 +840,7 @@ export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<R
|
|
|
819
840
|
}
|
|
820
841
|
}
|
|
821
842
|
|
|
822
|
-
if (url.pathname === "/api/client-integrations/restore") {
|
|
843
|
+
if (url.pathname === "/api/client-integrations/restore" || url.pathname === "/api/client-integrations/commandcode/restore") {
|
|
823
844
|
if (req.method !== "POST") return null;
|
|
824
845
|
const parsed = await readJsonBody(ctx);
|
|
825
846
|
if (parsed instanceof Response) return parsed;
|
|
@@ -837,6 +858,8 @@ export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<R
|
|
|
837
858
|
}
|
|
838
859
|
|
|
839
860
|
const opId = parsed.opId.trim();
|
|
861
|
+
const scopeResponse = restoreClientScopeResponse(ctx, opId, parsed.expectedClientId, url.pathname === "/api/client-integrations/commandcode/restore" ? "commandcode" : undefined);
|
|
862
|
+
if (scopeResponse) return scopeResponse;
|
|
840
863
|
const confirmDrift = parsed.confirmDrift ?? false;
|
|
841
864
|
const restoreBinding = planBindingOf(parsed);
|
|
842
865
|
if (restoreBinding === "half") return halfBoundResponse(ctx);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { isAnthropicOAuthInstance, configuredAnthropicInstance, type AnthropicInstanceId } from "../../providers/anthropic-instance";
|
|
2
|
+
import { resolveAnthropicAccountPoolConfig } from "../../oauth/anthropic-pool-config";
|
|
3
|
+
import { persistAnthropicPoolPatch, writeAnthropicPoolSettings } from "./anthropic-pool-settings";
|
|
3
4
|
import { parseAnthropicModelRoutes, readAnthropicModelRoutes } from "../../oauth/anthropic-model-routes";
|
|
4
|
-
import {
|
|
5
|
+
import { effectiveAnthropicAccountThresholdForInstance } from "../../oauth/anthropic-account-threshold";
|
|
5
6
|
import { handleAnthropicAccountThreshold } from "./anthropic-account-threshold";
|
|
6
7
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
7
8
|
import { readFileSync } from "node:fs";
|
|
@@ -18,7 +19,6 @@ import {
|
|
|
18
19
|
readConfigDiagnostics,
|
|
19
20
|
reconcileLiveConfigFromDisk,
|
|
20
21
|
saveConfigPreservingClaudeCode,
|
|
21
|
-
mutatePersistedConfig,
|
|
22
22
|
} from "../../config";
|
|
23
23
|
import {
|
|
24
24
|
clearLoginState,
|
|
@@ -39,7 +39,7 @@ import { enrichProviderFromCatalog, listKeyLoginProviders } from "../../oauth/ke
|
|
|
39
39
|
import { deriveProviderPresets } from "../../providers/derive";
|
|
40
40
|
import { providerCodexAccountMode } from "../../providers/registry";
|
|
41
41
|
import { routedSlug, slugEquals } from "../../providers/slug-codec";
|
|
42
|
-
import { clearAccountQuotaCache, clearProviderQuotaCache, fetchProviderAccountQuotas, fetchProviderApiKeyQuotas, fetchProviderQuotaReports, providerOAuthAccountQuotaMode, providerApiKeyQuotaMode, readPassiveProviderAccountQuotas } from "../../providers/quota";
|
|
42
|
+
import { clearAccountQuotaCache, clearProviderQuotaCache, clearProviderQuotaCacheFor, fetchProviderAccountQuotas, fetchProviderApiKeyQuotas, fetchProviderQuotaReports, providerOAuthAccountQuotaMode, providerApiKeyQuotaMode, readPassiveProviderAccountQuotas } from "../../providers/quota";
|
|
43
43
|
import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
44
44
|
import { clearThreadAccountMap } from "../../codex/routing";
|
|
45
45
|
import {
|
|
@@ -101,6 +101,21 @@ function isDevinCloudDirectProvider(provider: string): boolean {
|
|
|
101
101
|
return provider === "devin" || provider === "devin-cli";
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Quota invalidation after an account mutation. A B mutation retires only B's provider-level
|
|
106
|
+
* report, routing row and per-account rows; A's cache, epochs and in-flight probes survive.
|
|
107
|
+
* Other providers keep the global clear; `accountRows` also drops that provider's account rows.
|
|
108
|
+
*/
|
|
109
|
+
function clearQuotaCachesAfterAccountMutation(provider: string, accountRows: boolean): void {
|
|
110
|
+
if (provider === "anthropic2") {
|
|
111
|
+
clearProviderQuotaCacheFor(provider);
|
|
112
|
+
clearAccountQuotaCache(provider);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
clearProviderQuotaCache();
|
|
116
|
+
if (accountRows) clearAccountQuotaCache(provider);
|
|
117
|
+
}
|
|
118
|
+
|
|
104
119
|
async function clearDevinCloudDirectCaches(): Promise<void> {
|
|
105
120
|
const { clearCachedUserJwt, clearCachedCatalog } = await import("../../adapters/devin/cloud-direct");
|
|
106
121
|
clearCachedUserJwt();
|
|
@@ -208,49 +223,12 @@ function metaMuseConsentRequired(provider: string, principal: ManagementContext[
|
|
|
208
223
|
|
|
209
224
|
function genericOAuthProviderConfig(provider: string, config: ManagementContext["config"]) {
|
|
210
225
|
const configured = config.providers[provider];
|
|
226
|
+
if (provider === "anthropic2" && !configuredAnthropicInstance(config, provider)) return undefined;
|
|
211
227
|
if (configured) return configured;
|
|
212
228
|
const definition = OAUTH_PROVIDERS[provider];
|
|
213
229
|
return definition?.resolveProviderConfig?.(config) ?? definition?.providerConfig;
|
|
214
230
|
}
|
|
215
231
|
|
|
216
|
-
/** Both Anthropic writers share publication-aware recovery from the atomic mutation owner. */
|
|
217
|
-
function persistAnthropicPoolPatch(
|
|
218
|
-
config: OcxConfig,
|
|
219
|
-
patch: (target: OcxConfig) => { changed: boolean; value: NonNullable<OcxConfig["anthropicAccountPool"]> },
|
|
220
|
-
): { status: "saved"; warning?: "config_bookkeeping_failed" } | { status: "failed"; response: Response } {
|
|
221
|
-
const before = readConfigFileSnapshot();
|
|
222
|
-
const unknown = () => ({ status: "failed" as const, response: jsonResponse({
|
|
223
|
-
error: "Pool settings save state is unknown; reload settings before editing again",
|
|
224
|
-
code: "config_save_state_unknown",
|
|
225
|
-
}, 409) });
|
|
226
|
-
try {
|
|
227
|
-
const saved = mutatePersistedConfig(patch);
|
|
228
|
-
if (saved.status === "unavailable") return unknown();
|
|
229
|
-
config.anthropicAccountPool = saved.value;
|
|
230
|
-
reconcileLiveStateStores();
|
|
231
|
-
return { status: "saved" };
|
|
232
|
-
} catch (error) {
|
|
233
|
-
const persisted = readConfigFileSnapshot();
|
|
234
|
-
if (persisted.diagnostics.source !== "file" || persisted.raw === undefined) return unknown();
|
|
235
|
-
// File reads may salvage hand edits; recovery success requires a strict, authoritative document.
|
|
236
|
-
let validated: ReturnType<typeof validateConfigCandidate>;
|
|
237
|
-
try { validated = validateConfigCandidate(JSON.parse(persisted.raw.replace(/^\uFEFF/, ""))); }
|
|
238
|
-
catch { return unknown(); }
|
|
239
|
-
if (!validated.ok) return unknown();
|
|
240
|
-
const current = validated.config;
|
|
241
|
-
const expected = patch(structuredClone(current)).value;
|
|
242
|
-
const matches = JSON.stringify(current.anthropicAccountPool) === JSON.stringify(expected);
|
|
243
|
-
if (matches) {
|
|
244
|
-
config.anthropicAccountPool = current.anthropicAccountPool;
|
|
245
|
-
// A failed reconcile must not turn confirmed publication back into a rollback.
|
|
246
|
-
try { reconcileLiveStateStores(); } catch { /* Fixed warning covers bookkeeping failure. */ }
|
|
247
|
-
return { status: "saved", warning: "config_bookkeeping_failed" };
|
|
248
|
-
}
|
|
249
|
-
if (error instanceof ConfigWritePublishedError || persisted.raw !== before.raw) return unknown();
|
|
250
|
-
return { status: "failed", response: jsonResponse({ error: "Pool settings could not be saved" }, 500) };
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
232
|
export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
255
233
|
const { req, url, config, deps, principal, syncClaudeAgentDefsBestEffort } = ctx;
|
|
256
234
|
|
|
@@ -422,9 +400,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
422
400
|
clearModelCache(provider);
|
|
423
401
|
clearGatherRoutedModelsInflight();
|
|
424
402
|
// Drop cached/last-good quota rows tied to the removed credential.
|
|
425
|
-
|
|
426
|
-
clearProviderQuotaCache();
|
|
427
|
-
clearAccountQuotaCache(provider);
|
|
403
|
+
clearQuotaCachesAfterAccountMutation(provider, true);
|
|
428
404
|
// The cached user_jwt's payload contains the api_key, and the catalog is
|
|
429
405
|
// keyed by that key. Without this they outlive the credential in process
|
|
430
406
|
// memory until the JWT's own ~24 minute expiry. `devin-cli` is a deprecated
|
|
@@ -445,7 +421,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
445
421
|
const { isGenericFailoverProvider, kiroAutoSelection } = await import("../../oauth/generic-account-failover");
|
|
446
422
|
const effectiveProvider = genericOAuthProviderConfig(provider, config);
|
|
447
423
|
const supportsPause = effectiveProvider !== undefined
|
|
448
|
-
&& (provider
|
|
424
|
+
&& (isAnthropicOAuthInstance(provider) && effectiveProvider.authMode === "oauth"
|
|
449
425
|
|| isGenericFailoverProvider(provider, effectiveProvider));
|
|
450
426
|
const {
|
|
451
427
|
oauthAccountHealthFields,
|
|
@@ -456,7 +432,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
456
432
|
const set = getAccountSet(provider);
|
|
457
433
|
const current = getLoginStatus(provider, emailMaskingEnabled(config));
|
|
458
434
|
return {
|
|
459
|
-
activeAccountId: current.activeAccountId ?? null,
|
|
435
|
+
...(provider === "anthropic2" ? { provider } : {}), activeAccountId: current.activeAccountId ?? null,
|
|
460
436
|
accounts: (current.accounts ?? []).map(summary => {
|
|
461
437
|
const full = set?.accounts.find(account => account.id === summary.id);
|
|
462
438
|
const health = full
|
|
@@ -467,9 +443,9 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
467
443
|
});
|
|
468
444
|
return { ...summary, ...oauthAccountHealthFields(provider, summary.id, health), quotaMode,
|
|
469
445
|
...(supportsPause ? { paused: full?.paused === true } : {}),
|
|
470
|
-
...(provider
|
|
471
|
-
effectiveAutoSwitchThreshold:
|
|
472
|
-
autoSwitchThreshold:
|
|
446
|
+
...(isAnthropicOAuthInstance(provider) && supportsPause ? { autoSwitchThresholdOverride: full?.autoSwitchThresholdOverride ?? null,
|
|
447
|
+
effectiveAutoSwitchThreshold: effectiveAnthropicAccountThresholdForInstance(provider as AnthropicInstanceId, config, full),
|
|
448
|
+
autoSwitchThreshold: effectiveAnthropicAccountThresholdForInstance(provider as AnthropicInstanceId, config) } : {}),
|
|
473
449
|
...(provider === "kiro" && full ? kiroAutoSelection(full) : {}) };
|
|
474
450
|
}),
|
|
475
451
|
};
|
|
@@ -489,11 +465,11 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
489
465
|
// from the post-probe store so the response is not stale.
|
|
490
466
|
const rows = passiveQuota
|
|
491
467
|
? readPassiveProviderAccountQuotas(provider)
|
|
492
|
-
: await fetchProviderAccountQuotas(provider, forceRefresh, quotaProvider);
|
|
468
|
+
: await fetchProviderAccountQuotas(provider, forceRefresh, quotaProvider, config);
|
|
493
469
|
const byId = new Map(rows.map(row => [row.accountId, row]));
|
|
494
470
|
const projected = projectAccounts();
|
|
495
471
|
return jsonResponse({
|
|
496
|
-
activeAccountId: projected.activeAccountId,
|
|
472
|
+
...(provider === "anthropic2" ? { provider } : {}), activeAccountId: projected.activeAccountId,
|
|
497
473
|
accounts: projected.accounts.map(account => {
|
|
498
474
|
const row = byId.get(account.id);
|
|
499
475
|
if (!row) return account;
|
|
@@ -514,6 +490,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
514
490
|
const body = await readManagementJsonBodyOr(req, {}) as { provider?: string; accountId?: string };
|
|
515
491
|
const provider = (body.provider ?? "").trim().toLowerCase();
|
|
516
492
|
if (!isPublicOAuthProvider(provider)) return jsonResponse({ error: "unknown oauth provider" }, 400);
|
|
493
|
+
if (provider === "anthropic2" && !configuredAnthropicInstance(config, provider)) return jsonResponse({ error: "Anthropic pool instance is not configured" }, 409);
|
|
517
494
|
if (!body.accountId) return jsonResponse({ error: "missing accountId" }, 400);
|
|
518
495
|
const { getAccountCredentialWithStatus, setActiveAccount } = await import("../../oauth/store");
|
|
519
496
|
const current = getAccountCredentialWithStatus(provider, body.accountId);
|
|
@@ -532,16 +509,15 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
532
509
|
// side carries resetCodexRoutingForManualSelection for.
|
|
533
510
|
const { genericPoolKey, seedPoolRotationAccount } = await import("../../oauth/pool-kernel");
|
|
534
511
|
seedPoolRotationAccount(genericPoolKey(provider), body.accountId);
|
|
535
|
-
if (provider
|
|
536
|
-
const {
|
|
537
|
-
resetAnthropicRoutingForManualSelection(body.accountId);
|
|
512
|
+
if (isAnthropicOAuthInstance(provider)) {
|
|
513
|
+
const { anthropicRoutingFor } = await import("../../oauth/anthropic-routing");
|
|
514
|
+
anthropicRoutingFor(provider).resetAnthropicRoutingForManualSelection(body.accountId);
|
|
538
515
|
}
|
|
539
516
|
const { clearModelCache } = await import("../../codex/model-cache");
|
|
540
517
|
const { clearGatherRoutedModelsInflight } = await import("../../codex/catalog");
|
|
541
518
|
clearModelCache(provider);
|
|
542
519
|
clearGatherRoutedModelsInflight();
|
|
543
|
-
|
|
544
|
-
clearProviderQuotaCache();
|
|
520
|
+
clearQuotaCachesAfterAccountMutation(provider, false);
|
|
545
521
|
return jsonResponse({ ok: true, provider, activeAccountId: body.accountId });
|
|
546
522
|
}
|
|
547
523
|
|
|
@@ -558,7 +534,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
558
534
|
|
|
559
535
|
const { isGenericFailoverProvider } = await import("../../oauth/generic-account-failover");
|
|
560
536
|
const effectiveProvider = genericOAuthProviderConfig(provider, config);
|
|
561
|
-
if (!effectiveProvider || !(provider
|
|
537
|
+
if (!effectiveProvider || !(isAnthropicOAuthInstance(provider) && effectiveProvider.authMode === "oauth"
|
|
562
538
|
|| isGenericFailoverProvider(provider, effectiveProvider))) {
|
|
563
539
|
return jsonResponse({ error: "account pause is not supported for this OAuth provider" }, 400);
|
|
564
540
|
}
|
|
@@ -568,9 +544,9 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
568
544
|
if (result.status === "not-found") return jsonResponse({ error: "account not found" }, 404);
|
|
569
545
|
|
|
570
546
|
if (result.activeAccountChanged) {
|
|
571
|
-
if (provider
|
|
572
|
-
const {
|
|
573
|
-
resetAnthropicRoutingForManualSelection(result.activeAccountId);
|
|
547
|
+
if (isAnthropicOAuthInstance(provider)) {
|
|
548
|
+
const { anthropicRoutingFor } = await import("../../oauth/anthropic-routing");
|
|
549
|
+
anthropicRoutingFor(provider).resetAnthropicRoutingForManualSelection(result.activeAccountId);
|
|
574
550
|
} else {
|
|
575
551
|
const { genericPoolKey, seedPoolRotationAccount } = await import("../../oauth/pool-kernel");
|
|
576
552
|
seedPoolRotationAccount(genericPoolKey(provider), result.activeAccountId);
|
|
@@ -579,8 +555,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
579
555
|
const { clearGatherRoutedModelsInflight } = await import("../../codex/catalog");
|
|
580
556
|
clearModelCache(provider);
|
|
581
557
|
clearGatherRoutedModelsInflight();
|
|
582
|
-
|
|
583
|
-
clearProviderQuotaCache();
|
|
558
|
+
clearQuotaCachesAfterAccountMutation(provider, false);
|
|
584
559
|
}
|
|
585
560
|
|
|
586
561
|
return jsonResponse({
|
|
@@ -671,7 +646,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
671
646
|
if (autoSwitchThreshold !== undefined) config.autoSwitchThreshold = autoSwitchThreshold;
|
|
672
647
|
} else if (kind === "anthropic") {
|
|
673
648
|
const patchPool = (target: OcxConfig) => {
|
|
674
|
-
const pool = { ...(target
|
|
649
|
+
const pool = { ...resolveAnthropicAccountPoolConfig(target, provider as AnthropicInstanceId) };
|
|
675
650
|
if (fields.nativeMessages !== undefined) pool.nativeMessages = fields.nativeMessages as boolean;
|
|
676
651
|
if (fields.enabled !== undefined) pool.enabled = fields.enabled as boolean;
|
|
677
652
|
if (strategy !== undefined) pool.strategy = strategy as never;
|
|
@@ -682,10 +657,10 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
682
657
|
if (fields.routes === null) delete pool.routes;
|
|
683
658
|
else if (parsedRoutes?.ok) pool.routes = parsedRoutes.routes;
|
|
684
659
|
}
|
|
685
|
-
target
|
|
660
|
+
writeAnthropicPoolSettings(target, provider as AnthropicInstanceId, pool);
|
|
686
661
|
return { changed: true, value: pool };
|
|
687
662
|
};
|
|
688
|
-
const saved = persistAnthropicPoolPatch(config, patchPool);
|
|
663
|
+
const saved = persistAnthropicPoolPatch(config, provider as AnthropicInstanceId, patchPool);
|
|
689
664
|
if (saved.status === "failed") return saved.response;
|
|
690
665
|
return jsonResponse({ ...unifiedPoolSettingsDto(config, provider, kind), ...(saved.warning ? { warning: saved.warning } : {}) });
|
|
691
666
|
} else {
|
|
@@ -710,7 +685,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
710
685
|
// Opt-in Anthropic OAuth account pool (#294): enable/threshold/strategy + clear cooldown.
|
|
711
686
|
if (url.pathname === "/api/oauth/accounts/pool" && req.method === "GET") {
|
|
712
687
|
const provider = (url.searchParams.get("provider") ?? "").trim().toLowerCase();
|
|
713
|
-
if (provider
|
|
688
|
+
if (!isAnthropicOAuthInstance(provider)) {
|
|
714
689
|
// Generic OAuth pool-settings contract (#695 slice 1): persisted per provider. `strategy`
|
|
715
690
|
// and `autoSwitchThreshold` stay inert until the selector consumes them; `enabled` already
|
|
716
691
|
// governs the pre-dispatch account preference. Codex keeps /api/codex-auth; api-key
|
|
@@ -722,7 +697,8 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
722
697
|
}
|
|
723
698
|
return jsonResponse(genericPoolSettingsDto(provider, prov, config.pool?.kernel === true));
|
|
724
699
|
}
|
|
725
|
-
|
|
700
|
+
if (provider === "anthropic2" && !configuredAnthropicInstance(config, provider)) return jsonResponse({ error: "Anthropic pool instance is not configured" }, 409);
|
|
701
|
+
const pool = resolveAnthropicAccountPoolConfig(config, provider);
|
|
726
702
|
return jsonResponse({
|
|
727
703
|
provider,
|
|
728
704
|
enabled: pool.enabled === true,
|
|
@@ -753,10 +729,10 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
753
729
|
};
|
|
754
730
|
const provider = typeof body.provider === "string" ? body.provider.trim().toLowerCase() : "";
|
|
755
731
|
if (Object.hasOwn(body, "nativeMessages")) {
|
|
756
|
-
if (provider
|
|
732
|
+
if (!isAnthropicOAuthInstance(provider)) return jsonResponse({ error: "nativeMessages is only part of the anthropic pool contract" }, 400);
|
|
757
733
|
if (typeof body.nativeMessages !== "boolean") return jsonResponse({ error: "nativeMessages must be a boolean" }, 400);
|
|
758
734
|
}
|
|
759
|
-
if (provider
|
|
735
|
+
if (!isAnthropicOAuthInstance(provider)) {
|
|
760
736
|
const {
|
|
761
737
|
poolSettingsCapability, genericPoolSettingsDto, parseGenericPoolStrategy, parseGenericAutoSwitchThreshold,
|
|
762
738
|
parseGenericStickyLimit, parseKiroAccountCap,
|
|
@@ -810,12 +786,13 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
810
786
|
saveConfigPreservingClaudeCode(config);
|
|
811
787
|
return jsonResponse({ ok: true, ...genericPoolSettingsDto(provider, prov, config.pool?.kernel === true) });
|
|
812
788
|
}
|
|
813
|
-
|
|
789
|
+
if (provider === "anthropic2" && !configuredAnthropicInstance(config, provider)) return jsonResponse({ error: "Anthropic pool instance is not configured" }, 409);
|
|
790
|
+
let enabled = resolveAnthropicAccountPoolConfig(config, provider)?.enabled === true;
|
|
814
791
|
if (body.enabled !== undefined) {
|
|
815
792
|
if (typeof body.enabled !== "boolean") return jsonResponse({ error: "enabled must be a boolean" }, 400);
|
|
816
793
|
enabled = body.enabled;
|
|
817
794
|
}
|
|
818
|
-
let threshold = config
|
|
795
|
+
let threshold = resolveAnthropicAccountPoolConfig(config, provider)?.autoSwitchThreshold ?? 80;
|
|
819
796
|
if (body.autoSwitchThreshold !== undefined) {
|
|
820
797
|
if (
|
|
821
798
|
typeof body.autoSwitchThreshold !== "number"
|
|
@@ -827,7 +804,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
827
804
|
}
|
|
828
805
|
threshold = body.autoSwitchThreshold;
|
|
829
806
|
}
|
|
830
|
-
let strategy = config
|
|
807
|
+
let strategy = resolveAnthropicAccountPoolConfig(config, provider)?.strategy;
|
|
831
808
|
if (body.strategy !== undefined) {
|
|
832
809
|
const parsed = parseAccountPoolStrategy(body.strategy);
|
|
833
810
|
if (parsed === null) {
|
|
@@ -835,7 +812,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
835
812
|
}
|
|
836
813
|
strategy = parsed;
|
|
837
814
|
}
|
|
838
|
-
let stickyLimit = config
|
|
815
|
+
let stickyLimit = resolveAnthropicAccountPoolConfig(config, provider)?.stickyLimit;
|
|
839
816
|
if (body.stickyLimit !== undefined) {
|
|
840
817
|
const parsed = parseAccountPoolStickyLimit(body.stickyLimit);
|
|
841
818
|
if (parsed === null) {
|
|
@@ -843,7 +820,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
843
820
|
}
|
|
844
821
|
stickyLimit = parsed;
|
|
845
822
|
}
|
|
846
|
-
let quotaWindow = config
|
|
823
|
+
let quotaWindow = resolveAnthropicAccountPoolConfig(config, provider)?.quotaWindow;
|
|
847
824
|
if (body.quotaWindow !== undefined) {
|
|
848
825
|
const parsed = parseAccountPoolQuotaWindow(body.quotaWindow);
|
|
849
826
|
if (parsed === null) {
|
|
@@ -855,7 +832,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
855
832
|
? parseAnthropicModelRoutes(body.routes) : null;
|
|
856
833
|
if (parsedLegacyRoutes && !parsedLegacyRoutes.ok) return jsonResponse({ error: parsedLegacyRoutes.error }, 400);
|
|
857
834
|
const legacyPatch = (target: OcxConfig) => {
|
|
858
|
-
const pool = { ...(target
|
|
835
|
+
const pool = { ...resolveAnthropicAccountPoolConfig(target, provider as AnthropicInstanceId) };
|
|
859
836
|
if (body.enabled !== undefined) pool.enabled = enabled;
|
|
860
837
|
else if (pool.enabled === undefined) pool.enabled = false;
|
|
861
838
|
if (body.nativeMessages !== undefined) pool.nativeMessages = body.nativeMessages as boolean;
|
|
@@ -868,21 +845,21 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
868
845
|
if (body.routes === null) delete pool.routes;
|
|
869
846
|
else if (parsedLegacyRoutes?.ok) pool.routes = parsedLegacyRoutes.routes;
|
|
870
847
|
}
|
|
871
|
-
target
|
|
848
|
+
writeAnthropicPoolSettings(target, provider as AnthropicInstanceId, pool);
|
|
872
849
|
return { changed: true, value: pool };
|
|
873
850
|
};
|
|
874
|
-
const saved = persistAnthropicPoolPatch(config, legacyPatch);
|
|
851
|
+
const saved = persistAnthropicPoolPatch(config, provider as AnthropicInstanceId, legacyPatch);
|
|
875
852
|
if (saved.status === "failed") return saved.response;
|
|
876
853
|
return jsonResponse({
|
|
877
854
|
ok: true,
|
|
878
855
|
provider,
|
|
879
|
-
enabled: config
|
|
880
|
-
autoSwitchThreshold: config
|
|
881
|
-
strategy: normalizeAccountPoolStrategy(config
|
|
882
|
-
stickyLimit: normalizeAccountPoolStickyLimit(config
|
|
883
|
-
quotaWindow: normalizeAccountPoolQuotaWindow(config
|
|
884
|
-
routes: config
|
|
885
|
-
nativeMessages: !Object.hasOwn(config
|
|
856
|
+
enabled: resolveAnthropicAccountPoolConfig(config, provider)?.enabled === true,
|
|
857
|
+
autoSwitchThreshold: resolveAnthropicAccountPoolConfig(config, provider)?.autoSwitchThreshold ?? 80,
|
|
858
|
+
strategy: normalizeAccountPoolStrategy(resolveAnthropicAccountPoolConfig(config, provider)?.strategy),
|
|
859
|
+
stickyLimit: normalizeAccountPoolStickyLimit(resolveAnthropicAccountPoolConfig(config, provider)?.stickyLimit),
|
|
860
|
+
quotaWindow: normalizeAccountPoolQuotaWindow(resolveAnthropicAccountPoolConfig(config, provider)?.quotaWindow),
|
|
861
|
+
routes: resolveAnthropicAccountPoolConfig(config, provider)?.routes ?? null,
|
|
862
|
+
nativeMessages: !Object.hasOwn(resolveAnthropicAccountPoolConfig(config, provider), "nativeMessages") || resolveAnthropicAccountPoolConfig(config, provider)?.nativeMessages === true,
|
|
886
863
|
...(saved.warning ? { warning: saved.warning } : {}),
|
|
887
864
|
experimental: true,
|
|
888
865
|
});
|
|
@@ -891,10 +868,11 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
891
868
|
const body = await readManagementJsonBodyOr(req, {}) as { provider?: unknown; accountId?: unknown };
|
|
892
869
|
const provider = typeof body.provider === "string" ? body.provider.trim().toLowerCase() : "";
|
|
893
870
|
const accountId = typeof body.accountId === "string" ? body.accountId.trim() : "";
|
|
894
|
-
if (provider
|
|
871
|
+
if (!isAnthropicOAuthInstance(provider)) return jsonResponse({ error: "clear-cooldown is only supported for anthropic" }, 400);
|
|
872
|
+
if (provider === "anthropic2" && !configuredAnthropicInstance(config, provider)) return jsonResponse({ error: "Anthropic pool instance is not configured" }, 409);
|
|
895
873
|
if (!accountId) return jsonResponse({ error: "missing accountId" }, 400);
|
|
896
|
-
const {
|
|
897
|
-
const cleared = clearAnthropicAccountCooldown(accountId);
|
|
874
|
+
const { anthropicRoutingFor } = await import("../../oauth/anthropic-routing");
|
|
875
|
+
const cleared = anthropicRoutingFor(provider).clearAnthropicAccountCooldown(accountId);
|
|
898
876
|
return jsonResponse({ ok: true, cleared });
|
|
899
877
|
}
|
|
900
878
|
|
|
@@ -936,8 +914,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
936
914
|
const { clearGatherRoutedModelsInflight } = await import("../../codex/catalog");
|
|
937
915
|
clearModelCache(provider);
|
|
938
916
|
clearGatherRoutedModelsInflight();
|
|
939
|
-
|
|
940
|
-
clearAccountQuotaCache(provider);
|
|
917
|
+
clearQuotaCachesAfterAccountMutation(provider, true);
|
|
941
918
|
}
|
|
942
919
|
if (!imported.ok) return jsonResponse({ code: imported.code }, imported.status);
|
|
943
920
|
return jsonResponse(imported.result);
|
|
@@ -969,19 +946,17 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
969
946
|
const { removeAccount, getAccountSet } = await import("../../oauth/store");
|
|
970
947
|
if (!(await removeAccount(provider, id))) return jsonResponse({ error: "account not found" }, 404);
|
|
971
948
|
reconcileLiveStateStores();
|
|
972
|
-
if (provider
|
|
973
|
-
const {
|
|
974
|
-
clearAnthropicAccountCooldown(id);
|
|
975
|
-
clearAnthropicSessionAffinityForAccount(id);
|
|
949
|
+
if (isAnthropicOAuthInstance(provider)) {
|
|
950
|
+
const { anthropicRoutingFor } = await import("../../oauth/anthropic-routing");
|
|
951
|
+
anthropicRoutingFor(provider).clearAnthropicAccountCooldown(id);
|
|
952
|
+
anthropicRoutingFor(provider).clearAnthropicSessionAffinityForAccount(id);
|
|
976
953
|
}
|
|
977
954
|
if (!getAccountSet(provider)) clearLoginState(provider);
|
|
978
955
|
const { clearModelCache } = await import("../../codex/model-cache");
|
|
979
956
|
const { clearGatherRoutedModelsInflight } = await import("../../codex/catalog");
|
|
980
957
|
clearModelCache(provider);
|
|
981
958
|
clearGatherRoutedModelsInflight();
|
|
982
|
-
|
|
983
|
-
clearProviderQuotaCache();
|
|
984
|
-
clearAccountQuotaCache(provider);
|
|
959
|
+
clearQuotaCachesAfterAccountMutation(provider, true);
|
|
985
960
|
// Same reasoning as logout. Removing the last account for a provider used to
|
|
986
961
|
// leave the JWT and catalog in memory, because only the logout route cleared
|
|
987
962
|
// them.
|
|
@@ -1168,7 +1143,13 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
1168
1143
|
if (principal !== "gui-session") {
|
|
1169
1144
|
return jsonResponse({ error: "dashboard session required" }, 403, req, config);
|
|
1170
1145
|
}
|
|
1146
|
+
// Local page bootstrap is sufficient for the dashboard, not stored-secret disclosure.
|
|
1147
|
+
const authorized = () => ctx.sessionControl?.canRevealDataKeys?.(req, config) === true;
|
|
1148
|
+
const denied = () => jsonResponse({ error: "operator-authorized dashboard session required" }, 403, req, config);
|
|
1149
|
+
if (!authorized()) return denied();
|
|
1171
1150
|
const body = await readJsonBody(req);
|
|
1151
|
+
// Body reception can outlive the session or its revocation; do not trust cached admission.
|
|
1152
|
+
if (!authorized()) return denied();
|
|
1172
1153
|
if (!body || Object.keys(body).length !== 1 || typeof body.id !== "string" || !body.id) {
|
|
1173
1154
|
return jsonResponse({ error: "invalid body" }, 400, req, config);
|
|
1174
1155
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import type { OcxProviderConfig } from "../../types";
|
|
3
|
+
import type { ProviderEditorProviderDTO } from "../auth-cors";
|
|
4
|
+
import { anthropicInstanceRowShapeMatches } from "../../providers/anthropic-instance-id";
|
|
5
|
+
import { assertAnthropicInstanceLoginConfig } from "../../oauth/store-anthropic-instance";
|
|
6
|
+
|
|
7
|
+
/** Preset publication may create B, but may never take over an existing custom row. */
|
|
8
|
+
export function anthropicInstancePublicationError(name: string, next: OcxProviderConfig, existing: OcxProviderConfig | undefined): string | undefined {
|
|
9
|
+
if (name === "anthropic2" && anthropicInstanceRowShapeMatches(name, next)) {
|
|
10
|
+
try { assertAnthropicInstanceLoginConfig({ providers: existing ? { [name]: existing } : {} }, name); }
|
|
11
|
+
catch { return "cannot add Pool 2 over an existing custom provider or unreadable configuration; resolve it first"; }
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Full form edits omit provenance; preserve only an already owned compatible row. */
|
|
17
|
+
export function preserveAnthropicInstanceMarker(name: string, next: OcxProviderConfig, existing: OcxProviderConfig | undefined): void {
|
|
18
|
+
if (name !== "anthropic2" || Object.hasOwn(next, "anthropicOAuthInstance")) return;
|
|
19
|
+
if (anthropicInstanceRowShapeMatches(name, existing) && next.adapter === "anthropic" && next.authMode === "oauth") {
|
|
20
|
+
next.anthropicOAuthInstance = "anthropic2";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function mergeProviderEditorRow(
|
|
25
|
+
persisted: OcxProviderConfig | undefined,
|
|
26
|
+
baseline: ProviderEditorProviderDTO | undefined,
|
|
27
|
+
next: ProviderEditorProviderDTO,
|
|
28
|
+
): OcxProviderConfig {
|
|
29
|
+
const merged = structuredClone(persisted ?? {}) as Record<string, unknown>;
|
|
30
|
+
const fields = new Set([...Object.keys(baseline ?? {}), ...Object.keys(next)]);
|
|
31
|
+
for (const field of fields) {
|
|
32
|
+
const baselineHasField = baseline !== undefined && Object.hasOwn(baseline, field);
|
|
33
|
+
const nextHasField = Object.hasOwn(next, field);
|
|
34
|
+
if (
|
|
35
|
+
baselineHasField === nextHasField
|
|
36
|
+
&& (!baselineHasField || isDeepStrictEqual(baseline[field], next[field]))
|
|
37
|
+
) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (nextHasField) merged[field] = structuredClone(next[field]);
|
|
41
|
+
else delete merged[field];
|
|
42
|
+
}
|
|
43
|
+
return merged as unknown as OcxProviderConfig;
|
|
44
|
+
}
|