@bitkyc08/opencodex 2.86.0 → 2.88.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/gui/dist/assets/App-CHC89EXQ.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-B5bC7AWL.js} +1 -1
- package/gui/dist/assets/index-B5bsvxor.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-D8y2wTsB.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +9 -8
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/passthrough.ts +7 -23
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic-rejection-memo.ts +71 -0
- package/src/adapters/anthropic-replay.ts +135 -0
- package/src/adapters/anthropic-tool-catalog.ts +11 -0
- package/src/adapters/anthropic.ts +151 -33
- package/src/adapters/base.ts +2 -0
- package/src/adapters/claude-cli/adapter.ts +111 -23
- package/src/adapters/claude-cli/profiles.ts +2 -1
- package/src/adapters/claude-cli/stable-replay.ts +108 -0
- package/src/adapters/claude-cli/usage-admission.ts +230 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +18 -2
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-progress-history.ts +53 -0
- package/src/adapters/google.ts +52 -9
- package/src/adapters/openai-chat/messages.ts +4 -3
- package/src/adapters/openai-chat/passthrough.ts +8 -2
- package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
- package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
- package/src/adapters/openai-responses/passthrough.ts +57 -125
- package/src/adapters/openai-responses/request-strips.ts +25 -3
- package/src/adapters/openai-responses/transport.ts +81 -0
- package/src/adapters/qoder/adapter.ts +4 -2
- package/src/adapters/registry.ts +5 -0
- package/src/adapters/zed.ts +26 -1
- package/src/advisor/activity.ts +55 -0
- package/src/advisor/budget.ts +62 -0
- package/src/advisor/consult.ts +4 -1
- package/src/advisor/context.ts +16 -4
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +79 -31
- package/src/advisor/settings.ts +11 -2
- package/src/bridge/errors.ts +19 -1
- package/src/bridge/internal.ts +7 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +59 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/claude/desktop-3p-library.ts +32 -7
- package/src/claude/desktop-3p.ts +55 -13
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +77 -0
- package/src/claude/inbound.ts +33 -0
- package/src/claude/intercept/picker-ca-startup.ts +2 -1
- package/src/claude/intercept/picker-runtime.ts +6 -5
- package/src/claude/intercept/picker-trust-windows.ts +338 -0
- package/src/claude/intercept/picker-trust.ts +19 -0
- package/src/claude/intercept/port-policy.ts +30 -0
- package/src/claude/intercept/runtime.ts +97 -94
- package/src/claude/outbound.ts +46 -13
- package/src/cli/account-api.ts +1 -0
- package/src/cli/account-policy-dto.ts +6 -0
- package/src/cli/account-policy.ts +34 -5
- package/src/cli/account-preservation.ts +44 -0
- package/src/cli/account.ts +8 -2
- package/src/cli/advisor.ts +17 -3
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +5 -2
- package/src/cli/capabilities-base.ts +38 -10
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/codex-cli-update.ts +151 -41
- package/src/cli/codex-provider.ts +44 -0
- package/src/cli/combo-input.ts +10 -1
- package/src/cli/combo-result.ts +1 -1
- package/src/cli/combo.ts +2 -1
- package/src/cli/cross-home-owner.ts +33 -16
- package/src/cli/dispatch.ts +25 -4
- package/src/cli/doctor.ts +6 -1
- package/src/cli/ensure-desktop-cold-start.ts +60 -7
- package/src/cli/help.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/loopback-family.ts +31 -0
- package/src/cli/models-runtime-subcommands.ts +1 -0
- package/src/cli/models-runtime.ts +72 -8
- package/src/cli/observe-stream.ts +2 -0
- package/src/cli/observe.ts +16 -4
- package/src/cli/provider.ts +27 -5
- package/src/cli/registry.ts +28 -8
- package/src/cli/runtime-api.ts +5 -3
- package/src/cli/system-command.ts +40 -8
- package/src/cli/uninstall-client-state.ts +32 -0
- package/src/cli/usage-compare.ts +309 -0
- package/src/cli/usage-model-search.ts +4 -4
- package/src/cli/usage-report.ts +2 -2
- package/src/cli/v2.ts +1 -1
- package/src/client/machine-listener.ts +1 -3
- package/src/client/runtime.ts +1 -0
- package/src/clients/client-path.ts +51 -0
- package/src/clients/config-export/contracts.ts +2 -1
- package/src/clients/config-export.ts +18 -44
- package/src/clients/gentle-shell-path.ts +87 -0
- package/src/codex/account-auto-switch.ts +2 -0
- package/src/codex/account-identity.ts +29 -0
- package/src/codex/account-label.ts +1 -1
- package/src/codex/account-lifecycle.ts +21 -5
- package/src/codex/account-preservation.ts +28 -0
- package/src/codex/account-use-remaining.ts +23 -0
- package/src/codex/app-server-processes.ts +97 -7
- package/src/codex/app-server-restart-service.ts +55 -4
- package/src/codex/auth-api/account-list.ts +4 -0
- package/src/codex/auth-api/account-pause-group.ts +6 -50
- package/src/codex/auth-api/account-preservation.ts +57 -0
- package/src/codex/auth-api/routes.ts +8 -0
- package/src/codex/auth-api/use-remaining-route.ts +36 -0
- package/src/codex/auth-api.ts +0 -1
- package/src/codex/auth-context.ts +2 -2
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +9 -1
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +1 -1
- package/src/codex/catalog/gather-capture.ts +2 -0
- package/src/codex/catalog/metadata.ts +11 -9
- package/src/codex/catalog/model-hints.ts +21 -16
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/parsing.ts +30 -86
- package/src/codex/catalog/provider-models.ts +36 -34
- package/src/codex/catalog/routed-gather.ts +14 -13
- package/src/codex/cli-install-provenance.ts +105 -17
- package/src/codex/cli-update-plan.ts +545 -0
- package/src/codex/config-write-lock.ts +23 -2
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app-restart.ts +21 -0
- package/src/codex/desktop-compatibility/installed-build.ts +24 -6
- package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
- package/src/codex/desktop-compatibility/runtime.ts +12 -3
- package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
- package/src/codex/features.ts +85 -45
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/restore.ts +24 -2
- package/src/codex/inject/routing-classify.ts +4 -3
- package/src/codex/inject.ts +18 -27
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/native-profile-startup.ts +1 -1
- package/src/codex/ocx-compaction-history.ts +12 -10
- package/src/codex/paths.ts +86 -1
- package/src/codex/provider-routing.ts +174 -0
- package/src/codex/refresh.ts +2 -2
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/signed-in-identity.ts +43 -0
- package/src/codex/subagent-model-fallback.ts +1 -1
- package/src/codex/sync.ts +6 -1
- package/src/codex/user-identity.ts +16 -5
- package/src/codex/websocket-activity.ts +161 -0
- package/src/codex/websocket-registry.ts +25 -1
- package/src/codex/windows-local-appdata.ts +84 -0
- package/src/combos/failover.ts +6 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/resolve.ts +46 -10
- package/src/combos/types.ts +15 -0
- package/src/config/diagnostics.ts +33 -6
- package/src/config/live-reconcile.ts +38 -2
- package/src/config/load-degrade.ts +17 -0
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +53 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +22 -0
- package/src/config/schema/leaf-validators.ts +38 -9
- package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
- package/src/config/schema/native-reasoning-retention.ts +26 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +657 -297
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +116 -27
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +1 -0
- package/src/integrations/registry.ts +8 -0
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-upstream-tunnel.ts +18 -53
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +19 -3
- package/src/lib/gcp-adc.ts +96 -9
- package/src/lib/gcp-credential-input.ts +12 -0
- package/src/lib/lab-live-route-production.ts +5 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/redact.ts +2 -2
- package/src/lib/request-failure-model.ts +2 -0
- package/src/oauth/account-quota-rank.ts +69 -2
- package/src/oauth/anthropic-account-refusal.ts +20 -2
- package/src/oauth/anthropic-oauth-definitions.ts +3 -1
- package/src/oauth/anthropic-routing.ts +1 -21
- package/src/oauth/anthropic.ts +38 -7
- package/src/oauth/callback-server.ts +27 -1
- package/src/oauth/chatgpt-device.ts +1 -1
- package/src/oauth/chatgpt-shared.ts +83 -0
- package/src/oauth/chatgpt.ts +8 -72
- package/src/oauth/dsh.ts +203 -0
- package/src/oauth/generic-account-failover.ts +40 -11
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +184 -109
- package/src/oauth/local-token-detect.ts +21 -1
- package/src/oauth/login-cli-errors.ts +44 -0
- package/src/oauth/login-cli.ts +69 -7
- package/src/oauth/login-flow-state.ts +371 -23
- package/src/oauth/meta-muse-errors.ts +41 -0
- package/src/oauth/meta-muse-keychain.ts +52 -0
- package/src/oauth/meta-muse.ts +51 -84
- package/src/oauth/model-discovery-auth.ts +3 -0
- package/src/oauth/pool-settings-capability.ts +4 -2
- package/src/oauth/refresh-flight-retirement.ts +48 -0
- package/src/oauth/session-affinity-key.ts +23 -0
- package/src/oauth/store-anthropic-instance.ts +9 -2
- package/src/oauth/store.ts +12 -5
- package/src/protocols/encoders/adapter-events.ts +6 -5
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/provider-summary.ts +2 -1
- package/src/providers/api-key-cooldowns.ts +49 -0
- package/src/providers/api-key-resolve.ts +11 -6
- package/src/providers/api-key-response-ownership.ts +57 -0
- package/src/providers/api-key-selection.ts +31 -6
- package/src/providers/api-keys.ts +214 -3
- package/src/providers/claude-cli-usage.ts +96 -0
- package/src/providers/dsh-account-destination.ts +31 -0
- package/src/providers/dsh-account-models.ts +41 -0
- package/src/providers/gcp-credential-cleanup.ts +59 -0
- package/src/providers/key-failover.ts +54 -50
- package/src/providers/key-store.ts +7 -2
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/model-rename-migration.ts +2 -2
- package/src/providers/openai-tiers-destination.ts +11 -2
- package/src/providers/openai-tiers.ts +5 -12
- package/src/providers/opencode-go-transport.ts +3 -1
- package/src/providers/opencode-zen-rate-limit.ts +7 -2
- package/src/providers/quota/account-cache.ts +6 -5
- package/src/providers/quota/account-row.ts +34 -0
- package/src/providers/quota/antigravity.ts +28 -7
- package/src/providers/quota/dsh-account.ts +70 -0
- package/src/providers/quota/vendor-probes-oauth.ts +1 -93
- package/src/providers/quota-types.ts +2 -0
- package/src/providers/quota.ts +14 -20
- package/src/providers/registry/entries-core.ts +37 -8
- package/src/providers/registry/types.ts +1 -1
- package/src/providers/request-pacing.ts +77 -19
- package/src/providers/routed-config.ts +339 -0
- package/src/providers/service-tier.ts +2 -1
- package/src/quota/window-mapping.ts +5 -2
- package/src/responses/anthropic-progress.ts +34 -0
- package/src/responses/assistant-replay.ts +35 -0
- package/src/responses/compaction-history-framing.ts +65 -0
- package/src/responses/compaction.ts +6 -1
- package/src/responses/incomplete-reason.ts +31 -0
- package/src/responses/parser.ts +50 -5
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +74 -6
- package/src/responses/reasoning-retention.ts +173 -0
- package/src/responses/retained-compaction.ts +46 -0
- package/src/responses/schema.ts +23 -0
- package/src/router.ts +8 -321
- package/src/routing/analytics.ts +7 -15
- package/src/routing/health.ts +37 -22
- package/src/routing/history/indexer.ts +6 -5
- package/src/routing/history/ingest.ts +6 -2
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +17 -0
- package/src/server/auth-cors.ts +11 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native-reasoning.ts +123 -0
- package/src/server/chat-native-sse.ts +110 -7
- package/src/server/chat-native.ts +76 -17
- package/src/server/claude-messages.ts +56 -414
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/live-sideband.ts +3 -2
- package/src/server/index/model-alias-id.ts +36 -0
- package/src/server/index/optional-listeners.ts +2 -0
- package/src/server/index/serve-options.ts +20 -6
- package/src/server/index/startup-warnings.ts +2 -0
- package/src/server/index/websocket-handler.ts +18 -4
- package/src/server/index.ts +8 -8
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/lifecycle.ts +13 -1
- package/src/server/management/advisor-routes.ts +15 -4
- package/src/server/management/claude-desktop-picker-routes.ts +1 -1
- package/src/server/management/combo-routes.ts +176 -50
- package/src/server/management/companion-routes.ts +1 -1
- package/src/server/management/config-routes.ts +75 -4
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/model-routes.ts +11 -6
- package/src/server/management/model-rows.ts +14 -0
- package/src/server/management/native-integration-routes.ts +2 -7
- package/src/server/management/native-reasoning-retention-routes.ts +41 -0
- package/src/server/management/oauth-account-routes.ts +87 -14
- package/src/server/management/provider-gcp-credential-routes.ts +29 -0
- package/src/server/management/provider-key-pacing-routes.ts +31 -0
- package/src/server/management/provider-overwrite-carry.ts +7 -0
- package/src/server/management/provider-routes.ts +25 -30
- package/src/server/management/route-registry.ts +10 -1
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +117 -24
- package/src/server/messages-shared.ts +431 -0
- package/src/server/relay.ts +1 -1
- package/src/server/request-log-terminal-status.ts +1 -1
- package/src/server/request-log.ts +6 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +47 -19
- package/src/server/responses/adapter-delivery.ts +72 -12
- package/src/server/responses/adapter-dispatch.ts +46 -3
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +73 -41
- package/src/server/responses/anthropic-thinking-recovery.ts +153 -0
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/combo-stream-preflight.ts +3 -2
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +20 -10
- package/src/server/responses/compaction-retention.ts +61 -0
- package/src/server/responses/context-overflow.ts +11 -6
- package/src/server/responses/core-auth.ts +6 -0
- package/src/server/responses/core-codex-account.ts +11 -1
- package/src/server/responses/core-combo-failure.ts +2 -0
- package/src/server/responses/core-combo.ts +121 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +8 -4
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +7 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +23 -3
- package/src/server/responses/core.ts +2 -115
- package/src/server/responses/fetch-helpers.ts +73 -19
- package/src/server/responses/main-hard-lock-routing.ts +109 -0
- package/src/server/responses/native-response-control.ts +4 -2
- package/src/server/responses/passthrough-delivery.ts +3 -1
- package/src/server/responses/passthrough-dispatch.ts +15 -13
- package/src/server/responses/passthrough-execution.ts +3 -2
- package/src/server/responses/request-prepare.ts +77 -12
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +104 -27
- package/src/server/responses/response-effects.ts +2 -0
- package/src/server/responses/response-tool-ownership.ts +21 -0
- package/src/server/responses/run-turn-execution.ts +96 -30
- package/src/server/responses/sidecar-execution.ts +63 -9
- package/src/server/responses/sidecar-send-budget.ts +2 -2
- package/src/server/responses/terminal-guard.ts +2 -16
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/ws-bridge.ts +2 -0
- package/src/service/auth-preflight.ts +71 -0
- package/src/service/cli.ts +4 -2
- package/src/service/definition.ts +97 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +76 -57
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +72 -9
- package/src/service/repair.ts +2 -1
- package/src/service/state.ts +7 -8
- package/src/service/systemd.ts +1 -1
- package/src/service/windows-guarded-manager.ts +2 -1
- package/src/service/windows-ops.ts +1 -1
- package/src/service/windows-scheduler.ts +2 -7
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service/windows-wscript.ts +14 -0
- package/src/service.ts +4 -2
- package/src/sidecar/auth.ts +2 -0
- package/src/types/config.ts +64 -3
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +35 -0
- package/src/types/provider.ts +32 -6
- package/src/types/request.ts +31 -2
- package/src/types/wire.ts +31 -37
- package/src/types.ts +2 -0
- package/src/update/npm-invocation.mjs +12 -2
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +3 -0
- package/src/usage/ledger-retention.ts +2 -1
- package/src/usage/ledger-scanner.ts +6 -5
- package/src/usage/log.ts +17 -8
- package/src/usage/summary.ts +30 -11
- package/src/usage/telemetry-contract.ts +1 -0
- package/src/usage/user-cost-overlays.ts +14 -19
- package/src/usage/user-cost-validation.ts +38 -0
- package/src/vision/eligibility.ts +13 -0
- package/src/web-search/anthropic-history.ts +59 -0
- package/src/web-search/loop.ts +236 -73
- package/src/web-search/progress-stream.ts +15 -2
- package/src/web-search/run-turn-loop.ts +9 -1
- package/gui/dist/assets/App-BjlttVLy.js +0 -58
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-BMssP4k3.js +0 -94
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
|
@@ -289,13 +289,8 @@ function isConfigLockError(error: unknown): boolean {
|
|
|
289
289
|
*/
|
|
290
290
|
let grokToggleFlight: Promise<Response> | null = null;
|
|
291
291
|
|
|
292
|
-
/**
|
|
293
|
-
* Dynamic import, never static: management-api.ts statically imports THIS
|
|
294
|
-
* module, so a static import of fetchAllModels back from it would close a
|
|
295
|
-
* cycle. sync.ts:18-21 dodges the same cycle the same way.
|
|
296
|
-
*/
|
|
297
292
|
async function defaultFetchAllModels(config: OcxConfig) {
|
|
298
|
-
const { fetchAllModels } = await import("
|
|
293
|
+
const { fetchAllModels } = await import("./shared");
|
|
299
294
|
return fetchAllModels(config);
|
|
300
295
|
}
|
|
301
296
|
|
|
@@ -665,7 +660,7 @@ function pickerStateNote(picker: DesktopPickerStatus): string {
|
|
|
665
660
|
return "Picker mode is on: fully quit and reopen Claude Desktop to see OpenCodex models in the Code tab.";
|
|
666
661
|
}
|
|
667
662
|
if (picker.reason === "trust_pending" || picker.reason === "trust_declined") {
|
|
668
|
-
return `Picker mode is waiting for
|
|
663
|
+
return `Picker mode is waiting for certificate trust: run ${picker.hint ?? "ocx claude desktop picker trust"}.`;
|
|
669
664
|
}
|
|
670
665
|
return `Picker mode is off (${picker.reason}).`;
|
|
671
666
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
nativeReasoningRetentionSchema,
|
|
3
|
+
resolveNativeReasoningRetention,
|
|
4
|
+
} from "../../config/schema/native-reasoning-retention";
|
|
5
|
+
import { captureConfigTopLevelRollback, deleteConfigTopLevelKey } from "../../config/rebase-provenance";
|
|
6
|
+
import { ConfigWritePublishedError } from "../../config/persist-unlocked";
|
|
7
|
+
import { jsonResponse } from "../auth-cors";
|
|
8
|
+
import { readManagementJsonBodyOr } from "./body";
|
|
9
|
+
import type { ManagementContext } from "./context";
|
|
10
|
+
|
|
11
|
+
const INVALID_BODY = Symbol("invalid-body");
|
|
12
|
+
|
|
13
|
+
/** Partial PUTs preserve omitted allowances; null resets the entire block to defaults. */
|
|
14
|
+
export async function handleNativeReasoningRetentionRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
15
|
+
const { req, url, config } = ctx;
|
|
16
|
+
if (url.pathname === "/api/native-reasoning-retention" && req.method === "GET") {
|
|
17
|
+
return jsonResponse(resolveNativeReasoningRetention(config), 200, req, config);
|
|
18
|
+
}
|
|
19
|
+
if (url.pathname === "/api/native-reasoning-retention" && req.method === "PUT") {
|
|
20
|
+
const body = await readManagementJsonBodyOr(req, INVALID_BODY);
|
|
21
|
+
const parsed = body === null ? null : nativeReasoningRetentionSchema.safeParse(body);
|
|
22
|
+
if (parsed !== null && !parsed.success) {
|
|
23
|
+
return jsonResponse({ error: "body must be null or an object containing only boolean modelSwitch and accountSwitch fields" }, 400, req, config);
|
|
24
|
+
}
|
|
25
|
+
// Resolve the saver before changing the live object: no await may interleave mutation/save.
|
|
26
|
+
const persist = ctx.deps.saveConfigPreservingClaudeCode
|
|
27
|
+
?? (await import("../../config")).saveConfigPreservingClaudeCode;
|
|
28
|
+
const restore = captureConfigTopLevelRollback(config, ["nativeReasoningRetention"]);
|
|
29
|
+
try {
|
|
30
|
+
if (parsed === null) deleteConfigTopLevelKey(config, "nativeReasoningRetention");
|
|
31
|
+
else config.nativeReasoningRetention = { ...config.nativeReasoningRetention, ...parsed.data };
|
|
32
|
+
persist(config);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
// Published bytes remain authoritative even if later bookkeeping failed.
|
|
35
|
+
if (!(error instanceof ConfigWritePublishedError)) restore();
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
return jsonResponse(resolveNativeReasoningRetention(config), 200, req, config);
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
@@ -23,12 +23,17 @@ import {
|
|
|
23
23
|
import {
|
|
24
24
|
clearLoginState,
|
|
25
25
|
getLoginStatus,
|
|
26
|
+
isCodeLoginFlow,
|
|
26
27
|
isPublicOAuthProvider,
|
|
27
28
|
listOAuthProviders,
|
|
29
|
+
loginFlowAccountId,
|
|
28
30
|
OAUTH_PROVIDERS,
|
|
29
31
|
publicOAuthAuthenticationErrorMessage,
|
|
30
32
|
startLoginFlow,
|
|
33
|
+
submitLoginCode,
|
|
31
34
|
submitManualLoginCode,
|
|
35
|
+
supportsCodeLoginMode,
|
|
36
|
+
waitForLoginSettled,
|
|
32
37
|
} from "../../oauth";
|
|
33
38
|
import { OAuthMutationBusyError, removeCredential } from "../../oauth/store";
|
|
34
39
|
import { cancelKiroDeviceLogin, kiroDeviceConfigBaseline, startKiroDeviceLogin, statusKiroDeviceLogin, type KiroDeviceMethod } from "../../oauth/kiro-device-login";
|
|
@@ -253,9 +258,17 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
253
258
|
// the provider's loopback callback server (inside this process) captures the redirect in the
|
|
254
259
|
// background, then the credential is persisted. The GUI opens the URL and polls /api/oauth/status.
|
|
255
260
|
if (url.pathname === "/api/oauth/login" && req.method === "POST") {
|
|
256
|
-
const body = await readManagementJsonBodyOr(req, {}) as { provider?: string; addAccount?: boolean; accountId?: string; reauth?: boolean; openBrowser?: unknown; method?: unknown };
|
|
261
|
+
const body = await readManagementJsonBodyOr(req, {}) as { provider?: string; addAccount?: boolean; accountId?: string; reauth?: boolean; openBrowser?: unknown; method?: unknown; mode?: unknown };
|
|
257
262
|
const provider = (body.provider ?? "").trim().toLowerCase();
|
|
258
263
|
if (!isPublicOAuthProvider(provider)) return jsonResponse({ error: "unknown oauth provider" }, 400);
|
|
264
|
+
// Omitted mode is the localhost-callback flow this route has always run.
|
|
265
|
+
if (body.mode !== undefined && body.mode !== "callback" && body.mode !== "code") {
|
|
266
|
+
return jsonResponse({ error: "mode must be \"callback\" or \"code\"" }, 400);
|
|
267
|
+
}
|
|
268
|
+
const codeMode = body.mode === "code";
|
|
269
|
+
if (codeMode && !supportsCodeLoginMode(provider)) {
|
|
270
|
+
return jsonResponse({ error: "code_mode_unsupported" }, 400);
|
|
271
|
+
}
|
|
259
272
|
// Muse may import a local Keychain credential or start a device grant; add-account
|
|
260
273
|
// and reauth skip the import. All management login paths require the dashboard
|
|
261
274
|
// principal before credential acquisition. A raw token proves administration,
|
|
@@ -292,10 +305,15 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
292
305
|
// request may already have mutated live config and yielded before its save.
|
|
293
306
|
const persistedBaseline = readConfigDiagnostics().config;
|
|
294
307
|
// addAccount / reauth forces a fresh browser identity (skips local-CLI token import).
|
|
295
|
-
|
|
308
|
+
// Only code mode gets a flow id: it is what a later paste is pinned to. The callback
|
|
309
|
+
// flow's lifecycle argument stays exactly as it was.
|
|
310
|
+
const flowId = codeMode ? randomUUID() : undefined;
|
|
311
|
+
const { url: authUrl, instructions, deviceCode, expiresAt } = await startLoginFlow(provider, {
|
|
296
312
|
forceLogin: body.addAccount === true || reauth,
|
|
297
313
|
...(accountId ? { reauthAccountId: accountId } : {}),
|
|
314
|
+
...(codeMode ? { codeMode: true } : {}),
|
|
298
315
|
}, {
|
|
316
|
+
...(flowId ? { flowId } : {}),
|
|
299
317
|
// startLoginFlow returns the authorization URL before background persistence completes.
|
|
300
318
|
// Three-way reconcile settled disk changes so a failed login cannot leave a provider
|
|
301
319
|
// live-only and an in-flight management mutation cannot be erased before it saves.
|
|
@@ -322,7 +340,13 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
322
340
|
const { openUrl } = await import("../../lib/open-url");
|
|
323
341
|
browserLaunch = (await openUrl(authUrl)).status === "started" ? "started" : "failed";
|
|
324
342
|
}
|
|
325
|
-
return jsonResponse({
|
|
343
|
+
return jsonResponse({
|
|
344
|
+
url: authUrl,
|
|
345
|
+
instructions,
|
|
346
|
+
deviceCode,
|
|
347
|
+
browserLaunch,
|
|
348
|
+
...(codeMode ? { mode: "code", flowId, expiresAt } : {}),
|
|
349
|
+
});
|
|
326
350
|
} catch (err) {
|
|
327
351
|
if (err instanceof OAuthMutationBusyError) throw err;
|
|
328
352
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -347,25 +371,69 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
347
371
|
return result ? jsonResponse(result) : jsonResponse({ error: "unknown login flow" }, 404);
|
|
348
372
|
}
|
|
349
373
|
const { cancelLoginFlow } = await import("../../oauth");
|
|
350
|
-
|
|
374
|
+
// Without a flow id this cancels the provider's newest in-flight login, which is what the
|
|
375
|
+
// single-user GUI has always meant by "Cancel". A host running concurrent code logins for
|
|
376
|
+
// several users passes the flowId it was given at /api/oauth/login.
|
|
377
|
+
const cancelled = cancelLoginFlow(provider, typeof body.flowId === "string" ? body.flowId : undefined);
|
|
351
378
|
return jsonResponse({ ok: true, cancelled });
|
|
352
379
|
}
|
|
353
380
|
|
|
354
381
|
// Manual fallback for browser OAuth: paste the final redirect URL (or authorization code)
|
|
355
382
|
// when the browser cannot reach the loopback callback (remote/SSH/blocked localhost).
|
|
383
|
+
//
|
|
384
|
+
// A code-display flow (started with mode:"code", or identified by its flowId) gets the
|
|
385
|
+
// richer contract instead: the paste is answered with the OUTCOME of the token exchange,
|
|
386
|
+
// not merely with "accepted". A hosted caller has one paste box and no second channel, so
|
|
387
|
+
// "accepted" is indistinguishable from "worked" to the person typing.
|
|
356
388
|
if (url.pathname === "/api/oauth/login/code" && req.method === "POST") {
|
|
357
|
-
const body = await readManagementJsonBodyOr(req, {}) as { provider?: string; input?: string; code?: string };
|
|
389
|
+
const body = await readManagementJsonBodyOr(req, {}) as { provider?: string; input?: string; code?: string; flowId?: unknown };
|
|
358
390
|
const provider = (body.provider ?? "").trim().toLowerCase();
|
|
359
391
|
if (!isPublicOAuthProvider(provider)) return jsonResponse({ error: "unknown oauth provider" }, 400);
|
|
360
392
|
const consentRequired = metaMuseConsentRequired(provider, principal);
|
|
361
393
|
if (consentRequired) return consentRequired;
|
|
394
|
+
const flowId = typeof body.flowId === "string" ? body.flowId : undefined;
|
|
395
|
+
const maskEmails = emailMaskingEnabled(config);
|
|
396
|
+
// Only a code flow of THIS provider gets the outcome-waiting contract; that flow is single-use,
|
|
397
|
+
// so at most one request ever waits on it. A flow id naming anything else (a callback flow,
|
|
398
|
+
// another provider's flow, nothing) has no code login to complete.
|
|
399
|
+
const codeFlow = isCodeLoginFlow(provider, flowId);
|
|
400
|
+
if (flowId !== undefined && !codeFlow) return jsonResponse({ ok: false, error: "no_pending_login" }, 409);
|
|
362
401
|
const input = typeof body.input === "string" ? body.input : typeof body.code === "string" ? body.code : "";
|
|
363
402
|
// Authorization responses are measured in hundreds of bytes; never accept the
|
|
364
403
|
// generic management-body allowance here.
|
|
365
|
-
if (input.length > 4096)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
404
|
+
if (input.length > 4096) {
|
|
405
|
+
return codeFlow
|
|
406
|
+
? jsonResponse({ ok: false, error: "malformed_input" }, 400)
|
|
407
|
+
: jsonResponse({ error: "input too long" }, 400);
|
|
408
|
+
}
|
|
409
|
+
if (!codeFlow) {
|
|
410
|
+
const result = submitManualLoginCode(provider, input);
|
|
411
|
+
if (!result.ok) return jsonResponse({ error: result.error }, 409);
|
|
412
|
+
return jsonResponse({ ok: true });
|
|
413
|
+
}
|
|
414
|
+
const submitted = submitLoginCode(provider, input, flowId);
|
|
415
|
+
// Rejected before any token request: the flow stays alive so the user can paste again.
|
|
416
|
+
if (!submitted.ok) {
|
|
417
|
+
return jsonResponse({ ok: false, error: submitted.code }, submitted.code === "no_pending_login" ? 409 : 400);
|
|
418
|
+
}
|
|
419
|
+
// The exchange itself times out at 30s inside the provider client; this only has to
|
|
420
|
+
// outlast it so a hung socket surfaces as an answer rather than as a hung request.
|
|
421
|
+
// Pinned to the flow this paste actually landed in: with several code logins in flight for
|
|
422
|
+
// one provider, "the newest flow" by the time this resolves may be somebody else's.
|
|
423
|
+
const settled = await waitForLoginSettled(provider, 60_000, submitted.flowId);
|
|
424
|
+
const status = getLoginStatus(provider, maskEmails, submitted.flowId);
|
|
425
|
+
if (!settled) return jsonResponse({ ok: false, error: "provider_unreachable" }, 400);
|
|
426
|
+
if (status.error) {
|
|
427
|
+
const failure = status.errorCode ?? "invalid_or_expired_code";
|
|
428
|
+
return jsonResponse({ ok: false, error: failure }, failure === "no_pending_login" ? 409 : 400);
|
|
429
|
+
}
|
|
430
|
+
// A flow dropped while waiting (logout, clear) never completed, whatever the store now holds.
|
|
431
|
+
if (status.status !== "complete") return jsonResponse({ ok: false, error: "no_pending_login" }, 409);
|
|
432
|
+
// The account THIS flow wrote, which need not be the active one (reauth of an inactive account,
|
|
433
|
+
// or a concurrent login that finished later).
|
|
434
|
+
const savedId = loginFlowAccountId(provider, submitted.flowId);
|
|
435
|
+
const account = savedId === undefined ? undefined : status.accounts?.find(entry => entry.id === savedId);
|
|
436
|
+
return jsonResponse({ ok: true, ...(account ? { account: { id: account.id, email: account.email } } : {}) });
|
|
369
437
|
}
|
|
370
438
|
|
|
371
439
|
if (url.pathname === "/api/oauth/status" && req.method === "GET") {
|
|
@@ -597,10 +665,15 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
597
665
|
// sticky limit is refused identically whichever pool is addressed.
|
|
598
666
|
let strategy: string | undefined;
|
|
599
667
|
if (fields.strategy !== undefined) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
668
|
+
// reset-first is a Codex and generic strategy. The Anthropic runtime implements only
|
|
669
|
+
// quota, round-robin and fill-first, so that kind keeps refusing it rather than storing
|
|
670
|
+
// a strategy it would silently ignore.
|
|
671
|
+
const parsed = kind === "codex" ? parseCodexAccountPoolStrategy(fields.strategy)
|
|
672
|
+
: kind === "generic" ? parseGenericPoolStrategy(fields.strategy, provider)
|
|
673
|
+
: parseAccountPoolStrategy(fields.strategy);
|
|
674
|
+
if (parsed === null) return jsonResponse({ error: kind === "anthropic"
|
|
675
|
+
? "strategy must be one of: quota, round-robin, fill-first"
|
|
676
|
+
: `strategy must be one of: quota, round-robin, fill-first, reset-first${kind === "generic" && provider === "kiro" ? ", least-loaded" : ""}` }, 400);
|
|
604
677
|
strategy = parsed;
|
|
605
678
|
}
|
|
606
679
|
let stickyLimit: number | undefined;
|
|
@@ -754,7 +827,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
754
827
|
if (body.strategy === null) delete next.strategy;
|
|
755
828
|
else {
|
|
756
829
|
const parsed = parseGenericPoolStrategy(body.strategy, provider);
|
|
757
|
-
if (parsed === null) return jsonResponse({ error: `strategy must be one of: quota, round-robin, fill-first${provider === "kiro" ? ", least-loaded" : ""}` }, 400);
|
|
830
|
+
if (parsed === null) return jsonResponse({ error: `strategy must be one of: quota, round-robin, fill-first, reset-first${provider === "kiro" ? ", least-loaded" : ""}` }, 400);
|
|
758
831
|
next.strategy = parsed;
|
|
759
832
|
}
|
|
760
833
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { providerCredentialInputError } from "../../providers/api-keys";
|
|
2
|
+
import { commitProviderGcpCredentialRemoval } from "../../providers/gcp-credential-cleanup";
|
|
3
|
+
import { dropProviderCustomModels } from "../../providers/provider-id-rewrite";
|
|
4
|
+
import { forgetProviderContextCap } from "../../providers/context-cap";
|
|
5
|
+
import type { OcxConfig } from "../../types";
|
|
6
|
+
import { jsonResponse } from "../auth-cors";
|
|
7
|
+
|
|
8
|
+
/** Generic provider writes refuse credential JSON before any config mutation. */
|
|
9
|
+
export function providerGcpCredentialInputResponse(provider: { apiKey?: unknown; apiKeyPool?: unknown }): Response | null {
|
|
10
|
+
const error = providerCredentialInputError(provider);
|
|
11
|
+
return error ? jsonResponse({ error }, 400) : null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Keep marker capture, provider removal and persistence in one synchronous locked operation. */
|
|
15
|
+
export function removeProviderWithGcpCredentials(
|
|
16
|
+
config: OcxConfig,
|
|
17
|
+
name: string,
|
|
18
|
+
fallbackDefault: string | undefined,
|
|
19
|
+
save: (config: OcxConfig) => void,
|
|
20
|
+
): number {
|
|
21
|
+
return commitProviderGcpCredentialRemoval(config, name, () => {
|
|
22
|
+
if (fallbackDefault) config.defaultProvider = fallbackDefault;
|
|
23
|
+
delete config.providers[name];
|
|
24
|
+
const dropped = dropProviderCustomModels(config, name);
|
|
25
|
+
forgetProviderContextCap(config, name);
|
|
26
|
+
save(config);
|
|
27
|
+
return dropped;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { hasOwnProvider, isValidProviderName } from "../../config";
|
|
2
|
+
import { getKeyCooldownUntil } from "../../providers/key-failover";
|
|
3
|
+
import { providerRequestPacingStatus } from "../../providers/request-pacing";
|
|
4
|
+
import type { OcxProviderConfig } from "../../types";
|
|
5
|
+
import { jsonResponse } from "../auth-cors";
|
|
6
|
+
import type { ManagementContext } from "./context";
|
|
7
|
+
|
|
8
|
+
/** Read-only provider/key pacing diagnostics; configuration writes stay in the provider PATCH owner. */
|
|
9
|
+
export function handleProviderKeyPacingRoutes(
|
|
10
|
+
{ req, url, config }: Pick<ManagementContext, "req" | "url" | "config">,
|
|
11
|
+
): Response | null {
|
|
12
|
+
if (url.pathname !== "/api/provider-request-pacing" || req.method !== "GET") return null;
|
|
13
|
+
const statusFor = (name: string, provider: OcxProviderConfig) => {
|
|
14
|
+
const status = providerRequestPacingStatus(name, provider);
|
|
15
|
+
if (status.keys) status.keys = status.keys.map(row => ({ ...row, cooldownUntil: getKeyCooldownUntil(name, row.id) }));
|
|
16
|
+
return status;
|
|
17
|
+
};
|
|
18
|
+
const name = url.searchParams.get("name")?.trim();
|
|
19
|
+
if (name) {
|
|
20
|
+
if (!isValidProviderName(name) || !hasOwnProvider(config.providers, name)) {
|
|
21
|
+
return jsonResponse({ error: "unknown provider" }, 404);
|
|
22
|
+
}
|
|
23
|
+
return jsonResponse(statusFor(name, config.providers[name]!));
|
|
24
|
+
}
|
|
25
|
+
return jsonResponse(Object.fromEntries(
|
|
26
|
+
Object.entries(config.providers).map(([providerName, provider]) => [
|
|
27
|
+
providerName,
|
|
28
|
+
statusFor(providerName, provider),
|
|
29
|
+
]),
|
|
30
|
+
));
|
|
31
|
+
}
|
|
@@ -214,6 +214,13 @@ export function applyProviderCompatPatchFields(
|
|
|
214
214
|
else return { error: "preserveResponsesReasoningContent must be a boolean" };
|
|
215
215
|
touched = true;
|
|
216
216
|
}
|
|
217
|
+
if (Object.hasOwn(rawBody, "inferContextWindow")) {
|
|
218
|
+
const value = rawBody.inferContextWindow;
|
|
219
|
+
if (value === null) delete next.inferContextWindow;
|
|
220
|
+
else if (typeof value === "boolean") next.inferContextWindow = value;
|
|
221
|
+
else return { error: "inferContextWindow must be a boolean or null" };
|
|
222
|
+
touched = true;
|
|
223
|
+
}
|
|
217
224
|
if (Object.hasOwn(rawBody, "reasoningWireFormat")) {
|
|
218
225
|
const value = rawBody.reasoningWireFormat;
|
|
219
226
|
if (value === null) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { contextTierRecordConfigError, modelCapabilitiesConfigError, mergeModelCapabilities } from "../../config/provider-validation";
|
|
2
2
|
import { DECLARABLE_HOSTED_TOOL_TYPES } from "../../responses/hosted-tool-policy";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { cleanupRemovedGcpCredentials, providerGcpCredentialMarkers, snapshotProviderGcpCredentialMarkers } from "../../providers/gcp-credential-cleanup";
|
|
5
|
+
import { providerGcpCredentialInputResponse, removeProviderWithGcpCredentials } from "./provider-gcp-credential-routes";
|
|
4
6
|
import { readFileSync } from "node:fs";
|
|
5
7
|
import { isDeepStrictEqual } from "node:util";
|
|
6
8
|
import type { CatalogModel } from "../../codex/catalog";
|
|
@@ -78,7 +80,7 @@ import { cachedProviderQuotaIsExhausted } from "../../combos/resolve";
|
|
|
78
80
|
import { probeJevDecisionProvider } from "../../combos/jev";
|
|
79
81
|
import { comboDependsOnProviderRoute } from "./decision-model-validation";
|
|
80
82
|
import { clearKeyCooldowns, forgetApiKeyRotationCursor } from "../../providers/key-failover";
|
|
81
|
-
import {
|
|
83
|
+
import { handleProviderKeyPacingRoutes } from "./provider-key-pacing-routes";
|
|
82
84
|
import { CODEX_FORWARD_BASE_URL, isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
83
85
|
import { codexAccountNamespaceProviderCollisionError } from "../../codex/account-namespace-match";
|
|
84
86
|
import { clearThreadAccountMap } from "../../codex/routing";
|
|
@@ -853,7 +855,7 @@ function providerRoutingQuota(config: OcxConfig, name: string, now: number): Pro
|
|
|
853
855
|
|
|
854
856
|
// Removing search/MCP windows may leave only a timestamp. That is not inference evidence.
|
|
855
857
|
const percentages = [quota.fiveHourPercent, quota.weeklyPercent, quota.monthlyPercent,
|
|
856
|
-
...(quota.customWindows ?? []).map(window => window.percent)];
|
|
858
|
+
...(quota.customWindows ?? []).filter(window => window.modelId === undefined).map(window => window.percent)];
|
|
857
859
|
const hasPercentage = percentages.some(value => typeof value === "number" && Number.isFinite(value) && value >= 0);
|
|
858
860
|
const credits = quota.creditsUsd;
|
|
859
861
|
const hasCredits = credits !== undefined && Number.isFinite(credits.percent)
|
|
@@ -864,7 +866,7 @@ function providerRoutingQuota(config: OcxConfig, name: string, now: number): Pro
|
|
|
864
866
|
let validUntil = quota.updatedAt + PROVIDER_QUOTA_MAX_AGE_MS;
|
|
865
867
|
if (state === "exhausted") {
|
|
866
868
|
const resets = [quota.fiveHourResetAt, quota.weeklyResetAt, quota.monthlyResetAt,
|
|
867
|
-
...(quota.customWindows ?? []).map(window => window.resetAt)]
|
|
869
|
+
...(quota.customWindows ?? []).filter(window => window.modelId === undefined).map(window => window.resetAt)]
|
|
868
870
|
.filter((reset): reset is number => typeof reset === "number" && Number.isFinite(reset)
|
|
869
871
|
&& reset > now && reset < validUntil)
|
|
870
872
|
.sort((left, right) => left - right);
|
|
@@ -895,21 +897,8 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
895
897
|
});
|
|
896
898
|
}
|
|
897
899
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
if (name) {
|
|
901
|
-
if (!isValidProviderName(name) || !hasOwnProvider(config.providers, name)) {
|
|
902
|
-
return jsonResponse({ error: "unknown provider" }, 404);
|
|
903
|
-
}
|
|
904
|
-
return jsonResponse(providerRequestPacingStatus(name, config.providers[name]!));
|
|
905
|
-
}
|
|
906
|
-
return jsonResponse(Object.fromEntries(
|
|
907
|
-
Object.entries(config.providers).map(([providerName, provider]) => [
|
|
908
|
-
providerName,
|
|
909
|
-
providerRequestPacingStatus(providerName, provider),
|
|
910
|
-
]),
|
|
911
|
-
));
|
|
912
|
-
}
|
|
900
|
+
const keyPacingResponse = handleProviderKeyPacingRoutes(ctx);
|
|
901
|
+
if (keyPacingResponse) return keyPacingResponse;
|
|
913
902
|
|
|
914
903
|
if (url.pathname === "/api/providers" && req.method === "GET") {
|
|
915
904
|
return jsonResponse(Object.entries(config.providers).map(([name, p]) => ({
|
|
@@ -922,6 +911,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
922
911
|
requestPacing: p.requestPacing,
|
|
923
912
|
models: p.models ?? [],
|
|
924
913
|
contextWindow: p.contextWindow,
|
|
914
|
+
inferContextWindow: p.inferContextWindow,
|
|
925
915
|
modelContextWindows: p.modelContextWindows,
|
|
926
916
|
modelContextTiers: p.modelContextTiers,
|
|
927
917
|
modelCapabilities: p.modelCapabilities,
|
|
@@ -1073,6 +1063,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1073
1063
|
if (resolvedError) return jsonResponse({ error: resolvedError, code: "invalid_provider_destination" }, 400);
|
|
1074
1064
|
}
|
|
1075
1065
|
|
|
1066
|
+
let removedCredentials: string[] = [];
|
|
1076
1067
|
const outcome = mutatePersistedConfig<ProviderEditorMutationValue>(persisted => {
|
|
1077
1068
|
if (!isDeepStrictEqual(providerEditorConfigDTO(persisted), baselineResult.value)) {
|
|
1078
1069
|
return {
|
|
@@ -1095,6 +1086,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1095
1086
|
initializeProviderModelSelection(name, provider, undefined, candidate.config);
|
|
1096
1087
|
}
|
|
1097
1088
|
}
|
|
1089
|
+
removedCredentials = Object.values(persisted.providers).flatMap(providerGcpCredentialMarkers);
|
|
1098
1090
|
persisted.defaultProvider = candidate.config.defaultProvider;
|
|
1099
1091
|
persisted.providers = structuredClone(candidate.config.providers);
|
|
1100
1092
|
persisted.disabledModels = candidate.config.disabledModels;
|
|
@@ -1120,6 +1112,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1120
1112
|
return jsonResponse({ error: outcome.value.error, code: outcome.value.code }, outcome.value.status);
|
|
1121
1113
|
}
|
|
1122
1114
|
|
|
1115
|
+
cleanupRemovedGcpCredentials(removedCredentials);
|
|
1123
1116
|
adoptProviderEditorCandidate(config, outcome.value.config);
|
|
1124
1117
|
reconcileLiveStateStores();
|
|
1125
1118
|
refreshConfigDerivedRegistries(outcome.value.config);
|
|
@@ -1137,16 +1130,15 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1137
1130
|
return jsonResponse({ success: true, catalogRefresh });
|
|
1138
1131
|
}
|
|
1139
1132
|
|
|
1140
|
-
//
|
|
1141
|
-
// persists — existing providers' real keys are never round-tripped (unlike PUT /api/config,
|
|
1142
|
-
// which would re-save the masked keys from GET). Live routing picks it up immediately.
|
|
1133
|
+
// Provider POST merges directly; existing secrets never round-trip through masked DTOs.
|
|
1143
1134
|
if (url.pathname === "/api/providers" && req.method === "POST") {
|
|
1144
1135
|
let body: { name?: unknown; provider?: unknown; setDefault?: boolean };
|
|
1145
1136
|
try { body = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
|
|
1146
1137
|
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
1147
1138
|
if (!isPlainRecord(body.provider)) return jsonResponse({ error: "provider must be a plain object" }, 400);
|
|
1148
|
-
|
|
1149
|
-
|
|
1139
|
+
const credentialResponse = providerGcpCredentialInputResponse(body.provider);
|
|
1140
|
+
if (credentialResponse) return credentialResponse;
|
|
1141
|
+
// Reject canonical-row clears before normalization hides their ownership.
|
|
1150
1142
|
if (name === "openai" && (Object.hasOwn(body.provider, "autoReviewModel") || Object.hasOwn(body.provider, "autoReviewModelOverrides"))) {
|
|
1151
1143
|
return jsonResponse({ error: "provider openai must not include autoReviewModel or autoReviewModelOverrides" }, 400);
|
|
1152
1144
|
}
|
|
@@ -1357,7 +1349,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1357
1349
|
const candidate = stripRegistryOnlyStaticHeaders(name, prov);
|
|
1358
1350
|
const previous = Object.getOwnPropertyDescriptor(config.providers, name);
|
|
1359
1351
|
const rollback = pinsOwned ? captureConfigTopLevelRollback(config, ["defaultProvider", "modelDiscovery", "disabledModels"]) : undefined;
|
|
1360
|
-
let validationError: string | undefined;
|
|
1352
|
+
let validationError: string | undefined, removedCredentials: string[] = [];
|
|
1361
1353
|
withConfigMutationLockSync(() => {
|
|
1362
1354
|
validationError = anthropicInstancePublicationError(name, candidate, config.providers[name]);
|
|
1363
1355
|
if (validationError) return;
|
|
@@ -1365,6 +1357,12 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1365
1357
|
if (submittedModelContextTiers && liveTiers) candidate.modelContextTiers = Object.assign(Object.create(null), liveTiers, candidate.modelContextTiers ?? {});
|
|
1366
1358
|
else if (!submittedModelContextTiers && liveTiers) candidate.modelContextTiers = { ...liveTiers };
|
|
1367
1359
|
else if (!submittedModelContextTiers) delete candidate.modelContextTiers;
|
|
1360
|
+
// Full forms omit the opt-in; preserve the latest value, including a concurrent clear.
|
|
1361
|
+
if (!Object.hasOwn(rawProvider, "inferContextWindow")) {
|
|
1362
|
+
const liveInference = config.providers[name]?.inferContextWindow;
|
|
1363
|
+
if (liveInference === undefined) delete candidate.inferContextWindow;
|
|
1364
|
+
else candidate.inferContextWindow = liveInference;
|
|
1365
|
+
}
|
|
1368
1366
|
const draft = { ...(registrationDraft ?? config), providers: { ...config.providers, [name]: candidate }, ...(body.setDefault === true ? { defaultProvider: name } : {}) };
|
|
1369
1367
|
const validation = validateConfigCandidate(draft); if (!validation.ok) { validationError = validation.error; return; }
|
|
1370
1368
|
try {
|
|
@@ -1375,6 +1373,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1375
1373
|
});
|
|
1376
1374
|
}
|
|
1377
1375
|
}
|
|
1376
|
+
removedCredentials = snapshotProviderGcpCredentialMarkers(name, config.providers[name]);
|
|
1378
1377
|
config.providers[name] = candidate;
|
|
1379
1378
|
if (body.setDefault === true) config.defaultProvider = name;
|
|
1380
1379
|
(deps.saveConfigPreservingClaudeCode ?? save)(config);
|
|
@@ -1388,6 +1387,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1388
1387
|
}
|
|
1389
1388
|
});
|
|
1390
1389
|
if (validationError !== undefined) return jsonResponse({ error: validationError }, 400);
|
|
1390
|
+
cleanupRemovedGcpCredentials(removedCredentials);
|
|
1391
1391
|
reconcileLiveStateStores();
|
|
1392
1392
|
if (prov.apiKey && prov.apiKeyPool) {
|
|
1393
1393
|
const { addProviderApiKey } = await import("../../providers/api-keys");
|
|
@@ -1848,12 +1848,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1848
1848
|
const { saveConfigPreservingClaudeCode: save } = await import("../../config");
|
|
1849
1849
|
// Deleting still succeeds; the response names the shadow-call target left without a provider.
|
|
1850
1850
|
const shadowDependency = shadowInterceptProviderDependency(config, name);
|
|
1851
|
-
|
|
1852
|
-
delete config.providers[name];
|
|
1853
|
-
const { dropProviderCustomModels } = await import("../../providers/provider-id-rewrite");
|
|
1854
|
-
const droppedCustomModels = dropProviderCustomModels(config, name);
|
|
1855
|
-
forgetProviderContextCap(config, name);
|
|
1856
|
-
save(config);
|
|
1851
|
+
const droppedCustomModels = removeProviderWithGcpCredentials(config, name, fallbackDefault, save);
|
|
1857
1852
|
await replaceProviderAccountSet(name, null);
|
|
1858
1853
|
reconcileLiveStateStores();
|
|
1859
1854
|
const { clearModelCache: clearCache } = await import("../../codex/model-cache");
|
|
@@ -97,8 +97,12 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
97
97
|
// server/management-api
|
|
98
98
|
{ method: "POST", path: "/api/stop", module: "server/management-api", mutates: true },
|
|
99
99
|
// codex/auth-api
|
|
100
|
+
{ method: "GET", path: "/api/codex-auth/preserve-signed-in", module: "codex/auth-api/routes", mutates: false },
|
|
101
|
+
{ method: "PUT", path: "/api/codex-auth/preserve-signed-in", module: "codex/auth-api/routes", mutates: true },
|
|
100
102
|
{ method: "DELETE", path: "/api/codex-auth/accounts", module: "codex/auth-api/routes", mutates: true },
|
|
101
103
|
{ method: "GET", path: "/api/codex-auth/accounts", module: "codex/auth-api/routes", mutates: false },
|
|
104
|
+
{ method: "GET", path: "/api/codex-auth/accounts/use-remaining", module: "codex/auth-api/routes", mutates: false },
|
|
105
|
+
{ method: "PUT", path: "/api/codex-auth/accounts/use-remaining", module: "codex/auth-api/routes", mutates: true },
|
|
102
106
|
{ method: "GET", path: "/api/codex-auth/active", module: "codex/auth-api/routes", mutates: false },
|
|
103
107
|
{ method: "GET", path: "/api/codex-auth/login-status", module: "codex/auth-api/routes", mutates: false },
|
|
104
108
|
{ method: "GET", path: "/api/codex-auth/quota", module: "codex/auth-api/routes", mutates: false },
|
|
@@ -217,8 +221,11 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
217
221
|
{ method: "POST", path: "/api/combos/decision-test", module: "server/management/decision-routes", mutates: true },
|
|
218
222
|
// server/management/config-routes
|
|
219
223
|
{ method: "GET", path: "/api/config", module: "server/management/config-routes", mutates: false },
|
|
224
|
+
{ method: "GET", path: "/api/auto-review-settings", module: "server/management/config-routes", mutates: false },
|
|
220
225
|
{ method: "GET", path: "/api/diagnostics/project-config", module: "server/management/config-routes", mutates: false },
|
|
221
226
|
{ method: "GET", path: "/api/settings", module: "server/management/config-routes", mutates: false },
|
|
227
|
+
{ method: "GET", path: "/api/native-reasoning-retention", module: "server/management/native-reasoning-retention-routes", mutates: false, exempt: { reason: "local-transport", why: "The CLI reads the same nativeReasoningRetention policy through ocx config get; no separate HTTP-specific settings verb is required." } },
|
|
228
|
+
{ method: "PUT", path: "/api/native-reasoning-retention", module: "server/management/native-reasoning-retention-routes", mutates: true, exempt: { reason: "deferred-verb", why: "A dedicated live CLI verb for this policy is deferred to native-reasoning-retention-live-cli; ocx config set/unset currently edits the same block on disk without live convergence.", owner: "native-reasoning-retention-live-cli", ownerDoc: "structure/transports/native-reasoning-retention.md" } },
|
|
222
229
|
{ method: "GET", path: "/api/shadow-call-settings", module: "server/management/config-routes", mutates: false },
|
|
223
230
|
{ method: "GET", path: "/api/sidecar-settings", module: "server/management/config-routes", mutates: false },
|
|
224
231
|
{ method: "GET", path: "/api/startup-health", module: "server/management/config-routes", mutates: false },
|
|
@@ -231,6 +238,7 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
231
238
|
{ method: "POST", path: "/api/windows-tray", module: "server/management/config-routes", mutates: true },
|
|
232
239
|
{ method: "PUT", path: "/api/config", module: "server/management/config-routes", mutates: true, exempt: { reason: "disabled", why: "Returns 405 by design; provider changes go through POST /api/providers." } },
|
|
233
240
|
{ method: "PUT", path: "/api/settings", module: "server/management/config-routes", mutates: true },
|
|
241
|
+
{ method: "PUT", path: "/api/auto-review-settings", module: "server/management/config-routes", mutates: true },
|
|
234
242
|
{ method: "PUT", path: "/api/shadow-call-settings", module: "server/management/config-routes", mutates: true },
|
|
235
243
|
{ method: "PUT", path: "/api/sidecar-settings", module: "server/management/config-routes", mutates: true },
|
|
236
244
|
// server/management/integration-routes
|
|
@@ -277,6 +285,7 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
277
285
|
{ method: "GET", path: "/api/usage/timeline", module: "server/management/usage-timeline-routes", mutates: false },
|
|
278
286
|
// server/management/advisor-routes
|
|
279
287
|
{ method: "GET", path: "/api/advisor/settings", module: "server/management/advisor-routes", mutates: false },
|
|
288
|
+
{ method: "GET", path: "/api/advisor/activity", module: "server/management/advisor-routes", mutates: false },
|
|
280
289
|
{ method: "PUT", path: "/api/advisor/settings", module: "server/management/advisor-routes", mutates: true },
|
|
281
290
|
// server/management/companion-routes
|
|
282
291
|
{ method: "POST", path: "/api/companion/open-in-browser", module: "server/management/companion-routes", mutates: true, exempt: { reason: "browser-navigation", why: "Opens the companion's current view in the system browser under normal management admission; the CLI reads the underlying settings and usage directly." } },
|
|
@@ -360,7 +369,7 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
360
369
|
{ method: "GET", path: "/api/provider-context-caps", module: "server/management/provider-routes", mutates: false },
|
|
361
370
|
{ method: "GET", path: "/api/provider-presets", module: "server/management/provider-routes", mutates: false },
|
|
362
371
|
{ method: "GET", path: "/api/provider-quotas", module: "server/management/provider-routes", mutates: false },
|
|
363
|
-
{ method: "GET", path: "/api/provider-request-pacing", module: "server/management/provider-routes", mutates: false },
|
|
372
|
+
{ method: "GET", path: "/api/provider-request-pacing", module: "server/management/provider-key-pacing-routes", mutates: false },
|
|
364
373
|
{ method: "GET", path: "/api/providers", module: "server/management/provider-routes", mutates: false },
|
|
365
374
|
{ method: "PATCH", path: "/api/providers", module: "server/management/provider-routes", mutates: true },
|
|
366
375
|
{ method: "POST", path: "/api/providers", module: "server/management/provider-routes", mutates: true },
|
|
@@ -59,6 +59,7 @@ import { applySystemEnvToggle } from "./system-env";
|
|
|
59
59
|
|
|
60
60
|
import type { ManagementApiDeps } from "./management/context";
|
|
61
61
|
import { handleConfigRoutes } from "./management/config-routes";
|
|
62
|
+
import { handleNativeReasoningRetentionRoutes } from "./management/native-reasoning-retention-routes";
|
|
62
63
|
import { handleLogsUsageRoutes } from "./management/logs-usage-routes";
|
|
63
64
|
import { handleStorageLogGuardRoutes } from "./management/storage-log-guard-routes";
|
|
64
65
|
import { handleRequestHistoryRoutes } from "./management/request-history-routes";
|
|
@@ -344,6 +345,7 @@ export async function handleManagementAPI(
|
|
|
344
345
|
?? (await handleLinkRoutesOnDemand(ctx))
|
|
345
346
|
?? (await handleRemoteWorkspaceRoutesOnDemand(ctx))
|
|
346
347
|
?? (await handleConfigRoutes(ctx))
|
|
348
|
+
?? (await handleNativeReasoningRetentionRoutes(ctx))
|
|
347
349
|
?? (await handleStorageLogGuardRoutes(ctx))
|
|
348
350
|
?? (await handleLogsUsageRoutes(ctx))
|
|
349
351
|
?? (await handleRequestHistoryRoutes(ctx))
|
|
@@ -519,7 +521,7 @@ export async function handleManagementAPI(
|
|
|
519
521
|
const { handleMainDeviceReauthAPI } = await import("../codex/main-device-reauth-api");
|
|
520
522
|
return handleMainDeviceReauthAPI(req, url, config);
|
|
521
523
|
}
|
|
522
|
-
const { handleCodexAuthAPI } = await import("../codex/auth-api");
|
|
524
|
+
const { handleCodexAuthAPI } = await import("../codex/auth-api/routes");
|
|
523
525
|
const { ConfigMutationLockError } = await import("../config");
|
|
524
526
|
const { CodexCredentialRefreshLockTimeoutError } = await import("../codex/account-store");
|
|
525
527
|
try {
|