@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
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import type { OAuthController, OAuthCredentials } from "./types";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { isAddrInUse } from "../server/ports";
|
|
5
|
+
import { parseCallbackInput } from "./callback-server";
|
|
6
|
+
|
|
7
|
+
const COMMAND_CODE_STUDIO_URL = "https://commandcode.ai";
|
|
8
|
+
const COMMAND_CODE_CALLBACK_PORT = 5959;
|
|
9
|
+
const LOGIN_TIMEOUT_MS = 120_000;
|
|
10
|
+
const CALLBACK_PATH = "/callback";
|
|
11
|
+
|
|
12
|
+
interface CommandCodeCallback {
|
|
13
|
+
apiKey: string;
|
|
14
|
+
state: string;
|
|
15
|
+
userId: string;
|
|
16
|
+
userName: string;
|
|
17
|
+
keyName: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface CommandCodeLocalAuth {
|
|
21
|
+
apiKey?: unknown;
|
|
22
|
+
userId?: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface CommandCodeLoginOptions {
|
|
26
|
+
/** Add-account and reauthentication flows must select a fresh browser identity. */
|
|
27
|
+
importLocal?: "fallback" | "off";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function shouldImportLocalCommandCodeAuth(options: CommandCodeLoginOptions = {}): boolean {
|
|
31
|
+
return options.importLocal !== "off";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function importLocalCommandCodeAuth(signal?: AbortSignal): Promise<OAuthCredentials | undefined> {
|
|
35
|
+
if (signal?.aborted) {
|
|
36
|
+
throw signal.reason ?? new DOMException("Command Code login aborted", "AbortError");
|
|
37
|
+
}
|
|
38
|
+
let parsed: CommandCodeLocalAuth;
|
|
39
|
+
try {
|
|
40
|
+
parsed = JSON.parse(await Bun.file(join(homedir(), ".commandcode", "auth.json")).text()) as CommandCodeLocalAuth;
|
|
41
|
+
} catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
if (typeof parsed.apiKey !== "string" || parsed.apiKey.length === 0) return undefined;
|
|
45
|
+
let accountId: string | undefined;
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetch("https://api.commandcode.ai/alpha/whoami", {
|
|
48
|
+
headers: { Authorization: `Bearer ${parsed.apiKey}`, Accept: "application/json" },
|
|
49
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(10_000)]) : AbortSignal.timeout(10_000),
|
|
50
|
+
});
|
|
51
|
+
if (!response.ok) return undefined;
|
|
52
|
+
// Carry the validated whoami identity so an imported credential keeps multi-account
|
|
53
|
+
// semantics even when the local auth.json omits userId.
|
|
54
|
+
const body = (await response.json()) as { user?: { id?: unknown } };
|
|
55
|
+
if (typeof body.user?.id === "string" && body.user.id.length > 0) accountId = body.user.id;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (signal?.aborted) throw signal.reason ?? new DOMException("Command Code login aborted", "AbortError");
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
if (!accountId && typeof parsed.userId === "string" && parsed.userId.length > 0) accountId = parsed.userId;
|
|
61
|
+
return {
|
|
62
|
+
access: parsed.apiKey,
|
|
63
|
+
refresh: parsed.apiKey,
|
|
64
|
+
expires: Number.MAX_SAFE_INTEGER,
|
|
65
|
+
...(accountId ? { accountId } : {}),
|
|
66
|
+
source: "local-cli",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function randomState(): string {
|
|
71
|
+
const bytes = new Uint8Array(32);
|
|
72
|
+
crypto.getRandomValues(bytes);
|
|
73
|
+
return Buffer.from(bytes).toString("base64url");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function parseCommandCodeCallback(value: unknown, expectedState: string): CommandCodeCallback {
|
|
77
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
78
|
+
throw new Error("Command Code callback must be an object");
|
|
79
|
+
}
|
|
80
|
+
const body = value as Record<string, unknown>;
|
|
81
|
+
if (body.state !== expectedState) throw new Error("Command Code OAuth state mismatch");
|
|
82
|
+
for (const field of ["apiKey", "userId", "userName", "keyName"] as const) {
|
|
83
|
+
if (typeof body[field] !== "string" || body[field].length === 0) {
|
|
84
|
+
throw new Error(`Command Code callback missing ${field}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return body as unknown as CommandCodeCallback;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function createCallbackServer(state: string): {
|
|
91
|
+
servers: Array<ReturnType<typeof Bun.serve>>;
|
|
92
|
+
callback: Promise<CommandCodeCallback>;
|
|
93
|
+
} {
|
|
94
|
+
let resolve!: (value: CommandCodeCallback) => void;
|
|
95
|
+
const callback = new Promise<CommandCodeCallback>((res) => { resolve = res; });
|
|
96
|
+
const fetch = async (request: Request): Promise<Response> => {
|
|
97
|
+
const url = new URL(request.url);
|
|
98
|
+
const origin = request.headers.get("origin");
|
|
99
|
+
const headers = new Headers({
|
|
100
|
+
"Content-Type": "application/json",
|
|
101
|
+
"Access-Control-Allow-Origin": origin === COMMAND_CODE_STUDIO_URL ? origin : COMMAND_CODE_STUDIO_URL,
|
|
102
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
103
|
+
"Access-Control-Allow-Headers": "Content-Type",
|
|
104
|
+
});
|
|
105
|
+
if (request.method === "OPTIONS") return new Response(null, { status: 204, headers });
|
|
106
|
+
if (url.pathname !== CALLBACK_PATH) return Response.json({ success: false, error: "Not found" }, { status: 404, headers });
|
|
107
|
+
if (request.method !== "POST") return Response.json({ success: false, error: "Method not allowed" }, { status: 405, headers });
|
|
108
|
+
try {
|
|
109
|
+
const body = await request.json();
|
|
110
|
+
const parsed = parseCommandCodeCallback(body, state);
|
|
111
|
+
queueMicrotask(() => resolve(parsed));
|
|
112
|
+
return Response.json({ success: true }, { headers });
|
|
113
|
+
} catch (error) {
|
|
114
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
115
|
+
return Response.json({ success: false, error: message }, { status: 400, headers });
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const create = (port: number): Array<ReturnType<typeof Bun.serve>> => {
|
|
119
|
+
// The advertised callback host is `127.0.0.1`; on Windows `localhost` commonly resolves to `::1`
|
|
120
|
+
// first, so also bind the IPv6 loopback best-effort (mirrors the shared OAuthCallbackFlow).
|
|
121
|
+
const servers = [Bun.serve({ hostname: "127.0.0.1", port, fetch })];
|
|
122
|
+
try {
|
|
123
|
+
servers.push(Bun.serve({ hostname: "::1", port: servers[0].port, fetch }));
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (isAddrInUse(error)) {
|
|
126
|
+
for (const server of servers) server.stop(true);
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
// IPv6 unsupported (EAFNOSUPPORT etc.) degrades to the IPv4-only listener.
|
|
130
|
+
}
|
|
131
|
+
return servers;
|
|
132
|
+
};
|
|
133
|
+
try {
|
|
134
|
+
return { servers: create(COMMAND_CODE_CALLBACK_PORT), callback };
|
|
135
|
+
} catch {
|
|
136
|
+
return { servers: create(0), callback };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Validate a raw pasted Command Code API key and return the validated identity. */
|
|
141
|
+
async function validatePastedApiKey(apiKey: string): Promise<{ userId: string; userName: string } | undefined> {
|
|
142
|
+
try {
|
|
143
|
+
const response = await fetch("https://api.commandcode.ai/alpha/whoami", {
|
|
144
|
+
headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
145
|
+
signal: AbortSignal.timeout(10_000),
|
|
146
|
+
});
|
|
147
|
+
if (!response.ok) return undefined;
|
|
148
|
+
const body = (await response.json()) as { user?: { id?: unknown; userName?: unknown } };
|
|
149
|
+
const userId = body.user?.id;
|
|
150
|
+
const userName = body.user?.userName;
|
|
151
|
+
if (typeof userId !== "string" || typeof userName !== "string") return undefined;
|
|
152
|
+
if (!userId.trim() || !userName.trim()) return undefined;
|
|
153
|
+
return { userId, userName };
|
|
154
|
+
} catch {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Manual-paste fallback: a raw API key, or a pasted callback JSON/URL that carries `apiKey`. */
|
|
160
|
+
function parsePastedCommandCodeInput(input: string, expectedState: string): CommandCodeCallback | undefined {
|
|
161
|
+
const trimmed = input.trim();
|
|
162
|
+
if (!trimmed) return undefined;
|
|
163
|
+
if (trimmed.startsWith("{")) {
|
|
164
|
+
try {
|
|
165
|
+
return parseCommandCodeCallback(JSON.parse(trimmed) as unknown, expectedState);
|
|
166
|
+
} catch {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const parsed = parseCallbackInput(trimmed);
|
|
171
|
+
const apiKey = parsed.code?.trim();
|
|
172
|
+
if (!apiKey) return undefined;
|
|
173
|
+
// A URL/query-shaped paste is an authorization response and must carry a matching state,
|
|
174
|
+
// mirroring the shared OAuth callback flow; a stale or attacker-supplied URL from another
|
|
175
|
+
// session must not be accepted. Raw in-session keys are exempt (no state to compare).
|
|
176
|
+
if (parsed.kind !== "raw" && parsed.state !== expectedState) return undefined;
|
|
177
|
+
return { apiKey, state: expectedState, userId: "", userName: "", keyName: "manual" };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function loginCommandCode(ctrl: OAuthController, options: CommandCodeLoginOptions = {}): Promise<OAuthCredentials> {
|
|
181
|
+
if (ctrl.signal?.aborted) {
|
|
182
|
+
throw ctrl.signal.reason ?? new DOMException("Command Code login aborted", "AbortError");
|
|
183
|
+
}
|
|
184
|
+
if (shouldImportLocalCommandCodeAuth(options)) {
|
|
185
|
+
const local = await importLocalCommandCodeAuth(ctrl.signal);
|
|
186
|
+
if (local) {
|
|
187
|
+
ctrl.onProgress?.("Imported existing Command Code CLI authentication.");
|
|
188
|
+
return local;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const state = randomState();
|
|
192
|
+
let servers: Array<ReturnType<typeof Bun.serve>> = [];
|
|
193
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
194
|
+
try {
|
|
195
|
+
const created = createCallbackServer(state);
|
|
196
|
+
servers = created.servers;
|
|
197
|
+
const callbackUrl = `http://127.0.0.1:${servers[0].port}${CALLBACK_PATH}`;
|
|
198
|
+
const authUrl = `${COMMAND_CODE_STUDIO_URL}/studio/auth/cli?callback=${encodeURIComponent(callbackUrl)}&state=${encodeURIComponent(state)}`;
|
|
199
|
+
ctrl.onAuth?.({ url: authUrl, instructions: "Sign in with Command Code in the browser." });
|
|
200
|
+
ctrl.onProgress?.("Waiting for Command Code authentication...");
|
|
201
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
202
|
+
timeoutId = setTimeout(() => reject(new Error("Command Code OAuth callback timed out")), LOGIN_TIMEOUT_MS);
|
|
203
|
+
ctrl.signal?.addEventListener("abort", () => { if (timeoutId) clearTimeout(timeoutId); reject(ctrl.signal?.reason); }, { once: true });
|
|
204
|
+
});
|
|
205
|
+
const manual = ctrl.onManualCodeInput
|
|
206
|
+
? (async (): Promise<CommandCodeCallback> => {
|
|
207
|
+
while (true) {
|
|
208
|
+
// The loop keeps waiting until a valid paste arrives; invalid pastes re-prompt.
|
|
209
|
+
// Yield between iterations so an abort signal can interrupt a fast re-prompt loop.
|
|
210
|
+
if (ctrl.signal?.aborted) throw ctrl.signal.reason ?? new DOMException("Command Code login aborted", "AbortError");
|
|
211
|
+
const input = await ctrl.onManualCodeInput?.(state);
|
|
212
|
+
if (input === undefined) continue;
|
|
213
|
+
const pasted = parsePastedCommandCodeInput(input, state);
|
|
214
|
+
if (!pasted) continue;
|
|
215
|
+
const identity = await validatePastedApiKey(pasted.apiKey);
|
|
216
|
+
if (identity) return { ...pasted, ...identity };
|
|
217
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
218
|
+
}
|
|
219
|
+
})()
|
|
220
|
+
: undefined;
|
|
221
|
+
const result = await Promise.race([created.callback, timeout, ...(manual ? [manual] : [])]);
|
|
222
|
+
if (result === undefined) throw new Error("Command Code OAuth callback cancelled");
|
|
223
|
+
return {
|
|
224
|
+
access: result.apiKey,
|
|
225
|
+
refresh: result.apiKey,
|
|
226
|
+
expires: Number.MAX_SAFE_INTEGER,
|
|
227
|
+
accountId: result.userId,
|
|
228
|
+
source: "oauth",
|
|
229
|
+
};
|
|
230
|
+
} finally {
|
|
231
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
232
|
+
for (const server of servers) server.stop(true);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export async function refreshCommandCodeToken(apiKey: string): Promise<OAuthCredentials> {
|
|
237
|
+
if (!apiKey) throw new Error("Command Code API key missing; run ocx login command-code");
|
|
238
|
+
return { access: apiKey, refresh: apiKey, expires: Number.MAX_SAFE_INTEGER, source: "oauth" };
|
|
239
|
+
}
|
package/src/oauth/cursor.ts
CHANGED
|
@@ -52,8 +52,8 @@ function decodeCursorJwtPayload(token: string): CursorJwtPayload | undefined {
|
|
|
52
52
|
/** Build OAuthCredentials from Cursor tokens, extracting stable identity from JWT `sub` for multiauth. */
|
|
53
53
|
export function credentialsFromCursorTokens(accessToken: string, refreshToken: string): OAuthCredentials {
|
|
54
54
|
const payload = decodeCursorJwtPayload(accessToken) ?? decodeCursorJwtPayload(refreshToken);
|
|
55
|
-
const accountId =
|
|
56
|
-
const email =
|
|
55
|
+
const accountId = cursorJwtIdentity(payload?.sub);
|
|
56
|
+
const email = cursorJwtEmail(payload?.email);
|
|
57
57
|
return {
|
|
58
58
|
access: accessToken,
|
|
59
59
|
refresh: refreshToken,
|
|
@@ -63,10 +63,24 @@ export function credentialsFromCursorTokens(accessToken: string, refreshToken: s
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/** Coerce JWT `sub` (string or safe integer) into a stable multiauth account id. */
|
|
67
|
+
function cursorJwtIdentity(value: unknown): string | undefined {
|
|
68
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
69
|
+
if (typeof value === "number" && Number.isSafeInteger(value)) return String(value);
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function cursorJwtEmail(value: unknown): string | undefined {
|
|
74
|
+
if (typeof value !== "string" || value.length === 0) return undefined;
|
|
75
|
+
return value.toLowerCase();
|
|
76
|
+
}
|
|
77
|
+
|
|
66
78
|
/** Generate PKCE params + the cursor.com deep-link login URL (challenge only — never the verifier). */
|
|
67
|
-
export async function generateCursorAuthParams(): Promise<CursorAuthParams> {
|
|
79
|
+
export async function generateCursorAuthParams(_opts?: { forceLogin?: boolean }): Promise<CursorAuthParams> {
|
|
68
80
|
const { verifier, challenge } = await generatePKCE();
|
|
69
81
|
const uuid = crypto.randomUUID();
|
|
82
|
+
// Cursor's deep-control page has no documented account-picker query; only the stable
|
|
83
|
+
// PKCE params are sent. forceLogin is honored only in loginCursor instructions.
|
|
70
84
|
const params = new URLSearchParams({ challenge, uuid, mode: "login", redirectTarget: "cli" });
|
|
71
85
|
return { verifier, challenge, uuid, loginUrl: `${CURSOR_LOGIN_URL}?${params.toString()}` };
|
|
72
86
|
}
|
|
@@ -87,6 +101,19 @@ function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
|
87
101
|
});
|
|
88
102
|
}
|
|
89
103
|
|
|
104
|
+
/** Terminal poll statuses (T07, senpi PR #905): the login is denied/expired — retrying cannot succeed. */
|
|
105
|
+
const POLL_TERMINAL_STATUSES = new Set([400, 401, 403, 410]);
|
|
106
|
+
|
|
107
|
+
export class CursorAuthTerminalError extends Error {
|
|
108
|
+
readonly status: number;
|
|
109
|
+
|
|
110
|
+
constructor(status: number) {
|
|
111
|
+
super(`Cursor login rejected by the auth server (HTTP ${status}); start a new login`);
|
|
112
|
+
this.name = "CursorAuthTerminalError";
|
|
113
|
+
this.status = status;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
90
117
|
/**
|
|
91
118
|
* Poll cursor.com for login completion. 404 = still pending (back off), 200 = tokens.
|
|
92
119
|
* `baseDelayMs` is injectable so tests can avoid the real 1s cadence; production uses the default.
|
|
@@ -121,9 +148,17 @@ export async function pollCursorAuth(
|
|
|
121
148
|
return { accessToken: data.accessToken, refreshToken: data.refreshToken };
|
|
122
149
|
}
|
|
123
150
|
|
|
151
|
+
// T07: a terminal auth status means the login attempt itself is dead (denied,
|
|
152
|
+
// expired, revoked). Fail on the FIRST such response instead of burning the
|
|
153
|
+
// 3-strike retry budget and masking the reason behind a generic error.
|
|
154
|
+
if (POLL_TERMINAL_STATUSES.has(response.status)) {
|
|
155
|
+
throw new CursorAuthTerminalError(response.status);
|
|
156
|
+
}
|
|
157
|
+
|
|
124
158
|
throw new Error(`Cursor auth poll failed: ${response.status}`);
|
|
125
159
|
} catch (err) {
|
|
126
160
|
if (signal?.aborted) throw err instanceof Error ? err : new Error("Cursor login cancelled");
|
|
161
|
+
if (err instanceof CursorAuthTerminalError) throw err;
|
|
127
162
|
consecutiveErrors++;
|
|
128
163
|
if (consecutiveErrors >= 3) {
|
|
129
164
|
throw new Error("Too many consecutive errors during Cursor auth polling");
|
|
@@ -139,9 +174,15 @@ export async function pollCursorAuth(
|
|
|
139
174
|
export async function loginCursor(
|
|
140
175
|
ctrl: OAuthController,
|
|
141
176
|
pollBaseDelayMs: number = POLL_BASE_DELAY_MS,
|
|
177
|
+
opts?: { forceLogin?: boolean },
|
|
142
178
|
): Promise<OAuthCredentials> {
|
|
143
|
-
const { verifier, uuid, loginUrl } = await generateCursorAuthParams();
|
|
144
|
-
ctrl.onAuth?.({
|
|
179
|
+
const { verifier, uuid, loginUrl } = await generateCursorAuthParams({ forceLogin: opts?.forceLogin === true });
|
|
180
|
+
ctrl.onAuth?.({
|
|
181
|
+
url: loginUrl,
|
|
182
|
+
instructions: opts?.forceLogin
|
|
183
|
+
? "Log out of Cursor in the browser (or use a private window), sign in as the account you want to add, then approve and return here."
|
|
184
|
+
: "Approve the Cursor login in your browser, then return here.",
|
|
185
|
+
});
|
|
145
186
|
ctrl.onProgress?.("Waiting for Cursor login approval…");
|
|
146
187
|
const { accessToken, refreshToken } = await pollCursorAuth(uuid, verifier, ctrl.signal, pollBaseDelayMs);
|
|
147
188
|
return credentialsFromCursorTokens(accessToken, refreshToken);
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic OAuth multi-account 429 failover (#2568).
|
|
3
|
+
*
|
|
4
|
+
* The API-key twin (`providers/key-failover.ts`) rotates by default for any key provider with a
|
|
5
|
+
* 2+ pool, but it returns false for `authMode === "oauth"`, and the only OAuth rotator that
|
|
6
|
+
* exists is Anthropic's — behind its own opt-in. So xAI, Cursor, Kimi, GitHub Copilot,
|
|
7
|
+
* Antigravity and Nous have no recovery path on a 429 even with several accounts logged in.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately narrower than the Anthropic pool: no session affinity, no quota-ranked selection,
|
|
10
|
+
* no probe leases. Those carry provider-specific meaning; this module only answers "the account
|
|
11
|
+
* that just 429'd is cooled, is there another one we may use".
|
|
12
|
+
*
|
|
13
|
+
* NOT a home for Codex (`codex/routing.ts` owns quota scopes and probe leases) or Anthropic
|
|
14
|
+
* (`oauth/anthropic-routing.ts` owns affinity and a fail-closed local-cli credential rule).
|
|
15
|
+
* Both are excluded by `isGenericFailoverProvider`.
|
|
16
|
+
*/
|
|
17
|
+
import { getAccountSet } from "./store";
|
|
18
|
+
import { getValidAccessSnapshotForAccount, type OAuthAccessSnapshot } from "./index";
|
|
19
|
+
import { parseRetryAfterMs } from "../combos/failover";
|
|
20
|
+
import { sweepExpiredOnWrite } from "../lib/state-store-sweeper";
|
|
21
|
+
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
22
|
+
|
|
23
|
+
/** Cap same-request rotations so a short Retry-After cannot spin. Mirrors the Anthropic bound. */
|
|
24
|
+
export const GENERIC_OAUTH_MAX_FAILOVERS_PER_REQUEST = 3;
|
|
25
|
+
|
|
26
|
+
const DEFAULT_COOLDOWN_MS = 60_000;
|
|
27
|
+
const MAX_COOLDOWN_MS = 15 * 60_000;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* How long a presence answer may be reused before the store is consulted again.
|
|
31
|
+
*
|
|
32
|
+
* `loadAuthStore` has no cache: every call chmods the config dir and the secret, reads the whole
|
|
33
|
+
* file, parses it and normalizes the store (store.ts:136-151). Since presence now decides
|
|
34
|
+
* activation, this predicate runs on paths that have not seen a 429 at all — the streaming and
|
|
35
|
+
* non-streaming runTurn entry points evaluate it once per request — so an uncached check would put
|
|
36
|
+
* a synchronous file read in front of every request for every OAuth provider.
|
|
37
|
+
*
|
|
38
|
+
* Two seconds is short enough that a login in another window is picked up before the operator can
|
|
39
|
+
* switch back and send a prompt, and long enough that a burst of requests shares one read. The
|
|
40
|
+
* cache holds a COUNT, never a credential.
|
|
41
|
+
*/
|
|
42
|
+
const PRESENCE_CACHE_TTL_MS = 2_000;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Providers whose rotation is owned elsewhere and must not be handled here.
|
|
46
|
+
*
|
|
47
|
+
* `openai` is the Codex pool: quota scopes, probe leases and affinity semantics that this
|
|
48
|
+
* module deliberately does not reimplement. `anthropic` has its own pool with a fail-closed
|
|
49
|
+
* rule about background local-cli credential slots.
|
|
50
|
+
*/
|
|
51
|
+
const EXCLUDED_PROVIDERS = new Set(["openai", "anthropic"]);
|
|
52
|
+
|
|
53
|
+
interface AccountHealth {
|
|
54
|
+
cooldownUntil: number;
|
|
55
|
+
cooldownSource: "retry-after" | "default";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface PresenceEntry {
|
|
59
|
+
eligible: number;
|
|
60
|
+
readAt: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Process-local, like the Anthropic pool's: a restart is allowed to forget a cooldown. */
|
|
64
|
+
const health = new Map<string, AccountHealth>();
|
|
65
|
+
|
|
66
|
+
/** Provider -> recent eligible-account count. TTL-bounded; never holds credential material. */
|
|
67
|
+
const presence = new Map<string, PresenceEntry>();
|
|
68
|
+
|
|
69
|
+
const healthKey = (provider: string, accountId: string) => `${provider}\u0000${accountId}`;
|
|
70
|
+
|
|
71
|
+
function isCooled(provider: string, accountId: string, now: number): boolean {
|
|
72
|
+
const entry = health.get(healthKey(provider, accountId));
|
|
73
|
+
if (!entry) return false;
|
|
74
|
+
if (entry.cooldownUntil <= now) {
|
|
75
|
+
health.delete(healthKey(provider, accountId));
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** True when this provider participates in generic rotation at all. */
|
|
82
|
+
export function isGenericFailoverProvider(providerName: string, provider: OcxProviderConfig): boolean {
|
|
83
|
+
return provider.authMode === "oauth" && !EXCLUDED_PROVIDERS.has(providerName);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Stored accounts that could serve traffic if asked, ignoring cooldowns.
|
|
88
|
+
*
|
|
89
|
+
* Cooldowns are excluded on purpose: they are transient and per-request, while this answers the
|
|
90
|
+
* durable question "did the operator log in more than one account". Treating a cooled account as
|
|
91
|
+
* absent would switch the feature off for the rest of the cooldown, which is exactly when it is
|
|
92
|
+
* needed.
|
|
93
|
+
*/
|
|
94
|
+
function eligibleAccountCount(providerName: string, now: number): number {
|
|
95
|
+
const cached = presence.get(providerName);
|
|
96
|
+
if (cached && now >= cached.readAt && now - cached.readAt < PRESENCE_CACHE_TTL_MS) return cached.eligible;
|
|
97
|
+
const set = getAccountSet(providerName);
|
|
98
|
+
const eligible = set ? set.accounts.filter(account => account.needsReauth !== true).length : 0;
|
|
99
|
+
presence.set(providerName, { eligible, readAt: now });
|
|
100
|
+
return eligible;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Presence IS consent (#2568d).
|
|
105
|
+
*
|
|
106
|
+
* `hasKeyPoolFailover` already reads a 2+ key pool as the operator asking for rotation, and a
|
|
107
|
+
* second OAuth login is the same statement. One account stays a strict no-op either way, so this
|
|
108
|
+
* only changes behaviour for someone who deliberately logged in twice.
|
|
109
|
+
*/
|
|
110
|
+
export function hasFailoverAccountQuorum(providerName: string, now = Date.now()): boolean {
|
|
111
|
+
return eligibleAccountCount(providerName, now) >= 2;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Whether generic rotation is active for this provider.
|
|
116
|
+
*
|
|
117
|
+
* Precedence, most specific first:
|
|
118
|
+
*
|
|
119
|
+
* 1. `providers.<name>.oauthAccountFailover.enabled` — an operator may accept rotation on one
|
|
120
|
+
* provider and refuse it on another, because provider terms differ.
|
|
121
|
+
* 2. `oauthAccountFailover.enabled` — the global switch. Anyone who already wrote `false` keeps
|
|
122
|
+
* strict single-account behaviour across this change.
|
|
123
|
+
* 3. Presence: 2 or more eligible stored accounts (#2568d, owner decision).
|
|
124
|
+
*
|
|
125
|
+
* Only an explicit boolean overrides presence. A malformed value falls through instead of
|
|
126
|
+
* throwing, because a typo in a knob must not take a provider out of service.
|
|
127
|
+
*/
|
|
128
|
+
export function isGenericOAuthFailoverEnabled(
|
|
129
|
+
config: OcxConfig,
|
|
130
|
+
providerName: string,
|
|
131
|
+
now = Date.now(),
|
|
132
|
+
): boolean {
|
|
133
|
+
const provider = config.providers?.[providerName];
|
|
134
|
+
if (!provider || !isGenericFailoverProvider(providerName, provider)) return false;
|
|
135
|
+
const perProvider = provider.oauthAccountFailover?.enabled;
|
|
136
|
+
if (typeof perProvider === "boolean") return perProvider;
|
|
137
|
+
const global = config.oauthAccountFailover?.enabled;
|
|
138
|
+
if (typeof global === "boolean") return global;
|
|
139
|
+
return hasFailoverAccountQuorum(providerName, now);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Accounts that may serve traffic right now: not cooled, not flagged for reauth. */
|
|
143
|
+
export function eligibleFailoverAccounts(providerName: string, now = Date.now()): string[] {
|
|
144
|
+
const set = getAccountSet(providerName);
|
|
145
|
+
if (!set) return [];
|
|
146
|
+
return set.accounts
|
|
147
|
+
.filter(account => account.needsReauth !== true && !isCooled(providerName, account.id, now))
|
|
148
|
+
.map(account => account.id);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Cool the account that actually 429'd and name the next eligible one, or null.
|
|
153
|
+
*
|
|
154
|
+
* Returns the id only; the caller mints the credential so a failed refresh does not leave the
|
|
155
|
+
* cooldown applied to an account we then could not use.
|
|
156
|
+
*/
|
|
157
|
+
export function rotateGenericOAuthAccountOn429(
|
|
158
|
+
config: OcxConfig,
|
|
159
|
+
providerName: string,
|
|
160
|
+
failedAccountId: string,
|
|
161
|
+
retryAfterHeader: string | null | undefined,
|
|
162
|
+
now = Date.now(),
|
|
163
|
+
): string | null {
|
|
164
|
+
if (!isGenericOAuthFailoverEnabled(config, providerName)) return null;
|
|
165
|
+
const set = getAccountSet(providerName);
|
|
166
|
+
// A single stored account has nowhere to go; rotating to itself would just replay the 429.
|
|
167
|
+
if (!set || set.accounts.length < 2) return null;
|
|
168
|
+
|
|
169
|
+
const parsed = parseRetryAfterMs(retryAfterHeader, now);
|
|
170
|
+
const cooldownMs = Math.min(parsed ?? DEFAULT_COOLDOWN_MS, MAX_COOLDOWN_MS);
|
|
171
|
+
health.set(healthKey(providerName, failedAccountId), {
|
|
172
|
+
cooldownUntil: now + cooldownMs,
|
|
173
|
+
cooldownSource: parsed ? "retry-after" : "default",
|
|
174
|
+
});
|
|
175
|
+
sweepExpiredOnWrite(now);
|
|
176
|
+
|
|
177
|
+
const eligible = eligibleFailoverAccounts(providerName, now).filter(id => id !== failedAccountId);
|
|
178
|
+
if (eligible.length === 0) return null;
|
|
179
|
+
// A rotation means the roster in use just changed; do not answer the next activation question
|
|
180
|
+
// from a count read before the failure.
|
|
181
|
+
presence.delete(providerName);
|
|
182
|
+
// Deterministic: start after the failed account so repeated 429s walk the roster instead of
|
|
183
|
+
// hammering whichever id happens to sort first.
|
|
184
|
+
const order = set.accounts.map(account => account.id);
|
|
185
|
+
const start = order.indexOf(failedAccountId);
|
|
186
|
+
for (let i = 1; i <= order.length; i++) {
|
|
187
|
+
const candidate = order[(start + i) % order.length]!;
|
|
188
|
+
if (candidate !== failedAccountId && eligible.includes(candidate)) return candidate;
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Full credential snapshot for a rotated account.
|
|
195
|
+
*
|
|
196
|
+
* Returns the snapshot rather than a bare bearer: Antigravity pairs an account-matched
|
|
197
|
+
* `projectId` with its token and Kiro carries routing metadata, so a token-only swap would mix
|
|
198
|
+
* one account's bearer with another's routing data.
|
|
199
|
+
*/
|
|
200
|
+
export async function failoverAccountSnapshot(
|
|
201
|
+
providerName: string,
|
|
202
|
+
accountId: string,
|
|
203
|
+
): Promise<OAuthAccessSnapshot> {
|
|
204
|
+
return getValidAccessSnapshotForAccount(providerName, accountId);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Earliest remaining cooldown, for a client-facing Retry-After when every account is cooled. */
|
|
208
|
+
export function genericFailoverRetryAfterSeconds(providerName: string, now = Date.now()): number | null {
|
|
209
|
+
const set = getAccountSet(providerName);
|
|
210
|
+
if (!set) return null;
|
|
211
|
+
let earliest: number | null = null;
|
|
212
|
+
for (const account of set.accounts) {
|
|
213
|
+
const entry = health.get(healthKey(providerName, account.id));
|
|
214
|
+
if (!entry || entry.cooldownUntil <= now) continue;
|
|
215
|
+
if (earliest === null || entry.cooldownUntil < earliest) earliest = entry.cooldownUntil;
|
|
216
|
+
}
|
|
217
|
+
return earliest === null ? null : Math.max(1, Math.ceil((earliest - now) / 1000));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Test seam and manual-recovery hook. */
|
|
221
|
+
export function clearGenericFailoverHealth(providerName?: string): void {
|
|
222
|
+
if (!providerName) {
|
|
223
|
+
health.clear();
|
|
224
|
+
presence.clear();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
presence.delete(providerName);
|
|
228
|
+
for (const key of [...health.keys()]) {
|
|
229
|
+
if (key.startsWith(`${providerName}\u0000`)) health.delete(key);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { OAuthCallbackFlow, type OAuthCallbackFlowOptions } from "./callback-server";
|
|
13
13
|
import { generatePKCE } from "./pkce";
|
|
14
14
|
import type { OAuthController, OAuthCredentials } from "./types";
|
|
15
|
-
import { antigravityUserAgent,
|
|
15
|
+
import { antigravityUserAgent, ANTIGRAVITY_IDE_VERSION } from "../adapters/client-fingerprint";
|
|
16
16
|
|
|
17
17
|
const CLIENT_ID = process.env.GOOGLE_ANTIGRAVITY_CLIENT_ID
|
|
18
18
|
|| "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
|
|
@@ -20,6 +20,7 @@ const CLIENT_SECRET = process.env.GOOGLE_ANTIGRAVITY_CLIENT_SECRET
|
|
|
20
20
|
|| "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";
|
|
21
21
|
const AUTH_ENDPOINT = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
22
22
|
const TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";
|
|
23
|
+
const USERINFO_ENDPOINT = "https://www.googleapis.com/oauth2/v2/userinfo";
|
|
23
24
|
const PROD_API = "https://cloudcode-pa.googleapis.com";
|
|
24
25
|
const DAILY_API = "https://daily-cloudcode-pa.googleapis.com";
|
|
25
26
|
const API_VERSION = "v1internal";
|
|
@@ -109,8 +110,13 @@ async function onboardProject(accessToken: string, signal?: AbortSignal): Promis
|
|
|
109
110
|
if (signal?.aborted) throw signal.reason ?? new Error("Antigravity onboarding aborted");
|
|
110
111
|
const response = await fetch(`${DAILY_API}/${API_VERSION}:onboardUser`, {
|
|
111
112
|
method: "POST",
|
|
112
|
-
headers: { Authorization: `Bearer ${accessToken}`, Accept: "*/*", "Content-Type": "application/json", "User-Agent": antigravityUserAgent()
|
|
113
|
-
|
|
113
|
+
headers: { Authorization: `Bearer ${accessToken}`, Accept: "*/*", "Content-Type": "application/json", "User-Agent": antigravityUserAgent() },
|
|
114
|
+
// `ide_version` is a version, not a User-Agent. `antigravityUserAgent()` returns the whole
|
|
115
|
+
// header — `antigravity/ide/2.5.5 (aidev_client; os_type=...; arch=...)` — so onboarding was
|
|
116
|
+
// sending a parenthesized UA string in a field the real client fills with `2.5.5`. It is a
|
|
117
|
+
// fingerprint mismatch rather than a crash, which is why nothing failed: the request still
|
|
118
|
+
// succeeds, it just does not look like Antigravity.
|
|
119
|
+
body: JSON.stringify({ tier_id: "free-tier", metadata: { ide_type: "ANTIGRAVITY", ide_name: "antigravity", ide_version: ANTIGRAVITY_IDE_VERSION } }),
|
|
114
120
|
signal: requestSignal(signal),
|
|
115
121
|
});
|
|
116
122
|
if (!response.ok) {
|
|
@@ -228,3 +234,29 @@ export async function refreshAntigravityToken(refreshToken: string, signal?: Abo
|
|
|
228
234
|
const projectId = await discoverAntigravityProject(creds.access, signal).catch(() => undefined);
|
|
229
235
|
return projectId ? { ...creds, projectId } : creds;
|
|
230
236
|
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Refresh and derive import identity from Google's pinned userinfo endpoint. Imports may not
|
|
240
|
+
* borrow the email written in a local file: that would let one valid token overwrite another
|
|
241
|
+
* account's slot when a refresh response has no id_token.
|
|
242
|
+
*/
|
|
243
|
+
export async function validateAntigravityImportCredential(
|
|
244
|
+
refreshToken: string,
|
|
245
|
+
signal?: AbortSignal,
|
|
246
|
+
): Promise<OAuthCredentials> {
|
|
247
|
+
const credential = await refreshAntigravityToken(refreshToken, signal);
|
|
248
|
+
const response = await fetch(USERINFO_ENDPOINT, {
|
|
249
|
+
method: "GET",
|
|
250
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${credential.access}` },
|
|
251
|
+
signal: requestSignal(signal),
|
|
252
|
+
});
|
|
253
|
+
if (!response.ok) throw new Error(`Antigravity identity request failed: ${response.status}`);
|
|
254
|
+
const body = (await response.json().catch(() => undefined)) as { email?: unknown; id?: unknown } | undefined;
|
|
255
|
+
if (typeof body?.email !== "string" || body.email.length === 0) {
|
|
256
|
+
throw new Error("Antigravity identity response did not include an email");
|
|
257
|
+
}
|
|
258
|
+
if (typeof body.id !== "string" || body.id.length === 0) {
|
|
259
|
+
throw new Error("Antigravity identity response did not include an account id");
|
|
260
|
+
}
|
|
261
|
+
return { ...credential, accountId: body.id, email: body.email.toLowerCase() };
|
|
262
|
+
}
|