@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
package/src/oauth/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { MuseLoginError, museLoginErrorMessage } from "./meta-muse-errors";
|
|
1
2
|
import type { KiroOAuthMetadata, OAuthController, OAuthCredentials } from "./types";
|
|
3
|
+
import { mapFill } from "../providers/resolved-model-policy-merge";
|
|
2
4
|
import { initializeProviderModelSelection } from "../providers/initial-model-selection";
|
|
3
5
|
import type { OcxConfig, OcxProviderConfig, RefreshPolicy } from "../types";
|
|
4
6
|
import { getDefaultConfig, initializePersistedConfigIfMissing, loadConfig, mutatePersistedConfig, observeInitialConfigState, saveConfig } from "../config";
|
|
@@ -48,6 +50,7 @@ import { validateDevinApiBaseUrl } from "./devin/api-base";
|
|
|
48
50
|
import { loginGithubCopilot, refreshGithubCopilotToken, validateCopilotApiBaseUrl } from "./github-copilot";
|
|
49
51
|
import { loginCommandCode, refreshCommandCodeToken } from "./command-code";
|
|
50
52
|
import { loginMetaMuse, refreshMetaMuseToken } from "./meta-muse";
|
|
53
|
+
import { DSH_ACCOUNT_AUTH } from "./dsh";
|
|
51
54
|
import { loginOrcaRouter, orcaRouterInferenceBaseUrl, refreshOrcaRouterKey } from "./orcarouter";
|
|
52
55
|
import { ANTIGRAVITY_DISCOVERY_UA } from "../adapters/google-antigravity-wire";
|
|
53
56
|
import { deriveOAuthDefaultModel, deriveOAuthProviderConfig } from "../providers/derive";
|
|
@@ -57,9 +60,12 @@ import { providerModelsUrl, resolveProviderModelDiscoveryUrl } from "../provider
|
|
|
57
60
|
import { resolveProviderTransport } from "../providers/xai-transport";
|
|
58
61
|
import { detectGrokCliToken, hasComparableGrokIdentity, isSameGrokIdentity, shouldAdoptGrokGeneration } from "./local-token-detect";
|
|
59
62
|
import { logOAuthEvent } from "./log";
|
|
63
|
+
import { MAX_OAUTH_TOKEN_REFRESH_WAITERS, retireSettledOAuthRefreshFlight, tokenRefreshWaiters, type OAuthRefreshFlightEvidence } from "./refresh-flight-retirement";
|
|
64
|
+
export { MAX_OAUTH_TOKEN_REFRESH_WAITERS };
|
|
60
65
|
import { captureConfigGeneration, sweepExpiredOnWrite } from "../lib/state-store-sweeper";
|
|
61
|
-
import { clearManualCodeSlot, ensureManualCodeSlot, kiroLoginSettling, loginAbort, loginState, waitForManualLoginCode, type OAuthLoginHint } from "./login-flow-state";
|
|
62
|
-
export { reconcileOAuthFlowState, submitManualLoginCode } from "./login-flow-state";
|
|
66
|
+
import { admitLoginFlow, classifyLoginCodeError, clearManualCodeSlot, CodeLoginUnsupportedError, ensureManualCodeSlot, kiroLoginSettling, latestLoginFlowKey, loginAbort, loginFlowProvider, loginState, recordStartedLoginFlow, settleLoginFlow, waitForManualLoginCode, type LoginCodeError, type OAuthLoginHint } from "./login-flow-state";
|
|
67
|
+
export { cancelLoginFlow, clearLoginState, CodeLoginUnsupportedError, isCodeLoginFlow, loginFlowAccountId, reconcileOAuthFlowState, submitLoginCode, submitManualLoginCode, waitForLoginSettled } from "./login-flow-state";
|
|
68
|
+
export type { LoginCodeError, LoginFlowState } from "./login-flow-state";
|
|
63
69
|
import { randomUUID } from "node:crypto";
|
|
64
70
|
export {
|
|
65
71
|
CODEX_HEALTH_AUTH_FAILED_NOTE,
|
|
@@ -132,8 +138,7 @@ export type OAuthActiveTokenObservation =
|
|
|
132
138
|
|
|
133
139
|
const MAX_OAUTH_TOKEN_REFRESH_FLIGHTS = 32;
|
|
134
140
|
const OAUTH_TOKEN_REFRESH_FLIGHT_STALE_MS = 120_000;
|
|
135
|
-
interface OAuthRefreshFlightEvidence {
|
|
136
|
-
interface OAuthTokenRefreshFlight extends OAuthRefreshFlightEvidence { promise: Promise<OAuthAccessSnapshot>; startedAt: number; abort: AbortController }
|
|
141
|
+
interface OAuthTokenRefreshFlight extends OAuthRefreshFlightEvidence { promise: Promise<OAuthAccessSnapshot>; startedAt: number; abort: AbortController; settled?: boolean; retirementDeferredLogged?: boolean }
|
|
137
142
|
const tokenRefreshes = new Map<string, OAuthTokenRefreshFlight>();
|
|
138
143
|
export class OAuthTokenRefreshBusyError extends Error {
|
|
139
144
|
readonly code = "OAUTH_TOKEN_REFRESH_BUSY";
|
|
@@ -157,7 +162,8 @@ export function seedOAuthTokenRefreshFlightsForTests(rows: Array<{ key: string;
|
|
|
157
162
|
const promise = new Promise<OAuthAccessSnapshot>((_resolve, reject) => {
|
|
158
163
|
abort.signal.addEventListener("abort", () => reject(abort.signal.reason), { once: true });
|
|
159
164
|
});
|
|
160
|
-
const flight = { promise, startedAt: startedAt ?? Date.now(), abort, flightId: flightId ?? randomUUID(), dispatched: dispatched ?? false };
|
|
165
|
+
const flight: OAuthTokenRefreshFlight = { promise, startedAt: startedAt ?? Date.now(), abort, flightId: flightId ?? randomUUID(), dispatched: dispatched ?? false };
|
|
166
|
+
abort.signal.addEventListener("abort", () => { flight.settled = true; }, { once: true });
|
|
161
167
|
tokenRefreshes.set(key, flight);
|
|
162
168
|
inserted.push(flight);
|
|
163
169
|
return promise;
|
|
@@ -192,6 +198,8 @@ export interface LoginOpts {
|
|
|
192
198
|
* listener (#3366). Ignored by every other provider.
|
|
193
199
|
*/
|
|
194
200
|
flow?: ChatGPTLoginFlow;
|
|
201
|
+
/** Code-display login: no callback server; completes from a pasted code (needs `codeRedirectUri`). */
|
|
202
|
+
codeMode?: boolean;
|
|
195
203
|
}
|
|
196
204
|
|
|
197
205
|
export interface LoginFlowLifecycle {
|
|
@@ -220,6 +228,8 @@ export interface OAuthProviderDef {
|
|
|
220
228
|
* overrides this. Default when unset here: "lazy-only".
|
|
221
229
|
*/
|
|
222
230
|
defaultRefreshPolicy?: RefreshPolicy;
|
|
231
|
+
/** Provider-hosted page that DISPLAYS the code; its presence enables `codeMode`. */
|
|
232
|
+
codeRedirectUri?: string;
|
|
223
233
|
}
|
|
224
234
|
|
|
225
235
|
function oauthConfig(id: string): OcxProviderConfig {
|
|
@@ -235,6 +245,11 @@ function oauthDefaultModel(id: string): string {
|
|
|
235
245
|
}
|
|
236
246
|
|
|
237
247
|
export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
|
|
248
|
+
"dsh-account": {
|
|
249
|
+
...DSH_ACCOUNT_AUTH,
|
|
250
|
+
providerConfig: oauthConfig("dsh-account"),
|
|
251
|
+
defaultModel: oauthDefaultModel("dsh-account"),
|
|
252
|
+
},
|
|
238
253
|
"command-code": {
|
|
239
254
|
// Add-account/reauth must not reimport the current local CLI credential.
|
|
240
255
|
login: (ctrl, opts) => loginCommandCode(ctrl, { importLocal: opts?.forceLogin ? "off" : "fallback" }),
|
|
@@ -383,6 +398,11 @@ export function isPublicOAuthProvider(name: string): boolean {
|
|
|
383
398
|
return name !== "chatgpt" && isOAuthProvider(name);
|
|
384
399
|
}
|
|
385
400
|
|
|
401
|
+
/** Providers that can log in from a displayed code, with no inbound port and no browser here. */
|
|
402
|
+
export function supportsCodeLoginMode(name: string): boolean {
|
|
403
|
+
return OAUTH_PROVIDERS[name]?.codeRedirectUri !== undefined;
|
|
404
|
+
}
|
|
405
|
+
|
|
386
406
|
function isRefreshPolicy(value: unknown): value is RefreshPolicy {
|
|
387
407
|
return value === "proactive" || value === "lazy-only" || value === "disabled";
|
|
388
408
|
}
|
|
@@ -452,6 +472,14 @@ export class OAuthProviderPublicationError extends Error {
|
|
|
452
472
|
}
|
|
453
473
|
}
|
|
454
474
|
|
|
475
|
+
/** A local configuration conflict found before any credential is written; its text is fixed. */
|
|
476
|
+
export class OAuthProviderNamespaceCollisionError extends Error {
|
|
477
|
+
constructor(message: string) {
|
|
478
|
+
super(message);
|
|
479
|
+
this.name = "OAuthProviderNamespaceCollisionError";
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
455
483
|
export class OAuthReauthIdentityMismatchError extends Error {
|
|
456
484
|
constructor() {
|
|
457
485
|
super("Signed-in account does not match the selected account. Sign in with the same account.");
|
|
@@ -475,6 +503,7 @@ class OAuthLoginSupersededError extends Error {
|
|
|
475
503
|
|
|
476
504
|
/** Project arbitrary OAuth failures onto the small, stable public error vocabulary. */
|
|
477
505
|
export function publicOAuthAuthenticationErrorMessage(error: unknown): string {
|
|
506
|
+
if (error instanceof MuseLoginError) return museLoginErrorMessage(error.kind, error.status);
|
|
478
507
|
if (error instanceof OAuthMutationBusyError) {
|
|
479
508
|
return error.message === "OAuth mutation queue wait timed out"
|
|
480
509
|
? "OAuth mutation queue wait timed out"
|
|
@@ -484,6 +513,7 @@ export function publicOAuthAuthenticationErrorMessage(error: unknown): string {
|
|
|
484
513
|
(error instanceof OAuthLoginRequiredError && isOAuthProvider(error.provider))
|
|
485
514
|
|| error instanceof OAuthAccountPausedError
|
|
486
515
|
|| error instanceof OAuthProviderPublicationError
|
|
516
|
+
|| error instanceof OAuthProviderNamespaceCollisionError
|
|
487
517
|
// Reauth identity outcomes carry fixed, account-free remediation text. Dropping them to the
|
|
488
518
|
// generic message hides WHICH failure the user must fix (sign in with the selected account).
|
|
489
519
|
|| error instanceof OAuthReauthIdentityMismatchError
|
|
@@ -608,45 +638,71 @@ async function resolveAccessSnapshotForAccount(
|
|
|
608
638
|
const key = `${provider}\u0000${accountId}`;
|
|
609
639
|
let existing = tokenRefreshes.get(key);
|
|
610
640
|
let replacedStaleFlight: OAuthRefreshFlightEvidence | undefined;
|
|
611
|
-
if (existing &&
|
|
612
|
-
|
|
613
|
-
return existing.promise;
|
|
614
|
-
}
|
|
615
|
-
if (existing) {
|
|
641
|
+
if (existing && (existing.abort.signal.reason instanceof OAuthTokenRefreshStaleError
|
|
642
|
+
|| Date.now() - existing.startedAt > OAUTH_TOKEN_REFRESH_FLIGHT_STALE_MS)) {
|
|
616
643
|
replacedStaleFlight = { flightId: existing.flightId, dispatched: existing.dispatched };
|
|
617
644
|
existing.abort.abort(new OAuthTokenRefreshStaleError());
|
|
618
|
-
if (tokenRefreshes.get(key) === existing) tokenRefreshes.delete(key);
|
|
619
|
-
existing = undefined;
|
|
620
645
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
if (persisted.access !== accessToken) {
|
|
638
|
-
throw new Error(`OAuth refresh persisted an unexpected access token for ${provider}`);
|
|
646
|
+
// Count every consumer, including same-flight joins, until its promise settles. Cancelling
|
|
647
|
+
// a client cannot recycle this permit. Stale-flight abort above still runs under pressure;
|
|
648
|
+
// no replacement starts until the old consumers have actually released enough capacity.
|
|
649
|
+
const waiter = tokenRefreshWaiters.tryAcquire();
|
|
650
|
+
if (!waiter) throw new OAuthTokenRefreshBusyError();
|
|
651
|
+
try {
|
|
652
|
+
if (existing && replacedStaleFlight) {
|
|
653
|
+
// A replacement would wait for this owner's lock anyway. Keep its registry slot
|
|
654
|
+
// through definitive-result cleanup and durable retirement, even with spare capacity.
|
|
655
|
+
// In particular, an ignored abort must not mark a late explicit rejection as uncertain.
|
|
656
|
+
await existing.promise.catch(() => {});
|
|
657
|
+
if (tokenRefreshes.get(key) === existing && !retireSettledOAuthRefreshFlight(tokenRefreshes, key, existing)) {
|
|
658
|
+
throw new OAuthTokenRefreshStaleError();
|
|
659
|
+
}
|
|
660
|
+
// Other admitted replacements may have resumed first and installed the next flight.
|
|
661
|
+
existing = tokenRefreshes.get(key);
|
|
639
662
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
663
|
+
if (existing) {
|
|
664
|
+
logOAuthEvent("OAuth refresh joined existing operation", { provider, accountId });
|
|
665
|
+
return await existing.promise;
|
|
666
|
+
}
|
|
667
|
+
if (tokenRefreshes.size >= MAX_OAUTH_TOKEN_REFRESH_FLIGHTS) {
|
|
668
|
+
for (const [staleKey, stale] of tokenRefreshes) {
|
|
669
|
+
if (stale.abort.signal.reason instanceof OAuthTokenRefreshStaleError) retireSettledOAuthRefreshFlight(tokenRefreshes, staleKey, stale);
|
|
670
|
+
}
|
|
671
|
+
if (tokenRefreshes.size >= MAX_OAUTH_TOKEN_REFRESH_FLIGHTS) throw new OAuthTokenRefreshBusyError();
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const abort = new AbortController();
|
|
675
|
+
const flight: OAuthTokenRefreshFlight = {
|
|
676
|
+
promise: undefined as unknown as Promise<OAuthAccessSnapshot>,
|
|
677
|
+
startedAt: Date.now(),
|
|
678
|
+
abort,
|
|
679
|
+
flightId: randomUUID(),
|
|
680
|
+
dispatched: false,
|
|
681
|
+
};
|
|
682
|
+
const refresh = (async (): Promise<OAuthAccessSnapshot> => {
|
|
683
|
+
const accessToken = await refreshAndPersistAccessToken(provider, accountId, def, cred, abort.signal, flight, replacedStaleFlight);
|
|
684
|
+
const persistedRow = getAccountCredentialWithStatus(provider, accountId);
|
|
685
|
+
if (!persistedRow) throw new OAuthLoginRequiredError(provider);
|
|
686
|
+
if (persistedRow.paused === true) throw new OAuthAccountPausedError();
|
|
687
|
+
const persisted = persistedRow.credential;
|
|
688
|
+
if (persisted.access !== accessToken) {
|
|
689
|
+
throw new Error(`OAuth refresh persisted an unexpected access token for ${provider}`);
|
|
690
|
+
}
|
|
691
|
+
return accessSnapshot(provider, accountId, persisted, oauthProvider);
|
|
692
|
+
})().catch(error => {
|
|
693
|
+
if (abort.signal.reason instanceof OAuthTokenRefreshStaleError) throw abort.signal.reason;
|
|
694
|
+
throw error;
|
|
695
|
+
}).finally(() => {
|
|
696
|
+
flight.settled = true;
|
|
697
|
+
if (tokenRefreshes.get(key) !== flight) return;
|
|
698
|
+
if (abort.signal.reason instanceof OAuthTokenRefreshStaleError) {
|
|
699
|
+
retireSettledOAuthRefreshFlight(tokenRefreshes, key, flight);
|
|
700
|
+
} else tokenRefreshes.delete(key);
|
|
701
|
+
});
|
|
702
|
+
flight.promise = refresh;
|
|
703
|
+
tokenRefreshes.set(key, flight);
|
|
704
|
+
return await refresh;
|
|
705
|
+
} finally { waiter.release(); }
|
|
650
706
|
}
|
|
651
707
|
|
|
652
708
|
export async function getValidAccessTokenSnapshot(
|
|
@@ -666,6 +722,7 @@ const FORCE_REFRESH_PROVIDERS = new Set([
|
|
|
666
722
|
"google-antigravity",
|
|
667
723
|
"orcarouter-oauth",
|
|
668
724
|
"devin",
|
|
725
|
+
"dsh-account",
|
|
669
726
|
]);
|
|
670
727
|
|
|
671
728
|
export async function forceRefreshOAuthAccessSnapshot(
|
|
@@ -1239,26 +1296,21 @@ const OAUTH_RECONCILE_FIELDS: (keyof OcxProviderConfig)[] = [
|
|
|
1239
1296
|
// preserves explicit saved values.
|
|
1240
1297
|
|
|
1241
1298
|
/**
|
|
1242
|
-
*
|
|
1243
|
-
*
|
|
1244
|
-
*
|
|
1245
|
-
*
|
|
1246
|
-
*
|
|
1247
|
-
*
|
|
1248
|
-
* Without that, the fields behaved as if they could not be configured at all.
|
|
1249
|
-
* No OAuth preset seeds either one, so the delete branch was the only branch
|
|
1250
|
-
* these two ever took, and a hand-edited `defaultMaxOutputTokens` was gone
|
|
1251
|
-
* before the first turn of the next startup — leaving the adapter's own
|
|
1252
|
-
* fallback as the only reachable output cap (#5190).
|
|
1253
|
-
*
|
|
1254
|
-
* Scoped to the output budget on purpose. The input side (`contextWindow`,
|
|
1255
|
-
* `modelContextWindows`) describes what the account's models are, which the
|
|
1256
|
-
* preset and live discovery do own; an output budget is a spend decision the
|
|
1257
|
-
* operator makes.
|
|
1299
|
+
* Saved limits and per-model declarations are operator input, including Copilot's
|
|
1300
|
+
* long-context evidence. Presets supply missing defaults, not replacements for
|
|
1301
|
+
* configured entries. Catalog rosters, capability lists and seeded reasoning
|
|
1302
|
+
* ladders still refresh below; unseeded tiers remain operator input.
|
|
1303
|
+
* Without provenance, a saved seed cannot be distinguished from an operator edit.
|
|
1258
1304
|
*/
|
|
1305
|
+
const OAUTH_OPERATOR_MAP_FIELDS: ReadonlySet<keyof OcxProviderConfig> = new Set([
|
|
1306
|
+
"modelContextWindows", "modelMaxOutputTokens", "modelInputModalities",
|
|
1307
|
+
"modelReasoningEffortMap", "reasoningEffortMap",
|
|
1308
|
+
]);
|
|
1309
|
+
const OAUTH_OPERATOR_LIMIT_FIELDS: ReadonlySet<keyof OcxProviderConfig> = new Set([
|
|
1310
|
+
"contextWindow", "defaultMaxOutputTokens",
|
|
1311
|
+
]);
|
|
1259
1312
|
const OAUTH_PRESERVE_WHEN_PRESET_UNSET: ReadonlySet<keyof OcxProviderConfig> = new Set([
|
|
1260
|
-
"
|
|
1261
|
-
"modelMaxOutputTokens",
|
|
1313
|
+
"reasoningEfforts",
|
|
1262
1314
|
]);
|
|
1263
1315
|
|
|
1264
1316
|
const GOOGLE_ANTIGRAVITY_PROVIDER = "google-antigravity";
|
|
@@ -1298,7 +1350,23 @@ function applyOAuthPresetCatalog(
|
|
|
1298
1350
|
): void {
|
|
1299
1351
|
for (const field of OAUTH_RECONCILE_FIELDS) {
|
|
1300
1352
|
if (JSON.stringify(provider[field]) === JSON.stringify(preset[field])) continue;
|
|
1301
|
-
if (
|
|
1353
|
+
if (field === "modelReasoningEfforts") {
|
|
1354
|
+
// Known unmarked ladders are saved catalog seeds, not evidence of an
|
|
1355
|
+
// operator override. Preserve unseeded rows and explicit authoritative
|
|
1356
|
+
// overrides; refresh the preset-owned rows (e.g. xAI's effort ladder).
|
|
1357
|
+
const authoritative = provider.modelReasoningEffortsAuthoritative === true;
|
|
1358
|
+
provider.modelReasoningEfforts = mapFill(
|
|
1359
|
+
authoritative ? preset.modelReasoningEfforts : provider.modelReasoningEfforts,
|
|
1360
|
+
authoritative ? provider.modelReasoningEfforts : preset.modelReasoningEfforts,
|
|
1361
|
+
)[0];
|
|
1362
|
+
} else if (OAUTH_OPERATOR_MAP_FIELDS.has(field)) {
|
|
1363
|
+
provider[field] = mapFill<unknown>(
|
|
1364
|
+
preset[field] as Record<string, unknown> | undefined,
|
|
1365
|
+
provider[field] as Record<string, unknown> | undefined,
|
|
1366
|
+
)[0] as never;
|
|
1367
|
+
} else if (OAUTH_OPERATOR_LIMIT_FIELDS.has(field) && provider[field] !== undefined) {
|
|
1368
|
+
continue;
|
|
1369
|
+
} else if (preset[field] !== undefined) {
|
|
1302
1370
|
provider[field] = cloneProviderField(preset[field]) as never;
|
|
1303
1371
|
} else if (!OAUTH_PRESERVE_WHEN_PRESET_UNSET.has(field)) {
|
|
1304
1372
|
delete provider[field];
|
|
@@ -1494,7 +1562,7 @@ export function upsertOAuthProvider(config: OcxConfig, provider: string): void {
|
|
|
1494
1562
|
if (!def) return;
|
|
1495
1563
|
if (provider === GOOGLE_ANTIGRAVITY_PROVIDER) migrateLegacyAntigravityStaticCatalog(config);
|
|
1496
1564
|
const namespaceCollision = codexAccountNamespaceProviderCollisionError(config.codexAccountNamespaces, provider);
|
|
1497
|
-
if (namespaceCollision) throw new
|
|
1565
|
+
if (namespaceCollision) throw new OAuthProviderNamespaceCollisionError(namespaceCollision);
|
|
1498
1566
|
const existing = config.providers[provider];
|
|
1499
1567
|
const providerConfig = def.resolveProviderConfig?.(config) ?? def.providerConfig;
|
|
1500
1568
|
// Clone operator state, including xAI wire choices and their migration version.
|
|
@@ -1561,6 +1629,8 @@ interface RunLoginDeps {
|
|
|
1561
1629
|
settleKiroLoginTransaction?: typeof settleKiroLoginTransaction;
|
|
1562
1630
|
rollbackCredentialWrite?: typeof rollbackCredentialWriteIfMatch;
|
|
1563
1631
|
assertCurrentOwner?: () => void;
|
|
1632
|
+
/** Told which account row the credential write landed in (an id, never the credential). */
|
|
1633
|
+
onAccountPersisted?: (accountId: string) => void;
|
|
1564
1634
|
}
|
|
1565
1635
|
|
|
1566
1636
|
/** Run the login flow, persist the credential + upsert the provider entry to disk, return cred. */
|
|
@@ -1591,7 +1661,7 @@ export async function runLogin(
|
|
|
1591
1661
|
preflightConfig.codexAccountNamespaces,
|
|
1592
1662
|
provider,
|
|
1593
1663
|
);
|
|
1594
|
-
if (namespaceCollision) throw new
|
|
1664
|
+
if (namespaceCollision) throw new OAuthProviderNamespaceCollisionError(namespaceCollision);
|
|
1595
1665
|
}
|
|
1596
1666
|
if (provider === "anthropic2" && observeInitialConfigState() !== "exists") {
|
|
1597
1667
|
throw new OAuthProviderPublicationError(provider, false);
|
|
@@ -1640,10 +1710,12 @@ export async function runLogin(
|
|
|
1640
1710
|
// Proven same account: a reauth whose tier read failed keeps the stored label.
|
|
1641
1711
|
const reauthPlan = mergeOAuthPlan(cred, existing);
|
|
1642
1712
|
const reauthCred = reauthPlan === undefined ? cred : { ...cred, plan: reauthPlan };
|
|
1643
|
-
await (deps.saveAccountCredential ?? saveAccountCredential)(provider, opts.reauthAccountId, reauthCred, {
|
|
1713
|
+
const written = await (deps.saveAccountCredential ?? saveAccountCredential)(provider, opts.reauthAccountId, reauthCred, {
|
|
1644
1714
|
assertBeforePersist: deps.assertCurrentOwner,
|
|
1645
1715
|
rotateLoginId: true,
|
|
1646
1716
|
});
|
|
1717
|
+
// A reauth whose account was deleted meanwhile wrote nothing and owns no row to report.
|
|
1718
|
+
if (written) deps.onAccountPersisted?.(opts.reauthAccountId);
|
|
1647
1719
|
} else {
|
|
1648
1720
|
const saveOptions = {
|
|
1649
1721
|
preserveIdentityless: opts?.forceLogin === true || (provider === "anthropic" && cred.source === "local-cli"),
|
|
@@ -1655,8 +1727,11 @@ export async function runLogin(
|
|
|
1655
1727
|
cred,
|
|
1656
1728
|
saveOptions,
|
|
1657
1729
|
);
|
|
1730
|
+
if (kiroCredentialWrite) deps.onAccountPersisted?.(kiroCredentialWrite.accountId);
|
|
1658
1731
|
} else {
|
|
1659
|
-
|
|
1732
|
+
// The receipt names the row written; a stubbed save may return none.
|
|
1733
|
+
const receipt = await (deps.saveCredential ?? saveCredential)(provider, cred, saveOptions);
|
|
1734
|
+
if (receipt) deps.onAccountPersisted?.(receipt.accountId);
|
|
1660
1735
|
}
|
|
1661
1736
|
}
|
|
1662
1737
|
if (provider !== "chatgpt") {
|
|
@@ -1711,7 +1786,7 @@ export async function runLogin(
|
|
|
1711
1786
|
if (provider !== "chatgpt") {
|
|
1712
1787
|
try {
|
|
1713
1788
|
const { clearModelCache } = await import("../codex/model-cache");
|
|
1714
|
-
const { clearGatherRoutedModelsInflight } = await import("../codex/catalog");
|
|
1789
|
+
const { clearGatherRoutedModelsInflight } = await import("../codex/catalog/routed-gather");
|
|
1715
1790
|
clearModelCache(provider);
|
|
1716
1791
|
clearGatherRoutedModelsInflight();
|
|
1717
1792
|
const { clearAccountQuotaCache, clearProviderQuotaCache } = await import("../providers/quota");
|
|
@@ -1766,9 +1841,12 @@ export interface OAuthAccountSummary {
|
|
|
1766
1841
|
* the config at its request boundary and resolves the policy there with `emailMaskingEnabled`.
|
|
1767
1842
|
* The default masks, so every existing caller keeps today's behaviour.
|
|
1768
1843
|
*/
|
|
1769
|
-
export function getLoginStatus(provider: string, maskEmails = true): { loggedIn: boolean; email?: string; source?: OAuthCredentials["source"]; error?: string; done: boolean; hint?: OAuthLoginHint; activeAccountId?: string; accounts?: OAuthAccountSummary[] } {
|
|
1844
|
+
export function getLoginStatus(provider: string, maskEmails = true, flowId?: string): { loggedIn: boolean; email?: string; source?: OAuthCredentials["source"]; error?: string; errorCode?: LoginCodeError; done: boolean; status: "idle" | "pending" | "complete" | "error"; mode?: "callback" | "code"; flowId?: string; expiresAt?: number; hint?: OAuthLoginHint; activeAccountId?: string; accounts?: OAuthAccountSummary[] } {
|
|
1770
1845
|
const cred = getCredential(provider);
|
|
1771
|
-
|
|
1846
|
+
// `flowId` reads one of several concurrent flows; without it, the provider's newest flow.
|
|
1847
|
+
const flowKey = flowId ?? latestLoginFlowKey(provider);
|
|
1848
|
+
// An explicit flow id from another provider must not leak that flow's lifecycle or hint.
|
|
1849
|
+
const st = flowKey === undefined || loginFlowProvider(flowKey) !== provider ? undefined : loginState.get(flowKey);
|
|
1772
1850
|
const set = getAccountSet(provider);
|
|
1773
1851
|
const accounts: OAuthAccountSummary[] | undefined = set?.accounts.map(a => ({
|
|
1774
1852
|
id: a.id,
|
|
@@ -1794,7 +1872,14 @@ export function getLoginStatus(provider: string, maskEmails = true): { loggedIn:
|
|
|
1794
1872
|
email: projectEmail(cred?.email, maskEmails) ?? undefined,
|
|
1795
1873
|
source: cred?.source,
|
|
1796
1874
|
error: st?.error,
|
|
1875
|
+
...(st?.errorCode ? { errorCode: st.errorCode } : {}),
|
|
1797
1876
|
done: st?.done ?? false,
|
|
1877
|
+
// The flow's own lifecycle, which `done` alone cannot express: a finished flow that failed
|
|
1878
|
+
// and one that succeeded both report done:true. Callers poll this.
|
|
1879
|
+
status: !st ? "idle" : !st.done ? "pending" : st.error ? "error" : "complete",
|
|
1880
|
+
...(st?.mode ? { mode: st.mode } : {}),
|
|
1881
|
+
...(st?.flowId ? { flowId: st.flowId } : {}),
|
|
1882
|
+
...(st?.expiresAt !== undefined ? { expiresAt: st.expiresAt } : {}),
|
|
1798
1883
|
...(st?.hint && !st.done ? { hint: { url: st.hint.url, instructions: st.hint.instructions, deviceCode: st.hint.deviceCode } } : {}),
|
|
1799
1884
|
...(set ? { activeAccountId: set.activeAccountId, accounts } : {}),
|
|
1800
1885
|
};
|
|
@@ -1811,65 +1896,53 @@ export function oauthLoginSummary(maskEmails = true): Array<{ provider: string;
|
|
|
1811
1896
|
});
|
|
1812
1897
|
}
|
|
1813
1898
|
|
|
1814
|
-
export function clearLoginState(provider: string): void {
|
|
1815
|
-
loginAbort.get(provider)?.controller.abort("cleared");
|
|
1816
|
-
loginAbort.delete(provider);
|
|
1817
|
-
clearManualCodeSlot(provider);
|
|
1818
|
-
loginState.delete(provider);
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
|
-
export function cancelLoginFlow(provider: string, flowId?: string): boolean {
|
|
1822
|
-
const active = loginAbort.get(provider);
|
|
1823
|
-
const existing = loginState.get(provider);
|
|
1824
|
-
if (flowId !== undefined && active?.flowId !== flowId) return false;
|
|
1825
|
-
if (!active && (!existing || existing.done)) return false;
|
|
1826
|
-
active?.controller.abort("cancelled");
|
|
1827
|
-
loginAbort.delete(provider);
|
|
1828
|
-
clearManualCodeSlot(provider);
|
|
1829
|
-
loginState.set(provider, { done: true, error: "Login cancelled" });
|
|
1830
|
-
return true;
|
|
1831
|
-
}
|
|
1832
|
-
|
|
1833
1899
|
export async function startLoginFlow(
|
|
1834
1900
|
provider: string,
|
|
1835
1901
|
opts?: LoginOpts,
|
|
1836
1902
|
lifecycle?: LoginFlowLifecycle,
|
|
1837
|
-
): Promise<{ url: string; instructions?: string; deviceCode?: string }> {
|
|
1903
|
+
): Promise<{ url: string; instructions?: string; deviceCode?: string; expiresAt?: number }> {
|
|
1838
1904
|
const def = OAUTH_PROVIDERS[provider];
|
|
1839
1905
|
if (!def) throw new UnsupportedOAuthProviderError(provider);
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1906
|
+
// Never silently downgrade to the localhost redirect: the caller asked for code mode
|
|
1907
|
+
// precisely because it cannot reach a localhost page.
|
|
1908
|
+
if (opts?.codeMode && !supportsCodeLoginMode(provider)) throw new CodeLoginUnsupportedError(provider);
|
|
1909
|
+
admitLoginFlow(provider, opts?.codeMode === true);
|
|
1910
|
+
// The flow id is the key of every map below. Callers that own one (the management route, the
|
|
1911
|
+
// codex auth API) supply it so a later paste can be pinned to this exact attempt.
|
|
1912
|
+
const flowId = lifecycle?.flowId ?? randomUUID();
|
|
1913
|
+
const expiresAt = recordStartedLoginFlow(provider, flowId, opts?.codeMode === true);
|
|
1846
1914
|
const abort = new AbortController();
|
|
1847
|
-
loginAbort.set(
|
|
1915
|
+
loginAbort.set(flowId, { controller: abort, provider });
|
|
1848
1916
|
if (provider === "kiro") kiroLoginSettling.add(provider);
|
|
1849
1917
|
return new Promise((resolve, reject) => {
|
|
1850
1918
|
let urlResolved = false;
|
|
1851
1919
|
const ctrl: OAuthController = {
|
|
1852
1920
|
onAuth: ({ url, instructions, deviceCode }) => {
|
|
1853
|
-
if (abort.signal.aborted || loginAbort.get(
|
|
1921
|
+
if (abort.signal.aborted || loginAbort.get(flowId)?.controller !== abort) return;
|
|
1854
1922
|
// Device approval can fall back to manual input. Replace, never merge: the
|
|
1855
1923
|
// previous device code must disappear when the provider changes the next step.
|
|
1856
1924
|
const hint = { url, instructions, deviceCode };
|
|
1857
|
-
|
|
1925
|
+
// Merged, not replaced: the flow's own identity (mode/flowId/expiresAt) was recorded
|
|
1926
|
+
// before the provider produced a URL and must survive every hint update.
|
|
1927
|
+
loginState.set(flowId, { ...loginState.get(flowId), done: false, hint });
|
|
1858
1928
|
if (!urlResolved) {
|
|
1859
1929
|
urlResolved = true;
|
|
1860
|
-
resolve({ ...hint });
|
|
1930
|
+
resolve({ ...hint, ...(expiresAt !== undefined ? { expiresAt } : {}) });
|
|
1861
1931
|
}
|
|
1862
1932
|
},
|
|
1863
1933
|
onProgress: () => {},
|
|
1864
1934
|
// GUI fallback when the browser cannot hit the loopback callback server.
|
|
1865
|
-
onManualCodeInput: (expectedState?: string) => waitForManualLoginCode(
|
|
1935
|
+
onManualCodeInput: (expectedState?: string) => waitForManualLoginCode(flowId, abort.signal, expectedState),
|
|
1866
1936
|
signal: abort.signal,
|
|
1867
1937
|
};
|
|
1868
1938
|
const abandonIfNotOwner = (error?: unknown): boolean => {
|
|
1869
|
-
if (loginAbort.get(
|
|
1939
|
+
if (loginAbort.get(flowId)?.controller === abort) return false;
|
|
1870
1940
|
if (!urlResolved) reject(error ?? new Error("OAuth login was superseded"));
|
|
1871
1941
|
return true;
|
|
1872
1942
|
};
|
|
1943
|
+
// The row this flow's own credential write landed in, from the store's receipt. A token match
|
|
1944
|
+
// after the fact would miss it once a concurrent login for the same account rotates the token.
|
|
1945
|
+
let persistedAccountId: string | undefined;
|
|
1873
1946
|
const settle = async (error?: unknown): Promise<void> => {
|
|
1874
1947
|
// Cancellation deletes this controller and records its own terminal result. A late provider
|
|
1875
1948
|
// rejection (or an older flow settling after a replacement starts) must not overwrite that
|
|
@@ -1885,9 +1958,10 @@ export async function startLoginFlow(
|
|
|
1885
1958
|
}
|
|
1886
1959
|
if (abandonIfNotOwner(finalError)) return;
|
|
1887
1960
|
if (finalError === undefined) {
|
|
1888
|
-
loginAbort.delete(
|
|
1889
|
-
clearManualCodeSlot(
|
|
1890
|
-
|
|
1961
|
+
loginAbort.delete(flowId);
|
|
1962
|
+
clearManualCodeSlot(flowId);
|
|
1963
|
+
// Answer this flow's caller with the account it wrote, not whichever one is active now.
|
|
1964
|
+
settleLoginFlow(flowId, persistedAccountId ? { accountId: persistedAccountId } : {});
|
|
1891
1965
|
// Local-token import (grok-cli / Claude Code keychain) completes WITHOUT firing onAuth —
|
|
1892
1966
|
// resolve so the GUI call returns instead of hanging.
|
|
1893
1967
|
if (!urlResolved) resolve({ url: "", instructions: "Logged in via an existing local CLI/keychain token — no browser needed." });
|
|
@@ -1895,27 +1969,28 @@ export async function startLoginFlow(
|
|
|
1895
1969
|
}
|
|
1896
1970
|
|
|
1897
1971
|
const e = finalError;
|
|
1898
|
-
loginAbort.delete(
|
|
1899
|
-
clearManualCodeSlot(
|
|
1972
|
+
loginAbort.delete(flowId);
|
|
1973
|
+
clearManualCodeSlot(flowId);
|
|
1900
1974
|
const msg = publicOAuthAuthenticationErrorMessage(e);
|
|
1901
|
-
|
|
1975
|
+
settleLoginFlow(flowId, { error: msg, errorCode: classifyLoginCodeError(e) });
|
|
1902
1976
|
if (!urlResolved) reject(e);
|
|
1903
1977
|
};
|
|
1904
1978
|
// Background: runLogin persists the credential + provider entry to disk. The lifecycle hook
|
|
1905
1979
|
// lets a long-lived server config adopt that settled state before clients observe done=true.
|
|
1906
1980
|
const assertCurrentOwner = (): void => {
|
|
1907
|
-
if (loginAbort.get(
|
|
1981
|
+
if (loginAbort.get(flowId)?.controller !== abort) throw new OAuthLoginSupersededError();
|
|
1908
1982
|
};
|
|
1909
|
-
|
|
1983
|
+
const onAccountPersisted = (accountId: string): void => { persistedAccountId = accountId; };
|
|
1984
|
+
void runLogin(provider, ctrl, opts, { assertCurrentOwner, onAccountPersisted }).then(
|
|
1910
1985
|
() => settle(),
|
|
1911
1986
|
(e: unknown) => settle(e),
|
|
1912
1987
|
).catch((e: unknown) => {
|
|
1913
1988
|
// settle catches lifecycle failures, so this is only a defensive promise-boundary guard.
|
|
1914
1989
|
if (abandonIfNotOwner(e)) return;
|
|
1915
|
-
loginAbort.delete(
|
|
1916
|
-
clearManualCodeSlot(
|
|
1990
|
+
loginAbort.delete(flowId);
|
|
1991
|
+
clearManualCodeSlot(flowId);
|
|
1917
1992
|
const msg = publicOAuthAuthenticationErrorMessage(e);
|
|
1918
|
-
|
|
1993
|
+
settleLoginFlow(flowId, { error: msg, errorCode: classifyLoginCodeError(e) });
|
|
1919
1994
|
if (!urlResolved) reject(e);
|
|
1920
1995
|
}).finally(() => {
|
|
1921
1996
|
if (provider === "kiro") kiroLoginSettling.delete(provider);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Read-only: never writes to external credential stores.
|
|
4
4
|
* Ported from jawcode packages/ai/src/utils/oauth/local-token-detect.ts (xAI portion).
|
|
5
5
|
*/
|
|
6
|
-
import { execSync } from "node:child_process";
|
|
6
|
+
import { execFile, execSync } from "node:child_process";
|
|
7
7
|
import { existsSync, readFileSync } from "node:fs";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { join } from "node:path";
|
|
@@ -109,6 +109,26 @@ function readClaudeSecureStorage(): string | null {
|
|
|
109
109
|
return readClaudeKeychain() ?? readClaudeCredentialsFile();
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/** Non-blocking Keychain read for request paths; same item and timeout as the sync reader. */
|
|
113
|
+
function readClaudeKeychainAsync(): Promise<string | null> {
|
|
114
|
+
if (process.platform !== "darwin") return Promise.resolve(null);
|
|
115
|
+
return new Promise(resolve => {
|
|
116
|
+
execFile("security", ["find-generic-password", "-s", CLAUDE_KEYCHAIN_SERVICE, "-w"], { encoding: "utf8", timeout: 5000 },
|
|
117
|
+
(error, stdout) => resolve(error ? null : stdout.trim() || null));
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Async variant of {@link detectClaudeCodeToken} with the same source precedence. A request-path
|
|
123
|
+
* caller must not hold the event loop for a Keychain lookup (up to five seconds).
|
|
124
|
+
*/
|
|
125
|
+
export async function detectClaudeCodeTokenAsync(): Promise<OAuthCredentials | null> {
|
|
126
|
+
const raw = process.env.CLAUDE_CONFIG_DIR?.trim()
|
|
127
|
+
? readClaudeCredentialsFile() ?? await readClaudeKeychainAsync()
|
|
128
|
+
: await readClaudeKeychainAsync() ?? readClaudeCredentialsFile();
|
|
129
|
+
return raw ? parseClaudeOauthPayload(raw) : null;
|
|
130
|
+
}
|
|
131
|
+
|
|
112
132
|
export function parseClaudeOauthPayload(raw: string): OAuthCredentials | null {
|
|
113
133
|
try {
|
|
114
134
|
const data = JSON.parse(raw) as { claudeAiOauth?: { accessToken?: string; refreshToken?: string; expiresAt?: number } };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { AnthropicTokenError } from "./anthropic";
|
|
2
|
+
import { OAuthProviderNamespaceCollisionError, OAuthProviderPublicationError, publicOAuthAuthenticationErrorMessage } from "./index";
|
|
3
|
+
import { XaiTokenRequestError } from "./xai";
|
|
4
|
+
import { AnthropicInstanceCollisionError, AnthropicInstanceConfigReadError } from "./store-anthropic-instance";
|
|
5
|
+
|
|
6
|
+
// Standard RFC 6749 / RFC 6750 / RFC 8628 error codes. A provider's `error` field is printed only
|
|
7
|
+
// when it is one of these, so a body that echoes request parameters there still prints nothing.
|
|
8
|
+
const STANDARD_OAUTH_ERROR_CODES = new Set([
|
|
9
|
+
"invalid_request", "invalid_client", "invalid_grant", "unauthorized_client",
|
|
10
|
+
"unsupported_grant_type", "invalid_scope", "access_denied", "unsupported_response_type",
|
|
11
|
+
"server_error", "temporarily_unavailable", "invalid_token", "insufficient_scope",
|
|
12
|
+
"authorization_pending", "slow_down", "expired_token",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
/** Fixed, machine-readable reason for a failed login; never derived from free-form text. */
|
|
16
|
+
export function oauthLoginFailureDiagnostic(error: unknown): string {
|
|
17
|
+
if (error instanceof OAuthProviderNamespaceCollisionError || error instanceof OAuthProviderPublicationError
|
|
18
|
+
|| error instanceof AnthropicInstanceCollisionError || error instanceof AnthropicInstanceConfigReadError) {
|
|
19
|
+
return "configuration_error";
|
|
20
|
+
}
|
|
21
|
+
const status = error instanceof AnthropicTokenError ? error.httpStatus
|
|
22
|
+
: error instanceof XaiTokenRequestError ? error.status : undefined;
|
|
23
|
+
const oauthError = error instanceof AnthropicTokenError || error instanceof XaiTokenRequestError
|
|
24
|
+
? error.oauthError : undefined;
|
|
25
|
+
const parts: string[] = [];
|
|
26
|
+
if (typeof status === "number" && Number.isInteger(status) && status >= 400 && status <= 599) parts.push(`HTTP ${status}`);
|
|
27
|
+
if (typeof oauthError === "string" && STANDARD_OAUTH_ERROR_CODES.has(oauthError)) parts.push(oauthError);
|
|
28
|
+
if (parts.length > 0) return parts.join(" ");
|
|
29
|
+
const name = error instanceof Error ? error.name : "";
|
|
30
|
+
if (name === "TimeoutError" || name === "AbortError") return "timeout";
|
|
31
|
+
// fetch() reports refused connections and DNS failures as a TypeError carrying a string code; a
|
|
32
|
+
// programming TypeError carries none and stays in the generic bucket.
|
|
33
|
+
const code = error instanceof TypeError ? (error as { code?: unknown }).code : undefined;
|
|
34
|
+
if ((error instanceof AnthropicTokenError && error.requestNotSent)
|
|
35
|
+
|| (error instanceof XaiTokenRequestError && error.status === undefined)
|
|
36
|
+
|| (typeof code === "string" && code !== "")) return "network_error";
|
|
37
|
+
return "oauth_authentication_failed";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Keep terminal login failures within the public OAuth error vocabulary. */
|
|
41
|
+
export function reportOAuthLoginError(error: unknown): never {
|
|
42
|
+
console.error(`\n❌ OAuth login failed (${oauthLoginFailureDiagnostic(error)}): ${publicOAuthAuthenticationErrorMessage(error)}`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|