@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
|
@@ -7,9 +7,9 @@ import { isBuiltinAnthropicInstanceRow } from "../providers/anthropic-instance";
|
|
|
7
7
|
* exists is Anthropic's — behind its own opt-in. So xAI, Cursor, Kimi, GitHub Copilot,
|
|
8
8
|
* Antigravity and Nous have no recovery path on a 429 even with several accounts logged in.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Conversation ownership lives in generic-session-affinity.ts; this module owns account
|
|
11
|
+
* eligibility, quota-ranked preference and bounded refusal recovery. Probe leases remain
|
|
12
|
+
* provider-specific.
|
|
13
13
|
*
|
|
14
14
|
* NOT a home for Codex (`codex/routing.ts` owns quota scopes and probe leases) or Anthropic
|
|
15
15
|
* (`oauth/anthropic-routing.ts` owns affinity and a fail-closed local-cli credential rule).
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
hasHeadroomEvidence,
|
|
26
26
|
isAccountQuotaExhausted,
|
|
27
27
|
rankAccountsByHeadroom,
|
|
28
|
+
rankAccountsByResetFirst,
|
|
28
29
|
classifyModelFamilyForQuota,
|
|
29
30
|
type QuotaModelFamily,
|
|
30
31
|
} from "./account-quota-rank";
|
|
@@ -351,7 +352,7 @@ export function rotateAntigravityAccountOnAuthRefusal(
|
|
|
351
352
|
}
|
|
352
353
|
|
|
353
354
|
/** Generic pool strategies the kernel can actually run. `quota` IS the pre-kernel path. */
|
|
354
|
-
type ActiveGenericStrategy = "round-robin" | "fill-first" | "least-loaded";
|
|
355
|
+
type ActiveGenericStrategy = "round-robin" | "fill-first" | "reset-first" | "least-loaded";
|
|
355
356
|
|
|
356
357
|
/** Matches the Codex and Anthropic pools; the DTO still reports `null` for "not stored". */
|
|
357
358
|
const DEFAULT_GENERIC_AUTO_SWITCH_THRESHOLD = 80;
|
|
@@ -365,10 +366,13 @@ const DEFAULT_GENERIC_AUTO_SWITCH_THRESHOLD = 80;
|
|
|
365
366
|
* branch instead of a four-way one.
|
|
366
367
|
*/
|
|
367
368
|
function activeGenericStrategy(config: OcxConfig, providerName: string): ActiveGenericStrategy | null {
|
|
369
|
+
// All pool strategies run behind the kernel flag; `quota` is the pre-kernel path.
|
|
370
|
+
// Letting `reset-first` bypass it would change 429 selection for pools that never
|
|
371
|
+
// opted into kernel-gated strategies.
|
|
368
372
|
if (config.pool?.kernel !== true) return null;
|
|
369
373
|
const raw = config.providers?.[providerName]?.oauthAccountFailover?.strategy;
|
|
370
374
|
if (providerName === "kiro" && raw === "least-loaded") return raw;
|
|
371
|
-
return raw === "round-robin" || raw === "fill-first" ? raw : null;
|
|
375
|
+
return raw === "round-robin" || raw === "fill-first" || raw === "reset-first" ? raw : null;
|
|
372
376
|
}
|
|
373
377
|
|
|
374
378
|
function genericStickyLimit(config: OcxConfig, providerName: string): number {
|
|
@@ -416,6 +420,7 @@ function pickFillFirstGenericAccount(
|
|
|
416
420
|
now: number,
|
|
417
421
|
requestedModelId?: string | null,
|
|
418
422
|
accountRows?: ReadonlyMap<string, ProviderAccount>,
|
|
423
|
+
preferBelowThreshold = false,
|
|
419
424
|
): string | null {
|
|
420
425
|
const stableAll = stableGenericRoster(providerName);
|
|
421
426
|
if (stableAll.length < 2) return null;
|
|
@@ -431,6 +436,11 @@ function pickFillFirstGenericAccount(
|
|
|
431
436
|
}
|
|
432
437
|
const start = activeId ? stableAll.indexOf(activeId) : -1;
|
|
433
438
|
const ring = start >= 0 ? [...stableAll.slice(start + 1), ...stableAll.slice(0, start)] : stableAll;
|
|
439
|
+
if (preferBelowThreshold) {
|
|
440
|
+
const below = ring.find(id => id !== activeId && eligible.has(id)
|
|
441
|
+
&& !isOverAutoSwitchThreshold(providerName, id, threshold, requestedModelId, accountRows?.get(id)));
|
|
442
|
+
if (below) return below;
|
|
443
|
+
}
|
|
434
444
|
for (const id of ring) {
|
|
435
445
|
if (id !== activeId && eligible.has(id)) return id;
|
|
436
446
|
}
|
|
@@ -473,7 +483,8 @@ export function noteGenericPoolSelection(
|
|
|
473
483
|
* Cool the account that actually 429'd and name the next eligible one, or null.
|
|
474
484
|
*
|
|
475
485
|
* Returns the id only; the caller mints the credential so a failed refresh does not leave the
|
|
476
|
-
* cooldown applied to an account we then could not use.
|
|
486
|
+
* cooldown applied to an account we then could not use. `commit: false` still cools the failed
|
|
487
|
+
* account but only peeks the round-robin ring, for callers that will not dispatch the answer.
|
|
477
488
|
*/
|
|
478
489
|
export function rotateGenericOAuthAccountOn429(
|
|
479
490
|
config: OcxConfig,
|
|
@@ -482,9 +493,10 @@ export function rotateGenericOAuthAccountOn429(
|
|
|
482
493
|
retryAfterHeader: string | null | undefined,
|
|
483
494
|
now = Date.now(),
|
|
484
495
|
requestedModelId?: string | null,
|
|
496
|
+
commit = true,
|
|
485
497
|
): string | null {
|
|
486
498
|
return rotateGenericOAuthAccountOnRefusal(config, providerName, failedAccountId, "rate",
|
|
487
|
-
retryAfterHeader, now, requestedModelId);
|
|
499
|
+
retryAfterHeader, now, requestedModelId, undefined, commit);
|
|
488
500
|
}
|
|
489
501
|
|
|
490
502
|
/** Kiro adds account-scoped refusal classes; all other providers retain their 429 policy. */
|
|
@@ -497,6 +509,7 @@ export function rotateGenericOAuthAccountOnRefusal(
|
|
|
497
509
|
now = Date.now(),
|
|
498
510
|
requestedModelId?: string | null,
|
|
499
511
|
monthlyCooldownMs?: number,
|
|
512
|
+
commit = true,
|
|
500
513
|
): string | null {
|
|
501
514
|
if (kind === "other" || (providerName !== "kiro" && kind !== "rate")) return null;
|
|
502
515
|
if (!isGenericOAuthFailoverEnabled(config, providerName)) return null;
|
|
@@ -556,8 +569,8 @@ export function rotateGenericOAuthAccountOnRefusal(
|
|
|
556
569
|
}
|
|
557
570
|
if (strategy === "round-robin") {
|
|
558
571
|
// PICK here, not peek: the failure already happened and this answer is the one being used,
|
|
559
|
-
// so the ring genuinely advances.
|
|
560
|
-
return pickRoundRobinAccount(
|
|
572
|
+
// so the ring genuinely advances. A caller that only asks whether a sibling exists peeks.
|
|
573
|
+
return (commit ? pickRoundRobinAccount : peekRoundRobinAccount)(
|
|
561
574
|
genericPoolKey(providerName),
|
|
562
575
|
candidates,
|
|
563
576
|
genericStickyLimit(config, providerName),
|
|
@@ -574,6 +587,10 @@ export function rotateGenericOAuthAccountOnRefusal(
|
|
|
574
587
|
}
|
|
575
588
|
return null;
|
|
576
589
|
}
|
|
590
|
+
if (strategy === "reset-first") {
|
|
591
|
+
return rankAccountsByResetFirst(providerName, candidates, requestedModelId, now,
|
|
592
|
+
new Map(set.accounts.map(account => [account.id, account])))[0] ?? null;
|
|
593
|
+
}
|
|
577
594
|
// With no quota evidence this returns the ring untouched, so providers without
|
|
578
595
|
// per-account quota keep exactly the traversal they have today.
|
|
579
596
|
return rankAccountsByHeadroom(providerName, candidates, requestedModelId,
|
|
@@ -634,6 +651,7 @@ export function preferredInitialAccount(
|
|
|
634
651
|
providerName: string,
|
|
635
652
|
now = Date.now(),
|
|
636
653
|
requestedModelId?: string | null,
|
|
654
|
+
activeAccountOverride?: string,
|
|
637
655
|
): string | null {
|
|
638
656
|
// The PROACTIVE predicate, not the reactive one: this steers a request upstream has not
|
|
639
657
|
// refused, so `oauthAccountFailover.enabled: false` must still be able to refuse it.
|
|
@@ -642,7 +660,7 @@ export function preferredInitialAccount(
|
|
|
642
660
|
// active id separately would delay manual selection and account removal.
|
|
643
661
|
const selected = getAccountSet(providerName);
|
|
644
662
|
if (!selected) return null;
|
|
645
|
-
const active = selected.activeAccountId;
|
|
663
|
+
const active = activeAccountOverride ?? selected.activeAccountId;
|
|
646
664
|
const accountRows = new Map(selected.accounts.map(account => [account.id, account]));
|
|
647
665
|
const order = selected.accounts
|
|
648
666
|
.filter(account => account.paused !== true && account.needsReauth !== true)
|
|
@@ -694,9 +712,20 @@ export function preferredInitialAccount(
|
|
|
694
712
|
return picked && picked !== active ? picked : null;
|
|
695
713
|
}
|
|
696
714
|
if (strategy === "fill-first") {
|
|
697
|
-
const picked = pickFillFirstGenericAccount(config, providerName, active, now, requestedModelId, accountRows);
|
|
715
|
+
const picked = pickFillFirstGenericAccount(config, providerName, active, now, requestedModelId, accountRows, activeAccountOverride !== undefined);
|
|
698
716
|
return picked && picked !== active ? picked : null;
|
|
699
717
|
}
|
|
718
|
+
if (strategy === "reset-first") {
|
|
719
|
+
// Same candidate set as round-robin: paused, reauth-needed and cooled accounts are out,
|
|
720
|
+
// and an account whose model listing omits the requested model loses to one that has it.
|
|
721
|
+
// Reset ordering needs quota evidence; without it the ordinary active-account path runs.
|
|
722
|
+
const family = classifyModelFamilyForQuota(providerName, requestedModelId);
|
|
723
|
+
const eligibleNow = preferKiroModelSupport(providerName,
|
|
724
|
+
eligibleFailoverAccounts(providerName, now, family), requestedModelId, config);
|
|
725
|
+
if (eligibleNow.length === 0 || !hasHeadroomEvidence(providerName, eligibleNow, requestedModelId, accountRows)) return null;
|
|
726
|
+
const best = rankAccountsByResetFirst(providerName, eligibleNow, requestedModelId, now, accountRows)[0] ?? null;
|
|
727
|
+
return best && best !== active ? best : null;
|
|
728
|
+
}
|
|
700
729
|
|
|
701
730
|
const activeRow = selected.accounts.find(account => account.id === active);
|
|
702
731
|
if (activeRow && activeRow.paused !== true && activeRow.needsReauth !== true
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/** Request-local conversation ownership for opt-in generic OAuth pools. No credentials are retained. */
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { getConfigDir } from "../config/paths";
|
|
4
|
+
import { subscribeAccountSelections } from "../lib/account-selection-events";
|
|
5
|
+
import type { OcxConfig } from "../types";
|
|
6
|
+
import { classifyModelFamilyForQuota } from "./account-quota-rank";
|
|
7
|
+
import { eligibleFailoverAccounts, isGenericFailoverProvider, preferKiroModelSupport, preferredInitialAccount } from "./generic-account-failover";
|
|
8
|
+
import type { OAuthAccessSnapshot } from "./index";
|
|
9
|
+
import { captureOAuthAccountSelection, credentialGeneration, getAccountCredentialWithStatus } from "./store";
|
|
10
|
+
import type { OAuthAccountSelection } from "./types";
|
|
11
|
+
|
|
12
|
+
const IDLE_TTL_MS = 24 * 60 * 60_000;
|
|
13
|
+
const MAX_ENTRIES = 2_000;
|
|
14
|
+
const bindings = new Map<string, { accountId: string; lastUsedAt: number; selection: OAuthAccountSelection }>();
|
|
15
|
+
const prefix = (provider: string) => `${JSON.stringify([getConfigDir(), provider])}\u0000`;
|
|
16
|
+
const key = (provider: string, session: string) => prefix(provider) + "session:" + createHash("sha256").update(session).digest("hex");
|
|
17
|
+
const cursorKey = (provider: string) => prefix(provider) + "fill-first";
|
|
18
|
+
|
|
19
|
+
/** A persisted operator/roster/policy change revokes old session choices before its writer returns. */
|
|
20
|
+
export function clearGenericSessionAffinity(provider?: string): void {
|
|
21
|
+
if (!provider) { bindings.clear(); return; }
|
|
22
|
+
const scope = prefix(provider);
|
|
23
|
+
for (const id of bindings.keys()) if (id.startsWith(scope)) bindings.delete(id);
|
|
24
|
+
}
|
|
25
|
+
subscribeAccountSelections(event => { if (event.kind === "oauth") clearGenericSessionAffinity(event.provider); });
|
|
26
|
+
|
|
27
|
+
/** Unlike new-session strategy selection, keeping the surviving account needs no 2+ quorum. */
|
|
28
|
+
export function genericSessionAffinityEnabled(config: OcxConfig, provider: string): boolean {
|
|
29
|
+
const row = config.providers[provider];
|
|
30
|
+
const enabled = !!row && row.disabled !== true && provider !== "anthropic2" && isGenericFailoverProvider(provider, row)
|
|
31
|
+
&& row.oauthAccountFailover?.sessionAffinity === "conversation"
|
|
32
|
+
&& (row.oauthAccountFailover.enabled ?? config.oauthAccountFailover?.enabled) === true;
|
|
33
|
+
if (!enabled) clearGenericSessionAffinity(provider);
|
|
34
|
+
return enabled;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function genericSessionAccountEligible(
|
|
38
|
+
config: OcxConfig, provider: string, accountId: string, model?: string | null, now = Date.now(), allowActiveFallback = false,
|
|
39
|
+
): boolean {
|
|
40
|
+
const eligible = eligibleFailoverAccounts(provider, now, classifyModelFamilyForQuota(provider, model));
|
|
41
|
+
// Existing conversation ownership is independent of transient per-account load.
|
|
42
|
+
if (preferKiroModelSupport(provider, eligible, model).includes(accountId)) return true;
|
|
43
|
+
// Cached automatic exclusions remain preferences when no alternative exists. Preserve the
|
|
44
|
+
// ordinary active/singleton attempt and its upstream refusal instead of inventing a conflict.
|
|
45
|
+
if (!allowActiveFallback || eligible.length > 0 || captureOAuthAccountSelection(provider)?.accountId !== accountId) return false;
|
|
46
|
+
const row = getAccountCredentialWithStatus(provider, accountId);
|
|
47
|
+
return !!row && !row.paused && !row.needsReauth;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Peek only. The admitted snapshot, never a speculative token lookup, establishes ownership. */
|
|
51
|
+
export function genericSessionAccount(
|
|
52
|
+
config: OcxConfig, provider: string, session: string | null, model?: string | null, now = Date.now(),
|
|
53
|
+
): string | null {
|
|
54
|
+
if (!genericSessionAffinityEnabled(config, provider) || !session) return null;
|
|
55
|
+
return readBinding(config, provider, key(provider, session), model, now);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function readBinding(config: OcxConfig, provider: string, id: string, model: string | null | undefined, now: number): string | null {
|
|
59
|
+
const bound = bindings.get(id);
|
|
60
|
+
if (!bound) return null;
|
|
61
|
+
const current = captureOAuthAccountSelection(provider);
|
|
62
|
+
if (!current || current.accountId !== bound.selection.accountId || current.revision !== bound.selection.revision
|
|
63
|
+
|| now - bound.lastUsedAt >= IDLE_TTL_MS || now < bound.lastUsedAt
|
|
64
|
+
|| !genericSessionAccountEligible(config, provider, bound.accountId, model, now)) {
|
|
65
|
+
bindings.delete(id);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return bound.accountId;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** New sessions use the configured strategy; an ineligible active account cannot defeat release. */
|
|
72
|
+
export function preferredGenericSessionAccount(
|
|
73
|
+
config: OcxConfig, provider: string, session: string | null, model?: string | null, now = Date.now(),
|
|
74
|
+
): string | null {
|
|
75
|
+
if (!genericSessionAffinityEnabled(config, provider)) return null;
|
|
76
|
+
const bound = genericSessionAccount(config, provider, session, model, now);
|
|
77
|
+
if (bound) return bound;
|
|
78
|
+
const cursor = readBinding(config, provider, cursorKey(provider), model, now);
|
|
79
|
+
const active = cursor ?? captureOAuthAccountSelection(provider)?.accountId;
|
|
80
|
+
const preferred = preferredInitialAccount(config, provider, now, model, active);
|
|
81
|
+
if (preferred) return preferred;
|
|
82
|
+
if (active && genericSessionAccountEligible(config, provider, active, model, now)) return active;
|
|
83
|
+
return preferKiroModelSupport(provider,
|
|
84
|
+
eligibleFailoverAccounts(provider, now, classifyModelFamilyForQuota(provider, model)), model, config)[0]
|
|
85
|
+
?? captureOAuthAccountSelection(provider)?.accountId ?? null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validate against the operator's persisted selection without moving it. ALL automatic choices
|
|
90
|
+
* in affinity mode use this receipt, including unkeyed requests and failovers, so concurrent
|
|
91
|
+
* conversations cannot invalidate each other's selection revision. Manual A→B→A still fences.
|
|
92
|
+
*/
|
|
93
|
+
export function admitGenericSessionAccount(
|
|
94
|
+
config: OcxConfig, provider: string, expected: OAuthAccountSelection,
|
|
95
|
+
snapshot: OAuthAccessSnapshot, model?: string | null, modeEnabled = genericSessionAffinityEnabled(config, provider),
|
|
96
|
+
): OAuthAccountSelection | null {
|
|
97
|
+
if (!modeEnabled || snapshot.provider !== provider) return null;
|
|
98
|
+
const current = captureOAuthAccountSelection(provider);
|
|
99
|
+
if (!current || current.accountId !== expected.accountId || current.revision !== expected.revision) return null;
|
|
100
|
+
const row = getAccountCredentialWithStatus(provider, snapshot.accountId);
|
|
101
|
+
if (!row || row.paused || row.needsReauth || credentialGeneration(row.credential) !== snapshot.generation
|
|
102
|
+
|| !genericSessionAccountEligible(config, provider, snapshot.accountId, model, Date.now(), true)) return null;
|
|
103
|
+
return current;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function bindGenericSessionAccount(
|
|
107
|
+
config: OcxConfig, provider: string, session: string | null, accountId: string, model?: string | null, now = Date.now(),
|
|
108
|
+
): void {
|
|
109
|
+
if (!genericSessionAffinityEnabled(config, provider)
|
|
110
|
+
|| !genericSessionAccountEligible(config, provider, accountId, model, now)) return;
|
|
111
|
+
for (const [id, entry] of bindings) {
|
|
112
|
+
if (now - entry.lastUsedAt >= IDLE_TTL_MS || now < entry.lastUsedAt) bindings.delete(id);
|
|
113
|
+
}
|
|
114
|
+
const selection = captureOAuthAccountSelection(provider);
|
|
115
|
+
if (!selection) return;
|
|
116
|
+
const keep = (id: string) => {
|
|
117
|
+
bindings.delete(id);
|
|
118
|
+
bindings.set(id, { accountId, lastUsedAt: now, selection });
|
|
119
|
+
};
|
|
120
|
+
const hit = session && genericSessionAccount(config, provider, session, model, now) === accountId;
|
|
121
|
+
if (!hit && config.pool?.kernel === true && config.providers[provider]?.oauthAccountFailover?.strategy === "fill-first")
|
|
122
|
+
keep(cursorKey(provider));
|
|
123
|
+
if (session) keep(key(provider, session));
|
|
124
|
+
while (bindings.size > MAX_ENTRIES) bindings.delete(bindings.keys().next().value!);
|
|
125
|
+
}
|