@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,538 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { delimiter, join } from "node:path";
|
|
5
|
+
import { atomicWriteFile, getConfigDir } from "../config";
|
|
6
|
+
import { codexExecInvocation, isSpawnableCodexCandidate } from "./exec-invocation";
|
|
7
|
+
import { redactSecretString, redactUserPath } from "../lib/redact";
|
|
8
|
+
|
|
9
|
+
export type CodexRuntimeSource =
|
|
10
|
+
| "environment"
|
|
11
|
+
| "configured"
|
|
12
|
+
| "shim"
|
|
13
|
+
| "path"
|
|
14
|
+
| "fallback";
|
|
15
|
+
|
|
16
|
+
export interface ResolvedCodexRuntime {
|
|
17
|
+
command: string;
|
|
18
|
+
version: string | null;
|
|
19
|
+
source: CodexRuntimeSource;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RuntimeProbeFailure {
|
|
23
|
+
command: string;
|
|
24
|
+
source: CodexRuntimeSource;
|
|
25
|
+
reason: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface EffortClampDiagnostic {
|
|
29
|
+
runtimePath: string;
|
|
30
|
+
runtimeVersion: string | null;
|
|
31
|
+
removedEfforts: string[];
|
|
32
|
+
affectedModels: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ResolveCodexRuntimeResult {
|
|
36
|
+
runtime: ResolvedCodexRuntime;
|
|
37
|
+
failures: RuntimeProbeFailure[];
|
|
38
|
+
replacedConfigured?: { from: ResolvedCodexRuntime; reason: string };
|
|
39
|
+
newerAvailable?: ResolvedCodexRuntime;
|
|
40
|
+
/** Set when the selected runtime could not be written to codex-runtime.json. */
|
|
41
|
+
persistError?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type RuntimeExecFile = (
|
|
45
|
+
file: string,
|
|
46
|
+
args: string[],
|
|
47
|
+
options: {
|
|
48
|
+
encoding: "utf8";
|
|
49
|
+
stdio: ["ignore", "pipe", "ignore"];
|
|
50
|
+
timeout: number;
|
|
51
|
+
windowsHide: boolean;
|
|
52
|
+
shell?: boolean;
|
|
53
|
+
windowsVerbatimArguments?: boolean;
|
|
54
|
+
env?: NodeJS.ProcessEnv;
|
|
55
|
+
},
|
|
56
|
+
) => string;
|
|
57
|
+
|
|
58
|
+
export interface ResolveCodexRuntimeDeps {
|
|
59
|
+
env?: NodeJS.ProcessEnv;
|
|
60
|
+
platform?: NodeJS.Platform;
|
|
61
|
+
configDir?: string;
|
|
62
|
+
execFileSync?: RuntimeExecFile;
|
|
63
|
+
existsSync?: (path: string) => boolean;
|
|
64
|
+
readFileSync?: (path: string, encoding: "utf8") => string;
|
|
65
|
+
now?: () => number;
|
|
66
|
+
/**
|
|
67
|
+
* When false, stop after the first valid priority candidate (skip PATH-wide
|
|
68
|
+
* newerAvailable discovery). Use for hot UI/status paths.
|
|
69
|
+
*/
|
|
70
|
+
discoverAlternatives?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface PersistedRuntimeState {
|
|
74
|
+
version: 1;
|
|
75
|
+
command: string;
|
|
76
|
+
source: CodexRuntimeSource;
|
|
77
|
+
selectedVersion: string | null;
|
|
78
|
+
updatedAt: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const PERSIST_FILE = "codex-runtime.json";
|
|
82
|
+
const CLAMP_PERSIST_FILE = "codex-runtime-clamp.json";
|
|
83
|
+
|
|
84
|
+
export function codexRuntimeStatePath(configDir: string = getConfigDir()): string {
|
|
85
|
+
return join(configDir, PERSIST_FILE);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function codexRuntimeClampStatePath(configDir: string = getConfigDir()): string {
|
|
89
|
+
return join(configDir, CLAMP_PERSIST_FILE);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface PersistedClampState extends EffortClampDiagnostic {
|
|
93
|
+
version: 1;
|
|
94
|
+
updatedAt: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function loadLastEffortClamp(
|
|
98
|
+
deps: ResolveCodexRuntimeDeps = {},
|
|
99
|
+
): EffortClampDiagnostic | null {
|
|
100
|
+
const configDir = deps.configDir ?? getConfigDir();
|
|
101
|
+
const read = deps.readFileSync ?? ((path, encoding) => readFileSync(path, encoding));
|
|
102
|
+
try {
|
|
103
|
+
const raw = JSON.parse(read(codexRuntimeClampStatePath(configDir), "utf8")) as PersistedClampState;
|
|
104
|
+
if (raw?.version !== 1 || !Array.isArray(raw.removedEfforts)) return null;
|
|
105
|
+
return {
|
|
106
|
+
runtimePath: typeof raw.runtimePath === "string" ? raw.runtimePath : "codex",
|
|
107
|
+
runtimeVersion: typeof raw.runtimeVersion === "string" ? raw.runtimeVersion : null,
|
|
108
|
+
removedEfforts: raw.removedEfforts.filter((item): item is string => typeof item === "string"),
|
|
109
|
+
affectedModels: Array.isArray(raw.affectedModels)
|
|
110
|
+
? raw.affectedModels.filter((item): item is string => typeof item === "string")
|
|
111
|
+
: [],
|
|
112
|
+
};
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function persistEffortClamp(
|
|
119
|
+
diagnostic: EffortClampDiagnostic | null,
|
|
120
|
+
deps: ResolveCodexRuntimeDeps = {},
|
|
121
|
+
): void {
|
|
122
|
+
const configDir = deps.configDir ?? getConfigDir();
|
|
123
|
+
const path = codexRuntimeClampStatePath(configDir);
|
|
124
|
+
if (!diagnostic || diagnostic.removedEfforts.length === 0) {
|
|
125
|
+
try {
|
|
126
|
+
unlinkSync(path);
|
|
127
|
+
} catch {
|
|
128
|
+
/* absent is fine */
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const payload: PersistedClampState = {
|
|
133
|
+
version: 1,
|
|
134
|
+
updatedAt: new Date((deps.now ?? Date.now)()).toISOString(),
|
|
135
|
+
runtimePath: diagnostic.runtimePath,
|
|
136
|
+
runtimeVersion: diagnostic.runtimeVersion,
|
|
137
|
+
removedEfforts: diagnostic.removedEfforts,
|
|
138
|
+
affectedModels: diagnostic.affectedModels,
|
|
139
|
+
};
|
|
140
|
+
mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
141
|
+
atomicWriteFile(path, `${JSON.stringify(payload, null, 2)}\n`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function displayCodexRuntimePath(command: string): string {
|
|
145
|
+
if (command === "codex") return "codex";
|
|
146
|
+
return redactUserPath(command);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function parseCodexVersionOutput(raw: string): string | null {
|
|
150
|
+
const text = String(raw || "").trim();
|
|
151
|
+
if (!text) return null;
|
|
152
|
+
const match = text.match(/\b(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\b/);
|
|
153
|
+
return match?.[1] ?? null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Compare dotted Codex versions. Returns negative if a < b. */
|
|
157
|
+
export function compareCodexVersions(a: string | null, b: string | null): number {
|
|
158
|
+
if (!a && !b) return 0;
|
|
159
|
+
if (!a) return -1;
|
|
160
|
+
if (!b) return 1;
|
|
161
|
+
const parse = (value: string) => {
|
|
162
|
+
const dash = value.indexOf("-");
|
|
163
|
+
const core = dash < 0 ? value : value.slice(0, dash);
|
|
164
|
+
const pre = dash < 0 ? "" : value.slice(dash + 1);
|
|
165
|
+
const parts = core.split(".").map(part => Number.parseInt(part, 10) || 0);
|
|
166
|
+
// SemVer prerelease identifiers are '.'-separated; keep hyphenated ids intact.
|
|
167
|
+
const preParts = pre
|
|
168
|
+
? pre.split(".").map(part => (/^\d+$/.test(part) ? Number.parseInt(part, 10) : part))
|
|
169
|
+
: [];
|
|
170
|
+
return { parts, preParts };
|
|
171
|
+
};
|
|
172
|
+
const left = parse(a);
|
|
173
|
+
const right = parse(b);
|
|
174
|
+
const len = Math.max(left.parts.length, right.parts.length);
|
|
175
|
+
for (let i = 0; i < len; i += 1) {
|
|
176
|
+
const diff = (left.parts[i] ?? 0) - (right.parts[i] ?? 0);
|
|
177
|
+
if (diff !== 0) return diff;
|
|
178
|
+
}
|
|
179
|
+
if (left.preParts.length === 0 && right.preParts.length > 0) return 1;
|
|
180
|
+
if (left.preParts.length > 0 && right.preParts.length === 0) return -1;
|
|
181
|
+
const preLen = Math.max(left.preParts.length, right.preParts.length);
|
|
182
|
+
for (let i = 0; i < preLen; i += 1) {
|
|
183
|
+
const lp = left.preParts[i];
|
|
184
|
+
const rp = right.preParts[i];
|
|
185
|
+
if (lp === undefined) return -1;
|
|
186
|
+
if (rp === undefined) return 1;
|
|
187
|
+
if (typeof lp === "number" && typeof rp === "number") {
|
|
188
|
+
if (lp !== rp) return lp - rp;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (typeof lp === "number") return -1; // numeric < non-numeric (SemVer)
|
|
192
|
+
if (typeof rp === "number") return 1;
|
|
193
|
+
if (lp < rp) return -1;
|
|
194
|
+
if (lp > rp) return 1;
|
|
195
|
+
}
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function loadPersistedCodexRuntime(
|
|
200
|
+
deps: ResolveCodexRuntimeDeps = {},
|
|
201
|
+
): PersistedRuntimeState | null {
|
|
202
|
+
const configDir = deps.configDir ?? getConfigDir();
|
|
203
|
+
const read = deps.readFileSync ?? ((path, encoding) => readFileSync(path, encoding));
|
|
204
|
+
try {
|
|
205
|
+
const raw = JSON.parse(read(codexRuntimeStatePath(configDir), "utf8")) as PersistedRuntimeState;
|
|
206
|
+
if (raw?.version !== 1 || typeof raw.command !== "string" || !raw.command.trim()) return null;
|
|
207
|
+
return raw;
|
|
208
|
+
} catch {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function persistCodexRuntime(
|
|
214
|
+
runtime: ResolvedCodexRuntime,
|
|
215
|
+
deps: ResolveCodexRuntimeDeps = {},
|
|
216
|
+
): void {
|
|
217
|
+
const configDir = deps.configDir ?? getConfigDir();
|
|
218
|
+
mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
219
|
+
const payload: PersistedRuntimeState = {
|
|
220
|
+
version: 1,
|
|
221
|
+
command: runtime.command,
|
|
222
|
+
source: runtime.source,
|
|
223
|
+
selectedVersion: runtime.version,
|
|
224
|
+
updatedAt: new Date((deps.now ?? Date.now)()).toISOString(),
|
|
225
|
+
};
|
|
226
|
+
atomicWriteFile(codexRuntimeStatePath(configDir), `${JSON.stringify(payload, null, 2)}\n`);
|
|
227
|
+
// Same-process consumers must re-resolve; catalog cache is keyed by runtime identity.
|
|
228
|
+
resolveCache = null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function probeVersion(
|
|
232
|
+
command: string,
|
|
233
|
+
deps: ResolveCodexRuntimeDeps,
|
|
234
|
+
): { ok: true; version: string } | { ok: false; reason: string } {
|
|
235
|
+
const platform = deps.platform ?? process.platform;
|
|
236
|
+
if (command.includes("/") || command.includes("\\") || /^[A-Za-z]:/.test(command)) {
|
|
237
|
+
const exists = deps.existsSync ?? existsSync;
|
|
238
|
+
if (!exists(command)) return { ok: false, reason: "path does not exist" };
|
|
239
|
+
if (!isSpawnableCodexCandidate(command, platform)) {
|
|
240
|
+
return { ok: false, reason: "not a spawnable Codex launcher on this platform" };
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const execFile = deps.execFileSync ?? (execFileSync as unknown as RuntimeExecFile);
|
|
244
|
+
// Sandbox the probe's CODEX_HOME: a real Codex CLI creates state (tmp/, logs) under
|
|
245
|
+
// CODEX_HOME even for `--version`, and the probe inherits the caller's env — so a
|
|
246
|
+
// read-only `ocx status` would dirty the user's CODEX_HOME. Redirect it to a
|
|
247
|
+
// throwaway dir; if the sandbox cannot be created, skip the probe rather than
|
|
248
|
+
// probe with the inherited env (keeps probeVersion total: it never throws).
|
|
249
|
+
let probeHome: string | undefined;
|
|
250
|
+
try {
|
|
251
|
+
probeHome = mkdtempSync(join(tmpdir(), "ocx-codex-probe-"));
|
|
252
|
+
const invocation = codexExecInvocation(command, ["--version"], platform, {
|
|
253
|
+
env: deps.env,
|
|
254
|
+
exists: deps.existsSync,
|
|
255
|
+
});
|
|
256
|
+
const output = execFile(invocation.file, invocation.args, {
|
|
257
|
+
encoding: "utf8",
|
|
258
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
259
|
+
timeout: 8_000,
|
|
260
|
+
windowsHide: true,
|
|
261
|
+
env: { ...(deps.env ?? process.env), CODEX_HOME: probeHome },
|
|
262
|
+
...invocation.options,
|
|
263
|
+
});
|
|
264
|
+
const version = parseCodexVersionOutput(output);
|
|
265
|
+
if (!version) {
|
|
266
|
+
return { ok: false, reason: "unrecognized --version output" };
|
|
267
|
+
}
|
|
268
|
+
return { ok: true, version };
|
|
269
|
+
} catch (error) {
|
|
270
|
+
if (!probeHome) return { ok: false, reason: "probe sandbox unavailable" };
|
|
271
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
272
|
+
const redacted = redactUserPath(redactSecretString(message)).slice(0, 160);
|
|
273
|
+
return { ok: false, reason: `failed --version (${redacted})` };
|
|
274
|
+
} finally {
|
|
275
|
+
if (probeHome) {
|
|
276
|
+
// Nested catch: a transient Windows EBUSY must never mask the probe result.
|
|
277
|
+
try { rmSync(probeHome, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function shimCandidates(deps: ResolveCodexRuntimeDeps): string[] {
|
|
283
|
+
const configDir = deps.configDir ?? getConfigDir();
|
|
284
|
+
const read = deps.readFileSync ?? ((path, encoding) => readFileSync(path, encoding));
|
|
285
|
+
const platform = deps.platform ?? process.platform;
|
|
286
|
+
try {
|
|
287
|
+
const state = JSON.parse(read(join(configDir, "codex-shim.json"), "utf8")) as {
|
|
288
|
+
wrapperPath?: unknown;
|
|
289
|
+
originalPath?: unknown;
|
|
290
|
+
backupPath?: unknown;
|
|
291
|
+
wrappers?: Array<{ wrapperPath?: unknown; originalPath?: unknown; backupPath?: unknown }>;
|
|
292
|
+
};
|
|
293
|
+
const files = Array.isArray(state.wrappers) && state.wrappers.length > 0 ? state.wrappers : [state];
|
|
294
|
+
const out: string[] = [];
|
|
295
|
+
for (const file of files) {
|
|
296
|
+
for (const value of [file.backupPath, file.originalPath, file.wrapperPath]) {
|
|
297
|
+
if (typeof value !== "string" || value.length === 0) continue;
|
|
298
|
+
if (!isSpawnableCodexCandidate(value, platform)) continue;
|
|
299
|
+
out.push(value);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return [...new Set(out)];
|
|
303
|
+
} catch {
|
|
304
|
+
return [];
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function pathCandidates(deps: ResolveCodexRuntimeDeps): string[] {
|
|
309
|
+
const env = deps.env ?? process.env;
|
|
310
|
+
const platform = deps.platform ?? process.platform;
|
|
311
|
+
const out: string[] = [];
|
|
312
|
+
for (const dir of (env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
313
|
+
if (platform === "win32") {
|
|
314
|
+
out.push(join(dir, "codex.exe"), join(dir, "codex.cmd"));
|
|
315
|
+
} else {
|
|
316
|
+
out.push(join(dir, "codex"));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return [...new Set(out)];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
interface RankedCandidate {
|
|
323
|
+
command: string;
|
|
324
|
+
source: CodexRuntimeSource;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function tryCandidate(
|
|
328
|
+
candidate: RankedCandidate,
|
|
329
|
+
failures: RuntimeProbeFailure[],
|
|
330
|
+
deps: ResolveCodexRuntimeDeps,
|
|
331
|
+
): ResolvedCodexRuntime | null {
|
|
332
|
+
const probed = probeVersion(candidate.command, deps);
|
|
333
|
+
if (!probed.ok) {
|
|
334
|
+
failures.push({ command: candidate.command, source: candidate.source, reason: probed.reason });
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
command: candidate.command,
|
|
339
|
+
version: probed.version,
|
|
340
|
+
source: candidate.source,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function sameRuntimeCommand(a: string, b: string): boolean {
|
|
345
|
+
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** True when a persisted clamp diagnostic still applies to the currently selected runtime. */
|
|
349
|
+
export function effortClampAppliesToRuntime(
|
|
350
|
+
diagnostic: EffortClampDiagnostic | null | undefined,
|
|
351
|
+
runtime: Pick<ResolvedCodexRuntime, "command" | "version">,
|
|
352
|
+
): boolean {
|
|
353
|
+
if (!diagnostic || diagnostic.removedEfforts.length === 0) return false;
|
|
354
|
+
if (sameRuntimeCommand(diagnostic.runtimePath, runtime.command)) return true;
|
|
355
|
+
return Boolean(
|
|
356
|
+
diagnostic.runtimeVersion
|
|
357
|
+
&& runtime.version
|
|
358
|
+
&& diagnostic.runtimeVersion === runtime.version,
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const RESOLVE_CACHE_MS = 15_000;
|
|
363
|
+
let resolveCache: { key: string; at: number; value: ResolveCodexRuntimeResult } | null = null;
|
|
364
|
+
|
|
365
|
+
function persistedRuntimeCacheStamp(deps: ResolveCodexRuntimeDeps): string {
|
|
366
|
+
// Include on-disk selection so doctor --fix in another process busts this memo.
|
|
367
|
+
const configDir = deps.configDir ?? getConfigDir();
|
|
368
|
+
const read = deps.readFileSync ?? ((path, encoding) => readFileSync(path, encoding));
|
|
369
|
+
try {
|
|
370
|
+
const raw = JSON.parse(read(codexRuntimeStatePath(configDir), "utf8")) as PersistedRuntimeState;
|
|
371
|
+
if (raw?.version !== 1 || typeof raw.command !== "string") return "";
|
|
372
|
+
return `${raw.command}|${raw.selectedVersion ?? ""}|${raw.updatedAt ?? ""}`;
|
|
373
|
+
} catch {
|
|
374
|
+
return "";
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function resolveCacheKey(deps: ResolveCodexRuntimeDeps): string | null {
|
|
379
|
+
// Only memoize uninjected process-env resolves (settings/status hot paths).
|
|
380
|
+
if (deps.execFileSync || deps.existsSync || deps.readFileSync || deps.configDir || deps.now) {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
const env = deps.env ?? process.env;
|
|
384
|
+
return JSON.stringify({
|
|
385
|
+
cli: env.CODEX_CLI_PATH ?? "",
|
|
386
|
+
path: env.PATH ?? "",
|
|
387
|
+
platform: deps.platform ?? process.platform,
|
|
388
|
+
discover: deps.discoverAlternatives !== false,
|
|
389
|
+
home: process.env.OPENCODEX_HOME ?? "",
|
|
390
|
+
persisted: persistedRuntimeCacheStamp(deps),
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Resolve the single Codex runtime OpenCodex should use for sync, clamp, and probes.
|
|
396
|
+
*/
|
|
397
|
+
export function resolveCodexRuntime(deps: ResolveCodexRuntimeDeps = {}): ResolveCodexRuntimeResult {
|
|
398
|
+
const cacheKey = resolveCacheKey(deps);
|
|
399
|
+
if (cacheKey && resolveCache && resolveCache.key === cacheKey && Date.now() - resolveCache.at < RESOLVE_CACHE_MS) {
|
|
400
|
+
return resolveCache.value;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const result = resolveCodexRuntimeUncached(deps);
|
|
404
|
+
if (cacheKey) resolveCache = { key: cacheKey, at: Date.now(), value: result };
|
|
405
|
+
return result;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** Test-only: drop the short-lived process resolve cache. */
|
|
409
|
+
export function resetCodexRuntimeResolveCacheForTests(): void {
|
|
410
|
+
resolveCache = null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function resolveCodexRuntimeUncached(deps: ResolveCodexRuntimeDeps = {}): ResolveCodexRuntimeResult {
|
|
414
|
+
const env = deps.env ?? process.env;
|
|
415
|
+
const failures: RuntimeProbeFailure[] = [];
|
|
416
|
+
const ordered: RankedCandidate[] = [];
|
|
417
|
+
|
|
418
|
+
const envPath = env.CODEX_CLI_PATH?.trim();
|
|
419
|
+
if (envPath) ordered.push({ command: envPath, source: "environment" });
|
|
420
|
+
|
|
421
|
+
const persisted = loadPersistedCodexRuntime(deps);
|
|
422
|
+
if (persisted?.command) {
|
|
423
|
+
ordered.push({ command: persisted.command, source: "configured" });
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
for (const command of shimCandidates(deps)) {
|
|
427
|
+
ordered.push({ command, source: "shim" });
|
|
428
|
+
}
|
|
429
|
+
for (const command of pathCandidates(deps)) {
|
|
430
|
+
ordered.push({ command, source: "path" });
|
|
431
|
+
}
|
|
432
|
+
ordered.push({ command: "codex", source: "fallback" });
|
|
433
|
+
|
|
434
|
+
const seen = new Set<string>();
|
|
435
|
+
const valid: ResolvedCodexRuntime[] = [];
|
|
436
|
+
for (const candidate of ordered) {
|
|
437
|
+
const key = candidate.command.toLowerCase();
|
|
438
|
+
if (seen.has(key)) continue;
|
|
439
|
+
seen.add(key);
|
|
440
|
+
const resolved = tryCandidate(candidate, failures, deps);
|
|
441
|
+
if (!resolved) continue;
|
|
442
|
+
valid.push(resolved);
|
|
443
|
+
if (deps.discoverAlternatives === false) break;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (valid.length === 0) {
|
|
447
|
+
return {
|
|
448
|
+
runtime: { command: "codex", version: null, source: "fallback" },
|
|
449
|
+
failures,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Prefer first valid in priority order (environment → configured → shim → path → fallback).
|
|
454
|
+
let selected = valid[0]!;
|
|
455
|
+
let replacedConfigured: ResolveCodexRuntimeResult["replacedConfigured"];
|
|
456
|
+
|
|
457
|
+
const envValid = envPath
|
|
458
|
+
? valid.find(item => sameRuntimeCommand(item.command, envPath) && item.source === "environment")
|
|
459
|
+
: undefined;
|
|
460
|
+
if (envValid) selected = envValid;
|
|
461
|
+
|
|
462
|
+
if (persisted?.command) {
|
|
463
|
+
const configuredStillValid = valid.some(item => sameRuntimeCommand(item.command, persisted.command));
|
|
464
|
+
// Only emit replacement diagnostics when we actually searched alternatives or
|
|
465
|
+
// the preferred configured runtime was probed and rejected.
|
|
466
|
+
const configuredRejected = failures.some(item => sameRuntimeCommand(item.command, persisted.command));
|
|
467
|
+
if (!configuredStillValid && (configuredRejected || deps.discoverAlternatives !== false) && !envValid) {
|
|
468
|
+
replacedConfigured = {
|
|
469
|
+
from: {
|
|
470
|
+
command: persisted.command,
|
|
471
|
+
version: persisted.selectedVersion,
|
|
472
|
+
source: "configured",
|
|
473
|
+
},
|
|
474
|
+
reason: failures.find(item => sameRuntimeCommand(item.command, persisted.command))?.reason
|
|
475
|
+
?? "configured runtime is no longer valid",
|
|
476
|
+
};
|
|
477
|
+
// Keep priority-selected replacement (already in `selected`).
|
|
478
|
+
} else if (!envValid && configuredStillValid) {
|
|
479
|
+
// Stick to configured even when a later PATH entry is also valid.
|
|
480
|
+
selected = valid.find(item => sameRuntimeCommand(item.command, persisted.command)) ?? selected;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const newerAvailable = valid
|
|
485
|
+
.filter(item => !sameRuntimeCommand(item.command, selected.command))
|
|
486
|
+
.sort((a, b) => compareCodexVersions(b.version, a.version))[0];
|
|
487
|
+
const newer =
|
|
488
|
+
newerAvailable && compareCodexVersions(newerAvailable.version, selected.version) > 0
|
|
489
|
+
? newerAvailable
|
|
490
|
+
: undefined;
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
runtime: selected,
|
|
494
|
+
failures,
|
|
495
|
+
replacedConfigured,
|
|
496
|
+
newerAvailable: newer,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/** Resolve and persist a successful selection (unless source is ephemeral fallback-only with no path). */
|
|
501
|
+
export function resolveAndPersistCodexRuntime(
|
|
502
|
+
deps: ResolveCodexRuntimeDeps = {},
|
|
503
|
+
): ResolveCodexRuntimeResult {
|
|
504
|
+
const result = resolveCodexRuntime(deps);
|
|
505
|
+
// Only WRITE when the selection actually changed. persistCodexRuntime() clears the
|
|
506
|
+
// in-process resolve memo and persistedRuntimeCacheStamp() folds `updatedAt` into the
|
|
507
|
+
// cache key, so an unconditional rewrite made every caller re-run the ~1s
|
|
508
|
+
// `codex --version` probe even when the resolved runtime was byte-identical.
|
|
509
|
+
const persistedRuntime = loadPersistedCodexRuntime(deps);
|
|
510
|
+
const selectionUnchanged = persistedRuntime !== null
|
|
511
|
+
&& persistedRuntime.command === result.runtime.command
|
|
512
|
+
&& persistedRuntime.source === result.runtime.source
|
|
513
|
+
&& (persistedRuntime.selectedVersion ?? null) === (result.runtime.version ?? null);
|
|
514
|
+
if (result.runtime.command && result.runtime.source !== "fallback" && !selectionUnchanged) {
|
|
515
|
+
try {
|
|
516
|
+
persistCodexRuntime(result.runtime, deps);
|
|
517
|
+
} catch (error) {
|
|
518
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
519
|
+
const persistError = redactUserPath(redactSecretString(message)).slice(0, 200);
|
|
520
|
+
console.warn(`[opencodex] Failed to persist Codex runtime selection: ${persistError}`);
|
|
521
|
+
return { ...result, persistError };
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
export function formatRuntimeLogLine(runtime: ResolvedCodexRuntime): string {
|
|
528
|
+
const path = displayCodexRuntimePath(runtime.command);
|
|
529
|
+
return `[opencodex] Codex runtime: ${path} (version=${runtime.version ?? "unknown"}, source=${runtime.source})`;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
export function formatClampLogLines(diagnostic: EffortClampDiagnostic): string[] {
|
|
533
|
+
const efforts = diagnostic.removedEfforts.join(", ");
|
|
534
|
+
return [
|
|
535
|
+
`[opencodex] Removed unsupported reasoning efforts: ${efforts}`,
|
|
536
|
+
"[opencodex] Run ocx doctor for diagnosis and recovery.",
|
|
537
|
+
];
|
|
538
|
+
}
|