@iislee/opencodex 2.11.0 → 2.35.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/AGENTS_INSTALL.md +109 -0
- package/README.md +117 -22
- package/bin/ocx.mjs +245 -88
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +21 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +475 -62
- package/src/adapters/base.ts +82 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +637 -0
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +308 -0
- package/src/adapters/cursor/cursor-errors.ts +144 -6
- package/src/adapters/cursor/discovery.ts +122 -14
- package/src/adapters/cursor/effort-map.ts +106 -4
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +180 -59
- package/src/adapters/cursor/live-transport.ts +623 -170
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -3
- package/src/adapters/cursor/native-exec.ts +121 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +447 -75
- package/src/adapters/cursor/request-builder.ts +271 -35
- package/src/adapters/cursor/tool-definitions.ts +252 -12
- package/src/adapters/cursor/tool-result-normalize.ts +115 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +48 -1
- package/src/adapters/cursor.ts +336 -39
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +713 -41
- package/src/adapters/google-antigravity-wire.ts +48 -10
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +742 -86
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +65 -17
- package/src/adapters/kiro.ts +270 -73
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1377 -344
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +1164 -64
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +105 -21
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +186 -0
- package/src/bridge.ts +512 -92
- package/src/chat/inbound.ts +57 -20
- package/src/chat/outbound.ts +97 -33
- package/src/claude/agents-inject.ts +40 -10
- package/src/claude/context-windows.ts +37 -14
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +88 -21
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +150 -19
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +648 -28
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +97 -19
- package/src/cli/agent.ts +53 -2
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +60 -15
- package/src/cli/claude.ts +129 -35
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +821 -0
- package/src/cli/doctor.ts +440 -56
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +34 -274
- package/src/cli/index.ts +417 -528
- package/src/cli/init.ts +5 -17
- package/src/cli/inspect.ts +230 -0
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +130 -2
- package/src/cli/models.ts +118 -16
- package/src/cli/observe.ts +123 -11
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +34 -3
- package/src/cli/provider.ts +25 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +476 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +57 -10
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +51 -4
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +137 -18
- package/src/cli/version-skew.ts +46 -0
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1442 -23
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +35 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +84 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +570 -107
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +893 -246
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +348 -32
- package/src/codex/autostart-health.ts +24 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +86 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +138 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +555 -45
- package/src/codex/catalog/native-models.ts +75 -0
- package/src/codex/catalog/parsing.ts +269 -30
- package/src/codex/catalog/provider-fetch.ts +1626 -144
- package/src/codex/catalog/sync.ts +1576 -187
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +386 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +676 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +436 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +30 -24
- package/src/codex/history-provider.ts +1016 -235
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +223 -0
- package/src/codex/inject-coordination.ts +373 -0
- package/src/codex/inject.ts +1114 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +81 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +524 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +360 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +622 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +675 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +1313 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +264 -21
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +514 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1065 -31
- package/src/codex/subagent-model-fallback.ts +437 -43
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +720 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +47 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +91 -9
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-name.ts +24 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +1713 -709
- package/src/generated/compatibility-version.json +3324 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +157 -33
- package/src/images/plan.ts +24 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +155 -0
- package/src/integrations/registry.ts +166 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +433 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +715 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +167 -11
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +26 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +80 -5
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +6 -2
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +253 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +30 -4
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +462 -71
- package/src/oauth/key-providers.ts +25 -0
- package/src/oauth/kimi.ts +25 -3
- package/src/oauth/kiro.ts +95 -6
- package/src/oauth/local-token-detect.ts +13 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +133 -26
- package/src/oauth/token-guardian.ts +9 -3
- package/src/oauth/types.ts +15 -0
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +176 -0
- package/src/providers/context-cap.ts +26 -8
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +281 -6
- package/src/providers/fastwire.ts +507 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +35 -2
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +30 -0
- package/src/providers/quota.ts +1379 -40
- package/src/providers/registry.ts +1545 -112
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +94 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +32 -4
- package/src/reasoning-effort.ts +68 -3
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +384 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +435 -0
- package/src/responses/parser.ts +225 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +504 -0
- package/src/responses/schema.ts +12 -2
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +659 -31
- package/src/responses/thought-signature-replay.ts +364 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +405 -31
- package/src/routing/analytics.ts +379 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +155 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +289 -48
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +154 -72
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +445 -0
- package/src/server/claude-messages.ts +160 -44
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +1021 -190
- package/src/server/lifecycle.ts +336 -14
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +433 -116
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +64 -25
- package/src/server/management/config-routes.ts +511 -51
- package/src/server/management/context.ts +89 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +127 -52
- package/src/server/management/model-routes.ts +461 -133
- package/src/server/management/model-rows.ts +167 -0
- package/src/server/management/native-integration-routes.ts +770 -0
- package/src/server/management/oauth-account-routes.ts +90 -8
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +836 -159
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +289 -33
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +126 -23
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +161 -10
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +221 -54
- package/src/server/relay.ts +479 -87
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +298 -31
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +239 -36
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +444 -72
- package/src/server/responses/core.ts +3579 -436
- package/src/server/responses/empty-completion-guard.ts +333 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +89 -99
- package/src/server/responses/input-admission.ts +187 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +343 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +381 -0
- package/src/server/responses-custom-tool-repair.ts +363 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +363 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +376 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +319 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +53 -40
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1153 -94
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +911 -0
- package/src/types/provider.ts +614 -0
- package/src/types/request.ts +397 -0
- package/src/types/tools.ts +236 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +107 -1237
- package/src/update/index.ts +37 -23
- package/src/update/job.ts +445 -68
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +266 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +287 -16
- package/src/usage/log.ts +666 -50
- package/src/usage/summary.ts +388 -17
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +27 -16
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +119 -0
- package/src/web-search/executor.ts +21 -6
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +284 -54
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
package/src/oauth/index.ts
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import type { KiroOAuthMetadata, OAuthController, OAuthCredentials } from "./types";
|
|
2
2
|
import { parseCallbackInput } from "./callback-server";
|
|
3
3
|
import type { OcxConfig, OcxProviderConfig, RefreshPolicy } from "../types";
|
|
4
|
+
import { maskEmail } from "../lib/privacy";
|
|
4
5
|
import { loadConfig, resolveEnvValue, saveConfig } from "../config";
|
|
5
6
|
import { KiroTokenRefreshError, environmentKiroRoutingMetadata, loginKiro, refreshKiroToken, settleKiroLoginTransaction } from "./kiro";
|
|
6
|
-
import { getAccountCredential, getAccountSet, removeAccount, saveAccountCredential, saveCredential, setActiveAccount, getCredential, credentialGeneration, createOAuthRefreshIntentLock, mergeAccountCredential, markAccountNeedsReauthIfGeneration, readOAuthRefreshIntent, writeOAuthRefreshIntent, markOAuthRefreshIntentStaleOwner, clearOAuthRefreshIntent, OAuthMutationBusyError } from "./store";
|
|
7
|
+
import { getAccountCredential, getAccountSet, removeAccount, saveAccountCredential, saveCredential, setActiveAccount, getCredential, credentialGeneration, createOAuthRefreshIntentLock, mergeAccountCredential, markAccountNeedsReauthIfGeneration, readOAuthRefreshIntent, writeOAuthRefreshIntent, markOAuthRefreshIntentStaleOwner, clearOAuthRefreshIntent, normalizeAuthStoreBuffer, OAuthMutationBusyError } from "./store";
|
|
7
8
|
import { loginXai, refreshXaiToken, XAI_LOCAL_CLI_DETACH_WARNING, XaiTokenRequestError } from "./xai";
|
|
8
9
|
import { ANTHROPIC_OAUTH_BETA, AnthropicTokenError, loginAnthropic, refreshAnthropicToken } from "./anthropic";
|
|
9
10
|
import { loginKimi, refreshKimiToken } from "./kimi";
|
|
11
|
+
import { loginNous, NousTokenError, refreshNousToken, clearNousRefreshIntent, RefreshIntentIOError } from "./nous";
|
|
10
12
|
import { loginChatGPT, refreshChatGPTToken } from "./chatgpt";
|
|
11
13
|
import { loginAntigravity, refreshAntigravityToken } from "./google-antigravity";
|
|
12
14
|
import { loginCursor, refreshCursorToken } from "./cursor";
|
|
13
15
|
import { loginGithubCopilot, refreshGithubCopilotToken, validateCopilotApiBaseUrl } from "./github-copilot";
|
|
16
|
+
import { loginCommandCode, refreshCommandCodeToken } from "./command-code";
|
|
17
|
+
import { loginCline, refreshClineToken } from "./cline";
|
|
18
|
+
import { ANTIGRAVITY_REQUEST_UA } from "../adapters/google-antigravity-wire";
|
|
14
19
|
import { deriveOAuthDefaultModel, deriveOAuthProviderConfig } from "../providers/derive";
|
|
15
20
|
import { apiKeyPoolEntryId, sanitizeApiKeyValue } from "../providers/api-keys";
|
|
16
|
-
import { effectiveGoogleMode, getProviderRegistryEntry, providerMatchesRegistryTransport } from "../providers/registry";
|
|
21
|
+
import { effectiveGoogleMode, getProviderRegistryEntry, mergeRegistryStaticHeaders, providerMatchesRegistryTransport } from "../providers/registry";
|
|
17
22
|
import { resolveProviderModelDiscoveryUrl } from "../providers/model-discovery";
|
|
18
23
|
import { resolveProviderTransport } from "../providers/xai-transport";
|
|
19
24
|
import { detectClaudeCodeToken, detectGrokCliToken, hasComparableGrokIdentity, isSameGrokIdentity, shouldAdoptGrokGeneration } from "./local-token-detect";
|
|
@@ -51,10 +56,35 @@ export interface OAuthAccessSnapshot {
|
|
|
51
56
|
accountId: string;
|
|
52
57
|
generation: string;
|
|
53
58
|
accessToken: string;
|
|
59
|
+
/** Cloud Code Assist project selected during Antigravity login. */
|
|
60
|
+
projectId?: string;
|
|
54
61
|
/** Safe request-routing subset; refresh-only Kiro client secrets never leave the credential store. */
|
|
55
|
-
kiro?: Pick<KiroOAuthMetadata, "profileArn" | "apiRegion" | "ssoRegion">;
|
|
62
|
+
kiro?: Pick<KiroOAuthMetadata, "profileArn" | "apiRegion" | "ssoRegion" | "authType">;
|
|
63
|
+
/**
|
|
64
|
+
* Allowlisted GitHub Copilot API origin belonging to THIS account.
|
|
65
|
+
*
|
|
66
|
+
* Copilot pins its bearer to an account-scoped regional host, and the initial route already
|
|
67
|
+
* pairs the two (`core.ts` resolves transport with `getOAuthCredentialApiBaseUrl`). Account
|
|
68
|
+
* failover must carry the pairing across the rotation; without it, account B's token is sent to
|
|
69
|
+
* account A's origin (#2568d).
|
|
70
|
+
*/
|
|
71
|
+
apiBaseUrl?: string;
|
|
56
72
|
}
|
|
57
73
|
|
|
74
|
+
export interface ObservedOAuthAccessSnapshot extends OAuthAccessSnapshot {
|
|
75
|
+
/** Retained for callers that predate `apiBaseUrl` moving onto the base snapshot. */
|
|
76
|
+
apiBaseUrl?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type OAuthActiveTokenObservation =
|
|
80
|
+
| { readonly kind: "available"; readonly snapshot: ObservedOAuthAccessSnapshot }
|
|
81
|
+
| { readonly kind: "missing" }
|
|
82
|
+
| { readonly kind: "malformed" }
|
|
83
|
+
| { readonly kind: "needs-reauth" }
|
|
84
|
+
| { readonly kind: "expired" }
|
|
85
|
+
| { readonly kind: "near-expiry" }
|
|
86
|
+
| { readonly kind: "unsupported" };
|
|
87
|
+
|
|
58
88
|
const MAX_OAUTH_TOKEN_REFRESH_FLIGHTS = 32;
|
|
59
89
|
const OAUTH_TOKEN_REFRESH_FLIGHT_STALE_MS = 120_000;
|
|
60
90
|
interface OAuthRefreshFlightEvidence { flightId: string; dispatched: boolean }
|
|
@@ -145,6 +175,14 @@ function oauthDefaultModel(id: string): string {
|
|
|
145
175
|
}
|
|
146
176
|
|
|
147
177
|
export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
|
|
178
|
+
"command-code": {
|
|
179
|
+
// Add-account/reauth must not reimport the current local CLI credential.
|
|
180
|
+
login: (ctrl, opts) => loginCommandCode(ctrl, { importLocal: opts?.forceLogin ? "off" : "fallback" }),
|
|
181
|
+
refresh: refreshCommandCodeToken,
|
|
182
|
+
providerConfig: oauthConfig("command-code"),
|
|
183
|
+
defaultModel: oauthDefaultModel("command-code"),
|
|
184
|
+
defaultRefreshPolicy: "disabled",
|
|
185
|
+
},
|
|
148
186
|
xai: {
|
|
149
187
|
// forceLogin skips the local grok-cli import so a SECOND account can be chosen in the browser.
|
|
150
188
|
login: (ctrl, opts) => loginXai(ctrl, { importLocal: opts?.forceLogin ? "off" : "fallback" }),
|
|
@@ -167,6 +205,22 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
|
|
|
167
205
|
providerConfig: oauthConfig("kimi"),
|
|
168
206
|
defaultModel: oauthDefaultModel("kimi"),
|
|
169
207
|
},
|
|
208
|
+
nous: {
|
|
209
|
+
// Nous Portal device-grant login (RFC 8628) against portal.nousresearch.com.
|
|
210
|
+
// The access token is the per-request inference JWT (scope inference:invoke).
|
|
211
|
+
// Refresh tokens are single-use and rotated server-side on every refresh:
|
|
212
|
+
// keep background refresh lazy-only (the default) so concurrent refreshes
|
|
213
|
+
// cannot trip the Portal's token-reuse revocation.
|
|
214
|
+
login: (ctrl) => loginNous(ctrl),
|
|
215
|
+
refresh: (rt, signal) => refreshNousToken(rt, signal),
|
|
216
|
+
providerConfig: oauthConfig("nous"),
|
|
217
|
+
defaultModel: oauthDefaultModel("nous"),
|
|
218
|
+
// Single-use rotating refresh tokens must never be background-refreshed
|
|
219
|
+
// proactively: concurrent refreshes would trip the Portal's reuse
|
|
220
|
+
// revocation. Anchor the lazy-only default explicitly so it cannot be
|
|
221
|
+
// silently overridden to proactive.
|
|
222
|
+
defaultRefreshPolicy: "lazy-only",
|
|
223
|
+
},
|
|
170
224
|
kiro: {
|
|
171
225
|
login: (ctrl, opts) => loginKiro(ctrl, { forceLogin: opts?.forceLogin }),
|
|
172
226
|
refresh: (rt, signal, credential) => refreshKiroToken(rt, signal, credential),
|
|
@@ -180,7 +234,7 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
|
|
|
180
234
|
defaultModel: oauthDefaultModel("google-antigravity"),
|
|
181
235
|
},
|
|
182
236
|
cursor: {
|
|
183
|
-
login: (ctrl) => loginCursor(ctrl),
|
|
237
|
+
login: (ctrl, opts) => loginCursor(ctrl, undefined, { forceLogin: opts?.forceLogin }),
|
|
184
238
|
refresh: refreshCursorToken,
|
|
185
239
|
providerConfig: oauthConfig("cursor"),
|
|
186
240
|
defaultModel: oauthDefaultModel("cursor"),
|
|
@@ -193,6 +247,12 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
|
|
|
193
247
|
// Unofficial Copilot bridge — keep proactive traffic lazy-only (no background guardian spam).
|
|
194
248
|
defaultRefreshPolicy: "lazy-only",
|
|
195
249
|
},
|
|
250
|
+
cline: {
|
|
251
|
+
login: (ctrl, opts) => loginCline(ctrl, { importLocal: opts?.forceLogin ? "off" : "fallback", forceLogin: opts?.forceLogin }),
|
|
252
|
+
refresh: refreshClineToken,
|
|
253
|
+
providerConfig: oauthConfig("cline"),
|
|
254
|
+
defaultModel: oauthDefaultModel("cline"),
|
|
255
|
+
},
|
|
196
256
|
chatgpt: {
|
|
197
257
|
login: loginChatGPT,
|
|
198
258
|
refresh: (rt) => refreshChatGPTToken(rt),
|
|
@@ -248,35 +308,138 @@ export class UnsupportedOAuthProviderError extends Error {
|
|
|
248
308
|
}
|
|
249
309
|
|
|
250
310
|
export class OAuthLoginRequiredError extends Error {
|
|
311
|
+
readonly provider: string;
|
|
312
|
+
|
|
251
313
|
constructor(provider: string) {
|
|
252
314
|
super(`Not logged in to ${provider}. Run: ocx login ${provider}`);
|
|
253
315
|
this.name = "OAuthLoginRequiredError";
|
|
316
|
+
this.provider = provider;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export class OAuthProviderPublicationError extends Error {
|
|
321
|
+
constructor() {
|
|
322
|
+
super("OAuth credential was saved, but the provider entry was not written. Resolve the account namespace collision, then retry login.");
|
|
323
|
+
this.name = "OAuthProviderPublicationError";
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export class OAuthReauthIdentityMismatchError extends Error {
|
|
328
|
+
constructor() {
|
|
329
|
+
super("Signed-in account does not match the selected account. Sign in with the same account.");
|
|
330
|
+
this.name = "OAuthReauthIdentityMismatchError";
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export class OAuthReauthIdentityUnverifiedError extends Error {
|
|
335
|
+
constructor() {
|
|
336
|
+
super("Could not verify signed-in account identity for reauth.");
|
|
337
|
+
this.name = "OAuthReauthIdentityUnverifiedError";
|
|
254
338
|
}
|
|
255
339
|
}
|
|
256
340
|
|
|
341
|
+
class OAuthLoginSupersededError extends Error {
|
|
342
|
+
constructor() {
|
|
343
|
+
super("OAuth login was superseded before credential persistence");
|
|
344
|
+
this.name = "OAuthLoginSupersededError";
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Project arbitrary OAuth failures onto the small, stable public error vocabulary. */
|
|
349
|
+
export function publicOAuthAuthenticationErrorMessage(error: unknown): string {
|
|
350
|
+
if (error instanceof OAuthMutationBusyError) {
|
|
351
|
+
return error.message === "OAuth mutation queue wait timed out"
|
|
352
|
+
? "OAuth mutation queue wait timed out"
|
|
353
|
+
: "OAuth mutation queue is busy";
|
|
354
|
+
}
|
|
355
|
+
if (
|
|
356
|
+
(error instanceof OAuthLoginRequiredError && isOAuthProvider(error.provider))
|
|
357
|
+
|| error instanceof OAuthProviderPublicationError
|
|
358
|
+
// Reauth identity outcomes carry fixed, account-free remediation text. Dropping them to the
|
|
359
|
+
// generic message hides WHICH failure the user must fix (sign in with the selected account).
|
|
360
|
+
|| error instanceof OAuthReauthIdentityMismatchError
|
|
361
|
+
|| error instanceof OAuthReauthIdentityUnverifiedError
|
|
362
|
+
|| error instanceof OAuthTokenRefreshBusyError
|
|
363
|
+
|| error instanceof OAuthTokenRefreshStaleError
|
|
364
|
+
) return error.message;
|
|
365
|
+
return "OAuth authentication failed. Check the OpenCodex account status and retry.";
|
|
366
|
+
}
|
|
367
|
+
|
|
257
368
|
function accessSnapshot(provider: string, accountId: string, cred: OAuthCredentials): OAuthAccessSnapshot {
|
|
369
|
+
// Derived, not read back: a stored `authType` is trusted when present, but a credential imported
|
|
370
|
+
// before the field existed still routes correctly because the client pair implies SSO OIDC.
|
|
371
|
+
const kiroAuthType = cred.kiro?.authType
|
|
372
|
+
?? (cred.kiro?.clientId && cred.kiro?.clientSecret ? "aws_sso_oidc" as const : undefined);
|
|
258
373
|
const storedKiroRouting = {
|
|
259
374
|
...(cred.kiro?.profileArn ? { profileArn: cred.kiro.profileArn } : {}),
|
|
260
375
|
...(cred.kiro?.apiRegion ? { apiRegion: cred.kiro.apiRegion } : {}),
|
|
261
376
|
...(cred.kiro?.ssoRegion ? { ssoRegion: cred.kiro.ssoRegion } : {}),
|
|
262
377
|
};
|
|
378
|
+
// `authType` is a property OF the account, not routing the environment can substitute for, so it
|
|
379
|
+
// is merged after the environment fallback decision rather than counting as stored routing.
|
|
380
|
+
// Folding it into `storedKiroRouting` would make a client-pair-only credential look non-empty
|
|
381
|
+
// and silently disable `environmentKiroRoutingMetadata()` for it.
|
|
382
|
+
const kiroAuthTypeRouting = kiroAuthType ? { authType: kiroAuthType } : {};
|
|
383
|
+
// Validated here, not at the call site: an unvalidated origin from a legacy or crafted
|
|
384
|
+
// credential must never travel with a bearer, and dropping it makes the transport fall back to
|
|
385
|
+
// the canonical host rather than to whatever the previous account was using.
|
|
386
|
+
const copilotApiBaseUrl = provider === "github-copilot"
|
|
387
|
+
? validateCopilotApiBaseUrl(cred.apiBaseUrl)
|
|
388
|
+
: undefined;
|
|
263
389
|
return {
|
|
264
390
|
provider,
|
|
265
391
|
accountId,
|
|
266
392
|
generation: credentialGeneration(cred),
|
|
267
393
|
accessToken: cred.access,
|
|
394
|
+
...(cred.projectId ? { projectId: cred.projectId } : {}),
|
|
395
|
+
...(copilotApiBaseUrl ? { apiBaseUrl: copilotApiBaseUrl } : {}),
|
|
268
396
|
// Stored account metadata remains authoritative. Metadata-less legacy/environment credentials
|
|
269
397
|
// may use explicit environment routing, but never borrow the currently signed-in local CLI account.
|
|
270
398
|
...(provider === "kiro"
|
|
271
399
|
? {
|
|
272
|
-
kiro:
|
|
273
|
-
|
|
274
|
-
|
|
400
|
+
kiro: {
|
|
401
|
+
...(Object.keys(storedKiroRouting).length > 0
|
|
402
|
+
? storedKiroRouting
|
|
403
|
+
: environmentKiroRoutingMetadata() ?? {}),
|
|
404
|
+
...kiroAuthTypeRouting,
|
|
405
|
+
},
|
|
275
406
|
}
|
|
276
407
|
: {}),
|
|
277
408
|
};
|
|
278
409
|
}
|
|
279
410
|
|
|
411
|
+
/**
|
|
412
|
+
* Observe the active OAuth token from an auth-store buffer supplied by its filesystem owner.
|
|
413
|
+
* Missing, malformed, reauth-required, and expiring credentials are typed no-token outcomes;
|
|
414
|
+
* this path never refreshes, locks, hardens, backs up, or persists credentials.
|
|
415
|
+
*/
|
|
416
|
+
export function observeActiveOAuthAccessToken(
|
|
417
|
+
provider: string,
|
|
418
|
+
authStoreBuffer: Uint8Array | null,
|
|
419
|
+
now = Date.now(),
|
|
420
|
+
): OAuthActiveTokenObservation {
|
|
421
|
+
const authStore = normalizeAuthStoreBuffer(authStoreBuffer);
|
|
422
|
+
if (authStore.kind === "absent") return { kind: "missing" };
|
|
423
|
+
if (authStore.kind === "malformed") return { kind: "malformed" };
|
|
424
|
+
if (!isOAuthProvider(provider)) return { kind: "unsupported" };
|
|
425
|
+
|
|
426
|
+
const accountSet = authStore.store[provider];
|
|
427
|
+
const account = accountSet?.accounts.find(candidate => candidate.id === accountSet.activeAccountId);
|
|
428
|
+
if (!account) return { kind: "missing" };
|
|
429
|
+
if (account.needsReauth) return { kind: "needs-reauth" };
|
|
430
|
+
if (account.credential.expires <= now) return { kind: "expired" };
|
|
431
|
+
if (account.credential.expires <= now + REFRESH_SKEW_MS) return { kind: "near-expiry" };
|
|
432
|
+
|
|
433
|
+
const apiBaseUrl = validateCopilotApiBaseUrl(account.credential.apiBaseUrl);
|
|
434
|
+
return {
|
|
435
|
+
kind: "available",
|
|
436
|
+
snapshot: {
|
|
437
|
+
...accessSnapshot(provider, account.id, account.credential),
|
|
438
|
+
...(apiBaseUrl ? { apiBaseUrl } : {}),
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
280
443
|
async function resolveAccessSnapshotForAccount(
|
|
281
444
|
provider: string,
|
|
282
445
|
accountId: string,
|
|
@@ -339,7 +502,7 @@ export async function getValidAccessTokenSnapshot(provider: string): Promise<OAu
|
|
|
339
502
|
}
|
|
340
503
|
|
|
341
504
|
/** Providers whose upstream-401 replay path may force a snapshot refresh. */
|
|
342
|
-
const FORCE_REFRESH_PROVIDERS = new Set(["xai", "github-copilot", "kiro"]);
|
|
505
|
+
const FORCE_REFRESH_PROVIDERS = new Set(["xai", "github-copilot", "kiro", "cline"]);
|
|
343
506
|
|
|
344
507
|
export async function forceRefreshOAuthAccessSnapshot(
|
|
345
508
|
rejected: OAuthAccessSnapshot,
|
|
@@ -362,6 +525,22 @@ export async function getValidAccessTokenForAccount(provider: string, accountId:
|
|
|
362
525
|
return (await resolveAccessSnapshotForAccount(provider, accountId)).accessToken;
|
|
363
526
|
}
|
|
364
527
|
|
|
528
|
+
/**
|
|
529
|
+
* Account-scoped resolver returning the FULL snapshot, not just the bearer.
|
|
530
|
+
*
|
|
531
|
+
* A rotator that swaps only the token silently mixes credential generations: Antigravity pairs
|
|
532
|
+
* an account-matched `projectId` with its token (see the pairing comment in
|
|
533
|
+
* server/responses/core.ts), Kiro carries routing metadata, and Copilot's observed snapshot
|
|
534
|
+
* carries an account-specific API origin. Reading those back from "whichever account is active"
|
|
535
|
+
* after a rotation is exactly the mixing this returns in one piece to prevent (#2568).
|
|
536
|
+
*/
|
|
537
|
+
export async function getValidAccessSnapshotForAccount(
|
|
538
|
+
provider: string,
|
|
539
|
+
accountId: string,
|
|
540
|
+
): Promise<OAuthAccessSnapshot> {
|
|
541
|
+
return resolveAccessSnapshotForAccount(provider, accountId);
|
|
542
|
+
}
|
|
543
|
+
|
|
365
544
|
/** Terminal refresh failures (revoked/rotated-away grants) — retrying cannot succeed. */
|
|
366
545
|
function isTerminalRefreshError(err: unknown): boolean {
|
|
367
546
|
const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
@@ -376,6 +555,11 @@ function terminal(error:unknown):boolean{
|
|
|
376
555
|
if(error instanceof XaiTokenRequestError)return ["invalid_grant","refresh_token_reused","revoked_token"].includes(error.oauthError??"");
|
|
377
556
|
if(error instanceof AnthropicTokenError)return (error.httpStatus===400||error.httpStatus===401)&&["invalid_grant","refresh_token_reused","revoked","revoked_token","refresh_token_revoked"].includes(error.oauthError??"");
|
|
378
557
|
if(error instanceof KiroTokenRefreshError)return (error.httpStatus===400||error.httpStatus===401)&&error.oauthError!==undefined;
|
|
558
|
+
if(error instanceof NousTokenError)return error.terminal===true||["invalid_grant","refresh_token_reused","revoked","revoked_token","expired_token"].includes(error.oauthError??"");
|
|
559
|
+
// Local durable-write/read/cleanup failures are operational, not credential
|
|
560
|
+
// death: the provider credential was never rejected or consumed. Never mark
|
|
561
|
+
// the account needsReauth for broken local persistence infrastructure.
|
|
562
|
+
if (error instanceof RefreshIntentIOError) return false;
|
|
379
563
|
return isTerminalRefreshError(error);
|
|
380
564
|
}
|
|
381
565
|
function authoritative(stored:OAuthCredentials,active:boolean,now:()=>number):OAuthCredentials{if(stored.source!=="local-cli")return stored;const disk=detectGrokCliToken();if(!disk)return stored;const allowed=isSameGrokIdentity(stored,disk)||(active&&!hasComparableGrokIdentity(stored,disk));return allowed&&shouldAdoptGrokGeneration(stored,disk,now(),REFRESH_SKEW_MS)?disk:stored;}
|
|
@@ -399,6 +583,47 @@ function newerClaudeCredential(stored: OAuthCredentials, now: number): OAuthCred
|
|
|
399
583
|
return credentialGeneration(disk) !== credentialGeneration(stored) ? disk : undefined;
|
|
400
584
|
}
|
|
401
585
|
|
|
586
|
+
/**
|
|
587
|
+
* Preserve an already-rotated Nous refresh token (RT-B) after a terminal refresh
|
|
588
|
+
* error (e.g. the returned access JWT lacked `inference:invoke`). The unusable
|
|
589
|
+
* access token is NOT persisted as valid: the recovery credential carries an
|
|
590
|
+
* empty access placeholder with a past expiry so it can never be routed, and the
|
|
591
|
+
* account is marked needsReauth by the caller. Generation-safe: a concurrent
|
|
592
|
+
* newer write wins and is never overwritten.
|
|
593
|
+
*/
|
|
594
|
+
async function preserveNousRotatedRefresh(
|
|
595
|
+
provider: string,
|
|
596
|
+
accountId: string,
|
|
597
|
+
rotatedRefresh: string,
|
|
598
|
+
expectedGeneration: string,
|
|
599
|
+
previous: OAuthCredentials,
|
|
600
|
+
): Promise<{ kind: "persisted"; generation: string } | { kind: "superseded" } | { kind: "failed" }> {
|
|
601
|
+
try {
|
|
602
|
+
const recovery: OAuthCredentials = {
|
|
603
|
+
refresh: rotatedRefresh,
|
|
604
|
+
// Never persist the unusable access token: an empty placeholder with a
|
|
605
|
+
// past expiry can never be observed as a valid credential.
|
|
606
|
+
access: "",
|
|
607
|
+
expires: 0,
|
|
608
|
+
...(previous.accountId ? { accountId: previous.accountId } : {}),
|
|
609
|
+
...(previous.email ? { email: previous.email } : {}),
|
|
610
|
+
...(previous.source ? { source: previous.source } : {}),
|
|
611
|
+
};
|
|
612
|
+
const outcome = await mergeAccountCredential(provider, accountId, recovery, { expectedGeneration });
|
|
613
|
+
if (outcome.superseded) return { kind: "superseded" };
|
|
614
|
+
// Return the exact generation this write produced, so the caller never
|
|
615
|
+
// re-reads the store (a concurrent writer could otherwise supply a different
|
|
616
|
+
// credential generation and be marked needsReauth by mistake).
|
|
617
|
+
return { kind: "persisted", generation: credentialGeneration(recovery) };
|
|
618
|
+
} catch (error) {
|
|
619
|
+
// A store-mutation busy outcome is transient and retryable; surface it
|
|
620
|
+
// unchanged so the caller can retry rather than treating it as a permanent
|
|
621
|
+
// RT-B persistence failure.
|
|
622
|
+
if (error instanceof OAuthMutationBusyError) throw error;
|
|
623
|
+
return { kind: "failed" };
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
402
627
|
export async function refreshAnthropicAccountWithLock(
|
|
403
628
|
provider: string,
|
|
404
629
|
accountId: string,
|
|
@@ -512,10 +737,69 @@ export async function refreshGenericAccountWithLock(
|
|
|
512
737
|
throw new OAuthLoginRequiredError(provider);
|
|
513
738
|
}
|
|
514
739
|
logOAuthEvent("OAuth credentials rotated and persisted", { provider, accountId });
|
|
740
|
+
// Best-effort bookkeeping cleanup: the rotated credential is already
|
|
741
|
+
// durably persisted. A failure to unlink the old-token intent file
|
|
742
|
+
// (EACCES/EPERM/EBUSY/EROFS) must not turn a committed rotation into a
|
|
743
|
+
// failed refresh. The stale intent keys the OLD token, which is no longer
|
|
744
|
+
// stored, so leaving it behind blocks nothing and is safe. It must also
|
|
745
|
+
// never route through the generic refresh error path (no needsReauth).
|
|
746
|
+
if (provider === "nous") {
|
|
747
|
+
try {
|
|
748
|
+
clearNousRefreshIntent(stored.refresh);
|
|
749
|
+
} catch (cleanupErr) {
|
|
750
|
+
logOAuthEvent("OAuth refresh intent cleanup failed (non-fatal)", {
|
|
751
|
+
provider,
|
|
752
|
+
accountId,
|
|
753
|
+
cause: cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr),
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
}
|
|
515
757
|
return fresh.access;
|
|
516
758
|
} catch (error) {
|
|
517
759
|
if (error instanceof OAuthMutationBusyError) throw error;
|
|
518
760
|
if (!terminal(error)) throw error;
|
|
761
|
+
// Nous-specific failure-atomicity: a terminal refresh error that carries
|
|
762
|
+
// an already-issued rotated refresh token (e.g. the access JWT lacked the
|
|
763
|
+
// required `inference:invoke` scope) means the server consumed RT-A and
|
|
764
|
+
// issued RT-B. RT-B must be preserved generation-safely BEFORE forcing
|
|
765
|
+
// reauthentication; discarding it would lose the only usable refresh
|
|
766
|
+
// material and force a full re-auth for no reason.
|
|
767
|
+
if (provider === "nous" && error instanceof NousTokenError) {
|
|
768
|
+
const rotated = error.getRotatedRefresh();
|
|
769
|
+
if (rotated !== undefined && rotated !== stored.refresh) {
|
|
770
|
+
const outcome = await preserveNousRotatedRefresh(
|
|
771
|
+
provider,
|
|
772
|
+
accountId,
|
|
773
|
+
rotated,
|
|
774
|
+
generation,
|
|
775
|
+
stored,
|
|
776
|
+
);
|
|
777
|
+
if (outcome.kind === "persisted") {
|
|
778
|
+
// RT-A's intent is cleared only after RT-B is durably persisted;
|
|
779
|
+
// cleanup itself stays best-effort (a stale RT-A intent keys a token
|
|
780
|
+
// that is no longer stored).
|
|
781
|
+
try {
|
|
782
|
+
clearNousRefreshIntent(stored.refresh);
|
|
783
|
+
} catch (cleanupErr) {
|
|
784
|
+
logOAuthEvent("OAuth refresh intent cleanup failed (non-fatal)", {
|
|
785
|
+
provider,
|
|
786
|
+
accountId,
|
|
787
|
+
cause: cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr),
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
// Mark exactly the generation this write produced — never a
|
|
791
|
+
// credential written by a concurrent login between the merge and
|
|
792
|
+
// this step.
|
|
793
|
+
await markAccountNeedsReauthIfGeneration(provider, accountId, outcome.generation, writerGeneration);
|
|
794
|
+
} else {
|
|
795
|
+
// RT-B persistence failed or a newer generation superseded it:
|
|
796
|
+
// never clear RT-A's intent (RT-A was consumed), and mark the old
|
|
797
|
+
// generation needsReauth (a no-op if a newer generation won).
|
|
798
|
+
await markAccountNeedsReauthIfGeneration(provider, accountId, generation, writerGeneration);
|
|
799
|
+
}
|
|
800
|
+
throw new OAuthLoginRequiredError(provider);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
519
803
|
await markAccountNeedsReauthIfGeneration(provider, accountId, generation, writerGeneration);
|
|
520
804
|
throw new OAuthLoginRequiredError(provider);
|
|
521
805
|
}
|
|
@@ -572,31 +856,63 @@ function modelDiscoveryTransportSeed(providerName: string, prov: OcxProviderConf
|
|
|
572
856
|
}
|
|
573
857
|
|
|
574
858
|
/**
|
|
575
|
-
* Provider-correct
|
|
859
|
+
* Provider-correct model-discovery request (URL + headers), so both model-listing paths fetch the
|
|
576
860
|
* LIVE catalog correctly per adapter. Anthropic is the special case: its endpoint is `/v1/models`
|
|
577
861
|
* (not `/models`), it needs `anthropic-version`, and it authenticates with `x-api-key` by default
|
|
578
862
|
* (or `Authorization: Bearer` when `apiKeyTransport = "bearer"`), plus the OAuth beta for oauth
|
|
579
863
|
* mode — not a bare Bearer. Google (ai-studio mode)
|
|
580
864
|
* is the other special case: `x-goog-api-key` + `/v1beta/models`, returning `{ models: [...] }`.
|
|
581
865
|
* The catalog authority gate intentionally degrades that non-OpenAI shape to stale/static data.
|
|
582
|
-
*
|
|
583
|
-
* response.
|
|
866
|
+
* Antigravity uses its CCA `:fetchAvailableModels` RPC; everyone else uses the OpenAI-style
|
|
867
|
+
* `/models` + Bearer with a `{ data: [{ id, owned_by? }] }` response.
|
|
584
868
|
*/
|
|
585
|
-
export
|
|
869
|
+
export interface ModelsRequestObservedAuth {
|
|
870
|
+
readonly oauthApiBaseUrl?: string;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
export function buildModelsRequest(
|
|
874
|
+
prov: OcxProviderConfig,
|
|
875
|
+
apiKey: string | undefined,
|
|
876
|
+
providerName = "",
|
|
877
|
+
observedAuth?: ModelsRequestObservedAuth,
|
|
878
|
+
): { method?: "POST"; url: string; headers: Record<string, string> } {
|
|
586
879
|
const transportSeed = modelDiscoveryTransportSeed(providerName, prov);
|
|
880
|
+
const copilotApiBaseUrl = observedAuth === undefined
|
|
881
|
+
? (providerName === "github-copilot" ? getOAuthCredentialApiBaseUrl(providerName) : undefined)
|
|
882
|
+
: observedAuth.oauthApiBaseUrl;
|
|
587
883
|
const effectiveProvider = resolveProviderTransport(
|
|
588
884
|
providerName,
|
|
589
885
|
transportSeed,
|
|
590
886
|
undefined,
|
|
591
|
-
|
|
887
|
+
copilotApiBaseUrl,
|
|
592
888
|
);
|
|
593
|
-
|
|
889
|
+
// Model discovery is an upstream request like any other, so it carries the same registry
|
|
890
|
+
// static headers the inference path does. Without this a provider is identified correctly
|
|
891
|
+
// when it answers a completion but anonymously when it lists its own models, which is the
|
|
892
|
+
// kind of split fingerprint an upstream rate limiter reads as two different clients.
|
|
893
|
+
const registryStaticHeaders = providerMatchesRegistryTransport(providerName, effectiveProvider)
|
|
894
|
+
? getProviderRegistryEntry(providerName)?.staticHeaders
|
|
895
|
+
: undefined;
|
|
896
|
+
const headers: Record<string, string> = {
|
|
897
|
+
...(mergeRegistryStaticHeaders(registryStaticHeaders, effectiveProvider.headers) ?? {}),
|
|
898
|
+
};
|
|
594
899
|
const discoveryUrl = (defaultUrl: string): string => resolveProviderModelDiscoveryUrl(
|
|
595
900
|
providerName,
|
|
596
901
|
prov,
|
|
597
902
|
effectiveProvider.baseUrl,
|
|
598
903
|
defaultUrl,
|
|
599
904
|
);
|
|
905
|
+
if (effectiveGoogleMode(providerName, effectiveProvider) === "cloud-code-assist") {
|
|
906
|
+
headers.Accept = "application/json";
|
|
907
|
+
headers["Content-Type"] = "application/json";
|
|
908
|
+
headers["User-Agent"] = ANTIGRAVITY_REQUEST_UA;
|
|
909
|
+
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
|
910
|
+
return {
|
|
911
|
+
method: "POST",
|
|
912
|
+
url: discoveryUrl(`${effectiveProvider.baseUrl.replace(/\/+$/, "")}/v1internal:fetchAvailableModels`),
|
|
913
|
+
headers,
|
|
914
|
+
};
|
|
915
|
+
}
|
|
600
916
|
if (effectiveGoogleMode(providerName, effectiveProvider) === "ai-studio") {
|
|
601
917
|
// Generative Language API: API key goes in x-goog-api-key (never Authorization: Bearer),
|
|
602
918
|
// models live under /v1beta (v1 misses preview models), and pageSize maxes at 1000 —
|
|
@@ -630,9 +946,7 @@ export function buildModelsRequest(prov: OcxProviderConfig, apiKey: string | und
|
|
|
630
946
|
*
|
|
631
947
|
* Only touches providers that are registry-managed AND still `authMode: "oauth"`. Preset fields
|
|
632
948
|
* are refreshed, while the registry's `liveModels` default is normally filled only when no value
|
|
633
|
-
* is stored.
|
|
634
|
-
* cannot be distinguished from a hand-written pre-migration `true`. Persists + returns true when
|
|
635
|
-
* anything changed.
|
|
949
|
+
* is stored. Persists + returns true when anything changed.
|
|
636
950
|
*/
|
|
637
951
|
function cloneProviderField(value: unknown): unknown {
|
|
638
952
|
if (Array.isArray(value)) return [...value];
|
|
@@ -659,31 +973,62 @@ const OAUTH_RECONCILE_FIELDS: (keyof OcxProviderConfig)[] = [
|
|
|
659
973
|
"autoToolChoiceOnlyModels",
|
|
660
974
|
"preserveReasoningContentModels",
|
|
661
975
|
];
|
|
976
|
+
// `requiresReasoningPlaceholderModels` is deliberately NOT reconciled here: no
|
|
977
|
+
// OAuth preset seeds it, so the delete-when-preset-undefined branch would wipe
|
|
978
|
+
// an explicit user opt-out (`[]`) on every startup. Registry seeds still reach
|
|
979
|
+
// existing rows through enrichProviderFromRegistry, which is fill-only and
|
|
980
|
+
// preserves explicit saved values.
|
|
662
981
|
|
|
663
982
|
const GOOGLE_ANTIGRAVITY_PROVIDER = "google-antigravity";
|
|
664
|
-
const
|
|
983
|
+
const GOOGLE_ANTIGRAVITY_LIVE_DISCOVERY_VERSION = 2 as const;
|
|
984
|
+
|
|
985
|
+
/** Only migrate the three-model experimental seed; an operator's later `liveModels: false` wins. */
|
|
986
|
+
function isLegacyCommandCodeStaticCatalog(provider: OcxProviderConfig): boolean {
|
|
987
|
+
return provider.liveModels === false
|
|
988
|
+
&& provider.defaultModel === "deepseek-v4-flash"
|
|
989
|
+
&& JSON.stringify(provider.models) === JSON.stringify(["deepseek-v4-flash", "kimi-k3", "glm-5.2"]);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
function isLegacyAntigravityStaticCatalog(provider: OcxProviderConfig): boolean {
|
|
993
|
+
// A fingerprint of the shape version 1 actually shipped, NOT of the current registry.
|
|
994
|
+
// These literals must stay frozen as the model list moves on: matching them is how we
|
|
995
|
+
// know the row is the untouched v1 seed rather than a user's own selection.
|
|
996
|
+
return provider.liveModels === false
|
|
997
|
+
&& provider.adapter === "google"
|
|
998
|
+
&& provider.baseUrl === "https://daily-cloudcode-pa.googleapis.com"
|
|
999
|
+
&& provider.authMode === "oauth"
|
|
1000
|
+
&& provider.googleMode === "cloud-code-assist"
|
|
1001
|
+
&& provider.defaultModel === "gemini-3.6-flash"
|
|
1002
|
+
&& JSON.stringify(provider.models) === JSON.stringify([
|
|
1003
|
+
"gemini-3.6-flash",
|
|
1004
|
+
"gemini-3.1-pro",
|
|
1005
|
+
"gemini-3.1-flash-image",
|
|
1006
|
+
"claude-sonnet-4-6",
|
|
1007
|
+
"claude-opus-4-6-thinking",
|
|
1008
|
+
"gpt-oss-120b-medium",
|
|
1009
|
+
]);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/** Promote only the versioned canonical static seed; unmarked `liveModels: false` remains user intent. */
|
|
1013
|
+
function migrateLegacyAntigravityStaticCatalog(config: OcxConfig): boolean {
|
|
1014
|
+
if (config.googleAntigravityStaticCatalogVersion !== 1) return false;
|
|
1015
|
+
const provider = config.providers[GOOGLE_ANTIGRAVITY_PROVIDER];
|
|
1016
|
+
if (provider && isLegacyAntigravityStaticCatalog(provider)) provider.liveModels = true;
|
|
1017
|
+
config.googleAntigravityStaticCatalogVersion = GOOGLE_ANTIGRAVITY_LIVE_DISCOVERY_VERSION;
|
|
1018
|
+
return true;
|
|
1019
|
+
}
|
|
665
1020
|
|
|
666
1021
|
export function reconcileOAuthProviders(config: OcxConfig): boolean {
|
|
667
|
-
let changed =
|
|
668
|
-
const migrateAntigravityStaticCatalog =
|
|
669
|
-
config.googleAntigravityStaticCatalogVersion !== GOOGLE_ANTIGRAVITY_STATIC_CATALOG_VERSION;
|
|
1022
|
+
let changed = migrateLegacyAntigravityStaticCatalog(config);
|
|
670
1023
|
for (const [name, prov] of Object.entries(config.providers)) {
|
|
671
1024
|
const def = OAUTH_PROVIDERS[name];
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
// rows do not get stamped without actually receiving the new static default.
|
|
677
|
-
if (name === GOOGLE_ANTIGRAVITY_PROVIDER && migrateAntigravityStaticCatalog && prov.liveModels !== false) {
|
|
678
|
-
prov.liveModels = false;
|
|
1025
|
+
if (name === "command-code" && isLegacyCommandCodeStaticCatalog(prov)) {
|
|
1026
|
+
// The former experimental preset was the exact three-model seed above. It was not a user
|
|
1027
|
+
// choice to disable discovery, so promote only that shape to the account live catalog.
|
|
1028
|
+
prov.liveModels = true;
|
|
679
1029
|
changed = true;
|
|
680
1030
|
}
|
|
681
|
-
|
|
682
|
-
// fields when authMode is omitted or non-oauth. Otherwise liveModels flips to static while
|
|
683
|
-
// a stale models[] remains the published catalog forever.
|
|
684
|
-
const migrateAntigravityCatalogFields =
|
|
685
|
-
name === GOOGLE_ANTIGRAVITY_PROVIDER && migrateAntigravityStaticCatalog;
|
|
686
|
-
if (!def || (prov.authMode !== "oauth" && !migrateAntigravityCatalogFields)) continue;
|
|
1031
|
+
if (!def || prov.authMode !== "oauth") continue;
|
|
687
1032
|
const preset = def.providerConfig;
|
|
688
1033
|
for (const field of OAUTH_RECONCILE_FIELDS) {
|
|
689
1034
|
if (JSON.stringify(prov[field]) === JSON.stringify(preset[field])) continue;
|
|
@@ -694,23 +1039,19 @@ export function reconcileOAuthProviders(config: OcxConfig): boolean {
|
|
|
694
1039
|
}
|
|
695
1040
|
changed = true;
|
|
696
1041
|
}
|
|
697
|
-
// Before this marker existed, the GUI materialized an omitted `liveModels` as `true` on any
|
|
698
|
-
// settings save. Since persisted values have no provenance, the pre-guard normalization above
|
|
699
|
-
// intentionally resets all pre-marker `true` values once. Later choices are version-bounded.
|
|
700
1042
|
if (prov.liveModels === undefined && preset.liveModels !== undefined) {
|
|
701
1043
|
prov.liveModels = preset.liveModels;
|
|
702
1044
|
changed = true;
|
|
703
1045
|
}
|
|
704
1046
|
// Heal a defaultModel that no longer exists in the refreshed list (e.g. a deprecated snapshot).
|
|
705
|
-
|
|
1047
|
+
// Skip providers without a static preset `models` list: for live-discovery providers
|
|
1048
|
+
// (e.g. command-code OAuth) the account-scoped catalog is not enumerable here, so any
|
|
1049
|
+
// persisted defaultModel is a user selection and must not be overwritten by the seed.
|
|
1050
|
+
if (prov.defaultModel && preset.defaultModel && preset.models && preset.models.length > 0 && !(prov.models ?? []).includes(prov.defaultModel)) {
|
|
706
1051
|
prov.defaultModel = preset.defaultModel;
|
|
707
1052
|
changed = true;
|
|
708
1053
|
}
|
|
709
1054
|
}
|
|
710
|
-
if (migrateAntigravityStaticCatalog) {
|
|
711
|
-
config.googleAntigravityStaticCatalogVersion = GOOGLE_ANTIGRAVITY_STATIC_CATALOG_VERSION;
|
|
712
|
-
changed = true;
|
|
713
|
-
}
|
|
714
1055
|
if (changed) saveConfig(config);
|
|
715
1056
|
return changed;
|
|
716
1057
|
}
|
|
@@ -767,19 +1108,36 @@ export function upsertOAuthProvider(config: OcxConfig, provider: string): void {
|
|
|
767
1108
|
if (provider === "chatgpt") return;
|
|
768
1109
|
const def = OAUTH_PROVIDERS[provider];
|
|
769
1110
|
if (!def) return;
|
|
1111
|
+
if (provider === GOOGLE_ANTIGRAVITY_PROVIDER) migrateLegacyAntigravityStaticCatalog(config);
|
|
770
1112
|
const namespaceCollision = codexAccountNamespaceProviderCollisionError(config.codexAccountNamespaces, provider);
|
|
771
1113
|
if (namespaceCollision) throw new Error(namespaceCollision);
|
|
772
1114
|
const existing = config.providers[provider];
|
|
773
1115
|
const next: OcxProviderConfig = { ...def.providerConfig };
|
|
774
|
-
// `liveModels` is a user-facing provider toggle.
|
|
775
|
-
//
|
|
776
|
-
|
|
777
|
-
// reset once; users who deliberately forced discovery can re-enable it after migration.
|
|
778
|
-
const preserveExistingLiveModels = provider !== GOOGLE_ANTIGRAVITY_PROVIDER
|
|
779
|
-
|| config.googleAntigravityStaticCatalogVersion === GOOGLE_ANTIGRAVITY_STATIC_CATALOG_VERSION;
|
|
780
|
-
if (preserveExistingLiveModels && typeof existing?.liveModels === "boolean") {
|
|
1116
|
+
// `liveModels` is a user-facing provider toggle. Preserve either explicit setting across login;
|
|
1117
|
+
// Antigravity's CCA discovery now uses its real RPC, so legacy `true` remains a valid choice.
|
|
1118
|
+
if (typeof existing?.liveModels === "boolean" && !isLegacyCommandCodeStaticCatalog(existing)) {
|
|
781
1119
|
next.liveModels = existing.liveModels;
|
|
782
1120
|
}
|
|
1121
|
+
// The Command Code protocol-version pin is an operator compatibility control. A re-login,
|
|
1122
|
+
// add-account, or reauth rebuilds the row from the preset, which has no version; carry the
|
|
1123
|
+
// existing pin so authentication changes do not silently revert the documented control.
|
|
1124
|
+
if (existing?.commandCodeVersion !== undefined) {
|
|
1125
|
+
next.commandCodeVersion = existing.commandCodeVersion;
|
|
1126
|
+
}
|
|
1127
|
+
// User-configured price overlays are operator data, not preset state; a
|
|
1128
|
+
// re-login, add-account, or reauth must not silently drop them from the
|
|
1129
|
+
// Logs/Usage estimates.
|
|
1130
|
+
if (existing?.modelCosts !== undefined) {
|
|
1131
|
+
next.modelCosts = existing.modelCosts;
|
|
1132
|
+
}
|
|
1133
|
+
// The per-provider account-failover opt-out is operator intent about SPENDING, and the login
|
|
1134
|
+
// path is exactly where losing it does damage: adding a second account both rebuilds this row
|
|
1135
|
+
// from the preset and creates the 2-account quorum that turns presence-driven rotation on
|
|
1136
|
+
// (#2568d). Dropping the opt-out here would enable the thing the operator switched off, at the
|
|
1137
|
+
// moment they were doing something unrelated.
|
|
1138
|
+
if (existing?.oauthAccountFailover !== undefined) {
|
|
1139
|
+
next.oauthAccountFailover = existing.oauthAccountFailover;
|
|
1140
|
+
}
|
|
783
1141
|
if (existing && getProviderRegistryEntry(provider)?.allowKeyAuthOverride === true) {
|
|
784
1142
|
// Shared sanitizeApiKeyValue trim / no-CRLF checks from api-key pool writes.
|
|
785
1143
|
let storedApiKey = sanitizeApiKeyValue(existing.apiKey);
|
|
@@ -803,9 +1161,6 @@ export function upsertOAuthProvider(config: OcxConfig, provider: string): void {
|
|
|
803
1161
|
}
|
|
804
1162
|
}
|
|
805
1163
|
config.providers[provider] = next;
|
|
806
|
-
if (provider === GOOGLE_ANTIGRAVITY_PROVIDER) {
|
|
807
|
-
config.googleAntigravityStaticCatalogVersion = GOOGLE_ANTIGRAVITY_STATIC_CATALOG_VERSION;
|
|
808
|
-
}
|
|
809
1164
|
}
|
|
810
1165
|
|
|
811
1166
|
interface RunLoginDeps {
|
|
@@ -816,6 +1171,7 @@ interface RunLoginDeps {
|
|
|
816
1171
|
settleKiroLoginTransaction?: typeof settleKiroLoginTransaction;
|
|
817
1172
|
removeAccount?: typeof removeAccount;
|
|
818
1173
|
setActiveAccount?: typeof setActiveAccount;
|
|
1174
|
+
assertCurrentOwner?: () => void;
|
|
819
1175
|
}
|
|
820
1176
|
|
|
821
1177
|
/** Roll back only accounts created by this forced login, preserving concurrent refreshes of others. */
|
|
@@ -865,6 +1221,7 @@ export async function runLogin(
|
|
|
865
1221
|
const cred: OAuthCredentials = rawCred.source ? rawCred : { ...rawCred, source: "oauth" };
|
|
866
1222
|
const settleKiroTransaction = deps.settleKiroLoginTransaction ?? settleKiroLoginTransaction;
|
|
867
1223
|
try {
|
|
1224
|
+
deps.assertCurrentOwner?.();
|
|
868
1225
|
// Validate the provider row before credential persistence. A namespace claimed during the
|
|
869
1226
|
// credential write is handled again below before the latest row is re-upserted.
|
|
870
1227
|
if (provider !== "chatgpt") {
|
|
@@ -875,7 +1232,7 @@ export async function runLogin(
|
|
|
875
1232
|
const existing = getAccountCredential(provider, opts.reauthAccountId);
|
|
876
1233
|
if (!existing) throw new Error(`Unknown account for reauth: ${opts.reauthAccountId}`);
|
|
877
1234
|
if (!existing.accountId && !existing.email) {
|
|
878
|
-
throw new
|
|
1235
|
+
throw new OAuthReauthIdentityUnverifiedError();
|
|
879
1236
|
}
|
|
880
1237
|
const identityMatches = existing.accountId && cred.accountId
|
|
881
1238
|
? existing.accountId === cred.accountId
|
|
@@ -883,12 +1240,15 @@ export async function runLogin(
|
|
|
883
1240
|
? existing.email.toLowerCase() === cred.email.toLowerCase()
|
|
884
1241
|
: false;
|
|
885
1242
|
if (!identityMatches) {
|
|
886
|
-
throw new
|
|
1243
|
+
throw new OAuthReauthIdentityMismatchError();
|
|
887
1244
|
}
|
|
888
|
-
await (deps.saveAccountCredential ?? saveAccountCredential)(provider, opts.reauthAccountId, cred
|
|
1245
|
+
await (deps.saveAccountCredential ?? saveAccountCredential)(provider, opts.reauthAccountId, cred, {
|
|
1246
|
+
assertBeforePersist: deps.assertCurrentOwner,
|
|
1247
|
+
});
|
|
889
1248
|
} else {
|
|
890
1249
|
await (deps.saveCredential ?? saveCredential)(provider, cred, {
|
|
891
|
-
preserveIdentityless:
|
|
1250
|
+
preserveIdentityless: opts?.forceLogin === true,
|
|
1251
|
+
assertBeforePersist: deps.assertCurrentOwner,
|
|
892
1252
|
});
|
|
893
1253
|
}
|
|
894
1254
|
if (provider !== "chatgpt") {
|
|
@@ -900,10 +1260,7 @@ export async function runLogin(
|
|
|
900
1260
|
provider,
|
|
901
1261
|
);
|
|
902
1262
|
if (lateCollision) {
|
|
903
|
-
throw new
|
|
904
|
-
`${lateCollision}. The credential for "${provider}" was saved, but the provider entry was not written. `
|
|
905
|
-
+ "Rename the account selector, then re-run the login.",
|
|
906
|
-
);
|
|
1263
|
+
throw new OAuthProviderPublicationError();
|
|
907
1264
|
}
|
|
908
1265
|
upsertOAuthProvider(latestConfig, provider);
|
|
909
1266
|
saveLatestConfig(latestConfig);
|
|
@@ -933,11 +1290,15 @@ export async function runLogin(
|
|
|
933
1290
|
settleKiroTransaction(rawCred, true);
|
|
934
1291
|
if (provider !== "chatgpt") {
|
|
935
1292
|
try {
|
|
1293
|
+
const { clearModelCache } = await import("../codex/model-cache");
|
|
1294
|
+
const { clearGatherRoutedModelsInflight } = await import("../codex/catalog");
|
|
1295
|
+
clearModelCache(provider);
|
|
1296
|
+
clearGatherRoutedModelsInflight();
|
|
936
1297
|
const { clearAccountQuotaCache, clearProviderQuotaCache } = await import("../providers/quota");
|
|
937
1298
|
clearProviderQuotaCache();
|
|
938
1299
|
clearAccountQuotaCache(provider);
|
|
939
1300
|
} catch {
|
|
940
|
-
//
|
|
1301
|
+
// Optional state modules may be unavailable in tightly scoped unit tests.
|
|
941
1302
|
}
|
|
942
1303
|
}
|
|
943
1304
|
return cred;
|
|
@@ -954,6 +1315,7 @@ export async function runLogin(
|
|
|
954
1315
|
*/
|
|
955
1316
|
const loginState = new Map<string, { error?: string; done: boolean }>();
|
|
956
1317
|
const loginAbort = new Map<string, AbortController>();
|
|
1318
|
+
const kiroLoginSettling = new Set<string>();
|
|
957
1319
|
|
|
958
1320
|
/** Pending paste for a login in progress: either a waiter or a stashed early submission. */
|
|
959
1321
|
interface ManualCodeSlot {
|
|
@@ -1036,7 +1398,11 @@ export function submitManualLoginCode(provider: string, input: string): { ok: tr
|
|
|
1036
1398
|
// protected. Early posts (flow not yet waiting, no expectedState) are stashed and
|
|
1037
1399
|
// re-validated by the callback loop.
|
|
1038
1400
|
const parsed = parseCallbackInput(trimmed);
|
|
1039
|
-
|
|
1401
|
+
// Command Code's manual fallback accepts a pasted JSON callback payload
|
|
1402
|
+
// (`{ apiKey, state, ... }`) which has no `code` param. Let it through the
|
|
1403
|
+
// shared gate so the provider-specific parser can validate it.
|
|
1404
|
+
const isCommandCodeJson = provider === "command-code" && trimmed.startsWith("{") && !parsed.code;
|
|
1405
|
+
if (!parsed.code && !isCommandCodeJson) return { ok: false, error: "no authorization code found in input" };
|
|
1040
1406
|
if (parsed.kind !== "raw" && slot.expectedState !== undefined) {
|
|
1041
1407
|
if (parsed.state === undefined) return { ok: false, error: "redirect URL is missing the state parameter" };
|
|
1042
1408
|
if (parsed.state !== slot.expectedState) return { ok: false, error: "state mismatch — paste the redirect URL from THIS login attempt" };
|
|
@@ -1058,19 +1424,27 @@ export function getLoginStatus(provider: string): { loggedIn: boolean; email?: s
|
|
|
1058
1424
|
const cred = getCredential(provider);
|
|
1059
1425
|
const st = loginState.get(provider);
|
|
1060
1426
|
const set = getAccountSet(provider);
|
|
1061
|
-
//
|
|
1062
|
-
//
|
|
1427
|
+
// Masked emails on purpose: status DTOs are shared with GUI/CLI surfaces that
|
|
1428
|
+
// must not leak full local-parts. Tokens stay out of this surface entirely.
|
|
1063
1429
|
const accounts: OAuthAccountSummary[] | undefined = set?.accounts.map(a => ({
|
|
1064
1430
|
id: a.id,
|
|
1065
1431
|
...(a.alias ? { alias: a.alias } : {}),
|
|
1066
|
-
email: a.credential.email ?? undefined,
|
|
1432
|
+
email: maskEmail(a.credential.email) ?? undefined,
|
|
1067
1433
|
active: a.id === set.activeAccountId,
|
|
1068
1434
|
...(a.needsReauth ? { needsReauth: true } : {}),
|
|
1069
1435
|
expiresAt: a.credential.expires,
|
|
1070
1436
|
}));
|
|
1437
|
+
|
|
1438
|
+
// A stored credential counts as "logged in" when it exists and is not marked for
|
|
1439
|
+
// re-authentication. An expired access token with a valid refresh token is still
|
|
1440
|
+
// logged in: request resolution refreshes expired/near-expiry credentials lazily.
|
|
1441
|
+
// Invalid/unknown local-import expiries are handled at parse/adoption time
|
|
1442
|
+
// (local-token-detect.ts), never by over-reporting login state here.
|
|
1443
|
+
const activeNeedsReauth = set?.accounts
|
|
1444
|
+
.find(a => a.id === set.activeAccountId)?.needsReauth === true;
|
|
1071
1445
|
return {
|
|
1072
|
-
loggedIn: !!cred,
|
|
1073
|
-
email: cred?.email ?? undefined,
|
|
1446
|
+
loggedIn: !!cred && !activeNeedsReauth,
|
|
1447
|
+
email: maskEmail(cred?.email) ?? undefined,
|
|
1074
1448
|
source: cred?.source,
|
|
1075
1449
|
error: st?.error,
|
|
1076
1450
|
done: st?.done ?? false,
|
|
@@ -1112,13 +1486,14 @@ export async function startLoginFlow(
|
|
|
1112
1486
|
const def = OAUTH_PROVIDERS[provider];
|
|
1113
1487
|
if (!def) throw new UnsupportedOAuthProviderError(provider);
|
|
1114
1488
|
const existing = loginState.get(provider);
|
|
1115
|
-
if (existing && !existing.done) {
|
|
1489
|
+
if ((existing && !existing.done) || (provider === "kiro" && kiroLoginSettling.has(provider))) {
|
|
1116
1490
|
throw new Error(`A login for ${provider} is already in progress`);
|
|
1117
1491
|
}
|
|
1118
1492
|
clearManualCodeSlot(provider);
|
|
1119
1493
|
loginState.set(provider, { done: false });
|
|
1120
1494
|
const abort = new AbortController();
|
|
1121
1495
|
loginAbort.set(provider, abort);
|
|
1496
|
+
if (provider === "kiro") kiroLoginSettling.add(provider);
|
|
1122
1497
|
return new Promise((resolve, reject) => {
|
|
1123
1498
|
let urlResolved = false;
|
|
1124
1499
|
const ctrl: OAuthController = {
|
|
@@ -1131,7 +1506,16 @@ export async function startLoginFlow(
|
|
|
1131
1506
|
onManualCodeInput: (expectedState?: string) => waitForManualLoginCode(provider, abort.signal, expectedState),
|
|
1132
1507
|
signal: abort.signal,
|
|
1133
1508
|
};
|
|
1509
|
+
const abandonIfNotOwner = (error?: unknown): boolean => {
|
|
1510
|
+
if (loginAbort.get(provider) === abort) return false;
|
|
1511
|
+
if (!urlResolved) reject(error ?? new Error("OAuth login was superseded"));
|
|
1512
|
+
return true;
|
|
1513
|
+
};
|
|
1134
1514
|
const settle = async (error?: unknown): Promise<void> => {
|
|
1515
|
+
// Cancellation deletes this controller and records its own terminal result. A late provider
|
|
1516
|
+
// rejection (or an older flow settling after a replacement starts) must not overwrite that
|
|
1517
|
+
// state or delete the replacement flow's controller/manual-code slot.
|
|
1518
|
+
if (abandonIfNotOwner(error)) return;
|
|
1135
1519
|
let finalError = error;
|
|
1136
1520
|
try {
|
|
1137
1521
|
await lifecycle?.onSettled?.();
|
|
@@ -1140,6 +1524,7 @@ export async function startLoginFlow(
|
|
|
1140
1524
|
// runtime config. For an already-failed login, keep the original recovery error.
|
|
1141
1525
|
if (finalError === undefined) finalError = settleError;
|
|
1142
1526
|
}
|
|
1527
|
+
if (abandonIfNotOwner(finalError)) return;
|
|
1143
1528
|
if (finalError === undefined) {
|
|
1144
1529
|
loginAbort.delete(provider);
|
|
1145
1530
|
clearManualCodeSlot(provider);
|
|
@@ -1153,22 +1538,28 @@ export async function startLoginFlow(
|
|
|
1153
1538
|
const e = finalError;
|
|
1154
1539
|
loginAbort.delete(provider);
|
|
1155
1540
|
clearManualCodeSlot(provider);
|
|
1156
|
-
const msg =
|
|
1541
|
+
const msg = publicOAuthAuthenticationErrorMessage(e);
|
|
1157
1542
|
loginState.set(provider, { done: true, error: msg });
|
|
1158
1543
|
if (!urlResolved) reject(e);
|
|
1159
1544
|
};
|
|
1160
1545
|
// Background: runLogin persists the credential + provider entry to disk. The lifecycle hook
|
|
1161
1546
|
// lets a long-lived server config adopt that settled state before clients observe done=true.
|
|
1162
|
-
|
|
1547
|
+
const assertCurrentOwner = (): void => {
|
|
1548
|
+
if (loginAbort.get(provider) !== abort) throw new OAuthLoginSupersededError();
|
|
1549
|
+
};
|
|
1550
|
+
void runLogin(provider, ctrl, opts, { assertCurrentOwner }).then(
|
|
1163
1551
|
() => settle(),
|
|
1164
1552
|
(e: unknown) => settle(e),
|
|
1165
1553
|
).catch((e: unknown) => {
|
|
1166
1554
|
// settle catches lifecycle failures, so this is only a defensive promise-boundary guard.
|
|
1555
|
+
if (abandonIfNotOwner(e)) return;
|
|
1167
1556
|
loginAbort.delete(provider);
|
|
1168
1557
|
clearManualCodeSlot(provider);
|
|
1169
|
-
const msg =
|
|
1558
|
+
const msg = publicOAuthAuthenticationErrorMessage(e);
|
|
1170
1559
|
loginState.set(provider, { done: true, error: msg });
|
|
1171
1560
|
if (!urlResolved) reject(e);
|
|
1561
|
+
}).finally(() => {
|
|
1562
|
+
if (provider === "kiro") kiroLoginSettling.delete(provider);
|
|
1172
1563
|
});
|
|
1173
1564
|
});
|
|
1174
1565
|
}
|