@iislee/opencodex 2.11.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/LICENSE +21 -0
- package/README.md +225 -0
- package/assets/architecture.png +0 -0
- package/assets/banner.png +0 -0
- package/assets/claude-code-models.gif +0 -0
- package/assets/codex-app-picker.png +0 -0
- package/bin/ocx.mjs +451 -0
- package/bin/package-main.mjs +9 -0
- package/gui/dist/assets/index-DTpMHS4F.js +67 -0
- package/gui/dist/assets/index-ZNVDE3C7.css +1 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/icons.svg +24 -0
- package/gui/dist/index.html +25 -0
- package/gui/dist/logo.png +0 -0
- package/gui/dist/provider-icons/alibaba-color.svg +1 -0
- package/gui/dist/provider-icons/antigravity-color.svg +1 -0
- package/gui/dist/provider-icons/antigravity.svg +1 -0
- package/gui/dist/provider-icons/claude-color.svg +1 -0
- package/gui/dist/provider-icons/claude.svg +1 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -0
- package/gui/dist/provider-icons/copilot.svg +1 -0
- package/gui/dist/provider-icons/cursor-color.svg +2 -0
- package/gui/dist/provider-icons/cursor.svg +2 -0
- package/gui/dist/provider-icons/deepseek-color.svg +1 -0
- package/gui/dist/provider-icons/discord.svg +1 -0
- package/gui/dist/provider-icons/firepass-color.svg +1 -0
- package/gui/dist/provider-icons/fireworks-color.svg +1 -0
- package/gui/dist/provider-icons/gemini-color.svg +1 -0
- package/gui/dist/provider-icons/gemini.svg +1 -0
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
- package/gui/dist/provider-icons/grok-color.svg +1 -0
- package/gui/dist/provider-icons/grok.svg +1 -0
- package/gui/dist/provider-icons/groq-color.svg +1 -0
- package/gui/dist/provider-icons/huggingface-color.svg +1 -0
- package/gui/dist/provider-icons/kimi-color.svg +1 -0
- package/gui/dist/provider-icons/kiro-color.svg +15 -0
- package/gui/dist/provider-icons/kiro.svg +14 -0
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
- package/gui/dist/provider-icons/mistral-color.svg +1 -0
- package/gui/dist/provider-icons/moonshot-color.svg +1 -0
- package/gui/dist/provider-icons/nvidia-color.svg +1 -0
- package/gui/dist/provider-icons/ollama-color.svg +1 -0
- package/gui/dist/provider-icons/openai.svg +1 -0
- package/gui/dist/provider-icons/opencode.svg +1 -0
- package/gui/dist/provider-icons/openrouter-color.svg +1 -0
- package/gui/dist/provider-icons/pi.svg +21 -0
- package/gui/dist/provider-icons/qianfan-color.svg +1 -0
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
- package/gui/dist/provider-icons/telegram.svg +1 -0
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/vllm-color.svg +1 -0
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
- package/package.json +102 -0
- package/src/AGENTS.md +28 -0
- package/src/adapters/anthropic-image-guard.ts +251 -0
- package/src/adapters/anthropic-image-normalize.ts +518 -0
- package/src/adapters/anthropic.ts +1003 -0
- package/src/adapters/azure.ts +36 -0
- package/src/adapters/base.ts +72 -0
- package/src/adapters/client-fingerprint.ts +59 -0
- package/src/adapters/cursor/arg-codec.ts +38 -0
- package/src/adapters/cursor/arg-normalize.ts +104 -0
- package/src/adapters/cursor/cursor-errors.ts +165 -0
- package/src/adapters/cursor/discovery.ts +276 -0
- package/src/adapters/cursor/effort-map.ts +127 -0
- package/src/adapters/cursor/exec-policy.ts +88 -0
- package/src/adapters/cursor/framing.ts +211 -0
- package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
- package/src/adapters/cursor/kv-store.ts +52 -0
- package/src/adapters/cursor/live-models.ts +153 -0
- package/src/adapters/cursor/live-smoke-gate.ts +41 -0
- package/src/adapters/cursor/live-transport.ts +1214 -0
- package/src/adapters/cursor/mcp-config.ts +42 -0
- package/src/adapters/cursor/mcp-manager.ts +333 -0
- package/src/adapters/cursor/message-mapper.ts +49 -0
- package/src/adapters/cursor/native-exec-common.ts +55 -0
- package/src/adapters/cursor/native-exec-desktop.ts +184 -0
- package/src/adapters/cursor/native-exec-fs.ts +329 -0
- package/src/adapters/cursor/native-exec-mcp.ts +153 -0
- package/src/adapters/cursor/native-exec-network.ts +43 -0
- package/src/adapters/cursor/native-exec-shell.ts +548 -0
- package/src/adapters/cursor/native-exec-tools.ts +118 -0
- package/src/adapters/cursor/native-exec.ts +576 -0
- package/src/adapters/cursor/protobuf-events.ts +563 -0
- package/src/adapters/cursor/protobuf-request.ts +714 -0
- package/src/adapters/cursor/request-builder.ts +255 -0
- package/src/adapters/cursor/thread-continuity.ts +67 -0
- package/src/adapters/cursor/tool-definitions.ts +505 -0
- package/src/adapters/cursor/transport-retry.ts +132 -0
- package/src/adapters/cursor/transport.ts +57 -0
- package/src/adapters/cursor/types.ts +52 -0
- package/src/adapters/cursor.ts +196 -0
- package/src/adapters/google-antigravity-replay.ts +303 -0
- package/src/adapters/google-antigravity-wire.ts +108 -0
- package/src/adapters/google-errors.ts +85 -0
- package/src/adapters/google-http.ts +100 -0
- package/src/adapters/google-tool-schema.ts +173 -0
- package/src/adapters/google-truncation.ts +13 -0
- package/src/adapters/google-wire-compiler.ts +232 -0
- package/src/adapters/google.ts +758 -0
- package/src/adapters/identity.ts +44 -0
- package/src/adapters/image.ts +23 -0
- package/src/adapters/kiro-constants.ts +16 -0
- package/src/adapters/kiro-errors.ts +197 -0
- package/src/adapters/kiro-events.ts +179 -0
- package/src/adapters/kiro-images.ts +129 -0
- package/src/adapters/kiro-retry.ts +312 -0
- package/src/adapters/kiro-thinking.ts +96 -0
- package/src/adapters/kiro-tool-fallback.ts +36 -0
- package/src/adapters/kiro-tools.ts +215 -0
- package/src/adapters/kiro-truncation.ts +33 -0
- package/src/adapters/kiro-wire.ts +129 -0
- package/src/adapters/kiro.ts +1898 -0
- package/src/adapters/mimo-free.ts +263 -0
- package/src/adapters/openai-chat.ts +1005 -0
- package/src/adapters/openai-responses.ts +1137 -0
- package/src/adapters/run-turn-queue.ts +114 -0
- package/src/adapters/tool-catalog-nudge.ts +71 -0
- package/src/adapters/upstream-http-error.ts +48 -0
- package/src/bridge.ts +1619 -0
- package/src/chat/inbound.ts +295 -0
- package/src/chat/outbound.ts +765 -0
- package/src/claude/agents-inject.ts +243 -0
- package/src/claude/alias.ts +149 -0
- package/src/claude/auth-detect.ts +229 -0
- package/src/claude/auth-mode-migration.ts +32 -0
- package/src/claude/auth-mode.ts +62 -0
- package/src/claude/context-windows.ts +189 -0
- package/src/claude/desktop-3p-guard.ts +35 -0
- package/src/claude/desktop-3p-paths.ts +84 -0
- package/src/claude/desktop-3p.ts +381 -0
- package/src/claude/desktop-health.ts +26 -0
- package/src/claude/desktop-profile.ts +263 -0
- package/src/claude/gateway-cache.ts +70 -0
- package/src/claude/inbound-debug.ts +163 -0
- package/src/claude/inbound.ts +509 -0
- package/src/claude/model-info.ts +151 -0
- package/src/claude/outbound.ts +872 -0
- package/src/cli/access.ts +108 -0
- package/src/cli/account-api.ts +268 -0
- package/src/cli/account-auth.ts +223 -0
- package/src/cli/account-extended.ts +350 -0
- package/src/cli/account.ts +275 -0
- package/src/cli/agent-driven.ts +70 -0
- package/src/cli/agent.ts +184 -0
- package/src/cli/catalog-prewarm.ts +27 -0
- package/src/cli/claude-desktop.ts +188 -0
- package/src/cli/claude.ts +286 -0
- package/src/cli/codex-shim-autorestore.ts +45 -0
- package/src/cli/combo.ts +119 -0
- package/src/cli/config-command.ts +145 -0
- package/src/cli/debug.ts +228 -0
- package/src/cli/doctor.ts +930 -0
- package/src/cli/export-command.ts +187 -0
- package/src/cli/help.ts +354 -0
- package/src/cli/index.ts +1113 -0
- package/src/cli/init.ts +224 -0
- package/src/cli/integrations.ts +142 -0
- package/src/cli/interactive-confirm.ts +133 -0
- package/src/cli/internal-dispatch.ts +20 -0
- package/src/cli/models-runtime.ts +212 -0
- package/src/cli/models.ts +336 -0
- package/src/cli/observe.ts +117 -0
- package/src/cli/opencode.ts +586 -0
- package/src/cli/pi.ts +188 -0
- package/src/cli/provider-runtime.ts +162 -0
- package/src/cli/provider.ts +463 -0
- package/src/cli/runtime-api.ts +325 -0
- package/src/cli/star-prompt.ts +155 -0
- package/src/cli/status-oauth.ts +78 -0
- package/src/cli/status.ts +321 -0
- package/src/cli/sync-cloud.ts +283 -0
- package/src/cli/system-command.ts +112 -0
- package/src/cli/tray-proxy.ts +52 -0
- package/src/cli/v2.ts +173 -0
- package/src/cli.ts +10 -0
- package/src/clients/config-export.ts +377 -0
- package/src/clients/effective-status.ts +385 -0
- package/src/clients/probes/agy.ts +55 -0
- package/src/clients/probes/cc-switch.ts +110 -0
- package/src/clients/probes/claude.ts +90 -0
- package/src/clients/probes/codex.ts +125 -0
- package/src/clients/probes/grok.ts +29 -0
- package/src/clients/probes/opencode.ts +109 -0
- package/src/clients/probes/paseo.ts +55 -0
- package/src/clients/probes/pi.ts +55 -0
- package/src/cloud/onedrive-auth.ts +666 -0
- package/src/cloud/onedrive-graph.ts +108 -0
- package/src/cloud/settings.ts +75 -0
- package/src/cloud/sync.ts +212 -0
- package/src/cloud/types.ts +56 -0
- package/src/cloud/vault.ts +89 -0
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-label.ts +34 -0
- package/src/codex/account-lifecycle.ts +55 -0
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +149 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-runtime-state.ts +31 -0
- package/src/codex/account-store.ts +517 -0
- package/src/codex/account-usability.ts +20 -0
- package/src/codex/app-server-processes.ts +756 -0
- package/src/codex/auth-api.ts +1540 -0
- package/src/codex/auth-collision.ts +107 -0
- package/src/codex/auth-context.ts +352 -0
- package/src/codex/autostart-health.ts +149 -0
- package/src/codex/catalog/aggregation.ts +378 -0
- package/src/codex/catalog/bundled.ts +251 -0
- package/src/codex/catalog/effort.ts +355 -0
- package/src/codex/catalog/metadata.ts +180 -0
- package/src/codex/catalog/parsing.ts +456 -0
- package/src/codex/catalog/provider-fetch.ts +902 -0
- package/src/codex/catalog/sync.ts +620 -0
- package/src/codex/catalog.ts +12 -0
- package/src/codex/data/upstream-models.json +830 -0
- package/src/codex/exec-invocation.ts +22 -0
- package/src/codex/features.ts +969 -0
- package/src/codex/history-migration-guardian.ts +102 -0
- package/src/codex/history-provider.ts +776 -0
- package/src/codex/home.ts +206 -0
- package/src/codex/inject.ts +799 -0
- package/src/codex/injected-marker.ts +72 -0
- package/src/codex/journal.ts +163 -0
- package/src/codex/main-account-cache.ts +32 -0
- package/src/codex/main-account.ts +40 -0
- package/src/codex/model-cache.ts +227 -0
- package/src/codex/paths.ts +65 -0
- package/src/codex/plugins-doctor.ts +242 -0
- package/src/codex/pool-rotation.ts +225 -0
- package/src/codex/project-config-warnings.ts +411 -0
- package/src/codex/quota.ts +411 -0
- package/src/codex/refresh.ts +53 -0
- package/src/codex/routing.ts +1477 -0
- package/src/codex/runtime.ts +538 -0
- package/src/codex/shim.ts +1189 -0
- package/src/codex/subagent-defaults.ts +550 -0
- package/src/codex/subagent-model-fallback.ts +469 -0
- package/src/codex/sync.ts +130 -0
- package/src/codex/warmup.ts +192 -0
- package/src/codex/websocket-registry.ts +100 -0
- package/src/combos/failover.ts +140 -0
- package/src/combos/index.ts +41 -0
- package/src/combos/request.ts +62 -0
- package/src/combos/resolve.ts +232 -0
- package/src/combos/types.ts +326 -0
- package/src/config.ts +2356 -0
- package/src/generated/jawcode-model-metadata.ts +104 -0
- package/src/github/star-state.ts +203 -0
- package/src/grok/inject.ts +545 -0
- package/src/grok/status.ts +121 -0
- package/src/grok/sync.ts +103 -0
- package/src/grok/usage-hook/report.mjs +348 -0
- package/src/grok/usage-hook.ts +278 -0
- package/src/images/artifacts.ts +516 -0
- package/src/images/fulfill-video.ts +163 -0
- package/src/images/fulfill.ts +149 -0
- package/src/images/index.ts +4 -0
- package/src/images/loop.ts +829 -0
- package/src/images/plan.ts +133 -0
- package/src/images/synthetic-tool.ts +133 -0
- package/src/images/types.ts +41 -0
- package/src/images/xai-client.ts +141 -0
- package/src/images/xai-video-client.ts +163 -0
- package/src/index.ts +22 -0
- package/src/lib/abort.ts +146 -0
- package/src/lib/admin-secrets.ts +25 -0
- package/src/lib/admission.ts +83 -0
- package/src/lib/app-owned-memory-stores.ts +173 -0
- package/src/lib/app-owned-memory.ts +265 -0
- package/src/lib/bounded-body.ts +202 -0
- package/src/lib/bun-binary-validator.d.mts +3 -0
- package/src/lib/bun-binary-validator.mjs +18 -0
- package/src/lib/bun-runtime.ts +71 -0
- package/src/lib/bun-stream-caps.ts +126 -0
- package/src/lib/config-ownership.ts +360 -0
- package/src/lib/crash-guard.ts +344 -0
- package/src/lib/debug-log-buffer.ts +83 -0
- package/src/lib/debug-settings.ts +108 -0
- package/src/lib/debug.ts +31 -0
- package/src/lib/destination-policy.ts +316 -0
- package/src/lib/errors.ts +364 -0
- package/src/lib/eventstream-decoder.ts +253 -0
- package/src/lib/gcp-adc.ts +341 -0
- package/src/lib/injection-debug-log.ts +58 -0
- package/src/lib/open-url.ts +25 -0
- package/src/lib/pinned-http.ts +151 -0
- package/src/lib/privacy.ts +20 -0
- package/src/lib/process-control.ts +165 -0
- package/src/lib/provider-outbound.ts +170 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/redact.ts +105 -0
- package/src/lib/retry-after.ts +55 -0
- package/src/lib/service-secrets.ts +25 -0
- package/src/lib/shadow-call.ts +30 -0
- package/src/lib/sidecar-tracker.ts +52 -0
- package/src/lib/sse-decoder.ts +323 -0
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/test-home-guard.ts +90 -0
- package/src/lib/token-estimate.ts +69 -0
- package/src/lib/translator-budget.ts +356 -0
- package/src/lib/upstream-retry.ts +239 -0
- package/src/lib/win-exec.ts +115 -0
- package/src/lib/win-paths.ts +68 -0
- package/src/lib/windows-elevation.ts +705 -0
- package/src/lib/windows-secret-acl.ts +514 -0
- package/src/lib/winsw.ts +375 -0
- package/src/oauth/anthropic-routing.ts +594 -0
- package/src/oauth/anthropic.ts +177 -0
- package/src/oauth/callback-server.ts +294 -0
- package/src/oauth/chatgpt.ts +150 -0
- package/src/oauth/cursor.ts +211 -0
- package/src/oauth/github-copilot.ts +428 -0
- package/src/oauth/google-antigravity.ts +230 -0
- package/src/oauth/health.ts +399 -0
- package/src/oauth/index.ts +1174 -0
- package/src/oauth/key-providers.ts +108 -0
- package/src/oauth/kimi.ts +213 -0
- package/src/oauth/kiro-credentials.ts +726 -0
- package/src/oauth/kiro.ts +577 -0
- package/src/oauth/local-token-detect.ts +121 -0
- package/src/oauth/log.ts +48 -0
- package/src/oauth/login-cli.ts +163 -0
- package/src/oauth/pkce.ts +15 -0
- package/src/oauth/store.ts +630 -0
- package/src/oauth/token-guardian.ts +303 -0
- package/src/oauth/types.ts +62 -0
- package/src/oauth/xai.ts +241 -0
- package/src/pi/extensions.ts +72 -0
- package/src/pi/home.ts +42 -0
- package/src/pi/index.ts +40 -0
- package/src/pi/models.ts +278 -0
- package/src/pi/packages.ts +219 -0
- package/src/pi/settings.ts +365 -0
- package/src/pi/status.ts +68 -0
- package/src/pi/sync.ts +75 -0
- package/src/providers/alibaba-region-backup.ts +75 -0
- package/src/providers/alibaba-region-migration.ts +156 -0
- package/src/providers/alibaba-region-startup.ts +36 -0
- package/src/providers/antigravity-models.ts +205 -0
- package/src/providers/api-keys.ts +140 -0
- package/src/providers/base-url-choices.ts +64 -0
- package/src/providers/context-cap.ts +65 -0
- package/src/providers/derive.ts +339 -0
- package/src/providers/free-directory.ts +184 -0
- package/src/providers/github-copilot-transport.ts +56 -0
- package/src/providers/key-failover.ts +203 -0
- package/src/providers/kiro-models.ts +67 -0
- package/src/providers/label.ts +19 -0
- package/src/providers/model-discovery.ts +356 -0
- package/src/providers/openai-sidecar.ts +175 -0
- package/src/providers/openai-tier-startup.ts +27 -0
- package/src/providers/openai-tiers.ts +301 -0
- package/src/providers/openai-virtual-models.ts +82 -0
- package/src/providers/openrouter-routing.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +150 -0
- package/src/providers/quota.ts +1260 -0
- package/src/providers/registry.ts +1600 -0
- package/src/providers/slug-codec.ts +67 -0
- package/src/providers/xai-transport.ts +141 -0
- package/src/reasoning-effort.ts +135 -0
- package/src/responses/compaction.ts +117 -0
- package/src/responses/parser.ts +656 -0
- package/src/responses/reasoning-envelope.ts +52 -0
- package/src/responses/schema.ts +159 -0
- package/src/responses/spill-store.ts +394 -0
- package/src/responses/state.ts +895 -0
- package/src/responses/tool-groups.ts +19 -0
- package/src/router.ts +425 -0
- package/src/server/adapter-resolve.ts +80 -0
- package/src/server/auth-cors.ts +530 -0
- package/src/server/chat-completions.ts +368 -0
- package/src/server/claude-messages.ts +914 -0
- package/src/server/effort-policy.ts +172 -0
- package/src/server/gui-static.ts +123 -0
- package/src/server/image-retry.ts +42 -0
- package/src/server/images.ts +476 -0
- package/src/server/index.ts +1126 -0
- package/src/server/lifecycle.ts +227 -0
- package/src/server/live.ts +598 -0
- package/src/server/management/agent-settings-routes.ts +1169 -0
- package/src/server/management/api-access.ts +141 -0
- package/src/server/management/api-key-usage.ts +167 -0
- package/src/server/management/body.ts +35 -0
- package/src/server/management/clients-routes.ts +63 -0
- package/src/server/management/cloud-sync-routes.ts +266 -0
- package/src/server/management/combo-routes.ts +220 -0
- package/src/server/management/config-routes.ts +422 -0
- package/src/server/management/context.ts +31 -0
- package/src/server/management/logs-usage-routes.ts +707 -0
- package/src/server/management/model-routes.ts +525 -0
- package/src/server/management/oauth-account-routes.ts +563 -0
- package/src/server/management/provider-routes.ts +556 -0
- package/src/server/management/shared.ts +277 -0
- package/src/server/management/sidebar-routes.ts +90 -0
- package/src/server/management/system-restart.ts +179 -0
- package/src/server/management/system-routes.ts +117 -0
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +215 -0
- package/src/server/management-auth.ts +267 -0
- package/src/server/memory-watchdog.ts +156 -0
- package/src/server/port-reclaim.ts +307 -0
- package/src/server/ports.ts +116 -0
- package/src/server/proxy-liveness.ts +201 -0
- package/src/server/relay-eager.ts +313 -0
- package/src/server/relay.ts +1049 -0
- package/src/server/request-decompress.ts +132 -0
- package/src/server/request-log-conversation.ts +168 -0
- package/src/server/request-log.ts +1046 -0
- package/src/server/responses/collaboration.ts +354 -0
- package/src/server/responses/compact.ts +384 -0
- package/src/server/responses/core.ts +2758 -0
- package/src/server/responses/encrypted-payload.ts +308 -0
- package/src/server/responses/fetch-helpers.ts +157 -0
- package/src/server/responses/passthrough-error.ts +78 -0
- package/src/server/responses/terminal-guard.ts +230 -0
- package/src/server/responses/upstream-error.ts +48 -0
- package/src/server/responses-image-gen-repair.ts +132 -0
- package/src/server/responses-item-id-repair.ts +224 -0
- package/src/server/responses.ts +9 -0
- package/src/server/search.ts +136 -0
- package/src/server/sse-payload-rewrite.ts +175 -0
- package/src/server/startup-action-control.ts +308 -0
- package/src/server/startup-health-cache.ts +113 -0
- package/src/server/system-env.ts +413 -0
- package/src/server/windows-tcp-drop.ts +184 -0
- package/src/server/windows-tray-control.ts +41 -0
- package/src/server/ws-bridge.ts +471 -0
- package/src/service.ts +2554 -0
- package/src/stall-timeout.ts +20 -0
- package/src/storage/cleanup-job.ts +57 -0
- package/src/storage/cleanup.ts +3085 -0
- package/src/storage/policy-job.ts +457 -0
- package/src/storage/policy-scheduler.ts +40 -0
- package/src/storage/policy-worker.ts +59 -0
- package/src/storage/policy.ts +527 -0
- package/src/storage/restore-job.ts +299 -0
- package/src/storage/restore-worker.ts +58 -0
- package/src/storage/scanner.ts +238 -0
- package/src/storage/storage-mutation-coordinator.ts +139 -0
- package/src/storage/worker-lifecycle.ts +215 -0
- package/src/tray/assets/opencodex-tray-offline.ico +0 -0
- package/src/tray/assets/opencodex-tray-online.ico +0 -0
- package/src/tray/assets/opencodex-tray-warning.ico +0 -0
- package/src/tray/assets/opencodex-tray.png +0 -0
- package/src/tray/windows-tray.ps1 +290 -0
- package/src/tray/windows.ts +730 -0
- package/src/types.ts +1237 -0
- package/src/update/badge.ts +72 -0
- package/src/update/index.ts +407 -0
- package/src/update/job.ts +1520 -0
- package/src/update/notify.ts +257 -0
- package/src/update/npm-invocation.d.mts +23 -0
- package/src/update/npm-invocation.mjs +94 -0
- package/src/update/tray-update-plan.d.mts +18 -0
- package/src/update/tray-update-plan.mjs +38 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/debug.ts +97 -0
- package/src/usage/expected-prices.ts +164 -0
- package/src/usage/log.ts +658 -0
- package/src/usage/summary.ts +585 -0
- package/src/usage/totals.ts +14 -0
- package/src/vision/anthropic-describe.ts +185 -0
- package/src/vision/describe.ts +125 -0
- package/src/vision/index.ts +467 -0
- package/src/web-search/anthropic-executor.ts +189 -0
- package/src/web-search/executor.ts +105 -0
- package/src/web-search/format-result.ts +89 -0
- package/src/web-search/index.ts +196 -0
- package/src/web-search/loop.ts +664 -0
- package/src/web-search/parse.ts +220 -0
- package/src/web-search/progress-stream.ts +342 -0
- package/src/web-search/synthetic-tool.ts +47 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Antigravity (Cloud Code Assist) OAuth + project discovery.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors CLIProxyAPI `internal/auth/antigravity/*`. Flow: standard Google OAuth (PKCE) → discover
|
|
5
|
+
* the Cloud Code Assist project via `loadCodeAssist`, onboarding via `onboardUser` when the account
|
|
6
|
+
* has no project yet. The discovered `projectId` is stored on the credential and injected into the
|
|
7
|
+
* CCA request envelope by the google adapter.
|
|
8
|
+
*
|
|
9
|
+
* The client id/secret below are the public OAuth client identifiers embedded in the Antigravity
|
|
10
|
+
* desktop client (overridable via env), not user secrets. Tokens/refresh are never logged.
|
|
11
|
+
*/
|
|
12
|
+
import { OAuthCallbackFlow, type OAuthCallbackFlowOptions } from "./callback-server";
|
|
13
|
+
import { generatePKCE } from "./pkce";
|
|
14
|
+
import type { OAuthController, OAuthCredentials } from "./types";
|
|
15
|
+
import { antigravityUserAgent, ANTIGRAVITY_GOOG_API_CLIENT_UA } from "../adapters/client-fingerprint";
|
|
16
|
+
|
|
17
|
+
const CLIENT_ID = process.env.GOOGLE_ANTIGRAVITY_CLIENT_ID
|
|
18
|
+
|| "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
|
|
19
|
+
const CLIENT_SECRET = process.env.GOOGLE_ANTIGRAVITY_CLIENT_SECRET
|
|
20
|
+
|| "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";
|
|
21
|
+
const AUTH_ENDPOINT = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
22
|
+
const TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";
|
|
23
|
+
const PROD_API = "https://cloudcode-pa.googleapis.com";
|
|
24
|
+
const DAILY_API = "https://daily-cloudcode-pa.googleapis.com";
|
|
25
|
+
const API_VERSION = "v1internal";
|
|
26
|
+
const SCOPES = [
|
|
27
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
28
|
+
"https://www.googleapis.com/auth/userinfo.email",
|
|
29
|
+
"https://www.googleapis.com/auth/userinfo.profile",
|
|
30
|
+
"https://www.googleapis.com/auth/cclog",
|
|
31
|
+
"https://www.googleapis.com/auth/experimentsandconfigs",
|
|
32
|
+
];
|
|
33
|
+
const CALLBACK_PORT = 51121;
|
|
34
|
+
const CALLBACK_PATH = "/callback";
|
|
35
|
+
// Keep provider-side margins small: the shared OAuth freshness gate applies an additional minute.
|
|
36
|
+
const REFRESH_SKEW_MS = 5 * 60 * 1000;
|
|
37
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
38
|
+
const ONBOARD_ATTEMPTS = 5;
|
|
39
|
+
const ONBOARD_POLL_MS = 2_000;
|
|
40
|
+
|
|
41
|
+
function requestSignal(signal: AbortSignal | undefined): AbortSignal {
|
|
42
|
+
const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
|
|
43
|
+
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface GoogleTokenPayload {
|
|
47
|
+
access_token?: unknown;
|
|
48
|
+
refresh_token?: unknown;
|
|
49
|
+
expires_in?: unknown;
|
|
50
|
+
id_token?: unknown;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function decodeJwtPayload(token: string): Record<string, unknown> | undefined {
|
|
54
|
+
const part = token.split(".")[1];
|
|
55
|
+
if (!part) return undefined;
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(Buffer.from(part, "base64url").toString("utf8")) as Record<string, unknown>;
|
|
58
|
+
} catch {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function emailFromToken(accessToken: string, idToken: string | undefined): string | undefined {
|
|
64
|
+
const payload = (idToken ? decodeJwtPayload(idToken) : undefined) ?? decodeJwtPayload(accessToken);
|
|
65
|
+
const email = payload?.email;
|
|
66
|
+
return typeof email === "string" && email.length > 0 ? email.toLowerCase() : undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function postToken(body: Record<string, string>, signal?: AbortSignal): Promise<GoogleTokenPayload> {
|
|
70
|
+
const response = await fetch(TOKEN_ENDPOINT, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
|
|
73
|
+
body: new URLSearchParams(body).toString(),
|
|
74
|
+
signal: requestSignal(signal),
|
|
75
|
+
});
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
// Status only — the body can carry grant/account details.
|
|
78
|
+
throw new Error(`Antigravity token request failed: ${response.status}`);
|
|
79
|
+
}
|
|
80
|
+
return (await response.json()) as GoogleTokenPayload;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Pull a Cloud Code Assist project id out of a loadCodeAssist/onboardUser response shape. */
|
|
84
|
+
function extractProjectId(data: Record<string, unknown> | undefined): string | undefined {
|
|
85
|
+
if (!data) return undefined;
|
|
86
|
+
for (const key of ["cloudaicompanionProject", "projectId", "project"]) {
|
|
87
|
+
const value = data[key];
|
|
88
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
89
|
+
if (value && typeof value === "object" && typeof (value as { id?: unknown }).id === "string") {
|
|
90
|
+
return (value as { id: string }).id;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function loadCodeAssistProject(accessToken: string, signal?: AbortSignal): Promise<string | undefined> {
|
|
97
|
+
const response = await fetch(`${PROD_API}/${API_VERSION}:loadCodeAssist`, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: { Authorization: `Bearer ${accessToken}`, Accept: "*/*", "Content-Type": "application/json", "User-Agent": antigravityUserAgent() },
|
|
100
|
+
body: JSON.stringify({ metadata: { ideType: "ANTIGRAVITY" } }),
|
|
101
|
+
signal: requestSignal(signal),
|
|
102
|
+
});
|
|
103
|
+
if (!response.ok) return undefined;
|
|
104
|
+
return extractProjectId((await response.json().catch(() => undefined)) as Record<string, unknown> | undefined);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function onboardProject(accessToken: string, signal?: AbortSignal): Promise<string | undefined> {
|
|
108
|
+
for (let attempt = 0; attempt < ONBOARD_ATTEMPTS; attempt++) {
|
|
109
|
+
if (signal?.aborted) throw signal.reason ?? new Error("Antigravity onboarding aborted");
|
|
110
|
+
const response = await fetch(`${DAILY_API}/${API_VERSION}:onboardUser`, {
|
|
111
|
+
method: "POST",
|
|
112
|
+
headers: { Authorization: `Bearer ${accessToken}`, Accept: "*/*", "Content-Type": "application/json", "User-Agent": antigravityUserAgent(), "x-goog-api-client": ANTIGRAVITY_GOOG_API_CLIENT_UA },
|
|
113
|
+
body: JSON.stringify({ tier_id: "free-tier", metadata: { ide_type: "ANTIGRAVITY", ide_name: "antigravity", ide_version: antigravityUserAgent() } }),
|
|
114
|
+
signal: requestSignal(signal),
|
|
115
|
+
});
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
// Transient (429/5xx): keep polling within the attempt budget. Hard 4xx: give up now.
|
|
118
|
+
if (response.status === 429 || response.status >= 500) {
|
|
119
|
+
await new Promise(resolve => setTimeout(resolve, ONBOARD_POLL_MS));
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
const data = (await response.json().catch(() => ({}))) as Record<string, unknown>;
|
|
125
|
+
if (data.done === true) {
|
|
126
|
+
return extractProjectId(data.response as Record<string, unknown> | undefined);
|
|
127
|
+
}
|
|
128
|
+
await new Promise(resolve => setTimeout(resolve, ONBOARD_POLL_MS));
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Discover the CCA project for an access token (loadCodeAssist → onboardUser fallback). */
|
|
134
|
+
export async function discoverAntigravityProject(accessToken: string, signal?: AbortSignal): Promise<string | undefined> {
|
|
135
|
+
return (await loadCodeAssistProject(accessToken, signal)) ?? (await onboardProject(accessToken, signal));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function credentialsFromPayload(payload: GoogleTokenPayload, refreshFallback = ""): OAuthCredentials {
|
|
139
|
+
if (typeof payload.access_token !== "string" || payload.access_token.length === 0) {
|
|
140
|
+
throw new Error("Antigravity token response did not include an access token");
|
|
141
|
+
}
|
|
142
|
+
const refresh = typeof payload.refresh_token === "string" && payload.refresh_token.length > 0
|
|
143
|
+
? payload.refresh_token
|
|
144
|
+
: refreshFallback;
|
|
145
|
+
if (!refresh) throw new Error("Antigravity token response did not include a refresh token");
|
|
146
|
+
const expiresIn = typeof payload.expires_in === "number" && Number.isFinite(payload.expires_in) ? payload.expires_in : 3600;
|
|
147
|
+
const idToken = typeof payload.id_token === "string" ? payload.id_token : undefined;
|
|
148
|
+
return {
|
|
149
|
+
refresh,
|
|
150
|
+
access: payload.access_token,
|
|
151
|
+
expires: Date.now() + expiresIn * 1000 - REFRESH_SKEW_MS,
|
|
152
|
+
email: emailFromToken(payload.access_token, idToken),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
class AntigravityOAuthFlow extends OAuthCallbackFlow {
|
|
157
|
+
#verifier = "";
|
|
158
|
+
#forceAccountSelect: boolean;
|
|
159
|
+
|
|
160
|
+
constructor(ctrl: OAuthController, opts?: { forceAccountSelect?: boolean }) {
|
|
161
|
+
super(ctrl, {
|
|
162
|
+
preferredPort: CALLBACK_PORT,
|
|
163
|
+
callbackPath: CALLBACK_PATH,
|
|
164
|
+
callbackHostname: "127.0.0.1",
|
|
165
|
+
callbackBindHostname: "127.0.0.1",
|
|
166
|
+
redirectUri: `http://127.0.0.1:${CALLBACK_PORT}${CALLBACK_PATH}`,
|
|
167
|
+
} satisfies OAuthCallbackFlowOptions);
|
|
168
|
+
this.#forceAccountSelect = opts?.forceAccountSelect === true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async generateAuthUrl(state: string, redirectUri: string): Promise<{ url: string; instructions?: string }> {
|
|
172
|
+
const pkce = await generatePKCE();
|
|
173
|
+
this.#verifier = pkce.verifier;
|
|
174
|
+
const params = new URLSearchParams({
|
|
175
|
+
response_type: "code",
|
|
176
|
+
client_id: CLIENT_ID,
|
|
177
|
+
redirect_uri: redirectUri,
|
|
178
|
+
scope: SCOPES.join(" "),
|
|
179
|
+
code_challenge: pkce.challenge,
|
|
180
|
+
code_challenge_method: "S256",
|
|
181
|
+
access_type: "offline",
|
|
182
|
+
// select_account lets the user pick a DIFFERENT Google account when adding a second one.
|
|
183
|
+
prompt: this.#forceAccountSelect ? "consent select_account" : "consent",
|
|
184
|
+
state,
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
url: `${AUTH_ENDPOINT}?${params.toString()}`,
|
|
188
|
+
instructions: "Complete Google (Antigravity) login in your browser, then paste the redirect URL or code if prompted.",
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async exchangeToken(code: string, _state: string, redirectUri: string): Promise<OAuthCredentials> {
|
|
193
|
+
if (!this.#verifier) throw new Error("Antigravity OAuth PKCE verifier was not initialized");
|
|
194
|
+
const payload = await postToken({
|
|
195
|
+
grant_type: "authorization_code",
|
|
196
|
+
client_id: CLIENT_ID,
|
|
197
|
+
client_secret: CLIENT_SECRET,
|
|
198
|
+
code,
|
|
199
|
+
redirect_uri: redirectUri,
|
|
200
|
+
code_verifier: this.#verifier,
|
|
201
|
+
}, this.ctrl.signal);
|
|
202
|
+
const creds = credentialsFromPayload(payload);
|
|
203
|
+
this.ctrl.onProgress?.("Discovering Cloud Code Assist project");
|
|
204
|
+
const projectId = await discoverAntigravityProject(creds.access, this.ctrl.signal);
|
|
205
|
+
if (!projectId) {
|
|
206
|
+
// Fail the login rather than persisting a credential that every request would reject for a
|
|
207
|
+
// missing CCA project — otherwise status shows "logged in" while all calls fail closed.
|
|
208
|
+
throw new Error("Antigravity login could not discover a Cloud Code Assist project for this account. Ensure the account has Antigravity/Cloud Code Assist access and try again.");
|
|
209
|
+
}
|
|
210
|
+
return { ...creds, projectId };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function loginAntigravity(ctrl: OAuthController, opts?: { forceAccountSelect?: boolean }): Promise<OAuthCredentials> {
|
|
215
|
+
return new AntigravityOAuthFlow(ctrl, opts).login();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function refreshAntigravityToken(refreshToken: string, signal?: AbortSignal): Promise<OAuthCredentials> {
|
|
219
|
+
if (!refreshToken) throw new Error("Antigravity credentials are expired and do not include a refresh token");
|
|
220
|
+
const payload = await postToken({
|
|
221
|
+
grant_type: "refresh_token",
|
|
222
|
+
client_id: CLIENT_ID,
|
|
223
|
+
client_secret: CLIENT_SECRET,
|
|
224
|
+
refresh_token: refreshToken,
|
|
225
|
+
}, signal);
|
|
226
|
+
const creds = credentialsFromPayload(payload, refreshToken);
|
|
227
|
+
// Re-discover the project on refresh so a newly-onboarded account fills in projectId.
|
|
228
|
+
const projectId = await discoverAntigravityProject(creds.access, signal).catch(() => undefined);
|
|
229
|
+
return projectId ? { ...creds, projectId } : creds;
|
|
230
|
+
}
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { getCodexAccountHealthSnapshot, type CodexCooldownSource } from "../codex/routing";
|
|
2
|
+
import { getAnthropicAccountHealthSnapshot } from "./anthropic-routing";
|
|
3
|
+
import { isAccountNeedsReauth } from "../codex/account-runtime-state";
|
|
4
|
+
import { getCodexAccountCredential, listCodexAccountIds } from "../codex/account-store";
|
|
5
|
+
import { MAIN_CODEX_ACCOUNT_ID } from "../codex/main-account";
|
|
6
|
+
import { configuredAdminToken } from "../lib/admin-secrets";
|
|
7
|
+
import { maskAccountId } from "../lib/privacy";
|
|
8
|
+
import { findLiveProxy, probeHostname } from "../server/proxy-liveness";
|
|
9
|
+
import { loadAuthStore, peekAuthStore, peekOAuthRefreshIntent, readOAuthRefreshIntent } from "./store";
|
|
10
|
+
import type { ProviderAccount } from "./types";
|
|
11
|
+
|
|
12
|
+
export type OAuthAccountHealth =
|
|
13
|
+
| { status: "healthy" }
|
|
14
|
+
| { status: "cooldown"; until: string; reason: "rate_limit" | "quota" }
|
|
15
|
+
| { status: "reauth_required"; reason: "unauthorized" | "forbidden" | "refresh_failed" }
|
|
16
|
+
| { status: "warning"; reason: "refresh_conflict" | "metadata_mismatch" | "stale_credentials" };
|
|
17
|
+
|
|
18
|
+
export type OAuthHealthLabel =
|
|
19
|
+
| "Healthy"
|
|
20
|
+
| "Rate limited"
|
|
21
|
+
| "Quota limited"
|
|
22
|
+
| "Reauthentication required"
|
|
23
|
+
| "Refresh failed"
|
|
24
|
+
| "Metadata mismatch"
|
|
25
|
+
| "Credential conflict";
|
|
26
|
+
|
|
27
|
+
/** Shared masked-id fallback when `maskAccountId` returns nullish. */
|
|
28
|
+
export const MASKED_ACCOUNT_FALLBACK = "account-…????";
|
|
29
|
+
|
|
30
|
+
export type OAuthHealthEntry = {
|
|
31
|
+
provider: string;
|
|
32
|
+
accountId: string;
|
|
33
|
+
health: OAuthAccountHealth;
|
|
34
|
+
action?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type OAuthAccountHealthFields = {
|
|
38
|
+
health: OAuthAccountHealth;
|
|
39
|
+
healthLabel: OAuthHealthLabel;
|
|
40
|
+
healthSummary: string;
|
|
41
|
+
healthAction?: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type CollectOAuthHealthOptions = {
|
|
45
|
+
/** Skip chmod/backup side effects when reading credential files (doctor/status). */
|
|
46
|
+
observeOnly?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* When true (default), include Codex entries from this process's in-memory maps.
|
|
49
|
+
* CLI surfaces should prefer {@link collectOAuthHealthEntriesForCli} which queries the live proxy.
|
|
50
|
+
*/
|
|
51
|
+
includeLocalCodex?: boolean;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type OAuthWarningReason = "refresh_conflict" | "metadata_mismatch" | "stale_credentials";
|
|
55
|
+
|
|
56
|
+
export function projectOAuthAccountHealth(input: {
|
|
57
|
+
needsReauth?: boolean;
|
|
58
|
+
reauthReason?: "unauthorized" | "forbidden" | "refresh_failed";
|
|
59
|
+
cooldownUntilMs?: number;
|
|
60
|
+
cooldownReason?: "rate_limit" | "quota";
|
|
61
|
+
warningReason?: OAuthWarningReason;
|
|
62
|
+
now?: number;
|
|
63
|
+
}): OAuthAccountHealth {
|
|
64
|
+
const now = input.now ?? Date.now();
|
|
65
|
+
if (input.needsReauth) {
|
|
66
|
+
return { status: "reauth_required", reason: input.reauthReason ?? "refresh_failed" };
|
|
67
|
+
}
|
|
68
|
+
if (
|
|
69
|
+
typeof input.cooldownUntilMs === "number"
|
|
70
|
+
&& Number.isFinite(input.cooldownUntilMs)
|
|
71
|
+
&& input.cooldownUntilMs > now
|
|
72
|
+
) {
|
|
73
|
+
return {
|
|
74
|
+
status: "cooldown",
|
|
75
|
+
until: new Date(input.cooldownUntilMs).toISOString(),
|
|
76
|
+
reason: input.cooldownReason ?? "quota",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (input.warningReason) {
|
|
80
|
+
return { status: "warning", reason: input.warningReason };
|
|
81
|
+
}
|
|
82
|
+
return { status: "healthy" };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Codex pool accounts are not a public `ocx login` provider; reauth is dashboard-driven. */
|
|
86
|
+
export const CODEX_REAUTH_ACTION = "reauthenticate via the dashboard Codex account pool";
|
|
87
|
+
|
|
88
|
+
function actionFor(provider: string, health: OAuthAccountHealth): string | undefined {
|
|
89
|
+
if (health.status === "reauth_required") {
|
|
90
|
+
if (provider === "codex") return CODEX_REAUTH_ACTION;
|
|
91
|
+
return `run \`ocx login ${provider}\``;
|
|
92
|
+
}
|
|
93
|
+
if (health.status === "cooldown") {
|
|
94
|
+
// Keep the transported deadline machine-readable (ISO); presentation layers localize/format.
|
|
95
|
+
return `wait until ${health.until} or start a new session with another eligible account`;
|
|
96
|
+
}
|
|
97
|
+
if (health.status === "warning" && health.reason === "refresh_conflict") {
|
|
98
|
+
return "re-run `ocx doctor` after ensuring only one proxy process writes the credential store";
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function oauthHealthLabel(health: OAuthAccountHealth): OAuthHealthLabel {
|
|
104
|
+
switch (health.status) {
|
|
105
|
+
case "healthy":
|
|
106
|
+
return "Healthy";
|
|
107
|
+
case "cooldown":
|
|
108
|
+
return health.reason === "rate_limit" ? "Rate limited" : "Quota limited";
|
|
109
|
+
case "reauth_required":
|
|
110
|
+
return health.reason === "refresh_failed" ? "Refresh failed" : "Reauthentication required";
|
|
111
|
+
case "warning":
|
|
112
|
+
switch (health.reason) {
|
|
113
|
+
case "refresh_conflict":
|
|
114
|
+
return "Credential conflict";
|
|
115
|
+
case "metadata_mismatch":
|
|
116
|
+
return "Metadata mismatch";
|
|
117
|
+
case "stale_credentials":
|
|
118
|
+
return "Refresh failed";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function displayAccountForHealth(accountId: string): string {
|
|
124
|
+
if (accountId === MAIN_CODEX_ACCOUNT_ID) return "main account";
|
|
125
|
+
return maskAccountId(accountId) ?? MASKED_ACCOUNT_FALLBACK;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function oauthHealthSummary(
|
|
129
|
+
provider: string,
|
|
130
|
+
accountId: string,
|
|
131
|
+
health: OAuthAccountHealth,
|
|
132
|
+
action?: string,
|
|
133
|
+
): string {
|
|
134
|
+
const masked = displayAccountForHealth(accountId);
|
|
135
|
+
let summary: string;
|
|
136
|
+
switch (health.status) {
|
|
137
|
+
case "healthy":
|
|
138
|
+
summary = `${provider} ${masked}: healthy`;
|
|
139
|
+
break;
|
|
140
|
+
case "cooldown": {
|
|
141
|
+
const why = health.reason === "rate_limit" ? "rate limited" : "quota limited";
|
|
142
|
+
summary = `${provider} ${masked}: ${why} until ${health.until}. Routing for this account is paused until then.`;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case "reauth_required":
|
|
146
|
+
summary = `${provider} ${masked}: reauthentication required (${health.reason.replaceAll("_", " ")}).`;
|
|
147
|
+
break;
|
|
148
|
+
case "warning":
|
|
149
|
+
summary = `${provider} ${masked}: ${health.reason.replaceAll("_", " ")}.`;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
if (action && health.status !== "healthy") {
|
|
153
|
+
return `${summary} Next: ${action}`;
|
|
154
|
+
}
|
|
155
|
+
return summary;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function oauthAccountHealthFields(
|
|
159
|
+
provider: string,
|
|
160
|
+
accountId: string,
|
|
161
|
+
health: OAuthAccountHealth,
|
|
162
|
+
): OAuthAccountHealthFields {
|
|
163
|
+
const action = actionFor(provider, health);
|
|
164
|
+
return {
|
|
165
|
+
health,
|
|
166
|
+
healthLabel: oauthHealthLabel(health),
|
|
167
|
+
healthSummary: oauthHealthSummary(provider, accountId, health, action),
|
|
168
|
+
...(action ? { healthAction: action } : {}),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function projectStoredOAuthAccountHealth(
|
|
173
|
+
provider: string,
|
|
174
|
+
account: ProviderAccount,
|
|
175
|
+
now = Date.now(),
|
|
176
|
+
opts: { observeOnly?: boolean } = {},
|
|
177
|
+
): OAuthAccountHealth {
|
|
178
|
+
const anthropicSnap = provider === "anthropic"
|
|
179
|
+
? getAnthropicAccountHealthSnapshot(account.id, now)
|
|
180
|
+
: null;
|
|
181
|
+
return projectOAuthAccountHealth({
|
|
182
|
+
needsReauth: account.needsReauth === true,
|
|
183
|
+
reauthReason: account.needsReauth === true ? "refresh_failed" : undefined,
|
|
184
|
+
cooldownUntilMs: anthropicSnap?.cooldownUntil,
|
|
185
|
+
cooldownReason: anthropicSnap?.cooldownSource === "retry-after" ? "rate_limit" : anthropicSnap ? "quota" : undefined,
|
|
186
|
+
warningReason: detectOAuthWarning(provider, account, opts.observeOnly === true, now),
|
|
187
|
+
now,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function projectCodexAccountHealth(input: {
|
|
192
|
+
accountId: string;
|
|
193
|
+
needsReauth: boolean;
|
|
194
|
+
now?: number;
|
|
195
|
+
}): OAuthAccountHealth {
|
|
196
|
+
const now = input.now ?? Date.now();
|
|
197
|
+
const snap = getCodexAccountHealthSnapshot(input.accountId, now);
|
|
198
|
+
return projectOAuthAccountHealth({
|
|
199
|
+
needsReauth: input.needsReauth,
|
|
200
|
+
reauthReason: input.needsReauth ? "refresh_failed" : undefined,
|
|
201
|
+
cooldownUntilMs: snap?.cooldownUntil,
|
|
202
|
+
cooldownReason: cooldownReasonFromSource(snap?.cooldownSource),
|
|
203
|
+
now,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Incomplete credentials warning. Kiro may intentionally store an empty refresh
|
|
209
|
+
* when authenticated via KIRO_ACCESS_TOKEN or a pasted access-only token, as long
|
|
210
|
+
* as the access token is still unexpired.
|
|
211
|
+
*/
|
|
212
|
+
export function detectOAuthWarning(
|
|
213
|
+
provider: string,
|
|
214
|
+
account: ProviderAccount,
|
|
215
|
+
observeOnly = false,
|
|
216
|
+
now = Date.now(),
|
|
217
|
+
): OAuthWarningReason | undefined {
|
|
218
|
+
const intent = observeOnly
|
|
219
|
+
? peekOAuthRefreshIntent(provider, account.id)
|
|
220
|
+
: readOAuthRefreshIntent(provider, account.id);
|
|
221
|
+
if (intent?.uncertain) return "refresh_conflict";
|
|
222
|
+
const cred = account.credential;
|
|
223
|
+
if (!cred?.access) return "stale_credentials";
|
|
224
|
+
if (!cred.refresh) {
|
|
225
|
+
if (
|
|
226
|
+
provider === "kiro"
|
|
227
|
+
&& typeof cred.expires === "number"
|
|
228
|
+
&& Number.isFinite(cred.expires)
|
|
229
|
+
&& cred.expires > now
|
|
230
|
+
) {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
return "stale_credentials";
|
|
234
|
+
}
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function cooldownReasonFromSource(
|
|
239
|
+
source: CodexCooldownSource | undefined,
|
|
240
|
+
): "rate_limit" | "quota" | undefined {
|
|
241
|
+
if (!source) return undefined;
|
|
242
|
+
return source === "retry-after" ? "rate_limit" : "quota";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function pushEntry(
|
|
246
|
+
entries: OAuthHealthEntry[],
|
|
247
|
+
provider: string,
|
|
248
|
+
accountId: string,
|
|
249
|
+
health: OAuthAccountHealth,
|
|
250
|
+
): void {
|
|
251
|
+
const action = actionFor(provider, health);
|
|
252
|
+
entries.push({
|
|
253
|
+
provider,
|
|
254
|
+
accountId,
|
|
255
|
+
health,
|
|
256
|
+
...(action ? { action } : {}),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function collectLocalCodexEntries(now: number): OAuthHealthEntry[] {
|
|
261
|
+
const entries: OAuthHealthEntry[] = [];
|
|
262
|
+
const codexIds = new Set(listCodexAccountIds());
|
|
263
|
+
codexIds.add(MAIN_CODEX_ACCOUNT_ID);
|
|
264
|
+
for (const accountId of codexIds) {
|
|
265
|
+
const snap = getCodexAccountHealthSnapshot(accountId, now);
|
|
266
|
+
const needsReauth = isAccountNeedsReauth(accountId);
|
|
267
|
+
const hasPoolCredential = accountId !== MAIN_CODEX_ACCOUNT_ID && getCodexAccountCredential(accountId) !== null;
|
|
268
|
+
if (!hasPoolCredential && !needsReauth && !snap) continue;
|
|
269
|
+
|
|
270
|
+
const health = projectOAuthAccountHealth({
|
|
271
|
+
needsReauth,
|
|
272
|
+
reauthReason: needsReauth ? "refresh_failed" : undefined,
|
|
273
|
+
cooldownUntilMs: snap?.cooldownUntil,
|
|
274
|
+
cooldownReason: cooldownReasonFromSource(snap?.cooldownSource),
|
|
275
|
+
now,
|
|
276
|
+
});
|
|
277
|
+
pushEntry(entries, "codex", accountId, health);
|
|
278
|
+
}
|
|
279
|
+
return entries;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function collectOAuthHealthEntries(
|
|
283
|
+
now = Date.now(),
|
|
284
|
+
opts: CollectOAuthHealthOptions = {},
|
|
285
|
+
): OAuthHealthEntry[] {
|
|
286
|
+
const observeOnly = opts.observeOnly === true;
|
|
287
|
+
const includeLocalCodex = opts.includeLocalCodex !== false;
|
|
288
|
+
const entries: OAuthHealthEntry[] = [];
|
|
289
|
+
const store = observeOnly ? peekAuthStore() : loadAuthStore();
|
|
290
|
+
|
|
291
|
+
for (const [provider, set] of Object.entries(store)) {
|
|
292
|
+
for (const account of set.accounts) {
|
|
293
|
+
const health = projectStoredOAuthAccountHealth(provider, account, now, { observeOnly });
|
|
294
|
+
pushEntry(entries, provider, account.id, health);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (includeLocalCodex) {
|
|
299
|
+
for (const entry of collectLocalCodexEntries(now)) entries.push(entry);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return entries;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
type ProxyCodexAccountHealth = {
|
|
306
|
+
id: string;
|
|
307
|
+
health?: OAuthAccountHealth;
|
|
308
|
+
needsReauth?: boolean;
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const KNOWN_HEALTH_STATUSES = new Set(["healthy", "cooldown", "reauth_required", "warning"]);
|
|
312
|
+
|
|
313
|
+
function coerceRemoteAccountHealth(
|
|
314
|
+
account: ProxyCodexAccountHealth,
|
|
315
|
+
): OAuthAccountHealth {
|
|
316
|
+
const raw = account.health;
|
|
317
|
+
if (raw && typeof raw === "object" && KNOWN_HEALTH_STATUSES.has((raw as { status?: string }).status ?? "")) {
|
|
318
|
+
return raw;
|
|
319
|
+
}
|
|
320
|
+
return projectOAuthAccountHealth({ needsReauth: account.needsReauth === true });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
type LiveProxyCodexHealthResult = {
|
|
324
|
+
source: CodexHealthSource;
|
|
325
|
+
entries: OAuthHealthEntry[] | null;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
async function fetchCodexHealthFromLiveProxy(
|
|
329
|
+
fetchImpl: typeof fetch = fetch,
|
|
330
|
+
findLiveProxyImpl: typeof findLiveProxy = findLiveProxy,
|
|
331
|
+
): Promise<LiveProxyCodexHealthResult> {
|
|
332
|
+
const live = await findLiveProxyImpl();
|
|
333
|
+
if (!live) return { source: "unavailable", entries: null };
|
|
334
|
+
// This is a management-plane endpoint. A data-plane service token is intentionally not
|
|
335
|
+
// interchangeable with the admin credential even on loopback.
|
|
336
|
+
const token = configuredAdminToken();
|
|
337
|
+
const headers: Record<string, string> = {};
|
|
338
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
339
|
+
try {
|
|
340
|
+
const res = await fetchImpl(
|
|
341
|
+
`http://${probeHostname(live.hostname)}:${live.port}/api/codex-auth/accounts`,
|
|
342
|
+
{ headers, signal: AbortSignal.timeout(4000) },
|
|
343
|
+
);
|
|
344
|
+
if (res.status === 401 || res.status === 403) {
|
|
345
|
+
return { source: "management-auth-failed", entries: null };
|
|
346
|
+
}
|
|
347
|
+
if (!res.ok) return { source: "management-api-unavailable", entries: null };
|
|
348
|
+
const json = await res.json() as { accounts?: ProxyCodexAccountHealth[] };
|
|
349
|
+
if (!Array.isArray(json.accounts)) return { source: "management-api-unavailable", entries: null };
|
|
350
|
+
const entries: OAuthHealthEntry[] = [];
|
|
351
|
+
for (const account of json.accounts) {
|
|
352
|
+
if (!account?.id || typeof account.id !== "string") continue;
|
|
353
|
+
pushEntry(entries, "codex", account.id, coerceRemoteAccountHealth(account));
|
|
354
|
+
}
|
|
355
|
+
return { source: "management-api", entries };
|
|
356
|
+
} catch {
|
|
357
|
+
return { source: "management-api-unavailable", entries: null };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** How CLI/doctor obtained Codex cooldown/reauth (proxy memory only lives in the proxy). */
|
|
362
|
+
export type CodexHealthSource =
|
|
363
|
+
| "management-api"
|
|
364
|
+
| "unavailable"
|
|
365
|
+
| "management-auth-failed"
|
|
366
|
+
| "management-api-unavailable";
|
|
367
|
+
|
|
368
|
+
export type OAuthCliHealthReport = {
|
|
369
|
+
entries: OAuthHealthEntry[];
|
|
370
|
+
codexHealthSource: CodexHealthSource;
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
/** Shown by `ocx status` / `ocx doctor` when the proxy management API is unreachable. */
|
|
374
|
+
export const CODEX_HEALTH_UNAVAILABLE_NOTE =
|
|
375
|
+
"Codex health: unavailable (proxy not running; live cooldown/reauth requires the management API)";
|
|
376
|
+
export const CODEX_HEALTH_AUTH_FAILED_NOTE =
|
|
377
|
+
"Codex health: unavailable (proxy running; management authentication failed)";
|
|
378
|
+
export const CODEX_HEALTH_MANAGEMENT_API_UNAVAILABLE_NOTE =
|
|
379
|
+
"Codex health: unavailable (proxy running; management API did not return account health)";
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* CLI/doctor collector: observe-only OAuth store reads, and Codex health only from the
|
|
383
|
+
* running proxy management API. Never reads proxy process-local maps from the CLI process.
|
|
384
|
+
*/
|
|
385
|
+
export async function collectOAuthHealthEntriesForCli(
|
|
386
|
+
now = Date.now(),
|
|
387
|
+
deps: {
|
|
388
|
+
fetchImpl?: typeof fetch;
|
|
389
|
+
findLiveProxyImpl?: typeof findLiveProxy;
|
|
390
|
+
} = {},
|
|
391
|
+
): Promise<OAuthCliHealthReport> {
|
|
392
|
+
const entries = collectOAuthHealthEntries(now, { observeOnly: true, includeLocalCodex: false });
|
|
393
|
+
const remote = await fetchCodexHealthFromLiveProxy(deps.fetchImpl, deps.findLiveProxyImpl);
|
|
394
|
+
if (remote.entries) {
|
|
395
|
+
for (const entry of remote.entries) entries.push(entry);
|
|
396
|
+
return { entries, codexHealthSource: "management-api" };
|
|
397
|
+
}
|
|
398
|
+
return { entries, codexHealthSource: remote.source };
|
|
399
|
+
}
|