@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,86 @@
|
|
|
1
|
+
import { enforceAppOwnedMemoryBudget, type RetainedStoreSnapshot } from "../../lib/app-owned-memory";
|
|
2
|
+
import type { UsageSummary } from "../../usage/summary";
|
|
3
|
+
|
|
4
|
+
export type CachedUsageSummary = UsageSummary & {
|
|
5
|
+
historyTruncated: boolean;
|
|
6
|
+
truncatedPrefixBytes: number;
|
|
7
|
+
entriesTruncated: boolean;
|
|
8
|
+
entriesDropped: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
interface UsageSummaryCacheEntry {
|
|
12
|
+
revisionKey: string;
|
|
13
|
+
expiresAt: number;
|
|
14
|
+
summary: CachedUsageSummary;
|
|
15
|
+
revisionReadAt: number;
|
|
16
|
+
sizeBytes: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const usageSummaryCache = new Map<string, UsageSummaryCacheEntry>();
|
|
20
|
+
let usageSummaryCacheBytes = 0;
|
|
21
|
+
let oldestUsageSummaryKey: string | undefined;
|
|
22
|
+
let oldestUsageSummaryAt: number | null = null;
|
|
23
|
+
|
|
24
|
+
function recomputeOldestUsageSummary(): void {
|
|
25
|
+
oldestUsageSummaryKey = undefined;
|
|
26
|
+
oldestUsageSummaryAt = null;
|
|
27
|
+
for (const [key, entry] of usageSummaryCache) {
|
|
28
|
+
const readAt = entry.revisionReadAt;
|
|
29
|
+
if (oldestUsageSummaryAt !== null && readAt >= oldestUsageSummaryAt) continue;
|
|
30
|
+
oldestUsageSummaryKey = key;
|
|
31
|
+
oldestUsageSummaryAt = readAt;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function deleteUsageSummaryCacheEntry(key: string): number {
|
|
36
|
+
const entry = usageSummaryCache.get(key);
|
|
37
|
+
if (!entry) return 0;
|
|
38
|
+
usageSummaryCache.delete(key);
|
|
39
|
+
usageSummaryCacheBytes = Math.max(0, usageSummaryCacheBytes - entry.sizeBytes);
|
|
40
|
+
if (oldestUsageSummaryKey === key) recomputeOldestUsageSummary();
|
|
41
|
+
return entry.sizeBytes;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getUsageSummaryCacheEntry(key: string): UsageSummaryCacheEntry | undefined {
|
|
45
|
+
return usageSummaryCache.get(key);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function discardUsageSummaryCacheEntry(key: string): number {
|
|
49
|
+
return deleteUsageSummaryCacheEntry(key);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function setUsageSummaryCacheEntry(
|
|
53
|
+
key: string,
|
|
54
|
+
entry: Omit<UsageSummaryCacheEntry, "sizeBytes">,
|
|
55
|
+
): void {
|
|
56
|
+
deleteUsageSummaryCacheEntry(key);
|
|
57
|
+
const sizeBytes = Buffer.byteLength(JSON.stringify({ key, ...entry }), "utf8");
|
|
58
|
+
usageSummaryCache.set(key, { ...entry, sizeBytes });
|
|
59
|
+
usageSummaryCacheBytes += sizeBytes;
|
|
60
|
+
if (oldestUsageSummaryAt === null || entry.revisionReadAt < oldestUsageSummaryAt) {
|
|
61
|
+
oldestUsageSummaryKey = key;
|
|
62
|
+
oldestUsageSummaryAt = entry.revisionReadAt;
|
|
63
|
+
}
|
|
64
|
+
enforceAppOwnedMemoryBudget();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function usageSummaryRetainedStoreSnapshot(): RetainedStoreSnapshot {
|
|
68
|
+
return {
|
|
69
|
+
count: usageSummaryCache.size,
|
|
70
|
+
bytes: usageSummaryCacheBytes,
|
|
71
|
+
evictableBytes: usageSummaryCacheBytes,
|
|
72
|
+
pinnedBytes: 0,
|
|
73
|
+
oldestAt: oldestUsageSummaryAt,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function evictOldestUsageSummaryForBudget(): number {
|
|
78
|
+
return oldestUsageSummaryKey === undefined ? 0 : deleteUsageSummaryCacheEntry(oldestUsageSummaryKey);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function resetUsageSummaryCacheForTests(): void {
|
|
82
|
+
usageSummaryCache.clear();
|
|
83
|
+
usageSummaryCacheBytes = 0;
|
|
84
|
+
oldestUsageSummaryKey = undefined;
|
|
85
|
+
oldestUsageSummaryAt = null;
|
|
86
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import type { CatalogModel } from "../codex/catalog";
|
|
4
|
+
import { catalogModelSlug, invalidateCodexModelsCache, nativeModelRows, uniqueCatalogModelsForPublicList } from "../codex/catalog";
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_SUBAGENT_MODELS,
|
|
7
|
+
codexAutoStartEnabled,
|
|
8
|
+
hasOwnProvider,
|
|
9
|
+
isValidProviderName,
|
|
10
|
+
multiAgentGuidanceEnabled,
|
|
11
|
+
providerBaseUrlConfigError,
|
|
12
|
+
providerHeadersConfigError,
|
|
13
|
+
saveConfigPreservingClaudeCode,
|
|
14
|
+
} from "../config";
|
|
15
|
+
import {
|
|
16
|
+
clearLoginState,
|
|
17
|
+
getLoginStatus,
|
|
18
|
+
isPublicOAuthProvider,
|
|
19
|
+
listOAuthProviders,
|
|
20
|
+
startLoginFlow,
|
|
21
|
+
submitManualLoginCode,
|
|
22
|
+
upsertOAuthProvider,
|
|
23
|
+
} from "../oauth";
|
|
24
|
+
import { OAuthMutationBusyError, removeCredential } from "../oauth/store";
|
|
25
|
+
import { providerDestinationResolvedError } from "../lib/destination-policy";
|
|
26
|
+
import { enrichProviderFromCatalog, listKeyLoginProviders } from "../oauth/key-providers";
|
|
27
|
+
import { deriveProviderPresets } from "../providers/derive";
|
|
28
|
+
import { providerCodexAccountMode } from "../providers/registry";
|
|
29
|
+
import { routedSlug, slugEquals } from "../providers/slug-codec";
|
|
30
|
+
import { clearProviderQuotaCache, fetchProviderQuotaReports } from "../providers/quota";
|
|
31
|
+
import { isCanonicalOpenAiForwardProvider } from "../providers/openai-tiers";
|
|
32
|
+
import { clearThreadAccountMap } from "../codex/routing";
|
|
33
|
+
import { primeCodexPoolQuotas } from "../codex/auth-api";
|
|
34
|
+
import { DEFAULT_PROVIDER_CONTEXT_CAP, globalContextCapValue, providerContextCap, providerContextCaps, setAllProviderContextCaps, setGlobalContextCapValue, setProviderContextCap } from "../providers/context-cap";
|
|
35
|
+
import { resolveCodexHomeDir } from "../codex/home";
|
|
36
|
+
import { readUsageEntries } from "../usage/log";
|
|
37
|
+
import { getUsageDebugLogEntries } from "../usage/debug";
|
|
38
|
+
import { parseRange, parseUsageSurface, summarizeUsage } from "../usage/summary";
|
|
39
|
+
import { stripCodexRuntimeProviderFields } from "../codex/auth-context";
|
|
40
|
+
import { getProviderRegistryEntry } from "../providers/registry";
|
|
41
|
+
import { getDebugLogEntries } from "../lib/debug-log-buffer";
|
|
42
|
+
import { getInjectionDebugLogEntries } from "../lib/injection-debug-log";
|
|
43
|
+
import {
|
|
44
|
+
clearDebugSettings,
|
|
45
|
+
clearDebugSetting,
|
|
46
|
+
getDebugSettings,
|
|
47
|
+
setDebugSettings,
|
|
48
|
+
type DebugFlag,
|
|
49
|
+
} from "../lib/debug-settings";
|
|
50
|
+
import type { OcxClaudeCodeConfig, OcxClaudeDesktopProfile, OcxConfig, OcxCustomModel, OcxProviderConfig } from "../types";
|
|
51
|
+
import type { DesktopProfileModel } from "../claude/desktop-profile";
|
|
52
|
+
import { drainAndShutdown } from "./lifecycle";
|
|
53
|
+
import { filterRequestLogs, getRequestLogEntries, type RequestLogEntry } from "./request-log";
|
|
54
|
+
import { estimateComboCost, estimateRequestCost, normalizeCostTokens, tokensPerSecond } from "../usage/cost";
|
|
55
|
+
import type { PersistedUsageAttempt } from "../usage/log";
|
|
56
|
+
import { isAllowedManagementOrigin, jsonResponse, providerManagementConfigError, publicProviderBaseUrl, safeConfigDTO } from "./auth-cors";
|
|
57
|
+
import { applySystemEnvToggle } from "./system-env";
|
|
58
|
+
|
|
59
|
+
import type { ManagementApiDeps } from "./management/context";
|
|
60
|
+
import { handleConfigRoutes } from "./management/config-routes";
|
|
61
|
+
import { handleLogsUsageRoutes } from "./management/logs-usage-routes";
|
|
62
|
+
import { handleProviderRoutes } from "./management/provider-routes";
|
|
63
|
+
import { handleModelRoutes } from "./management/model-routes";
|
|
64
|
+
import { handleAgentSettingsRoutes } from "./management/agent-settings-routes";
|
|
65
|
+
import { handleClientsRoutes } from "./management/clients-routes";
|
|
66
|
+
import { handleOauthAccountRoutes } from "./management/oauth-account-routes";
|
|
67
|
+
import { handleComboRoutes } from "./management/combo-routes";
|
|
68
|
+
import { handleSystemRoutes } from "./management/system-routes";
|
|
69
|
+
import { handleCloudSyncRoutes } from "./management/cloud-sync-routes";
|
|
70
|
+
import { handleSidebarRoutes } from "./management/sidebar-routes";
|
|
71
|
+
import type { ManagementContext } from "./management/context";
|
|
72
|
+
export type { ManagementApiDeps } from "./management/context";
|
|
73
|
+
import { fetchAllModels } from "./management/shared";
|
|
74
|
+
import { CatalogGatherBusyError } from "../codex/catalog/provider-fetch";
|
|
75
|
+
import { managementBodyTooLargeResponse } from "./management/body";
|
|
76
|
+
|
|
77
|
+
// installed npm version instead of a stale hardcode.
|
|
78
|
+
export const VERSION = (() => {
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8")).version as string;
|
|
81
|
+
} catch {
|
|
82
|
+
return "0.0.0";
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
85
|
+
|
|
86
|
+
export async function handleManagementAPI(req: Request, url: URL, config: OcxConfig, deps: ManagementApiDeps = {}): Promise<Response | null> {
|
|
87
|
+
if (!isAllowedManagementOrigin(req, config)) {
|
|
88
|
+
return jsonResponse({ error: "cross-origin request blocked" }, 403, req, config);
|
|
89
|
+
}
|
|
90
|
+
// Management bodies are small JSON (provider names, key ids, settings). Reject oversized
|
|
91
|
+
// payloads before any handler buffers them — the data plane has its own decompression cap.
|
|
92
|
+
if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") {
|
|
93
|
+
const contentLength = Number(req.headers.get("content-length") ?? "0");
|
|
94
|
+
if (Number.isFinite(contentLength) && contentLength > 2 * 1024 * 1024) {
|
|
95
|
+
return jsonResponse({ error: "request body too large" }, 413, req, config);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function refreshCodexCatalogBestEffort(): Promise<void> {
|
|
99
|
+
if (deps.refreshCodexCatalog) return deps.refreshCodexCatalog();
|
|
100
|
+
try {
|
|
101
|
+
const { refreshCodexModelCatalog } = await import("../codex/refresh");
|
|
102
|
+
await refreshCodexModelCatalog(config);
|
|
103
|
+
} catch {
|
|
104
|
+
/* catalog absent */
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function syncClaudeAgentDefsBestEffort(): Promise<void> {
|
|
109
|
+
try {
|
|
110
|
+
const { injectClaudeAgentDefs } = await import("../claude/agents-inject");
|
|
111
|
+
if (config.claudeCode?.enabled === false || config.claudeCode?.injectAgents === false) {
|
|
112
|
+
injectClaudeAgentDefs(config, {});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const [models, { buildClaudeContextWindows }, { visibleNativeSlugs }] = await Promise.all([
|
|
117
|
+
fetchAllModels(config),
|
|
118
|
+
import("../claude/context-windows"),
|
|
119
|
+
import("../codex/catalog"),
|
|
120
|
+
]);
|
|
121
|
+
injectClaudeAgentDefs(config, buildClaudeContextWindows([...visibleNativeSlugs(config)], models));
|
|
122
|
+
} catch {
|
|
123
|
+
// Keep routes available through a provider-discovery blip. A later
|
|
124
|
+
// launch-time sync restores any context markers missing from this pass.
|
|
125
|
+
injectClaudeAgentDefs(config, {});
|
|
126
|
+
}
|
|
127
|
+
} catch { /* best-effort */ }
|
|
128
|
+
}
|
|
129
|
+
const ctx: ManagementContext = { req, url, config, deps, refreshCodexCatalogBestEffort, syncClaudeAgentDefsBestEffort };
|
|
130
|
+
let routed: Response | null;
|
|
131
|
+
try {
|
|
132
|
+
routed = (await handleConfigRoutes(ctx))
|
|
133
|
+
?? (await handleLogsUsageRoutes(ctx))
|
|
134
|
+
?? (await handleProviderRoutes(ctx))
|
|
135
|
+
?? (await handleModelRoutes(ctx))
|
|
136
|
+
?? (await handleAgentSettingsRoutes(ctx))
|
|
137
|
+
?? (await handleClientsRoutes(ctx))
|
|
138
|
+
?? (await handleOauthAccountRoutes(ctx))
|
|
139
|
+
?? (await handleComboRoutes(ctx))
|
|
140
|
+
?? (await handleSystemRoutes(ctx))
|
|
141
|
+
?? (await handleCloudSyncRoutes(ctx))
|
|
142
|
+
?? (await handleSidebarRoutes(ctx));
|
|
143
|
+
} catch (error) {
|
|
144
|
+
const tooLarge = managementBodyTooLargeResponse(error, req, config);
|
|
145
|
+
if (tooLarge) return tooLarge;
|
|
146
|
+
if (error instanceof OAuthMutationBusyError) {
|
|
147
|
+
return new Response(JSON.stringify({ error: { type: "server_error", code: "oauth_mutation_busy", message: error.message } }), {
|
|
148
|
+
status: 503,
|
|
149
|
+
headers: { "content-type": "application/json", "Retry-After": "1" },
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (!(error instanceof CatalogGatherBusyError)) throw error;
|
|
153
|
+
return new Response(JSON.stringify({ error: { type: "server_error", code: "catalog_busy", message: error.message } }), {
|
|
154
|
+
status: 503,
|
|
155
|
+
headers: { "content-type": "application/json", "Retry-After": "1" },
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (routed) return routed;
|
|
159
|
+
|
|
160
|
+
if (url.pathname === "/api/stop" && req.method === "POST") {
|
|
161
|
+
const { restoreNativeCodex } = await import("../codex/inject");
|
|
162
|
+
const { stopServiceIfInstalled, isServiceOwnershipError } = await import("../service");
|
|
163
|
+
try {
|
|
164
|
+
stopServiceIfInstalled();
|
|
165
|
+
} catch (err) {
|
|
166
|
+
if (isServiceOwnershipError(err)) {
|
|
167
|
+
// The installed service belongs to another CODEX_HOME/OPENCODEX_HOME: it would respawn
|
|
168
|
+
// this proxy immediately, and its shared config is not ours to tear down. Refuse the
|
|
169
|
+
// stop instead of half-performing it. 409, not 500 — the request is well-formed.
|
|
170
|
+
return jsonResponse({ success: false, message: err.message }, 409, req, config);
|
|
171
|
+
}
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
const restore = restoreNativeCodex();
|
|
175
|
+
// Both managed configs come down together on an explicit teardown. The daemon's own
|
|
176
|
+
// syncCleanup skips this when OCX_SERVICE is set (so a crash/respawn keeps the fence),
|
|
177
|
+
// which is exactly why an intentional stop has to do it here.
|
|
178
|
+
const { stripGrokConfig } = await import("../grok/inject");
|
|
179
|
+
const grok = stripGrokConfig();
|
|
180
|
+
setTimeout(async () => {
|
|
181
|
+
await drainAndShutdown(undefined, config.shutdownTimeoutMs ?? 5000);
|
|
182
|
+
process.exit(0);
|
|
183
|
+
}, 200);
|
|
184
|
+
const grokNote = grok.ok ? "" : ` Grok config cleanup failed: ${grok.message}`;
|
|
185
|
+
return jsonResponse(restore.success
|
|
186
|
+
? { success: true, message: `Proxy stopping, native Codex restored.${grokNote}` }
|
|
187
|
+
: { success: false, message: `Proxy stopping, but native Codex restore failed: ${restore.message}. Run \`ocx restore\`.${grokNote}` });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (url.pathname.startsWith("/api/codex-auth/")) {
|
|
191
|
+
const { handleCodexAuthAPI } = await import("../codex/auth-api");
|
|
192
|
+
const { ConfigMutationLockError } = await import("../config");
|
|
193
|
+
const { CodexCredentialRefreshLockTimeoutError } = await import("../codex/account-store");
|
|
194
|
+
try {
|
|
195
|
+
return await handleCodexAuthAPI(req, url, config);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
// Credential writers remap ConfigMutationLockError to CodexCredentialRefreshLockTimeoutError;
|
|
198
|
+
// treat both as the same retryable busy response.
|
|
199
|
+
if (error instanceof ConfigMutationLockError || error instanceof CodexCredentialRefreshLockTimeoutError) {
|
|
200
|
+
return jsonResponse(
|
|
201
|
+
{ error: "Configuration is busy; retry shortly", code: "CONFIG_MUTATION_LOCK_UNAVAILABLE" },
|
|
202
|
+
503,
|
|
203
|
+
req,
|
|
204
|
+
config,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
export { buildClaudeDesktopState, fetchAllModels } from "./management/shared";
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
chmodSync,
|
|
4
|
+
closeSync,
|
|
5
|
+
fsyncSync,
|
|
6
|
+
linkSync,
|
|
7
|
+
lstatSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
openSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
unlinkSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from "node:fs";
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
15
|
+
import { adminApiTokenFilePath } from "../lib/admin-secrets";
|
|
16
|
+
import { forgetHardenedSecretPath, hardenSecretDir, hardenSecretPath } from "../lib/windows-secret-acl";
|
|
17
|
+
import type { OcxConfig } from "../types";
|
|
18
|
+
import {
|
|
19
|
+
isAllowedManagementOrigin,
|
|
20
|
+
isApiAuthRequired,
|
|
21
|
+
isDataPlaneAdmissionSecret,
|
|
22
|
+
isLoopbackHostname,
|
|
23
|
+
managementRequestOrigin,
|
|
24
|
+
parseHttpHost,
|
|
25
|
+
} from "./auth-cors";
|
|
26
|
+
|
|
27
|
+
const GUI_SESSION_TTL_MS = 5 * 60_000;
|
|
28
|
+
const GUI_SESSION_LIMIT = 128;
|
|
29
|
+
|
|
30
|
+
interface GuiSessionRecord {
|
|
31
|
+
csrfToken: string;
|
|
32
|
+
origin: string;
|
|
33
|
+
expiresAt: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface GuiSessionBootstrap extends GuiSessionRecord {
|
|
37
|
+
token: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type ManagementAuthState =
|
|
41
|
+
| {
|
|
42
|
+
available: true;
|
|
43
|
+
token: string;
|
|
44
|
+
source: "environment" | "file";
|
|
45
|
+
sessions: Map<string, GuiSessionRecord>;
|
|
46
|
+
}
|
|
47
|
+
| { available: false; reason: string };
|
|
48
|
+
|
|
49
|
+
function fail(reason: string): ManagementAuthState {
|
|
50
|
+
return { available: false, reason };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function assertSafeDirectory(path: string): void {
|
|
54
|
+
mkdirSync(path, { recursive: true, mode: 0o700 });
|
|
55
|
+
const stat = lstatSync(path);
|
|
56
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error("management token directory is not a regular directory");
|
|
57
|
+
chmodSync(path, 0o700);
|
|
58
|
+
let hardened: { ok: boolean };
|
|
59
|
+
try {
|
|
60
|
+
hardened = hardenSecretDir(path, { required: true });
|
|
61
|
+
} catch {
|
|
62
|
+
// required:true hardening now fails closed on genuine ACL timeouts too;
|
|
63
|
+
// keep the actionable guidance in the surfaced reason.
|
|
64
|
+
hardened = { ok: false };
|
|
65
|
+
}
|
|
66
|
+
if (!hardened.ok) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"management token directory ACL hardening did not complete; set OPENCODEX_ADMIN_AUTH_TOKEN to use an environment token instead of a file-backed token",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function readExistingToken(path: string): string {
|
|
74
|
+
const stat = lstatSync(path);
|
|
75
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 512) {
|
|
76
|
+
throw new Error("management token path is not a regular secret file");
|
|
77
|
+
}
|
|
78
|
+
chmodSync(path, 0o600);
|
|
79
|
+
let hardened: { ok: boolean };
|
|
80
|
+
try {
|
|
81
|
+
hardened = hardenSecretPath(path, { required: true });
|
|
82
|
+
} catch {
|
|
83
|
+
hardened = { ok: false };
|
|
84
|
+
}
|
|
85
|
+
if (!hardened.ok) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"management token file ACL hardening did not complete; set OPENCODEX_ADMIN_AUTH_TOKEN to use an environment token instead of a file-backed token",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const token = readFileSync(path, "utf8").trim();
|
|
91
|
+
if (!/^ocx_admin_[A-Za-z0-9_-]{43}$/.test(token)) throw new Error("management token file is invalid");
|
|
92
|
+
return token;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function removeManagementTokenPathBestEffort(
|
|
96
|
+
path: string,
|
|
97
|
+
remove: (path: string) => void = unlinkSync,
|
|
98
|
+
): void {
|
|
99
|
+
try {
|
|
100
|
+
remove(path);
|
|
101
|
+
forgetHardenedSecretPath(path);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") forgetHardenedSecretPath(path);
|
|
104
|
+
/* other failures retain fail-closed state for the caller */
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function createTokenFile(path: string): string {
|
|
109
|
+
const directory = dirname(path);
|
|
110
|
+
const token = `ocx_admin_${randomBytes(32).toString("base64url")}`;
|
|
111
|
+
const temporary = join(directory, `.${randomUUID()}.admin-token.tmp`);
|
|
112
|
+
let linked = false;
|
|
113
|
+
let fd: number | null = null;
|
|
114
|
+
try {
|
|
115
|
+
fd = openSync(temporary, "wx", 0o600);
|
|
116
|
+
writeFileSync(fd, `${token}\n`, "utf8");
|
|
117
|
+
fsyncSync(fd);
|
|
118
|
+
closeSync(fd);
|
|
119
|
+
fd = null;
|
|
120
|
+
chmodSync(temporary, 0o600);
|
|
121
|
+
let temporaryHardened: { ok: boolean };
|
|
122
|
+
try {
|
|
123
|
+
temporaryHardened = hardenSecretPath(temporary, { required: true });
|
|
124
|
+
} catch {
|
|
125
|
+
temporaryHardened = { ok: false };
|
|
126
|
+
}
|
|
127
|
+
if (!temporaryHardened.ok) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
"management token temporary ACL hardening did not complete; set OPENCODEX_ADMIN_AUTH_TOKEN to use an environment token instead of a file-backed token",
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
linkSync(temporary, path);
|
|
134
|
+
linked = true;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if ((error as NodeJS.ErrnoException).code === "EEXIST") return readExistingToken(path);
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
let finalHardened: { ok: boolean };
|
|
140
|
+
try {
|
|
141
|
+
finalHardened = hardenSecretPath(path, { required: true });
|
|
142
|
+
} catch {
|
|
143
|
+
finalHardened = { ok: false };
|
|
144
|
+
}
|
|
145
|
+
if (!finalHardened.ok) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
"management token file ACL hardening did not complete; set OPENCODEX_ADMIN_AUTH_TOKEN to use an environment token instead of a file-backed token",
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
return token;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (linked) removeManagementTokenPathBestEffort(path);
|
|
153
|
+
throw error;
|
|
154
|
+
} finally {
|
|
155
|
+
if (fd !== null) {
|
|
156
|
+
try { closeSync(fd); } catch { /* best effort */ }
|
|
157
|
+
}
|
|
158
|
+
removeManagementTokenPathBestEffort(temporary);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function ready(token: string, source: "environment" | "file", config: OcxConfig): ManagementAuthState {
|
|
163
|
+
if (isDataPlaneAdmissionSecret(token, config)) {
|
|
164
|
+
return fail("management credential conflicts with a data-plane credential");
|
|
165
|
+
}
|
|
166
|
+
return { available: true, token, source, sessions: new Map() };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function initializeManagementAuthState(config: OcxConfig): ManagementAuthState {
|
|
170
|
+
const environmentToken = process.env.OPENCODEX_ADMIN_AUTH_TOKEN?.trim();
|
|
171
|
+
if (environmentToken) {
|
|
172
|
+
return ready(environmentToken, "environment", config);
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
const path = adminApiTokenFilePath();
|
|
176
|
+
assertSafeDirectory(dirname(path));
|
|
177
|
+
let token: string;
|
|
178
|
+
try {
|
|
179
|
+
token = readExistingToken(path);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
182
|
+
token = createTokenFile(path);
|
|
183
|
+
}
|
|
184
|
+
return ready(token, "file", config);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
return fail(error instanceof Error ? error.message : "management token initialization failed");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function equalSecret(actual: string, expected: string): boolean {
|
|
191
|
+
const encoder = new TextEncoder();
|
|
192
|
+
const left = encoder.encode(actual);
|
|
193
|
+
const right = encoder.encode(expected);
|
|
194
|
+
return left.length === right.length && timingSafeEqual(left, right);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function removeExpiredSessions(state: Extract<ManagementAuthState, { available: true }>, now = Date.now()): void {
|
|
198
|
+
for (const [token, session] of state.sessions) {
|
|
199
|
+
if (session.expiresAt <= now) state.sessions.delete(token);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function randomSessionSecret(prefix: "ocx_session_"): string {
|
|
204
|
+
return `${prefix}${randomBytes(32).toString("base64url")}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function issueGuiSession(
|
|
208
|
+
req: Request,
|
|
209
|
+
config: OcxConfig,
|
|
210
|
+
state: ManagementAuthState,
|
|
211
|
+
): GuiSessionBootstrap | null {
|
|
212
|
+
if (isApiAuthRequired(config) || !state.available || req.method !== "GET" || !isAllowedManagementOrigin(req, config)) return null;
|
|
213
|
+
const host = parseHttpHost(req.headers.get("Host"));
|
|
214
|
+
if (!host || !isLoopbackHostname(host.hostname)) return null;
|
|
215
|
+
const origin = managementRequestOrigin(req, config);
|
|
216
|
+
if (!origin) return null;
|
|
217
|
+
const now = Date.now();
|
|
218
|
+
removeExpiredSessions(state, now);
|
|
219
|
+
while (state.sessions.size >= GUI_SESSION_LIMIT) {
|
|
220
|
+
const oldest = state.sessions.keys().next().value as string | undefined;
|
|
221
|
+
if (!oldest) break;
|
|
222
|
+
state.sessions.delete(oldest);
|
|
223
|
+
}
|
|
224
|
+
const token = randomSessionSecret("ocx_session_");
|
|
225
|
+
const session: GuiSessionRecord = {
|
|
226
|
+
csrfToken: randomBytes(32).toString("base64url"),
|
|
227
|
+
origin,
|
|
228
|
+
expiresAt: now + GUI_SESSION_TTL_MS,
|
|
229
|
+
};
|
|
230
|
+
state.sessions.set(token, session);
|
|
231
|
+
return { token, ...session };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function requireManagementAuth(
|
|
235
|
+
req: Request,
|
|
236
|
+
state: ManagementAuthState,
|
|
237
|
+
config?: OcxConfig,
|
|
238
|
+
): Response | null {
|
|
239
|
+
if (!state.available) {
|
|
240
|
+
return Response.json({
|
|
241
|
+
error: "management API unavailable",
|
|
242
|
+
reason: state.reason,
|
|
243
|
+
hint: "Set OPENCODEX_ADMIN_AUTH_TOKEN to bypass file-backed admin token ACL hardening",
|
|
244
|
+
}, { status: 503 });
|
|
245
|
+
}
|
|
246
|
+
const actual = req.headers.get("x-opencodex-api-key")?.trim()
|
|
247
|
+
|| req.headers.get("authorization")?.replace(/^Bearer\s+/i, "").trim();
|
|
248
|
+
if (actual && equalSecret(actual, state.token)) return null;
|
|
249
|
+
if (actual && config) {
|
|
250
|
+
removeExpiredSessions(state);
|
|
251
|
+
const session = state.sessions.get(actual);
|
|
252
|
+
if (session) {
|
|
253
|
+
const requestOrigin = managementRequestOrigin(req, config);
|
|
254
|
+
const claimedOrigin = req.headers.get("x-opencodex-gui-origin");
|
|
255
|
+
const browserOrigin = req.headers.get("Origin");
|
|
256
|
+
const sameOrigin = requestOrigin === session.origin
|
|
257
|
+
&& claimedOrigin === session.origin
|
|
258
|
+
&& (!browserOrigin || browserOrigin === session.origin);
|
|
259
|
+
const safeMethod = req.method === "GET" || req.method === "HEAD";
|
|
260
|
+
const csrf = req.headers.get("x-opencodex-csrf-token")?.trim();
|
|
261
|
+
if (sameOrigin && (safeMethod || (browserOrigin === session.origin && !!csrf && equalSecret(csrf, session.csrfToken)))) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return Response.json({ error: "opencodex admin token required" }, { status: 401 });
|
|
267
|
+
}
|