@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,257 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
5
|
+
import { atomicWriteFile, getConfigDir } from "../config";
|
|
6
|
+
import { hasStarPromptRun } from "../cli/star-prompt";
|
|
7
|
+
import {
|
|
8
|
+
type Channel,
|
|
9
|
+
currentVersion,
|
|
10
|
+
detectInstall,
|
|
11
|
+
latestVersion,
|
|
12
|
+
runUpdate,
|
|
13
|
+
updateCommandStr,
|
|
14
|
+
updateTag,
|
|
15
|
+
} from "./index";
|
|
16
|
+
|
|
17
|
+
const VERSION_FILENAME = "version.json";
|
|
18
|
+
const REFRESH_INTERVAL_MS = 20 * 60 * 60 * 1000; // 20h, matching codex-rs
|
|
19
|
+
const RELEASE_NOTES_URL = "https://github.com/lidge-jun/opencodex/releases/latest";
|
|
20
|
+
|
|
21
|
+
export interface VersionCache {
|
|
22
|
+
latest_version: string;
|
|
23
|
+
/** ISO-8601 (RFC3339) timestamp of the last successful registry check. */
|
|
24
|
+
last_checked_at: string;
|
|
25
|
+
dismissed_version?: string;
|
|
26
|
+
tag: Channel;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function versionFilePath(): string {
|
|
30
|
+
return join(getConfigDir(), VERSION_FILENAME);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Read the cached version info. Returns null on any error or when the cached
|
|
35
|
+
* channel differs from the current one (so a stable<->preview switch re-fetches
|
|
36
|
+
* instead of comparing across channels).
|
|
37
|
+
*/
|
|
38
|
+
export function readVersionCache(channel: Channel): VersionCache | null {
|
|
39
|
+
try {
|
|
40
|
+
const raw = readFileSync(versionFilePath(), "utf8");
|
|
41
|
+
const parsed = JSON.parse(raw) as Partial<VersionCache>;
|
|
42
|
+
if (typeof parsed.latest_version !== "string" || typeof parsed.last_checked_at !== "string") return null;
|
|
43
|
+
if (parsed.tag !== channel) return null;
|
|
44
|
+
return {
|
|
45
|
+
latest_version: parsed.latest_version,
|
|
46
|
+
last_checked_at: parsed.last_checked_at,
|
|
47
|
+
dismissed_version: typeof parsed.dismissed_version === "string" ? parsed.dismissed_version : undefined,
|
|
48
|
+
tag: parsed.tag,
|
|
49
|
+
};
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function writeVersionCache(cache: VersionCache): void {
|
|
56
|
+
try {
|
|
57
|
+
atomicWriteFile(versionFilePath(), `${JSON.stringify(cache)}\n`);
|
|
58
|
+
} catch {
|
|
59
|
+
/* best-effort; never block startup */
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function parseStable(v: string): [number, number, number] | null {
|
|
64
|
+
const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v.trim());
|
|
65
|
+
if (!m) return null;
|
|
66
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function parsePreview(v: string): [number, number, number, number] | null {
|
|
70
|
+
const m = /^(\d+)\.(\d+)\.(\d+)-preview\.(\d+)$/.exec(v.trim());
|
|
71
|
+
if (!m) return null;
|
|
72
|
+
return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function gt(a: number[], b: number[]): boolean {
|
|
76
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
77
|
+
const av = a[i] ?? 0;
|
|
78
|
+
const bv = b[i] ?? 0;
|
|
79
|
+
if (av !== bv) return av > bv;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Channel-aware "is latest newer than current?".
|
|
86
|
+
* - latest channel: compare maj.min.pat only; prereleases are never "newer"
|
|
87
|
+
* (parity with codex-rs), so stable users are not pushed onto previews.
|
|
88
|
+
* - preview channel: preview-vs-preview compares the trailing -preview.N; a
|
|
89
|
+
* stable release with a strictly higher base counts as newer (O3), while a
|
|
90
|
+
* stable release with the same base as the current preview does not.
|
|
91
|
+
*/
|
|
92
|
+
export function isNewer(latest: string, current: string, channel: Channel): boolean {
|
|
93
|
+
if (channel === "latest") {
|
|
94
|
+
const l = parseStable(latest);
|
|
95
|
+
const c = parseStable(current);
|
|
96
|
+
if (!l || !c) return false;
|
|
97
|
+
return gt(l, c);
|
|
98
|
+
}
|
|
99
|
+
// preview channel
|
|
100
|
+
const lPre = parsePreview(latest);
|
|
101
|
+
const cPre = parsePreview(current);
|
|
102
|
+
if (lPre && cPre) return gt(lPre, cPre);
|
|
103
|
+
|
|
104
|
+
const lStable = parseStable(latest);
|
|
105
|
+
if (lStable && cPre) {
|
|
106
|
+
// Stable release vs current preview: newer only if the base is strictly
|
|
107
|
+
// higher than the preview's base (equal base would be a downgrade nag).
|
|
108
|
+
return gt(lStable, [cPre[0], cPre[1], cPre[2]]);
|
|
109
|
+
}
|
|
110
|
+
const cStable = parseStable(current);
|
|
111
|
+
if (lStable && cStable) return gt(lStable, cStable);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function isSourceBuildVersion(v: string): boolean {
|
|
116
|
+
return v.trim() === "0.0.0";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The interactive/TTY + install-method gate shared with the star prompt. */
|
|
120
|
+
function interactiveGuardOk(): boolean {
|
|
121
|
+
return !(process.env.OCX_SERVICE || !process.stdin.isTTY || !process.stdout.isTTY);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Decide whether this run should even consider showing the prompt. Returns the
|
|
126
|
+
* channel + current version when eligible, else null. Eligibility requires a
|
|
127
|
+
* real global install, a non-source version, the interactive guard, and that
|
|
128
|
+
* the one-time star prompt has already run (first-run yield, O1).
|
|
129
|
+
*/
|
|
130
|
+
export function shouldConsider(): { channel: Channel; current: string } | null {
|
|
131
|
+
if (detectInstall() === "source") return null;
|
|
132
|
+
const current = currentVersion();
|
|
133
|
+
if (current === "?" || isSourceBuildVersion(current)) return null;
|
|
134
|
+
if (!interactiveGuardOk()) return null;
|
|
135
|
+
if (!hasStarPromptRun()) return null; // yield on the very first run
|
|
136
|
+
return { channel: updateTag(current), current };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** The cached upgrade version to surface, honoring the user's dismissal. */
|
|
140
|
+
export function getUpgradeVersionForPopup(
|
|
141
|
+
cache: VersionCache | null,
|
|
142
|
+
current: string,
|
|
143
|
+
channel: Channel,
|
|
144
|
+
): string | null {
|
|
145
|
+
if (!cache) return null;
|
|
146
|
+
if (!isNewer(cache.latest_version, current, channel)) return null;
|
|
147
|
+
if (cache.dismissed_version === cache.latest_version) return null;
|
|
148
|
+
return cache.latest_version;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function cacheIsStale(cache: VersionCache | null): boolean {
|
|
152
|
+
if (!cache) return true;
|
|
153
|
+
const checked = Date.parse(cache.last_checked_at);
|
|
154
|
+
if (!Number.isFinite(checked)) return true;
|
|
155
|
+
return Date.now() - checked > REFRESH_INTERVAL_MS;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* If the cache is missing or older than 20h, kick off a detached helper to
|
|
160
|
+
* refresh it without blocking this (soon-to-be daemon) process. Fire-and-forget.
|
|
161
|
+
*/
|
|
162
|
+
export function triggerBackgroundRefreshIfStale(channel: Channel, cache: VersionCache | null): void {
|
|
163
|
+
if (!cacheIsStale(cache)) return;
|
|
164
|
+
try {
|
|
165
|
+
const entry = process.argv[1];
|
|
166
|
+
if (!entry || !existsSync(entry)) return;
|
|
167
|
+
const child = spawn(process.execPath, [entry, "__refresh-version", channel], {
|
|
168
|
+
detached: true,
|
|
169
|
+
stdio: "ignore",
|
|
170
|
+
windowsHide: true,
|
|
171
|
+
env: { ...process.env, OCX_SERVICE: "1" }, // never let the helper prompt
|
|
172
|
+
});
|
|
173
|
+
child.unref();
|
|
174
|
+
} catch {
|
|
175
|
+
/* best-effort */
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Body of the hidden `__refresh-version` subcommand: fetch the latest version
|
|
181
|
+
* for the channel and persist it. Only advances `last_checked_at` on success so
|
|
182
|
+
* a failed fetch retries on the next start.
|
|
183
|
+
*/
|
|
184
|
+
export async function refreshVersionCache(channel: Channel): Promise<void> {
|
|
185
|
+
const latest = latestVersion(channel);
|
|
186
|
+
if (!latest) return; // do not dirty the cache or advance the timestamp
|
|
187
|
+
const prev = readVersionCache(channel);
|
|
188
|
+
writeVersionCache({
|
|
189
|
+
latest_version: latest,
|
|
190
|
+
last_checked_at: new Date().toISOString(),
|
|
191
|
+
dismissed_version: prev?.dismissed_version,
|
|
192
|
+
tag: channel,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Persist a dismissal so this exact version stops prompting. */
|
|
197
|
+
function dismissVersion(channel: Channel, version: string): void {
|
|
198
|
+
const cache = readVersionCache(channel);
|
|
199
|
+
if (!cache) return;
|
|
200
|
+
writeVersionCache({ ...cache, dismissed_version: version });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function renderPrompt(current: string, latest: string, channel: Channel): string {
|
|
204
|
+
const command = updateCommandStr(detectInstall(), channel);
|
|
205
|
+
return [
|
|
206
|
+
"",
|
|
207
|
+
` \x1b[38;5;141m✨ Update available!\x1b[0m \x1b[2m${current} -> ${latest}\x1b[0m`,
|
|
208
|
+
"",
|
|
209
|
+
` \x1b[2mRelease notes:\x1b[0m ${RELEASE_NOTES_URL}`,
|
|
210
|
+
"",
|
|
211
|
+
` 1) Update now (runs \`${command}\`)`,
|
|
212
|
+
" 2) Skip",
|
|
213
|
+
" 3) Skip until next version",
|
|
214
|
+
"",
|
|
215
|
+
" [1/2/3] (default 1): ",
|
|
216
|
+
].join("\n");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Interactive-only update prompt for `ocx start`. Must be called BEFORE the
|
|
221
|
+
* server binds a port / writes a PID, because "Update now" installs globally
|
|
222
|
+
* and exits. No-op for service/daemon/non-TTY runs and source checkouts.
|
|
223
|
+
* Never throws.
|
|
224
|
+
*/
|
|
225
|
+
export async function maybeShowUpdatePrompt(): Promise<void> {
|
|
226
|
+
try {
|
|
227
|
+
const eligible = shouldConsider();
|
|
228
|
+
if (!eligible) return;
|
|
229
|
+
const { channel, current } = eligible;
|
|
230
|
+
|
|
231
|
+
const cache = readVersionCache(channel);
|
|
232
|
+
triggerBackgroundRefreshIfStale(channel, cache);
|
|
233
|
+
|
|
234
|
+
const latest = getUpgradeVersionForPopup(cache, current, channel);
|
|
235
|
+
if (!latest) return;
|
|
236
|
+
|
|
237
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
238
|
+
let answer = "";
|
|
239
|
+
try {
|
|
240
|
+
answer = (await rl.question(renderPrompt(current, latest, channel))).trim();
|
|
241
|
+
} finally {
|
|
242
|
+
rl.close();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const choice = answer === "" ? "1" : answer;
|
|
246
|
+
if (choice === "1") {
|
|
247
|
+
await runUpdate();
|
|
248
|
+
console.log("\nRestart the proxy: ocx start");
|
|
249
|
+
process.exit(0);
|
|
250
|
+
} else if (choice === "3") {
|
|
251
|
+
dismissVersion(channel, latest);
|
|
252
|
+
}
|
|
253
|
+
// "2" (or anything else) -> Skip: continue this run unchanged.
|
|
254
|
+
} catch {
|
|
255
|
+
/* never let the update prompt disrupt startup */
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface NpmInvocationDeps {
|
|
2
|
+
cwd?: string;
|
|
3
|
+
exists?: (path: string) => boolean;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface NpmInvocation {
|
|
7
|
+
file: string;
|
|
8
|
+
args: string[];
|
|
9
|
+
options: { windowsVerbatimArguments?: boolean };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export declare function resolveNpmCommand(
|
|
13
|
+
platform?: NodeJS.Platform,
|
|
14
|
+
env?: Record<string, string | undefined>,
|
|
15
|
+
deps?: NpmInvocationDeps,
|
|
16
|
+
): string | null;
|
|
17
|
+
|
|
18
|
+
export declare function npmInvocation(
|
|
19
|
+
args: readonly string[],
|
|
20
|
+
platform?: NodeJS.Platform,
|
|
21
|
+
env?: Record<string, string | undefined>,
|
|
22
|
+
deps?: NpmInvocationDeps,
|
|
23
|
+
): NpmInvocation | null;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { win32 } from "node:path";
|
|
3
|
+
|
|
4
|
+
const CMD_META = /([()%!^"`<>&|;, *?])/g;
|
|
5
|
+
|
|
6
|
+
function escapeCmdArg(arg) {
|
|
7
|
+
let out = String(arg).replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, "$1$1");
|
|
8
|
+
return `"${out}"`.replace(CMD_META, "^$1");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function escapeCmdCommand(command) {
|
|
12
|
+
return command.replace(CMD_META, "^$1");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Whether a PATH entry *is* the current directory. The hijack this guards against is
|
|
17
|
+
* cmd.exe resolving a bare `npm` out of the directory opencodex was launched from, so
|
|
18
|
+
* only that exact directory has to be skipped — every candidate we hand to spawn is an
|
|
19
|
+
* absolute path, which is what actually defeats the implicit cwd-first search.
|
|
20
|
+
*
|
|
21
|
+
* Deliberately not a subtree test: npm's default Windows global prefix is
|
|
22
|
+
* `%AppData%\npm` (`C:\Users\x\AppData\Roaming\npm`), so excluding everything under the
|
|
23
|
+
* cwd would fail closed for anyone whose shell sits in their home directory — a normal
|
|
24
|
+
* setup, not the untrusted-project case this hardening is for.
|
|
25
|
+
*/
|
|
26
|
+
function isCurrentDirectory(cwd, entry) {
|
|
27
|
+
const left = win32.resolve(entry);
|
|
28
|
+
const right = win32.resolve(cwd);
|
|
29
|
+
return left.toLowerCase() === right.toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function cleanPathEntry(entry) {
|
|
33
|
+
const trimmed = entry.trim();
|
|
34
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) return trimmed.slice(1, -1);
|
|
35
|
+
return trimmed;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveNpmCommand(
|
|
39
|
+
platform = process.platform,
|
|
40
|
+
env = process.env,
|
|
41
|
+
deps = {},
|
|
42
|
+
) {
|
|
43
|
+
if (platform !== "win32") return "npm";
|
|
44
|
+
const exists = deps.exists ?? existsSync;
|
|
45
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
46
|
+
const extensions = (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD")
|
|
47
|
+
.split(";")
|
|
48
|
+
.filter(Boolean);
|
|
49
|
+
const pathEntries = (env.PATH ?? env.Path ?? "")
|
|
50
|
+
.split(win32.delimiter)
|
|
51
|
+
.map(cleanPathEntry)
|
|
52
|
+
.filter(Boolean);
|
|
53
|
+
|
|
54
|
+
for (const entry of pathEntries) {
|
|
55
|
+
if (!win32.isAbsolute(entry)) continue;
|
|
56
|
+
if (isCurrentDirectory(cwd, entry)) continue;
|
|
57
|
+
for (const extension of extensions) {
|
|
58
|
+
const candidate = win32.join(entry, `npm${extension.toLowerCase()}`);
|
|
59
|
+
if (exists(candidate)) return win32.resolve(candidate);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function systemCommandProcessor(env) {
|
|
66
|
+
const systemRoot = env.SystemRoot ?? env.windir;
|
|
67
|
+
if (systemRoot && win32.isAbsolute(systemRoot)) {
|
|
68
|
+
return win32.join(systemRoot, "System32", "cmd.exe");
|
|
69
|
+
}
|
|
70
|
+
const comSpec = env.ComSpec;
|
|
71
|
+
return comSpec && win32.isAbsolute(comSpec) ? win32.resolve(comSpec) : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function npmInvocation(
|
|
75
|
+
args,
|
|
76
|
+
platform = process.platform,
|
|
77
|
+
env = process.env,
|
|
78
|
+
deps = {},
|
|
79
|
+
) {
|
|
80
|
+
const npm = resolveNpmCommand(platform, env, deps);
|
|
81
|
+
if (!npm) return null;
|
|
82
|
+
if (platform !== "win32" || !/\.(cmd|bat)$/i.test(npm)) {
|
|
83
|
+
return { file: npm, args: [...args], options: {} };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const commandProcessor = systemCommandProcessor(env);
|
|
87
|
+
if (!commandProcessor) return null;
|
|
88
|
+
const line = [escapeCmdCommand(npm), ...args.map(escapeCmdArg)].join(" ");
|
|
89
|
+
return {
|
|
90
|
+
file: commandProcessor,
|
|
91
|
+
args: ["/d", "/s", "/c", `"${line}"`],
|
|
92
|
+
options: { windowsVerbatimArguments: true },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface WindowsTrayUpdatePlan {
|
|
2
|
+
installed: boolean;
|
|
3
|
+
running: boolean;
|
|
4
|
+
stopBeforeReplacement: boolean;
|
|
5
|
+
restoreOnFailure: boolean;
|
|
6
|
+
refreshAfterReplacement: boolean;
|
|
7
|
+
installArgs: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export declare function planWindowsTrayUpdate(status: { installed?: boolean; running?: boolean }): WindowsTrayUpdatePlan;
|
|
11
|
+
export declare function windowsTrayStopConfirmed(exitStatus: number | null, stillRunning: boolean): boolean;
|
|
12
|
+
export declare function handoffWindowsTrayForUpdate(
|
|
13
|
+
status: { installed?: boolean; running?: boolean },
|
|
14
|
+
io: {
|
|
15
|
+
stop: () => { exitStatus: number | null; running: boolean };
|
|
16
|
+
start: () => unknown;
|
|
17
|
+
},
|
|
18
|
+
): WindowsTrayUpdatePlan;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared, side-effect-free contract for preserving the Windows tray across all
|
|
3
|
+
* updater entry points (npm launcher, CLI updater, and GUI worker).
|
|
4
|
+
*/
|
|
5
|
+
export function planWindowsTrayUpdate(status) {
|
|
6
|
+
const installed = status?.installed === true;
|
|
7
|
+
const running = status?.running === true;
|
|
8
|
+
return {
|
|
9
|
+
installed,
|
|
10
|
+
running,
|
|
11
|
+
stopBeforeReplacement: running,
|
|
12
|
+
restoreOnFailure: running,
|
|
13
|
+
refreshAfterReplacement: installed,
|
|
14
|
+
installArgs: ["tray", "install", ...(!running ? ["--no-start"] : [])],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function windowsTrayStopConfirmed(exitStatus, stillRunning) {
|
|
19
|
+
return exitStatus === 0 && !stillRunning;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Stop before replacement and restore best-effort when the handoff cannot be confirmed. */
|
|
23
|
+
export function handoffWindowsTrayForUpdate(status, io) {
|
|
24
|
+
const plan = planWindowsTrayUpdate(status);
|
|
25
|
+
if (!plan.stopBeforeReplacement) return plan;
|
|
26
|
+
try {
|
|
27
|
+
const stopped = io.stop();
|
|
28
|
+
if (!windowsTrayStopConfirmed(stopped.exitStatus, stopped.running)) {
|
|
29
|
+
throw new Error("the tray still reports running after shutdown");
|
|
30
|
+
}
|
|
31
|
+
return plan;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (plan.restoreOnFailure) {
|
|
34
|
+
try { io.start(); } catch { /* preserve the handoff failure */ }
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/** Usage-shape diagnostic JSONL. Enable with `ocx debug usage on` or OPENCODEX_USAGE_DEBUG=1. */
|
|
2
|
+
|
|
3
|
+
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { getConfigDir } from "../config";
|
|
6
|
+
import { DEBUG_ENV } from "../lib/debug-settings";
|
|
7
|
+
import { recordOwnedConfigPath } from "../lib/config-ownership";
|
|
8
|
+
import type { DebugLogEntry } from "../lib/debug-log-buffer";
|
|
9
|
+
import { redactSecretString, redactSecrets } from "../lib/redact";
|
|
10
|
+
import type { OcxUsage } from "../types";
|
|
11
|
+
|
|
12
|
+
export const USAGE_DEBUG_ENV = DEBUG_ENV.usage;
|
|
13
|
+
export { isUsageDebugEnabled } from "../lib/debug-settings";
|
|
14
|
+
export const USAGE_DEBUG_BODY_SAMPLE_BYTES = 2048;
|
|
15
|
+
export const USAGE_DEBUG_MAX_LINES = 200;
|
|
16
|
+
export const USAGE_DEBUG_KEEP_LINES = 100;
|
|
17
|
+
|
|
18
|
+
export type UsageDebugBodyKind = "sse" | "json" | "other" | "none";
|
|
19
|
+
|
|
20
|
+
export interface UsageDebugRecord {
|
|
21
|
+
ts: number;
|
|
22
|
+
requestId: string;
|
|
23
|
+
provider: string;
|
|
24
|
+
model: string;
|
|
25
|
+
upstreamContentType: string | null;
|
|
26
|
+
upstreamStatus: number;
|
|
27
|
+
bodyKind: UsageDebugBodyKind;
|
|
28
|
+
bodySample: string;
|
|
29
|
+
extractedUsage: OcxUsage | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function usageDebugPath(): string {
|
|
33
|
+
return join(getConfigDir(), "usage-debug.jsonl");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function truncateForDebug(text: string, max = USAGE_DEBUG_BODY_SAMPLE_BYTES): string {
|
|
37
|
+
const redacted = redactSecretString(text);
|
|
38
|
+
if (redacted.length <= max) return redacted;
|
|
39
|
+
const cut = redacted.slice(0, max);
|
|
40
|
+
const remaining = redacted.length - max;
|
|
41
|
+
return `${cut}... [+${remaining} more]`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function ensureUsageDebugDir(): void {
|
|
45
|
+
const dir = getConfigDir();
|
|
46
|
+
recordOwnedConfigPath(dir, usageDebugPath());
|
|
47
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
48
|
+
try { chmodSync(dir, 0o700); } catch { /* best-effort */ }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function trimRollingFile(path: string): void {
|
|
52
|
+
const text = readFileSync(path, "utf-8");
|
|
53
|
+
const lines = text.split(/\r?\n/).filter(line => line.length > 0);
|
|
54
|
+
if (lines.length <= USAGE_DEBUG_MAX_LINES) return;
|
|
55
|
+
const kept = lines.slice(-USAGE_DEBUG_KEEP_LINES).join("\n") + "\n";
|
|
56
|
+
writeFileSync(path, kept, { encoding: "utf-8", mode: 0o600 });
|
|
57
|
+
try { chmodSync(path, 0o600); } catch { /* best-effort */ }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function appendUsageDebug(record: UsageDebugRecord): void {
|
|
61
|
+
try {
|
|
62
|
+
ensureUsageDebugDir();
|
|
63
|
+
const path = usageDebugPath();
|
|
64
|
+
const safeRecord = redactSecrets(record) as UsageDebugRecord;
|
|
65
|
+
appendFileSync(path, `${JSON.stringify(safeRecord)}\n`, { encoding: "utf-8", mode: 0o600 });
|
|
66
|
+
try { chmodSync(path, 0o600); } catch { /* best-effort */ }
|
|
67
|
+
if (existsSync(path)) trimRollingFile(path);
|
|
68
|
+
} catch {
|
|
69
|
+
/* debug capture must never break the proxy */
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Tail usage-debug.jsonl for GUI / management API (same shape as provider debug buffer). */
|
|
74
|
+
export function getUsageDebugLogEntries(options?: { after?: number; limit?: number }): DebugLogEntry[] {
|
|
75
|
+
const after = options?.after ?? 0;
|
|
76
|
+
const limit = options?.limit ?? 500;
|
|
77
|
+
try {
|
|
78
|
+
const content = readFileSync(usageDebugPath(), "utf8");
|
|
79
|
+
const entries: DebugLogEntry[] = [];
|
|
80
|
+
let seq = 0;
|
|
81
|
+
for (const line of content.split(/\r?\n/).filter(Boolean)) {
|
|
82
|
+
seq += 1;
|
|
83
|
+
if (after > 0 && seq <= after) continue;
|
|
84
|
+
try {
|
|
85
|
+
const record = JSON.parse(line) as UsageDebugRecord;
|
|
86
|
+
const at = typeof record.ts === "number" ? record.ts : 0;
|
|
87
|
+
entries.push({ seq, at, line });
|
|
88
|
+
} catch {
|
|
89
|
+
entries.push({ seq, at: 0, line });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (entries.length <= limit) return entries;
|
|
93
|
+
return entries.slice(-limit);
|
|
94
|
+
} catch {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
}
|