@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,586 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ocx opencode [opencode args...]` — launch opencode wired to the local proxy.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `ocx claude` (src/cli/claude.ts): ensure the proxy is running, then exec the
|
|
5
|
+
* client with stdio inherited. The wiring channel differs — opencode reads providers
|
|
6
|
+
* from merged JSON config layers rather than env slots.
|
|
7
|
+
*
|
|
8
|
+
* The launcher never copies or rewrites the user's opencode config files. It may read
|
|
9
|
+
* global/project config to detect an existing `provider.opencodex` override, then injects
|
|
10
|
+
* only the generated provider block through OpenCode's inline runtime layer
|
|
11
|
+
* (`OPENCODE_CONFIG_CONTENT`), which outranks project/global/custom config and avoids
|
|
12
|
+
* duplicating API keys, MCP credentials, or breaking relative `{file:…}` paths.
|
|
13
|
+
*
|
|
14
|
+
* The admission key is never serialized into that inline config. The provider block
|
|
15
|
+
* carries opencode's documented `{env:VAR}` reference and the real value is passed
|
|
16
|
+
* only through the child process environment.
|
|
17
|
+
*/
|
|
18
|
+
import { spawn } from "node:child_process";
|
|
19
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
20
|
+
import { homedir } from "node:os";
|
|
21
|
+
import { dirname, join } from "node:path";
|
|
22
|
+
import { loadConfig } from "../config";
|
|
23
|
+
import {
|
|
24
|
+
OPENCODE_API_KEY_ENV,
|
|
25
|
+
OPENCODE_CONFIG_SCHEMA,
|
|
26
|
+
OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG,
|
|
27
|
+
OPENCODE_PROVIDER_ID,
|
|
28
|
+
buildOpencodeProviderBlockFromCatalog,
|
|
29
|
+
opencodeGlobalConfigPath,
|
|
30
|
+
} from "../clients/config-export";
|
|
31
|
+
import type {
|
|
32
|
+
OpencodeCatalogModel,
|
|
33
|
+
OpencodeGeneratedConfig,
|
|
34
|
+
OpencodeLaunchEnv,
|
|
35
|
+
OpencodeProviderBlock,
|
|
36
|
+
} from "../clients/config-export";
|
|
37
|
+
import { visibleNativeSlugs } from "../codex/catalog";
|
|
38
|
+
import { commandInvocation } from "../lib/win-exec";
|
|
39
|
+
import { loadServiceTokenFromFile, serviceApiTokenFilePath } from "../lib/service-secrets";
|
|
40
|
+
import { providerCodexAccountMode } from "../providers/registry";
|
|
41
|
+
import { findLiveProxy, probeHostname, type LiveProxy } from "../server/proxy-liveness";
|
|
42
|
+
import type { OcxConfig } from "../types";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The provider-block serializer, its constants, and the config-path helpers now live in
|
|
46
|
+
* `src/clients/config-export.ts`, shared with every other client-config export surface.
|
|
47
|
+
* They are re-exported here so the launcher's long-standing import surface is unchanged.
|
|
48
|
+
*/
|
|
49
|
+
export {
|
|
50
|
+
OPENCODE_API_KEY_ENV,
|
|
51
|
+
OPENCODE_API_KEY_ENV_REF,
|
|
52
|
+
OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG,
|
|
53
|
+
OPENCODE_PROVIDER_ID,
|
|
54
|
+
SCHEMA_REQUIRED_OUTPUT_BUDGET,
|
|
55
|
+
buildOpencodeProviderBlockFromCatalog,
|
|
56
|
+
opencodeGlobalConfigPath,
|
|
57
|
+
opencodeProxyBaseUrl,
|
|
58
|
+
} from "../clients/config-export";
|
|
59
|
+
export type {
|
|
60
|
+
OpencodeCatalogModel,
|
|
61
|
+
OpencodeGeneratedConfig,
|
|
62
|
+
OpencodeLaunchEnv,
|
|
63
|
+
OpencodeModelEntry,
|
|
64
|
+
OpencodeProviderBlock,
|
|
65
|
+
} from "../clients/config-export";
|
|
66
|
+
|
|
67
|
+
/** One proxy-routed model destined for the generated provider block. */
|
|
68
|
+
export interface OpencodeRoutedModel {
|
|
69
|
+
provider: string;
|
|
70
|
+
id: string;
|
|
71
|
+
/** Authoritative context window (CatalogModel.contextWindow); optional. */
|
|
72
|
+
contextWindow?: number;
|
|
73
|
+
/** Authoritative display label (CatalogModel.displayName); optional. */
|
|
74
|
+
displayName?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Row shape from authenticated GET /api/models on the running proxy. */
|
|
78
|
+
export interface OpencodeProxyModelRow {
|
|
79
|
+
provider?: string;
|
|
80
|
+
id?: string;
|
|
81
|
+
namespaced?: string;
|
|
82
|
+
native?: boolean;
|
|
83
|
+
disabled?: boolean;
|
|
84
|
+
displayName?: string;
|
|
85
|
+
contextWindow?: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const PROJECT_CONFIG_FILENAMES = ["opencode.json", "opencode.jsonc"] as const;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* OpenCode's inline runtime config layer. It merges after project/global/custom config
|
|
92
|
+
* and carries only the generated provider block for this launch.
|
|
93
|
+
*/
|
|
94
|
+
export const OPENCODE_CONFIG_CONTENT_ENV = "OPENCODE_CONFIG_CONTENT";
|
|
95
|
+
|
|
96
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
97
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Strip `//` and block comments outside string literals. Escape-aware so a quote inside
|
|
102
|
+
* an escaped sequence cannot flip string state and expose config text to the stripper.
|
|
103
|
+
*/
|
|
104
|
+
function stripJsonComments(text: string): string {
|
|
105
|
+
let out = "";
|
|
106
|
+
let inString = false;
|
|
107
|
+
let inLine = false;
|
|
108
|
+
let inBlock = false;
|
|
109
|
+
for (let i = 0; i < text.length; i++) {
|
|
110
|
+
const ch = text[i]!;
|
|
111
|
+
const next = text[i + 1];
|
|
112
|
+
if (inLine) {
|
|
113
|
+
if (ch === "\n") {
|
|
114
|
+
inLine = false;
|
|
115
|
+
out += ch;
|
|
116
|
+
}
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (inBlock) {
|
|
120
|
+
// Newlines are preserved so JSON.parse error positions stay meaningful.
|
|
121
|
+
if (ch === "\n") out += ch;
|
|
122
|
+
else if (ch === "*" && next === "/") { inBlock = false; i++; }
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (inString) {
|
|
126
|
+
out += ch;
|
|
127
|
+
if (ch === "\\") {
|
|
128
|
+
const escaped = text[i + 1];
|
|
129
|
+
if (escaped !== undefined) { out += escaped; i++; }
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (ch === "\"") inString = false;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (ch === "\"") { inString = true; out += ch; continue; }
|
|
136
|
+
if (ch === "/" && next === "/") { inLine = true; i++; continue; }
|
|
137
|
+
if (ch === "/" && next === "*") { inBlock = true; i++; continue; }
|
|
138
|
+
out += ch;
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Drop commas that sit directly before `}` or `]`, ignoring string contents. */
|
|
144
|
+
function stripTrailingCommas(text: string): string {
|
|
145
|
+
let out = "";
|
|
146
|
+
let inString = false;
|
|
147
|
+
for (let i = 0; i < text.length; i++) {
|
|
148
|
+
const ch = text[i]!;
|
|
149
|
+
if (inString) {
|
|
150
|
+
out += ch;
|
|
151
|
+
if (ch === "\\") {
|
|
152
|
+
const escaped = text[i + 1];
|
|
153
|
+
if (escaped !== undefined) { out += escaped; i++; }
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (ch === "\"") inString = false;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (ch === "\"") { inString = true; out += ch; continue; }
|
|
160
|
+
if (ch === ",") {
|
|
161
|
+
let j = i + 1;
|
|
162
|
+
while (j < text.length && /\s/.test(text[j]!)) j++;
|
|
163
|
+
if (text[j] === "}" || text[j] === "]") continue;
|
|
164
|
+
}
|
|
165
|
+
out += ch;
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* opencode documents opencode.json as JSONC, so a valid user config may carry comments
|
|
172
|
+
* or trailing commas. Strict JSON.parse runs first and untouched — the tolerant path is
|
|
173
|
+
* only attempted when that throws, keeping well-formed configs away from the stripper.
|
|
174
|
+
*/
|
|
175
|
+
export function parseJsonc(text: string): unknown {
|
|
176
|
+
try {
|
|
177
|
+
return JSON.parse(text);
|
|
178
|
+
} catch {
|
|
179
|
+
return JSON.parse(stripTrailingCommas(stripJsonComments(text)));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Model key as the proxy routes it: `provider/id` for routed models, bare slug for native OpenAI entries. */
|
|
184
|
+
export function opencodeModelKey(provider: string, id: string): string {
|
|
185
|
+
return provider === "native" ? id : `${provider}/${id}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Native OpenAI slugs advertised to opencode. Omitted in Codex Direct mode because native
|
|
190
|
+
* chat-completions require the caller's real ChatGPT OAuth bearer, not proxy admission.
|
|
191
|
+
*/
|
|
192
|
+
export function opencodeLaunchNativeSlugs(config: OcxConfig): string[] {
|
|
193
|
+
if (providerCodexAccountMode("openai", config.providers?.openai) === "direct") return [];
|
|
194
|
+
return [...visibleNativeSlugs(config)];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Back-compat helper for unit tests that assemble slugs/routed rows directly. */
|
|
198
|
+
export function buildOpencodeProviderBlock(
|
|
199
|
+
port: number,
|
|
200
|
+
nativeSlugs: readonly string[],
|
|
201
|
+
routedModels: readonly OpencodeRoutedModel[],
|
|
202
|
+
nativeContextWindow: (slug: string) => number | undefined = () => undefined,
|
|
203
|
+
hostname?: string,
|
|
204
|
+
config: OcxConfig = OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG,
|
|
205
|
+
): OpencodeProviderBlock {
|
|
206
|
+
const catalog: OpencodeCatalogModel[] = [
|
|
207
|
+
...nativeSlugs.map(id => ({
|
|
208
|
+
namespaced: id,
|
|
209
|
+
native: true,
|
|
210
|
+
provider: "openai",
|
|
211
|
+
id,
|
|
212
|
+
contextWindow: nativeContextWindow(id),
|
|
213
|
+
})),
|
|
214
|
+
...routedModels.map(model => ({
|
|
215
|
+
namespaced: opencodeModelKey(model.provider, model.id),
|
|
216
|
+
native: false,
|
|
217
|
+
provider: model.provider,
|
|
218
|
+
id: model.id,
|
|
219
|
+
contextWindow: model.contextWindow,
|
|
220
|
+
displayName: model.displayName,
|
|
221
|
+
})),
|
|
222
|
+
];
|
|
223
|
+
return buildOpencodeProviderBlockFromCatalog(port, catalog, hostname, config);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Default deadline for authenticated GET /api/models during `ocx opencode` launch. */
|
|
227
|
+
export const OPENCODE_PROXY_MODELS_TIMEOUT_MS = 8_000;
|
|
228
|
+
|
|
229
|
+
/** Fetch the live model catalog from a running proxy's management API. */
|
|
230
|
+
export async function fetchOpencodeProxyModels(
|
|
231
|
+
live: LiveProxy,
|
|
232
|
+
apiKey: string,
|
|
233
|
+
deps: { fetchImpl?: typeof fetch; timeoutMs?: number } = {},
|
|
234
|
+
): Promise<OpencodeProxyModelRow[]> {
|
|
235
|
+
const baseUrl = `http://${probeHostname(live.hostname)}:${live.port}`;
|
|
236
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
237
|
+
const headers = new Headers({ Accept: "application/json" });
|
|
238
|
+
const token = apiKey.trim();
|
|
239
|
+
if (token) headers.set("X-OpenCodex-API-Key", token);
|
|
240
|
+
const controller = new AbortController();
|
|
241
|
+
const timeout = setTimeout(() => controller.abort(), deps.timeoutMs ?? OPENCODE_PROXY_MODELS_TIMEOUT_MS);
|
|
242
|
+
const abortIfTimedOut = (): Promise<never> => new Promise((_, reject) => {
|
|
243
|
+
if (controller.signal.aborted) {
|
|
244
|
+
reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
controller.signal.addEventListener(
|
|
248
|
+
"abort",
|
|
249
|
+
() => reject(new DOMException("The operation was aborted.", "AbortError")),
|
|
250
|
+
{ once: true },
|
|
251
|
+
);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
let response: Response;
|
|
255
|
+
let text: string;
|
|
256
|
+
try {
|
|
257
|
+
response = await Promise.race([
|
|
258
|
+
fetchImpl(`${baseUrl}/api/models`, {
|
|
259
|
+
headers,
|
|
260
|
+
signal: controller.signal,
|
|
261
|
+
}),
|
|
262
|
+
abortIfTimedOut(),
|
|
263
|
+
]);
|
|
264
|
+
text = await Promise.race([response.text(), abortIfTimedOut()]);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
const timedOut = error instanceof Error && error.name === "AbortError";
|
|
267
|
+
throw new Error(
|
|
268
|
+
timedOut
|
|
269
|
+
? "Management API timed out while fetching /api/models."
|
|
270
|
+
: `Management API is unreachable: ${error instanceof Error ? error.message : String(error)}`,
|
|
271
|
+
);
|
|
272
|
+
} finally {
|
|
273
|
+
clearTimeout(timeout);
|
|
274
|
+
}
|
|
275
|
+
let body: unknown = null;
|
|
276
|
+
if (text) {
|
|
277
|
+
try { body = JSON.parse(text); }
|
|
278
|
+
catch { body = text; }
|
|
279
|
+
}
|
|
280
|
+
if (!response.ok) {
|
|
281
|
+
const message = body && typeof body === "object" && typeof (body as Record<string, unknown>).error === "string"
|
|
282
|
+
? (body as Record<string, string>).error
|
|
283
|
+
: `Management request failed (${response.status})`;
|
|
284
|
+
throw new Error(message);
|
|
285
|
+
}
|
|
286
|
+
if (!Array.isArray(body)) {
|
|
287
|
+
throw new Error("Management API returned an unexpected /api/models payload.");
|
|
288
|
+
}
|
|
289
|
+
return body as OpencodeProxyModelRow[];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Visible OpenCode catalog entries from proxy /api/models rows. Disabled rows are omitted;
|
|
294
|
+
* native rows are omitted in Codex Direct mode.
|
|
295
|
+
*/
|
|
296
|
+
export function opencodeCatalogFromProxyRows(
|
|
297
|
+
rows: readonly OpencodeProxyModelRow[],
|
|
298
|
+
config: OcxConfig,
|
|
299
|
+
): OpencodeCatalogModel[] {
|
|
300
|
+
const omitNative = providerCodexAccountMode("openai", config.providers?.openai) === "direct";
|
|
301
|
+
const seen = new Set<string>();
|
|
302
|
+
const catalog: OpencodeCatalogModel[] = [];
|
|
303
|
+
for (const row of rows) {
|
|
304
|
+
const namespaced = row.namespaced?.trim();
|
|
305
|
+
if (!namespaced || row.disabled === true) continue;
|
|
306
|
+
if (omitNative && row.native === true) continue;
|
|
307
|
+
if (seen.has(namespaced)) continue;
|
|
308
|
+
seen.add(namespaced);
|
|
309
|
+
catalog.push({
|
|
310
|
+
namespaced,
|
|
311
|
+
native: row.native === true,
|
|
312
|
+
provider: row.provider,
|
|
313
|
+
id: row.id,
|
|
314
|
+
contextWindow: row.contextWindow,
|
|
315
|
+
displayName: row.displayName,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
return catalog;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export type OpencodeRuntimeConfigError = { error: string };
|
|
322
|
+
|
|
323
|
+
/** True when mergeOpencodeRuntimeConfig rejected inherited inline config. */
|
|
324
|
+
export function isOpencodeRuntimeConfigError(
|
|
325
|
+
value: OpencodeGeneratedConfig | OpencodeRuntimeConfigError,
|
|
326
|
+
): value is OpencodeRuntimeConfigError {
|
|
327
|
+
return "error" in value;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Merge inherited `OPENCODE_CONFIG_CONTENT` and override only `provider.opencodex`.
|
|
332
|
+
* When no inline layer is present, emit the minimal runtime object for this launcher.
|
|
333
|
+
*/
|
|
334
|
+
export function mergeOpencodeRuntimeConfig(
|
|
335
|
+
inheritedContent: string | undefined,
|
|
336
|
+
providerBlock: OpencodeProviderBlock,
|
|
337
|
+
): OpencodeGeneratedConfig | OpencodeRuntimeConfigError {
|
|
338
|
+
if (!inheritedContent?.trim()) {
|
|
339
|
+
return {
|
|
340
|
+
$schema: OPENCODE_CONFIG_SCHEMA,
|
|
341
|
+
provider: { [OPENCODE_PROVIDER_ID]: providerBlock },
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
let parsed: unknown;
|
|
345
|
+
try {
|
|
346
|
+
parsed = JSON.parse(inheritedContent);
|
|
347
|
+
} catch {
|
|
348
|
+
return { error: "OPENCODE_CONFIG_CONTENT is not valid JSON." };
|
|
349
|
+
}
|
|
350
|
+
if (!isRecord(parsed)) {
|
|
351
|
+
return { error: "OPENCODE_CONFIG_CONTENT must be a JSON object." };
|
|
352
|
+
}
|
|
353
|
+
const existingProvider = parsed.provider;
|
|
354
|
+
if (existingProvider !== undefined && !isRecord(existingProvider)) {
|
|
355
|
+
return { error: "OPENCODE_CONFIG_CONTENT provider must be a JSON object when present." };
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
...parsed,
|
|
359
|
+
$schema: typeof parsed.$schema === "string" ? parsed.$schema : OPENCODE_CONFIG_SCHEMA,
|
|
360
|
+
provider: {
|
|
361
|
+
...(isRecord(existingProvider) ? existingProvider : {}),
|
|
362
|
+
[OPENCODE_PROVIDER_ID]: providerBlock,
|
|
363
|
+
},
|
|
364
|
+
} as OpencodeGeneratedConfig;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** Inline runtime config carrying only the provider block this launcher owns. */
|
|
368
|
+
export function buildOpencodeConfig(
|
|
369
|
+
port: number,
|
|
370
|
+
nativeSlugs: readonly string[],
|
|
371
|
+
routedModels: readonly OpencodeRoutedModel[],
|
|
372
|
+
nativeContextWindow: (slug: string) => number | undefined = () => undefined,
|
|
373
|
+
hostname?: string,
|
|
374
|
+
config: OcxConfig = OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG,
|
|
375
|
+
): OpencodeGeneratedConfig {
|
|
376
|
+
const merged = mergeOpencodeRuntimeConfig(
|
|
377
|
+
undefined,
|
|
378
|
+
buildOpencodeProviderBlock(port, nativeSlugs, routedModels, nativeContextWindow, hostname, config),
|
|
379
|
+
);
|
|
380
|
+
if (isOpencodeRuntimeConfigError(merged)) {
|
|
381
|
+
throw new Error(merged.error);
|
|
382
|
+
}
|
|
383
|
+
return merged;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** Serialize the inline runtime config OpenCode merges on launch. */
|
|
387
|
+
export function serializeOpencodeRuntimeConfig(config: OpencodeGeneratedConfig): string {
|
|
388
|
+
return JSON.stringify(config);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function findGitRoot(start: string): string | null {
|
|
392
|
+
let dir = start;
|
|
393
|
+
while (true) {
|
|
394
|
+
if (existsSync(join(dir, ".git"))) return dir;
|
|
395
|
+
const parent = dirname(dir);
|
|
396
|
+
if (parent === dir) return null;
|
|
397
|
+
dir = parent;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function configFileDefinesProvider(path: string): boolean {
|
|
402
|
+
if (!existsSync(path)) return false;
|
|
403
|
+
try {
|
|
404
|
+
const parsed = parseJsonc(readFileSync(path, "utf8"));
|
|
405
|
+
return isRecord(parsed) && isRecord(parsed.provider) && OPENCODE_PROVIDER_ID in parsed.provider;
|
|
406
|
+
} catch {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Detect global or project-level opencode.json/jsonc that defines our provider key.
|
|
413
|
+
* Informational only: the inline runtime layer from `OPENCODE_CONFIG_CONTENT` outranks both.
|
|
414
|
+
*/
|
|
415
|
+
export function opencodeProviderOverridePath(
|
|
416
|
+
cwd: string,
|
|
417
|
+
env: OpencodeLaunchEnv = process.env,
|
|
418
|
+
home: string = homedir(),
|
|
419
|
+
): string | null {
|
|
420
|
+
const globalPath = opencodeGlobalConfigPath(env, home);
|
|
421
|
+
if (configFileDefinesProvider(globalPath)) return globalPath;
|
|
422
|
+
|
|
423
|
+
const gitRoot = findGitRoot(cwd);
|
|
424
|
+
let dir = cwd;
|
|
425
|
+
while (true) {
|
|
426
|
+
for (const name of PROJECT_CONFIG_FILENAMES) {
|
|
427
|
+
const candidate = join(dir, name);
|
|
428
|
+
if (configFileDefinesProvider(candidate)) return candidate;
|
|
429
|
+
}
|
|
430
|
+
if (gitRoot && dir === gitRoot) break;
|
|
431
|
+
const parent = dirname(dir);
|
|
432
|
+
if (parent === dir) break;
|
|
433
|
+
dir = parent;
|
|
434
|
+
}
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** @deprecated Use {@link opencodeProviderOverridePath}. */
|
|
439
|
+
export function projectConfigOverridesProvider(cwd: string): string | null {
|
|
440
|
+
return opencodeProviderOverridePath(cwd);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function serviceTokenLookupEnv(env: OpencodeLaunchEnv): OpencodeLaunchEnv {
|
|
444
|
+
if (env.OCX_API_TOKEN_FILE?.trim()) return env;
|
|
445
|
+
return { ...env, OCX_API_TOKEN_FILE: serviceApiTokenFilePath() };
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Child env for a detached `ocx start` from `ocx opencode`. When the admission token is
|
|
450
|
+
* not already in the environment, pass through an existing `OCX_API_TOKEN_FILE` or the
|
|
451
|
+
* default hardened service token path so `handleStart` can load it before bind.
|
|
452
|
+
*/
|
|
453
|
+
export function opencodeProxyStartEnv(base: OpencodeLaunchEnv = process.env): OpencodeLaunchEnv {
|
|
454
|
+
const withTokenFile = base.OPENCODEX_API_AUTH_TOKEN?.trim()
|
|
455
|
+
? base
|
|
456
|
+
: serviceTokenLookupEnv(base);
|
|
457
|
+
return { ...withTokenFile, OCX_SERVICE: "1" };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Env assembly (unit-tested). Inherited inline config is merged and only
|
|
462
|
+
* `provider.opencodex` is replaced; disk config layers stay untouched. The admission
|
|
463
|
+
* key travels in the child env rather than in the inline config payload.
|
|
464
|
+
*/
|
|
465
|
+
export function buildOpencodeEnv(
|
|
466
|
+
providerBlock: OpencodeProviderBlock,
|
|
467
|
+
apiKey: string,
|
|
468
|
+
base: OpencodeLaunchEnv,
|
|
469
|
+
): OpencodeLaunchEnv | OpencodeRuntimeConfigError {
|
|
470
|
+
const runtimeConfig = mergeOpencodeRuntimeConfig(base[OPENCODE_CONFIG_CONTENT_ENV], providerBlock);
|
|
471
|
+
if (isOpencodeRuntimeConfigError(runtimeConfig)) return runtimeConfig;
|
|
472
|
+
return {
|
|
473
|
+
...base,
|
|
474
|
+
[OPENCODE_CONFIG_CONTENT_ENV]: serializeOpencodeRuntimeConfig(runtimeConfig),
|
|
475
|
+
[OPENCODE_API_KEY_ENV]: apiKey,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Admission key for the proxy: env token, hardened service token file, configured API
|
|
481
|
+
* key, then the open-loopback placeholder. Never serialized into runtime config.
|
|
482
|
+
*/
|
|
483
|
+
export function opencodeApiKey(config: OcxConfig, env: OpencodeLaunchEnv = process.env): string {
|
|
484
|
+
const envToken = env.OPENCODEX_API_AUTH_TOKEN?.trim();
|
|
485
|
+
if (envToken) return envToken;
|
|
486
|
+
const serviceToken = loadServiceTokenFromFile(serviceTokenLookupEnv(env));
|
|
487
|
+
if (serviceToken) return serviceToken;
|
|
488
|
+
return config.apiKeys?.[0]?.key || "ocx";
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async function ensureProxyForOpencode(config: OcxConfig): Promise<LiveProxy | null> {
|
|
492
|
+
const live = await findLiveProxy();
|
|
493
|
+
if (live) return live;
|
|
494
|
+
const cfgPort = config.port;
|
|
495
|
+
const pinPort = typeof cfgPort === "number" && cfgPort > 0 ? cfgPort : 10100;
|
|
496
|
+
const child = spawn(process.execPath, [process.argv[1], "start", "--port", String(pinPort)], {
|
|
497
|
+
detached: true,
|
|
498
|
+
stdio: "ignore",
|
|
499
|
+
windowsHide: true,
|
|
500
|
+
env: opencodeProxyStartEnv(process.env) as NodeJS.ProcessEnv,
|
|
501
|
+
});
|
|
502
|
+
// Without a listener an 'error' (bad argv[1], EMFILE, AV denial) throws synchronously
|
|
503
|
+
// and kills this process; the health poll below already reports the failure properly.
|
|
504
|
+
child.on("error", () => { /* handled by the deadline loop returning null */ });
|
|
505
|
+
child.unref();
|
|
506
|
+
const deadline = Date.now() + 8_000;
|
|
507
|
+
while (Date.now() < deadline) {
|
|
508
|
+
const started = await findLiveProxy();
|
|
509
|
+
if (started) return started;
|
|
510
|
+
await new Promise(resolve => setTimeout(resolve, 250));
|
|
511
|
+
}
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const OPENCODE_INSTALL_HINT = "❌ `opencode` CLI not found. Install it first: npm install -g opencode-ai";
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* cmd.exe reports command-not-found as exit 9009 (the win32 launcher routes `.cmd`
|
|
519
|
+
* shims through cmd.exe, so ENOENT never fires there). Signal exits are not hints.
|
|
520
|
+
* Same contract as claudeNotFoundHint (devlog 260715_cross_platform_audit/020).
|
|
521
|
+
*/
|
|
522
|
+
export function opencodeNotFoundHint(
|
|
523
|
+
code: number | null,
|
|
524
|
+
signal: NodeJS.Signals | null,
|
|
525
|
+
platform: NodeJS.Platform = process.platform,
|
|
526
|
+
): string | null {
|
|
527
|
+
return platform === "win32" && code === 9009 && !signal ? OPENCODE_INSTALL_HINT : null;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export async function cmdOpencode(args: string[]): Promise<number> {
|
|
531
|
+
const config = loadConfig();
|
|
532
|
+
const live = await ensureProxyForOpencode(config);
|
|
533
|
+
if (!live) {
|
|
534
|
+
console.error("❌ Proxy did not become healthy after starting.");
|
|
535
|
+
return 1;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const apiKey = opencodeApiKey(config);
|
|
539
|
+
let proxyModels: OpencodeProxyModelRow[];
|
|
540
|
+
try {
|
|
541
|
+
proxyModels = await fetchOpencodeProxyModels(live, apiKey);
|
|
542
|
+
} catch (error) {
|
|
543
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
544
|
+
console.error(`❌ Could not fetch the model catalog from the proxy: ${reason}`);
|
|
545
|
+
return 1;
|
|
546
|
+
}
|
|
547
|
+
const catalog = opencodeCatalogFromProxyRows(proxyModels, config);
|
|
548
|
+
const providerBlock = buildOpencodeProviderBlockFromCatalog(
|
|
549
|
+
live.port,
|
|
550
|
+
catalog,
|
|
551
|
+
live.hostname,
|
|
552
|
+
config,
|
|
553
|
+
);
|
|
554
|
+
const baseUrl = providerBlock.options.baseURL;
|
|
555
|
+
const modelCount = catalog.length;
|
|
556
|
+
console.error(`✅ opencode wired to ${baseUrl} — ${modelCount} model(s) under provider \`${OPENCODE_PROVIDER_ID}\`.`);
|
|
557
|
+
console.error(" Your existing opencode config files are left untouched; only the runtime provider block is injected.");
|
|
558
|
+
const providerOverride = opencodeProviderOverridePath(process.cwd());
|
|
559
|
+
if (providerOverride) {
|
|
560
|
+
console.error(`ℹ ${providerOverride} also defines provider.${OPENCODE_PROVIDER_ID}; the runtime layer from ocx opencode overrides it for this launch.`);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const builtEnv = buildOpencodeEnv(providerBlock, apiKey, process.env);
|
|
564
|
+
if ("error" in builtEnv) {
|
|
565
|
+
console.error(`❌ ${builtEnv.error}`);
|
|
566
|
+
return 1;
|
|
567
|
+
}
|
|
568
|
+
const env = builtEnv;
|
|
569
|
+
return await new Promise<number>(resolve => {
|
|
570
|
+
const inv = commandInvocation("opencode", args);
|
|
571
|
+
const child = spawn(inv.file, inv.args, { stdio: "inherit", env: env as NodeJS.ProcessEnv, ...inv.options });
|
|
572
|
+
child.on("error", (err: NodeJS.ErrnoException) => {
|
|
573
|
+
if (err.code === "ENOENT") {
|
|
574
|
+
console.error(OPENCODE_INSTALL_HINT);
|
|
575
|
+
} else {
|
|
576
|
+
console.error(`❌ Failed to launch opencode: ${err.message}`);
|
|
577
|
+
}
|
|
578
|
+
resolve(1);
|
|
579
|
+
});
|
|
580
|
+
child.on("exit", (code, signal) => {
|
|
581
|
+
const hint = opencodeNotFoundHint(code, signal);
|
|
582
|
+
if (hint) console.error(hint);
|
|
583
|
+
resolve(signal ? 1 : code ?? 0);
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
}
|