@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,556 @@
|
|
|
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 { removeCredential } from "../../oauth/store";
|
|
25
|
+
import { providerDestinationResolvedError } from "../../lib/destination-policy";
|
|
26
|
+
import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
|
|
27
|
+
import { ProviderOutboundPolicyError, providerOutboundGet, providerRedirectError } from "../../lib/provider-outbound";
|
|
28
|
+
import { enrichProviderFromCatalog, listKeyLoginProviders } from "../../oauth/key-providers";
|
|
29
|
+
import { deriveProviderPresets } from "../../providers/derive";
|
|
30
|
+
import { providerCodexAccountMode } from "../../providers/registry";
|
|
31
|
+
import {
|
|
32
|
+
extractModelEnvelopeRows,
|
|
33
|
+
extractProviderModelItems,
|
|
34
|
+
readBoundedDiscoveryJson,
|
|
35
|
+
resolveProviderModelDiscovery,
|
|
36
|
+
} from "../../providers/model-discovery";
|
|
37
|
+
import { routedSlug, slugEquals } from "../../providers/slug-codec";
|
|
38
|
+
import { clearProviderQuotaCache, fetchProviderQuotaReports } from "../../providers/quota";
|
|
39
|
+
import { CODEX_FORWARD_BASE_URL, isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
40
|
+
import { codexAccountNamespaceProviderCollisionError } from "../../codex/account-namespace-match";
|
|
41
|
+
import { clearThreadAccountMap } from "../../codex/routing";
|
|
42
|
+
import { primeCodexPoolQuotas } from "../../codex/auth-api";
|
|
43
|
+
import { getProviderDiscoveryStatus } from "../../codex/model-cache";
|
|
44
|
+
import { DEFAULT_PROVIDER_CONTEXT_CAP, globalContextCapValue, providerContextCap, providerContextCaps, setAllProviderContextCaps, setGlobalContextCapValue, setProviderContextCap } from "../../providers/context-cap";
|
|
45
|
+
import { resolveCodexHomeDir } from "../../codex/home";
|
|
46
|
+
import { readUsageEntries } from "../../usage/log";
|
|
47
|
+
import { getUsageDebugLogEntries } from "../../usage/debug";
|
|
48
|
+
import { parseRange, parseUsageSurface, summarizeUsage } from "../../usage/summary";
|
|
49
|
+
import { stripCodexRuntimeProviderFields } from "../../codex/auth-context";
|
|
50
|
+
import { getProviderRegistryEntry } from "../../providers/registry";
|
|
51
|
+
import { getDebugLogEntries } from "../../lib/debug-log-buffer";
|
|
52
|
+
import { getInjectionDebugLogEntries } from "../../lib/injection-debug-log";
|
|
53
|
+
import {
|
|
54
|
+
clearDebugSettings,
|
|
55
|
+
clearDebugSetting,
|
|
56
|
+
getDebugSettings,
|
|
57
|
+
setDebugSettings,
|
|
58
|
+
type DebugFlag,
|
|
59
|
+
} from "../../lib/debug-settings";
|
|
60
|
+
import type { OcxClaudeCodeConfig, OcxConfig, OcxCustomModel, OcxProviderConfig } from "../../types";
|
|
61
|
+
import { drainAndShutdown } from "../lifecycle";
|
|
62
|
+
import { filterRequestLogs, getRequestLogEntries, type RequestLogEntry } from "../request-log";
|
|
63
|
+
import { estimateComboCost, estimateRequestCost, normalizeCostTokens, tokensPerSecond } from "../../usage/cost";
|
|
64
|
+
import type { PersistedUsageAttempt } from "../../usage/log";
|
|
65
|
+
import { isAllowedRequestOrigin, jsonResponse, providerManagementConfigError, publicProviderBaseUrl, safeConfigDTO } from "../auth-cors";
|
|
66
|
+
import { applySystemEnvToggle } from "../system-env";
|
|
67
|
+
|
|
68
|
+
import { isPlainRecord, parseDebugLogQuery, tokPerSecondResult, unavailableCostReason, costResult, requestLogDto, stripRegistryOnlyStaticHeaders, fetchAllModels } from "./shared";
|
|
69
|
+
import type { MetricUnavailableReason, TokPerSecondResult, CostEstimateReason, CostResult, MetricSource } from "./shared";
|
|
70
|
+
import type { ManagementContext } from "./context";
|
|
71
|
+
import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
|
|
72
|
+
|
|
73
|
+
export async function handleProviderRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
74
|
+
const { req, url, config, deps, refreshCodexCatalogBestEffort, syncClaudeAgentDefsBestEffort } = ctx;
|
|
75
|
+
|
|
76
|
+
if (url.pathname === "/api/provider-quotas" && req.method === "GET") {
|
|
77
|
+
const forceRefresh = url.searchParams.get("refresh") === "1" || url.searchParams.get("refresh") === "true";
|
|
78
|
+
return jsonResponse(await fetchProviderQuotaReports(config, forceRefresh));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (url.pathname === "/api/providers" && req.method === "GET") {
|
|
82
|
+
return jsonResponse(Object.entries(config.providers).map(([name, p]) => ({
|
|
83
|
+
name, adapter: p.adapter, baseUrl: publicProviderBaseUrl(p.baseUrl), defaultModel: p.defaultModel,
|
|
84
|
+
hasApiKey: !!p.apiKey,
|
|
85
|
+
allowPrivateNetwork: p.allowPrivateNetwork === true,
|
|
86
|
+
liveModels: p.liveModels !== false,
|
|
87
|
+
models: p.models ?? [],
|
|
88
|
+
authMode: p.authMode,
|
|
89
|
+
apiKeyTransport: p.apiKeyTransport,
|
|
90
|
+
disabled: p.disabled === true,
|
|
91
|
+
codexAccountMode: providerCodexAccountMode(name, p),
|
|
92
|
+
discovery: p.liveModels === false ? undefined : getProviderDiscoveryStatus(name),
|
|
93
|
+
})));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Add (or overwrite) a single provider. Merges into the live in-memory config and
|
|
97
|
+
// persists — existing providers' real keys are never round-tripped (unlike PUT /api/config,
|
|
98
|
+
// which would re-save the masked keys from GET). Live routing picks it up immediately.
|
|
99
|
+
if (url.pathname === "/api/providers" && req.method === "POST") {
|
|
100
|
+
let body: { name?: unknown; provider?: unknown; setDefault?: boolean };
|
|
101
|
+
try { body = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
|
|
102
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
103
|
+
const providerError = providerManagementConfigError(name, body.provider);
|
|
104
|
+
if (providerError) return jsonResponse({ error: providerError }, 400);
|
|
105
|
+
const prov = body.provider ? stripCodexRuntimeProviderFields(body.provider as OcxProviderConfig) : undefined;
|
|
106
|
+
if (!name || !prov?.adapter || !prov?.baseUrl) {
|
|
107
|
+
return jsonResponse({ error: "name, provider.adapter and provider.baseUrl are required" }, 400);
|
|
108
|
+
}
|
|
109
|
+
if (!isValidProviderName(name)) {
|
|
110
|
+
return jsonResponse({ error: "provider name must use letters, numbers, dot, underscore, or hyphen and cannot be a reserved object key" }, 400);
|
|
111
|
+
}
|
|
112
|
+
const namespaceCollision = codexAccountNamespaceProviderCollisionError(config.codexAccountNamespaces, name);
|
|
113
|
+
if (namespaceCollision) {
|
|
114
|
+
return jsonResponse({ error: namespaceCollision }, 409);
|
|
115
|
+
}
|
|
116
|
+
// Hostname destinations additionally get a DNS-resolved SSRF check at write time —
|
|
117
|
+
// the sync check above only classifies literal IPs (review finding, PR #96).
|
|
118
|
+
// Canonical openai still runs the resolver: only Clash fake-IP (198.18.0.0/15)
|
|
119
|
+
// answers are ignored; loopback/RFC1918/metadata/mixed sets still fail.
|
|
120
|
+
const allowBenchmarkAddresses = name === "openai" && isCanonicalOpenAiForwardProvider(prov);
|
|
121
|
+
const resolvedError = await providerDestinationResolvedError(name, prov, { allowBenchmarkAddresses });
|
|
122
|
+
if (resolvedError) return jsonResponse({ error: resolvedError }, 400);
|
|
123
|
+
if (body.setDefault !== undefined && typeof body.setDefault !== "boolean") {
|
|
124
|
+
return jsonResponse({ error: "setDefault must be a boolean" }, 400);
|
|
125
|
+
}
|
|
126
|
+
if (body.setDefault === true && prov.disabled) {
|
|
127
|
+
return jsonResponse({ error: "cannot set a disabled provider as default", code: "default_provider_disabled" }, 400);
|
|
128
|
+
}
|
|
129
|
+
// Catalog providers (e.g. ollama-cloud) carry a models + vision/reasoning classification the GUI
|
|
130
|
+
// doesn't send — merge it in so the sidecars are gated correctly.
|
|
131
|
+
enrichProviderFromCatalog(name, prov);
|
|
132
|
+
const { saveConfigPreservingClaudeCode: save } = await import("../../config");
|
|
133
|
+
// Overwriting an existing provider must not drop its multi-key pool: carry it over, then
|
|
134
|
+
// let the (possibly new) apiKey join the pool as the active entry.
|
|
135
|
+
const existingPool = config.providers[name]?.apiKeyPool;
|
|
136
|
+
if (existingPool && !prov.apiKeyPool) prov.apiKeyPool = existingPool;
|
|
137
|
+
config.providers[name] = stripRegistryOnlyStaticHeaders(name, prov);
|
|
138
|
+
if (body.setDefault === true) config.defaultProvider = name;
|
|
139
|
+
save(config);
|
|
140
|
+
reconcileLiveStateStores();
|
|
141
|
+
if (prov.apiKey && prov.apiKeyPool) {
|
|
142
|
+
const { addProviderApiKey } = await import("../../providers/api-keys");
|
|
143
|
+
addProviderApiKey(config, name, prov.apiKey);
|
|
144
|
+
}
|
|
145
|
+
const { clearModelCache } = await import("../../codex/model-cache");
|
|
146
|
+
clearModelCache(name);
|
|
147
|
+
await refreshCodexCatalogBestEffort();
|
|
148
|
+
return jsonResponse({ success: true, name });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (url.pathname === "/api/providers" && req.method === "PATCH") {
|
|
152
|
+
const name = url.searchParams.get("name")?.trim();
|
|
153
|
+
if (!name || !isValidProviderName(name) || !hasOwnProvider(config.providers, name)) return jsonResponse({ error: "unknown provider" }, 404);
|
|
154
|
+
let rawBody: unknown;
|
|
155
|
+
try { rawBody = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
|
|
156
|
+
if (!isPlainRecord(rawBody)) return jsonResponse({ error: "provider patch body must be a plain object" }, 400);
|
|
157
|
+
const keys = Object.keys(rawBody);
|
|
158
|
+
const hasMode = Object.hasOwn(rawBody, "codexAccountMode");
|
|
159
|
+
const hasSetDefault = Object.hasOwn(rawBody, "setDefault");
|
|
160
|
+
|
|
161
|
+
// codexAccountMode keeps its dedicated side-effect path (quota cache clear, thread map
|
|
162
|
+
// clear, pool prime) and is mutually exclusive with every other patch field.
|
|
163
|
+
if (hasMode) {
|
|
164
|
+
if (keys.length !== 1) {
|
|
165
|
+
return jsonResponse({ error: "codexAccountMode cannot be combined with other patch fields" }, 400);
|
|
166
|
+
}
|
|
167
|
+
if (name !== "openai") return jsonResponse({ error: "codexAccountMode is valid only for provider openai" }, 400);
|
|
168
|
+
const mode = rawBody.codexAccountMode;
|
|
169
|
+
if (mode !== "pool" && mode !== "direct") {
|
|
170
|
+
return jsonResponse({ error: "codexAccountMode must be pool or direct" }, 400);
|
|
171
|
+
}
|
|
172
|
+
const provider = config.providers.openai;
|
|
173
|
+
if (!provider || !isCanonicalOpenAiForwardProvider(provider)) {
|
|
174
|
+
return jsonResponse({ error: "provider openai must be the canonical built-in provider" }, 400);
|
|
175
|
+
}
|
|
176
|
+
const { saveConfigPreservingClaudeCode: save } = await import("../../config");
|
|
177
|
+
config.providers.openai = { ...provider, codexAccountMode: mode };
|
|
178
|
+
save(config);
|
|
179
|
+
reconcileLiveStateStores();
|
|
180
|
+
(deps.clearProviderQuotaCache ?? clearProviderQuotaCache)();
|
|
181
|
+
(deps.clearThreadAccountMap ?? clearThreadAccountMap)();
|
|
182
|
+
if (mode === "pool") {
|
|
183
|
+
try {
|
|
184
|
+
const prime = deps.primeCodexPoolQuotas ?? primeCodexPoolQuotas;
|
|
185
|
+
void Promise.resolve(prime(config, "mode-change")).catch(() => undefined);
|
|
186
|
+
} catch {
|
|
187
|
+
// Quota priming is best-effort; the persisted live mode is already authoritative.
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return jsonResponse({ success: true, name: "openai", codexAccountMode: mode });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Default-provider changes must be a deliberate, standalone action. This keeps
|
|
194
|
+
// routing changes out of ordinary provider edits and lets the dashboard expose a
|
|
195
|
+
// simple "Set as default" control without round-tripping the full config.
|
|
196
|
+
if (hasSetDefault) {
|
|
197
|
+
if (keys.length !== 1 || rawBody.setDefault !== true) {
|
|
198
|
+
return jsonResponse({ error: "setDefault must be true and cannot be combined with other patch fields" }, 400);
|
|
199
|
+
}
|
|
200
|
+
if (config.providers[name]!.disabled) {
|
|
201
|
+
return jsonResponse({ error: "cannot set a disabled provider as default", code: "default_provider_disabled" }, 400);
|
|
202
|
+
}
|
|
203
|
+
const { saveConfigPreservingClaudeCode: save } = await import("../../config");
|
|
204
|
+
config.defaultProvider = name;
|
|
205
|
+
save(config);
|
|
206
|
+
reconcileLiveStateStores();
|
|
207
|
+
return jsonResponse({ success: true, name, defaultProvider: name });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Field-mask editor: apply recognized fields onto a copy, then validate the MERGED
|
|
211
|
+
// provider (canonical-seed guard covers openai; local-guard covers registry key providers).
|
|
212
|
+
// API keys are never writable here — the api-keys endpoints own pool-integrated key writes.
|
|
213
|
+
if (Object.hasOwn(rawBody, "apiKey")) {
|
|
214
|
+
return jsonResponse({ error: "apiKey cannot be patched here; use the provider API-key endpoints" }, 400);
|
|
215
|
+
}
|
|
216
|
+
const next: OcxProviderConfig = { ...config.providers[name]! };
|
|
217
|
+
let touched = false;
|
|
218
|
+
|
|
219
|
+
if (Object.hasOwn(rawBody, "disabled")) {
|
|
220
|
+
if (typeof rawBody.disabled !== "boolean") return jsonResponse({ error: "disabled must be a boolean" }, 400);
|
|
221
|
+
if (rawBody.disabled && name === config.defaultProvider) {
|
|
222
|
+
return jsonResponse({ error: "cannot disable the default provider; set another default first" }, 400);
|
|
223
|
+
}
|
|
224
|
+
next.disabled = rawBody.disabled;
|
|
225
|
+
touched = true;
|
|
226
|
+
}
|
|
227
|
+
if (Object.hasOwn(rawBody, "adapter")) {
|
|
228
|
+
if (typeof rawBody.adapter !== "string" || !rawBody.adapter.trim()) return jsonResponse({ error: "adapter must be a non-empty string" }, 400);
|
|
229
|
+
next.adapter = rawBody.adapter.trim();
|
|
230
|
+
touched = true;
|
|
231
|
+
}
|
|
232
|
+
if (Object.hasOwn(rawBody, "baseUrl")) {
|
|
233
|
+
if (typeof rawBody.baseUrl !== "string" || !rawBody.baseUrl.trim()) return jsonResponse({ error: "baseUrl must be a non-empty string" }, 400);
|
|
234
|
+
next.baseUrl = rawBody.baseUrl.trim();
|
|
235
|
+
touched = true;
|
|
236
|
+
}
|
|
237
|
+
if (Object.hasOwn(rawBody, "defaultModel")) {
|
|
238
|
+
if (typeof rawBody.defaultModel !== "string") return jsonResponse({ error: "defaultModel must be a string" }, 400);
|
|
239
|
+
const dm = rawBody.defaultModel.trim();
|
|
240
|
+
if (dm) next.defaultModel = dm;
|
|
241
|
+
else delete next.defaultModel;
|
|
242
|
+
touched = true;
|
|
243
|
+
}
|
|
244
|
+
if (Object.hasOwn(rawBody, "authMode")) {
|
|
245
|
+
if (typeof rawBody.authMode !== "string") return jsonResponse({ error: "authMode must be a string" }, 400);
|
|
246
|
+
const mode = rawBody.authMode.trim();
|
|
247
|
+
if (mode === "key" || mode === "forward" || mode === "oauth" || mode === "local") {
|
|
248
|
+
next.authMode = mode;
|
|
249
|
+
touched = true;
|
|
250
|
+
} else if (mode === "") {
|
|
251
|
+
delete next.authMode;
|
|
252
|
+
touched = true;
|
|
253
|
+
} else {
|
|
254
|
+
return jsonResponse({ error: "authMode must be key, forward, oauth, or local" }, 400);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (Object.hasOwn(rawBody, "apiKeyTransport")) {
|
|
258
|
+
const transport = rawBody.apiKeyTransport;
|
|
259
|
+
if (transport === "x-api-key" || transport === "bearer") {
|
|
260
|
+
next.apiKeyTransport = transport;
|
|
261
|
+
touched = true;
|
|
262
|
+
} else if (transport === "") {
|
|
263
|
+
delete next.apiKeyTransport;
|
|
264
|
+
touched = true;
|
|
265
|
+
} else {
|
|
266
|
+
return jsonResponse({ error: "apiKeyTransport must be x-api-key, bearer, or empty to clear" }, 400);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (Object.hasOwn(rawBody, "note")) {
|
|
270
|
+
if (typeof rawBody.note !== "string") return jsonResponse({ error: "note must be a string" }, 400);
|
|
271
|
+
const note = rawBody.note.trim();
|
|
272
|
+
if (note) next.note = note;
|
|
273
|
+
else delete next.note;
|
|
274
|
+
touched = true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (Object.hasOwn(rawBody, "allowPrivateNetwork")) {
|
|
278
|
+
if (typeof rawBody.allowPrivateNetwork !== "boolean") return jsonResponse({ error: "allowPrivateNetwork must be a boolean" }, 400);
|
|
279
|
+
next.allowPrivateNetwork = rawBody.allowPrivateNetwork;
|
|
280
|
+
touched = true;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (Object.hasOwn(rawBody, "liveModels")) {
|
|
284
|
+
if (typeof rawBody.liveModels !== "boolean") return jsonResponse({ error: "liveModels must be a boolean" }, 400);
|
|
285
|
+
next.liveModels = rawBody.liveModels;
|
|
286
|
+
touched = true;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!touched) return jsonResponse({ error: "no recognized fields to update" }, 400);
|
|
290
|
+
|
|
291
|
+
// A disabled-only toggle preserves the v2 fast lane for non-openai providers: it changes
|
|
292
|
+
// routing eligibility, not the provider shape. Re-enabling `openai` is different — a
|
|
293
|
+
// malformed disabled row must not come back online unchanged, so canonicalize/reject
|
|
294
|
+
// against the same built-in gate used by mode PATCH and POST.
|
|
295
|
+
const editorTouched = keys.some(key => key !== "disabled");
|
|
296
|
+
const enablingOpenAi = name === "openai"
|
|
297
|
+
&& Object.hasOwn(rawBody, "disabled")
|
|
298
|
+
&& rawBody.disabled === false
|
|
299
|
+
&& config.providers[name]?.disabled === true;
|
|
300
|
+
if (editorTouched) {
|
|
301
|
+
const providerError = providerManagementConfigError(name, next);
|
|
302
|
+
if (providerError) return jsonResponse({ error: providerError }, 400);
|
|
303
|
+
const resolvedError = await providerDestinationResolvedError(name, next);
|
|
304
|
+
if (resolvedError) return jsonResponse({ error: resolvedError }, 400);
|
|
305
|
+
} else if (enablingOpenAi) {
|
|
306
|
+
if (!isCanonicalOpenAiForwardProvider(next)) {
|
|
307
|
+
return jsonResponse({ error: "provider openai must be the canonical built-in provider" }, 400);
|
|
308
|
+
}
|
|
309
|
+
// Persist the byte-identical canonical URL so config.ts startup checks (case-sensitive)
|
|
310
|
+
// accept the row after we fill mode. Equivalent hosts like CHATGPT.com/:443 normalize here.
|
|
311
|
+
next.baseUrl = CODEX_FORWARD_BASE_URL;
|
|
312
|
+
// Fill missing mode so a disabled canonical row becomes a complete live openai entry.
|
|
313
|
+
if (next.codexAccountMode !== "pool" && next.codexAccountMode !== "direct") {
|
|
314
|
+
next.codexAccountMode = "pool";
|
|
315
|
+
}
|
|
316
|
+
// Same DNS gate as POST: Clash fake-IP only. Never honor a persisted
|
|
317
|
+
// allowPrivateNetwork on this path — it must not bypass the built-in guard.
|
|
318
|
+
const resolvedError = await providerDestinationResolvedError(
|
|
319
|
+
"openai",
|
|
320
|
+
{ baseUrl: CODEX_FORWARD_BASE_URL },
|
|
321
|
+
{ allowBenchmarkAddresses: true },
|
|
322
|
+
);
|
|
323
|
+
if (resolvedError) return jsonResponse({ error: resolvedError }, 400);
|
|
324
|
+
if (next.disabled === false) delete next.disabled;
|
|
325
|
+
// Canonical openai never uses private-network opt-in; drop a stale flag that
|
|
326
|
+
// was ignored for the DNS probe so it cannot linger on the live row.
|
|
327
|
+
delete next.allowPrivateNetwork;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const { saveConfigPreservingClaudeCode: save } = await import("../../config");
|
|
331
|
+
config.providers[name] = stripRegistryOnlyStaticHeaders(name, next);
|
|
332
|
+
save(config);
|
|
333
|
+
reconcileLiveStateStores();
|
|
334
|
+
if (editorTouched) {
|
|
335
|
+
const { clearModelCache } = await import("../../codex/model-cache");
|
|
336
|
+
clearModelCache(name);
|
|
337
|
+
}
|
|
338
|
+
await refreshCodexCatalogBestEffort();
|
|
339
|
+
return jsonResponse({
|
|
340
|
+
success: true,
|
|
341
|
+
name,
|
|
342
|
+
disabled: config.providers[name]!.disabled === true,
|
|
343
|
+
hasApiKey: !!config.providers[name]!.apiKey,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Lightweight connectivity probe: perform the provider's live /models fetch DIRECTLY and
|
|
348
|
+
// report only real upstream evidence. The catalog aggregate (fetchAllModels) deliberately
|
|
349
|
+
// hides fetch failures behind stale/static fallbacks, so a catalog-presence check would
|
|
350
|
+
// let a static-catalog provider with a fake key "pass" — this endpoint never uses it.
|
|
351
|
+
if (url.pathname === "/api/providers/test" && req.method === "POST") {
|
|
352
|
+
const name = url.searchParams.get("name")?.trim();
|
|
353
|
+
if (!name || !isValidProviderName(name) || !hasOwnProvider(config.providers, name)) {
|
|
354
|
+
return jsonResponse({ error: "unknown provider" }, 404);
|
|
355
|
+
}
|
|
356
|
+
const prov = config.providers[name]!;
|
|
357
|
+
if (prov.disabled) {
|
|
358
|
+
return jsonResponse({ ok: false, error: "Provider is disabled", latencyMs: 0 });
|
|
359
|
+
}
|
|
360
|
+
if (prov.authMode === "forward") {
|
|
361
|
+
return jsonResponse({
|
|
362
|
+
ok: true,
|
|
363
|
+
latencyMs: 0,
|
|
364
|
+
message: "Passthrough provider is configured (forwards your Codex login; no upstream /models).",
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
if (prov.liveModels === false) {
|
|
368
|
+
// A static catalog has no live discovery endpoint to test. This is neither
|
|
369
|
+
// positive connectivity evidence nor an outage, and it must stay before
|
|
370
|
+
// credential resolution/network access for providers such as Antigravity.
|
|
371
|
+
return jsonResponse({ applicable: false, reason: "static_catalog", latencyMs: 0 });
|
|
372
|
+
}
|
|
373
|
+
const { resolveModelsAuthToken, buildModelsRequest } = await import("../../oauth");
|
|
374
|
+
const apiKey = await resolveModelsAuthToken(name, prov);
|
|
375
|
+
if (prov.authMode === "oauth" && !apiKey) {
|
|
376
|
+
return jsonResponse({ ok: false, latencyMs: 0, error: "static catalog only — upstream not verified (not logged in)" });
|
|
377
|
+
}
|
|
378
|
+
const { url: modelsUrl, headers } = buildModelsRequest(prov, apiKey, name);
|
|
379
|
+
const discovery = resolveProviderModelDiscovery(name, prov);
|
|
380
|
+
const started = Date.now();
|
|
381
|
+
try {
|
|
382
|
+
const res = await providerOutboundGet(name, prov, modelsUrl, {
|
|
383
|
+
headers,
|
|
384
|
+
signal: AbortSignal.timeout(8000),
|
|
385
|
+
});
|
|
386
|
+
const latencyMs = Date.now() - started;
|
|
387
|
+
const redirectError = await providerRedirectError(res, modelsUrl);
|
|
388
|
+
if (redirectError) {
|
|
389
|
+
return jsonResponse({
|
|
390
|
+
ok: false,
|
|
391
|
+
latencyMs,
|
|
392
|
+
error: redirectError,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
if (!res.ok) {
|
|
396
|
+
try {
|
|
397
|
+
void res.body?.cancel().catch(() => undefined);
|
|
398
|
+
} catch {
|
|
399
|
+
// Best-effort release for non-conforming response streams.
|
|
400
|
+
}
|
|
401
|
+
return jsonResponse({ ok: false, latencyMs, error: `upstream /models returned ${res.status}` });
|
|
402
|
+
}
|
|
403
|
+
const bounded = await readBoundedDiscoveryJson(res, discovery.maxResponseBytes);
|
|
404
|
+
if (!bounded.ok) {
|
|
405
|
+
return jsonResponse({
|
|
406
|
+
ok: false,
|
|
407
|
+
latencyMs,
|
|
408
|
+
error: bounded.reason === "response_too_large"
|
|
409
|
+
? `upstream /models exceeded the ${discovery.maxResponseBytes}-byte response limit`
|
|
410
|
+
: "upstream /models returned invalid JSON",
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
// OpenAI-style lists (and Together top-level arrays) use the same validation/dedupe/filter
|
|
414
|
+
// as catalog discovery. Google's /v1beta/models uses `models[].name` and remains a
|
|
415
|
+
// connectivity-only count because it is not an authoritative catalog source.
|
|
416
|
+
const record = bounded.value !== null && typeof bounded.value === "object" && !Array.isArray(bounded.value)
|
|
417
|
+
? bounded.value as Record<string, unknown>
|
|
418
|
+
: undefined;
|
|
419
|
+
const extracted = Array.isArray(bounded.value) || Array.isArray(record?.data)
|
|
420
|
+
? extractProviderModelItems(bounded.value, discovery)
|
|
421
|
+
: extractModelEnvelopeRows(bounded.value, discovery.maxModels, ["models"]);
|
|
422
|
+
if (!extracted.ok) {
|
|
423
|
+
return jsonResponse({
|
|
424
|
+
ok: false,
|
|
425
|
+
latencyMs,
|
|
426
|
+
error: extracted.reason === "too_many_models"
|
|
427
|
+
? `upstream /models exceeded the ${discovery.maxModels}-row model limit`
|
|
428
|
+
: "upstream /models returned an unexpected shape",
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
const models = "items" in extracted ? extracted.items.length : extracted.rows.length;
|
|
432
|
+
return jsonResponse({
|
|
433
|
+
ok: true,
|
|
434
|
+
latencyMs,
|
|
435
|
+
models,
|
|
436
|
+
message: `Connected — ${models} model${models === 1 ? "" : "s"} available.`,
|
|
437
|
+
});
|
|
438
|
+
} catch (err) {
|
|
439
|
+
return jsonResponse({
|
|
440
|
+
ok: false,
|
|
441
|
+
latencyMs: Date.now() - started,
|
|
442
|
+
error: err instanceof ProviderOutboundPolicyError
|
|
443
|
+
? `upstream /models blocked by destination policy: ${err.message}`
|
|
444
|
+
: err instanceof Error ? err.message : "Connection test failed",
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (url.pathname === "/api/providers" && req.method === "DELETE") {
|
|
450
|
+
const name = url.searchParams.get("name")?.trim();
|
|
451
|
+
if (!name || !isValidProviderName(name) || !hasOwnProvider(config.providers, name)) return jsonResponse({ error: "unknown provider" }, 404);
|
|
452
|
+
// Config validation requires a default provider. Reassigning before deletion keeps
|
|
453
|
+
// the persisted config valid and makes removal of the current default a one-step UI
|
|
454
|
+
// operation. Prefer the first remaining *enabled* provider so DELETE cannot leave a
|
|
455
|
+
// disabled default that setDefault / disable already refuse. Object-key order is the
|
|
456
|
+
// documented configuration order and is stable through JSON persistence.
|
|
457
|
+
const fallbackDefault = name === config.defaultProvider
|
|
458
|
+
? Object.entries(config.providers)
|
|
459
|
+
.find(([provider, providerConfig]) => provider !== name && providerConfig.disabled !== true)
|
|
460
|
+
?.[0]
|
|
461
|
+
: undefined;
|
|
462
|
+
if (name === config.defaultProvider && !fallbackDefault) {
|
|
463
|
+
return jsonResponse({
|
|
464
|
+
error: "cannot delete the default provider when no enabled replacement remains",
|
|
465
|
+
code: "last_provider",
|
|
466
|
+
}, 409);
|
|
467
|
+
}
|
|
468
|
+
const dependentCombos = Object.entries(config.combos ?? {})
|
|
469
|
+
.filter(([, combo]) => combo.targets.some(target => target.provider === name))
|
|
470
|
+
.map(([id]) => id)
|
|
471
|
+
.sort((a, b) => a.localeCompare(b));
|
|
472
|
+
if (dependentCombos.length > 0) {
|
|
473
|
+
return jsonResponse({
|
|
474
|
+
error: `cannot delete provider "${name}" while combos depend on it`,
|
|
475
|
+
code: "provider_has_dependent_combos",
|
|
476
|
+
combos: dependentCombos,
|
|
477
|
+
}, 409);
|
|
478
|
+
}
|
|
479
|
+
const { saveConfigPreservingClaudeCode: save } = await import("../../config");
|
|
480
|
+
if (fallbackDefault) config.defaultProvider = fallbackDefault;
|
|
481
|
+
delete config.providers[name];
|
|
482
|
+
setProviderContextCap(config, name, false);
|
|
483
|
+
save(config);
|
|
484
|
+
reconcileLiveStateStores();
|
|
485
|
+
const { clearModelCache: clearCache } = await import("../../codex/model-cache");
|
|
486
|
+
clearCache(name);
|
|
487
|
+
await refreshCodexCatalogBestEffort();
|
|
488
|
+
return jsonResponse({ success: true, ...(fallbackDefault ? { defaultProvider: fallbackDefault } : {}) });
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (url.pathname === "/api/provider-context-caps" && req.method === "GET") {
|
|
492
|
+
return jsonResponse({ cap: DEFAULT_PROVIDER_CONTEXT_CAP, value: globalContextCapValue(config), caps: providerContextCaps(config) });
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (url.pathname === "/api/provider-context-caps" && req.method === "PUT") {
|
|
496
|
+
let body: { provider?: unknown; enabled?: unknown; value?: unknown; setAll?: unknown };
|
|
497
|
+
try { body = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
|
|
498
|
+
const { saveConfigPreservingClaudeCode: save } = await import("../../config");
|
|
499
|
+
const { clearModelCache } = await import("../../codex/model-cache");
|
|
500
|
+
const respond = () => jsonResponse({ ok: true, cap: DEFAULT_PROVIDER_CONTEXT_CAP, value: globalContextCapValue(config), caps: providerContextCaps(config) });
|
|
501
|
+
|
|
502
|
+
// Branch 1: set the global cap value and re-point every enabled provider to it.
|
|
503
|
+
if (body.value !== undefined) {
|
|
504
|
+
if (typeof body.value !== "number" || !Number.isFinite(body.value) || body.value <= 0) {
|
|
505
|
+
return jsonResponse({ error: "value must be a positive number" }, 400);
|
|
506
|
+
}
|
|
507
|
+
const affected = Object.keys(providerContextCaps(config));
|
|
508
|
+
setGlobalContextCapValue(config, body.value);
|
|
509
|
+
save(config);
|
|
510
|
+
reconcileLiveStateStores();
|
|
511
|
+
for (const provider of affected) clearModelCache(provider);
|
|
512
|
+
await refreshCodexCatalogBestEffort();
|
|
513
|
+
return respond();
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Branch 2: enable/clear the cap for every provider at once.
|
|
517
|
+
if (body.setAll !== undefined) {
|
|
518
|
+
if (typeof body.setAll !== "boolean") {
|
|
519
|
+
return jsonResponse({ error: "setAll must be a boolean" }, 400);
|
|
520
|
+
}
|
|
521
|
+
const before = Object.keys(providerContextCaps(config));
|
|
522
|
+
const names = Object.keys(config.providers);
|
|
523
|
+
setAllProviderContextCaps(config, names, body.setAll);
|
|
524
|
+
save(config);
|
|
525
|
+
reconcileLiveStateStores();
|
|
526
|
+
for (const provider of new Set([...before, ...names])) clearModelCache(provider);
|
|
527
|
+
await refreshCodexCatalogBestEffort();
|
|
528
|
+
return respond();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Branch 3: existing per-provider toggle (enable writes the current global value).
|
|
532
|
+
if (typeof body.provider !== "string" || typeof body.enabled !== "boolean") {
|
|
533
|
+
return jsonResponse({ error: "provider string and enabled boolean are required" }, 400);
|
|
534
|
+
}
|
|
535
|
+
const provider = body.provider.trim();
|
|
536
|
+
if (!isValidProviderName(provider)) {
|
|
537
|
+
return jsonResponse({ error: "provider name must use letters, numbers, dot, underscore, or hyphen and cannot be a reserved object key" }, 400);
|
|
538
|
+
}
|
|
539
|
+
if (!hasOwnProvider(config.providers, provider)) {
|
|
540
|
+
return jsonResponse({ error: "unknown provider" }, 404);
|
|
541
|
+
}
|
|
542
|
+
setProviderContextCap(config, provider, body.enabled);
|
|
543
|
+
save(config);
|
|
544
|
+
reconcileLiveStateStores();
|
|
545
|
+
clearModelCache(provider);
|
|
546
|
+
await refreshCodexCatalogBestEffort();
|
|
547
|
+
return respond();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Complete GUI picker presets, derived from the canonical provider registry. The GUI is a
|
|
551
|
+
// standalone Vite package, so it consumes this runtime view instead of importing repo-root src.
|
|
552
|
+
if (url.pathname === "/api/provider-presets" && req.method === "GET") {
|
|
553
|
+
return jsonResponse({ providers: deriveProviderPresets() });
|
|
554
|
+
}
|
|
555
|
+
return null;
|
|
556
|
+
}
|