@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,563 @@
|
|
|
1
|
+
import { randomBytes, 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
|
+
readConfigDiagnostics,
|
|
14
|
+
reconcileLiveConfigFromDisk,
|
|
15
|
+
saveConfigPreservingClaudeCode,
|
|
16
|
+
} from "../../config";
|
|
17
|
+
import {
|
|
18
|
+
clearLoginState,
|
|
19
|
+
getLoginStatus,
|
|
20
|
+
isPublicOAuthProvider,
|
|
21
|
+
listOAuthProviders,
|
|
22
|
+
startLoginFlow,
|
|
23
|
+
submitManualLoginCode,
|
|
24
|
+
} from "../../oauth";
|
|
25
|
+
import { OAuthMutationBusyError, removeCredential } from "../../oauth/store";
|
|
26
|
+
import { providerDestinationResolvedError } from "../../lib/destination-policy";
|
|
27
|
+
import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
|
|
28
|
+
import { enrichProviderFromCatalog, listKeyLoginProviders } from "../../oauth/key-providers";
|
|
29
|
+
import { deriveProviderPresets } from "../../providers/derive";
|
|
30
|
+
import { providerCodexAccountMode } from "../../providers/registry";
|
|
31
|
+
import { routedSlug, slugEquals } from "../../providers/slug-codec";
|
|
32
|
+
import { clearProviderQuotaCache, fetchProviderAccountQuotas, fetchProviderQuotaReports, supportsPerAccountQuota } from "../../providers/quota";
|
|
33
|
+
import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
34
|
+
import { clearThreadAccountMap } from "../../codex/routing";
|
|
35
|
+
import {
|
|
36
|
+
normalizeAccountPoolStickyLimit,
|
|
37
|
+
normalizeAccountPoolStrategy,
|
|
38
|
+
parseAccountPoolStickyLimit,
|
|
39
|
+
parseAccountPoolStrategy,
|
|
40
|
+
} from "../../codex/pool-rotation";
|
|
41
|
+
import { primeCodexPoolQuotas } from "../../codex/auth-api";
|
|
42
|
+
import { DEFAULT_PROVIDER_CONTEXT_CAP, globalContextCapValue, providerContextCap, providerContextCaps, setAllProviderContextCaps, setGlobalContextCapValue, setProviderContextCap } from "../../providers/context-cap";
|
|
43
|
+
import { resolveCodexHomeDir } from "../../codex/home";
|
|
44
|
+
import { readUsageEntries } from "../../usage/log";
|
|
45
|
+
import { getUsageDebugLogEntries } from "../../usage/debug";
|
|
46
|
+
import { parseRange, parseUsageSurface, summarizeUsage } from "../../usage/summary";
|
|
47
|
+
import { stripCodexRuntimeProviderFields } from "../../codex/auth-context";
|
|
48
|
+
import { getProviderRegistryEntry } from "../../providers/registry";
|
|
49
|
+
import { getDebugLogEntries } from "../../lib/debug-log-buffer";
|
|
50
|
+
import { getInjectionDebugLogEntries } from "../../lib/injection-debug-log";
|
|
51
|
+
import {
|
|
52
|
+
clearDebugSettings,
|
|
53
|
+
clearDebugSetting,
|
|
54
|
+
getDebugSettings,
|
|
55
|
+
setDebugSettings,
|
|
56
|
+
type DebugFlag,
|
|
57
|
+
} from "../../lib/debug-settings";
|
|
58
|
+
import type { OcxClaudeCodeConfig, OcxConfig, OcxCustomModel, OcxProviderConfig } from "../../types";
|
|
59
|
+
import { drainAndShutdown } from "../lifecycle";
|
|
60
|
+
import { filterRequestLogs, getRequestLogEntries, type RequestLogEntry } from "../request-log";
|
|
61
|
+
import { estimateComboCost, estimateRequestCost, normalizeCostTokens, tokensPerSecond } from "../../usage/cost";
|
|
62
|
+
import type { PersistedUsageAttempt } from "../../usage/log";
|
|
63
|
+
import { AUTH_MATRIX, isAllowedRequestOrigin, jsonResponse, providerManagementConfigError, publicProviderBaseUrl, safeConfigDTO } from "../auth-cors";
|
|
64
|
+
import { applySystemEnvToggle } from "../system-env";
|
|
65
|
+
import { buildApiAccessEndpoints } from "./api-access";
|
|
66
|
+
|
|
67
|
+
import { isPlainRecord, parseDebugLogQuery, tokPerSecondResult, unavailableCostReason, costResult, requestLogDto, stripRegistryOnlyStaticHeaders, fetchAllModels } from "./shared";
|
|
68
|
+
import type { MetricUnavailableReason, TokPerSecondResult, CostEstimateReason, CostResult, MetricSource } from "./shared";
|
|
69
|
+
import type { ManagementContext } from "./context";
|
|
70
|
+
import { readManagementJsonBody, readManagementJsonBodyOr, rethrowManagementBodyTooLarge } from "./body";
|
|
71
|
+
import { codexAccountNamespaceProviderCollisionError } from "../../codex/account-namespace-match";
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Parses a bounded JSON object body, or null. Malformed JSON is swallowed; an
|
|
75
|
+
* oversized body still throws so the management dispatcher can return 413.
|
|
76
|
+
* a malformed body, which used to surface as a 500 from the key routes.
|
|
77
|
+
*/
|
|
78
|
+
async function readJsonBody(req: Request): Promise<Record<string, unknown> | null> {
|
|
79
|
+
try {
|
|
80
|
+
const parsed: unknown = await readManagementJsonBody(req);
|
|
81
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
|
|
82
|
+
? parsed as Record<string, unknown>
|
|
83
|
+
: null;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
rethrowManagementBodyTooLarge(error);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The single place key-name rules live. The config read schema is deliberately
|
|
92
|
+
* permissive so an existing config can never become unloadable; this is the write
|
|
93
|
+
* boundary that keeps new junk out. A non-string name used to reach `.trim()` and
|
|
94
|
+
* throw.
|
|
95
|
+
*/
|
|
96
|
+
function validateKeyName(
|
|
97
|
+
raw: unknown,
|
|
98
|
+
opts: { required: boolean },
|
|
99
|
+
): { value: string } | { error: string } {
|
|
100
|
+
if (raw === undefined || raw === null) {
|
|
101
|
+
return opts.required ? { error: "name required" } : { value: "" };
|
|
102
|
+
}
|
|
103
|
+
if (typeof raw !== "string") return { error: "name must be a string" };
|
|
104
|
+
// Check the RAW string: trimming first would silently accept "deploy\n" by
|
|
105
|
+
// deleting the very character being rejected.
|
|
106
|
+
// eslint-disable-next-line no-control-regex
|
|
107
|
+
if (/[\u0000-\u001f\u007f]/.test(raw)) return { error: "invalid name" };
|
|
108
|
+
const value = raw.trim();
|
|
109
|
+
if (opts.required && !value) return { error: "name required" };
|
|
110
|
+
if (value.length > 64) return { error: "name too long" };
|
|
111
|
+
return { value };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
115
|
+
const { req, url, config, deps, refreshCodexCatalogBestEffort, syncClaudeAgentDefsBestEffort } = ctx;
|
|
116
|
+
|
|
117
|
+
// Which providers support real OAuth login (drives the GUI's "Log in with …" buttons).
|
|
118
|
+
if (url.pathname === "/api/oauth/providers" && req.method === "GET") {
|
|
119
|
+
return jsonResponse({ providers: listOAuthProviders() });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// API-key "login" providers (open dashboard → paste key). Drives the GUI's key-provider picker.
|
|
123
|
+
if (url.pathname === "/api/key-providers" && req.method === "GET") {
|
|
124
|
+
return jsonResponse({ providers: listKeyLoginProviders() });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// OAuth login (xai now; anthropic/kimi in cycle 2). Starts the flow and returns the auth URL;
|
|
128
|
+
// the provider's loopback callback server (inside this process) captures the redirect in the
|
|
129
|
+
// background, then the credential is persisted. The GUI opens the URL and polls /api/oauth/status.
|
|
130
|
+
if (url.pathname === "/api/oauth/login" && req.method === "POST") {
|
|
131
|
+
const body = await readManagementJsonBodyOr(req, {}) as { provider?: string; addAccount?: boolean; accountId?: string; reauth?: boolean };
|
|
132
|
+
const provider = (body.provider ?? "").trim().toLowerCase();
|
|
133
|
+
if (!isPublicOAuthProvider(provider)) return jsonResponse({ error: "unknown oauth provider" }, 400);
|
|
134
|
+
const namespaceCollision = codexAccountNamespaceProviderCollisionError(config.codexAccountNamespaces, provider);
|
|
135
|
+
if (namespaceCollision) return jsonResponse({ error: namespaceCollision }, 409);
|
|
136
|
+
const accountId = body.accountId?.trim();
|
|
137
|
+
const reauth = body.reauth === true || Boolean(accountId);
|
|
138
|
+
try {
|
|
139
|
+
if (accountId) {
|
|
140
|
+
const { getAccountSet } = await import("../../oauth/store");
|
|
141
|
+
const set = getAccountSet(provider);
|
|
142
|
+
if (!set?.accounts.some(a => a.id === accountId)) {
|
|
143
|
+
return jsonResponse({ error: "Unknown account for reauth" }, 404);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Use persisted state, not the live object, as the merge base: another management
|
|
147
|
+
// request may already have mutated live config and yielded before its save.
|
|
148
|
+
const persistedBaseline = readConfigDiagnostics().config;
|
|
149
|
+
// addAccount / reauth forces a fresh browser identity (skips local-CLI token import).
|
|
150
|
+
const { url: authUrl, instructions, deviceCode } = await startLoginFlow(provider, {
|
|
151
|
+
forceLogin: body.addAccount === true || reauth,
|
|
152
|
+
...(accountId ? { reauthAccountId: accountId } : {}),
|
|
153
|
+
}, {
|
|
154
|
+
// startLoginFlow returns the authorization URL before background persistence completes.
|
|
155
|
+
// Three-way reconcile settled disk changes so a failed login cannot leave a provider
|
|
156
|
+
// live-only and an in-flight management mutation cannot be erased before it saves.
|
|
157
|
+
onSettled: () => {
|
|
158
|
+
reconcileLiveConfigFromDisk(config, persistedBaseline);
|
|
159
|
+
reconcileLiveStateStores();
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
if (authUrl && !deviceCode) {
|
|
163
|
+
// Open the browser server-side (the proxy runs on the user's machine) — the GUI's
|
|
164
|
+
// window.open is popup-blocked because it runs after an await, not a direct click.
|
|
165
|
+
const { openUrl } = await import("../../lib/open-url");
|
|
166
|
+
openUrl(authUrl);
|
|
167
|
+
}
|
|
168
|
+
return jsonResponse({ url: authUrl, instructions, deviceCode });
|
|
169
|
+
} catch (err) {
|
|
170
|
+
if (err instanceof OAuthMutationBusyError) throw err;
|
|
171
|
+
return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, 409);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Cancel an in-progress browser/device OAuth login (GUI "Cancel" / modal close). Guarded by
|
|
176
|
+
// the same public predicate as /api/oauth/login — only publicly startable flows are cancellable.
|
|
177
|
+
if (url.pathname === "/api/oauth/login/cancel" && req.method === "POST") {
|
|
178
|
+
const body = await readManagementJsonBodyOr(req, {}) as { provider?: string };
|
|
179
|
+
const provider = (body.provider ?? "").trim().toLowerCase();
|
|
180
|
+
if (!isPublicOAuthProvider(provider)) return jsonResponse({ error: "unknown oauth provider" }, 400);
|
|
181
|
+
const { cancelLoginFlow } = await import("../../oauth");
|
|
182
|
+
const cancelled = cancelLoginFlow(provider);
|
|
183
|
+
return jsonResponse({ ok: true, cancelled });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Manual fallback for browser OAuth: paste the final redirect URL (or authorization code)
|
|
187
|
+
// when the browser cannot reach the loopback callback (remote/SSH/blocked localhost).
|
|
188
|
+
if (url.pathname === "/api/oauth/login/code" && req.method === "POST") {
|
|
189
|
+
const body = await readManagementJsonBodyOr(req, {}) as { provider?: string; input?: string; code?: string };
|
|
190
|
+
const provider = (body.provider ?? "").trim().toLowerCase();
|
|
191
|
+
if (!isPublicOAuthProvider(provider)) return jsonResponse({ error: "unknown oauth provider" }, 400);
|
|
192
|
+
const input = typeof body.input === "string" ? body.input : typeof body.code === "string" ? body.code : "";
|
|
193
|
+
// Authorization responses are measured in hundreds of bytes; never accept the
|
|
194
|
+
// generic management-body allowance here.
|
|
195
|
+
if (input.length > 4096) return jsonResponse({ error: "input too long" }, 400);
|
|
196
|
+
const result = submitManualLoginCode(provider, input);
|
|
197
|
+
if (!result.ok) return jsonResponse({ error: result.error }, 409);
|
|
198
|
+
return jsonResponse({ ok: true });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (url.pathname === "/api/oauth/status" && req.method === "GET") {
|
|
202
|
+
const provider = (url.searchParams.get("provider") ?? "").trim().toLowerCase();
|
|
203
|
+
if (!isPublicOAuthProvider(provider)) return jsonResponse({ error: "unknown oauth provider" }, 400);
|
|
204
|
+
return jsonResponse(getLoginStatus(provider));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (url.pathname === "/api/oauth/logout" && req.method === "POST") {
|
|
208
|
+
const provider = (url.searchParams.get("provider") ?? "").trim().toLowerCase();
|
|
209
|
+
if (!isPublicOAuthProvider(provider)) return jsonResponse({ error: "unknown oauth provider" }, 400);
|
|
210
|
+
await removeCredential(provider);
|
|
211
|
+
reconcileLiveStateStores();
|
|
212
|
+
clearLoginState(provider);
|
|
213
|
+
// Drop cached/last-good quota rows tied to the removed credential.
|
|
214
|
+
const { clearProviderQuotaCache, clearAccountQuotaCache } = await import("../../providers/quota");
|
|
215
|
+
clearProviderQuotaCache();
|
|
216
|
+
clearAccountQuotaCache(provider);
|
|
217
|
+
return jsonResponse({ success: true });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Multiauth account management: list a provider's logged-in accounts, switch the active
|
|
221
|
+
// one, or remove one. Emails are masked; tokens never leave the store.
|
|
222
|
+
if (url.pathname === "/api/oauth/accounts" && req.method === "GET") {
|
|
223
|
+
const provider = (url.searchParams.get("provider") ?? "").trim().toLowerCase();
|
|
224
|
+
if (!isPublicOAuthProvider(provider)) return jsonResponse({ error: "unknown oauth provider" }, 400);
|
|
225
|
+
const status = getLoginStatus(provider);
|
|
226
|
+
const { getAccountSet } = await import("../../oauth/store");
|
|
227
|
+
const {
|
|
228
|
+
oauthAccountHealthFields,
|
|
229
|
+
projectOAuthAccountHealth,
|
|
230
|
+
projectStoredOAuthAccountHealth,
|
|
231
|
+
} = await import("../../oauth/health");
|
|
232
|
+
const projectAccounts = () => {
|
|
233
|
+
const set = getAccountSet(provider);
|
|
234
|
+
const current = getLoginStatus(provider);
|
|
235
|
+
return {
|
|
236
|
+
activeAccountId: current.activeAccountId ?? null,
|
|
237
|
+
accounts: (current.accounts ?? []).map(summary => {
|
|
238
|
+
const full = set?.accounts.find(account => account.id === summary.id);
|
|
239
|
+
const health = full
|
|
240
|
+
? projectStoredOAuthAccountHealth(provider, full)
|
|
241
|
+
: projectOAuthAccountHealth({
|
|
242
|
+
needsReauth: summary.needsReauth === true,
|
|
243
|
+
reauthReason: summary.needsReauth === true ? "refresh_failed" : undefined,
|
|
244
|
+
});
|
|
245
|
+
return { ...summary, ...oauthAccountHealthFields(provider, summary.id, health) };
|
|
246
|
+
}),
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
// Per-account rate limits: Anthropic (5h/weekly) and xAI (monthly credits + plan badge)
|
|
250
|
+
// report usage per credential, so every logged-in account can show its own bars — not
|
|
251
|
+
// just the active one. Opt-in via ?quota=1 so the plain account list stays a cheap local
|
|
252
|
+
// read; ?refresh=1 bypasses the TTL.
|
|
253
|
+
const wantQuota = url.searchParams.get("quota") === "1" && supportsPerAccountQuota(provider);
|
|
254
|
+
if (!wantQuota) return jsonResponse(projectAccounts());
|
|
255
|
+
const forceRefresh = url.searchParams.get("refresh") === "1";
|
|
256
|
+
// Probing may refresh the active credential and mark needsReauth — project health
|
|
257
|
+
// from the post-probe store so the response is not stale.
|
|
258
|
+
const rows = await fetchProviderAccountQuotas(provider, forceRefresh);
|
|
259
|
+
const byId = new Map(rows.map(row => [row.accountId, row]));
|
|
260
|
+
const projected = projectAccounts();
|
|
261
|
+
return jsonResponse({
|
|
262
|
+
activeAccountId: projected.activeAccountId,
|
|
263
|
+
accounts: projected.accounts.map(account => {
|
|
264
|
+
const row = byId.get(account.id);
|
|
265
|
+
if (!row) return account;
|
|
266
|
+
return {
|
|
267
|
+
...account,
|
|
268
|
+
quota: row.quota,
|
|
269
|
+
...(row.plan ? { plan: row.plan } : {}),
|
|
270
|
+
...(row.unavailable ? { quotaUnavailable: true } : {}),
|
|
271
|
+
};
|
|
272
|
+
}),
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (url.pathname === "/api/oauth/accounts/active" && req.method === "PUT") {
|
|
276
|
+
const body = await readManagementJsonBodyOr(req, {}) as { provider?: string; accountId?: string };
|
|
277
|
+
const provider = (body.provider ?? "").trim().toLowerCase();
|
|
278
|
+
if (!isPublicOAuthProvider(provider)) return jsonResponse({ error: "unknown oauth provider" }, 400);
|
|
279
|
+
if (!body.accountId) return jsonResponse({ error: "missing accountId" }, 400);
|
|
280
|
+
const { setActiveAccount } = await import("../../oauth/store");
|
|
281
|
+
if (!(await setActiveAccount(provider, body.accountId))) return jsonResponse({ error: "account not found" }, 404);
|
|
282
|
+
if (provider === "anthropic") {
|
|
283
|
+
const { resetAnthropicRoutingForManualSelection } = await import("../../oauth/anthropic-routing");
|
|
284
|
+
resetAnthropicRoutingForManualSelection(body.accountId);
|
|
285
|
+
}
|
|
286
|
+
const { clearProviderQuotaCache } = await import("../../providers/quota");
|
|
287
|
+
clearProviderQuotaCache();
|
|
288
|
+
return jsonResponse({ ok: true, provider, activeAccountId: body.accountId });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Opt-in Anthropic OAuth account pool (#294): enable/threshold/strategy + clear cooldown.
|
|
292
|
+
if (url.pathname === "/api/oauth/accounts/pool" && req.method === "GET") {
|
|
293
|
+
const provider = (url.searchParams.get("provider") ?? "").trim().toLowerCase();
|
|
294
|
+
if (provider !== "anthropic") return jsonResponse({ error: "pool config is only supported for anthropic" }, 400);
|
|
295
|
+
const pool = config.anthropicAccountPool ?? {};
|
|
296
|
+
return jsonResponse({
|
|
297
|
+
provider,
|
|
298
|
+
enabled: pool.enabled === true,
|
|
299
|
+
autoSwitchThreshold: typeof pool.autoSwitchThreshold === "number" ? pool.autoSwitchThreshold : 80,
|
|
300
|
+
strategy: normalizeAccountPoolStrategy(pool.strategy),
|
|
301
|
+
stickyLimit: normalizeAccountPoolStickyLimit(pool.stickyLimit),
|
|
302
|
+
experimental: true,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
if (url.pathname === "/api/oauth/accounts/pool" && (req.method === "PUT" || req.method === "PATCH")) {
|
|
306
|
+
const parsedBody = await readManagementJsonBodyOr(req, {});
|
|
307
|
+
if (!isPlainRecord(parsedBody)) {
|
|
308
|
+
return jsonResponse({ error: "body must be an object" }, 400);
|
|
309
|
+
}
|
|
310
|
+
const body = parsedBody as {
|
|
311
|
+
provider?: unknown;
|
|
312
|
+
enabled?: unknown;
|
|
313
|
+
autoSwitchThreshold?: unknown;
|
|
314
|
+
strategy?: unknown;
|
|
315
|
+
stickyLimit?: unknown;
|
|
316
|
+
};
|
|
317
|
+
const provider = typeof body.provider === "string" ? body.provider.trim().toLowerCase() : "";
|
|
318
|
+
if (provider !== "anthropic") return jsonResponse({ error: "pool config is only supported for anthropic" }, 400);
|
|
319
|
+
let enabled = config.anthropicAccountPool?.enabled === true;
|
|
320
|
+
if (body.enabled !== undefined) {
|
|
321
|
+
if (typeof body.enabled !== "boolean") return jsonResponse({ error: "enabled must be a boolean" }, 400);
|
|
322
|
+
enabled = body.enabled;
|
|
323
|
+
}
|
|
324
|
+
let threshold = config.anthropicAccountPool?.autoSwitchThreshold ?? 80;
|
|
325
|
+
if (body.autoSwitchThreshold !== undefined) {
|
|
326
|
+
if (
|
|
327
|
+
typeof body.autoSwitchThreshold !== "number"
|
|
328
|
+
|| !Number.isInteger(body.autoSwitchThreshold)
|
|
329
|
+
|| body.autoSwitchThreshold < 0
|
|
330
|
+
|| body.autoSwitchThreshold > 100
|
|
331
|
+
) {
|
|
332
|
+
return jsonResponse({ error: "autoSwitchThreshold must be an integer 0-100" }, 400);
|
|
333
|
+
}
|
|
334
|
+
threshold = body.autoSwitchThreshold;
|
|
335
|
+
}
|
|
336
|
+
let strategy = config.anthropicAccountPool?.strategy;
|
|
337
|
+
if (body.strategy !== undefined) {
|
|
338
|
+
const parsed = parseAccountPoolStrategy(body.strategy);
|
|
339
|
+
if (parsed === null) {
|
|
340
|
+
return jsonResponse({ error: "strategy must be one of: quota, round-robin, fill-first" }, 400);
|
|
341
|
+
}
|
|
342
|
+
strategy = parsed;
|
|
343
|
+
}
|
|
344
|
+
let stickyLimit = config.anthropicAccountPool?.stickyLimit;
|
|
345
|
+
if (body.stickyLimit !== undefined) {
|
|
346
|
+
const parsed = parseAccountPoolStickyLimit(body.stickyLimit);
|
|
347
|
+
if (parsed === null) {
|
|
348
|
+
return jsonResponse({ error: "stickyLimit must be an integer 1-100" }, 400);
|
|
349
|
+
}
|
|
350
|
+
stickyLimit = parsed;
|
|
351
|
+
}
|
|
352
|
+
config.anthropicAccountPool = {
|
|
353
|
+
enabled,
|
|
354
|
+
autoSwitchThreshold: threshold,
|
|
355
|
+
...(strategy !== undefined ? { strategy } : {}),
|
|
356
|
+
...(stickyLimit !== undefined ? { stickyLimit } : {}),
|
|
357
|
+
};
|
|
358
|
+
saveConfigPreservingClaudeCode(config);
|
|
359
|
+
reconcileLiveStateStores();
|
|
360
|
+
return jsonResponse({
|
|
361
|
+
ok: true,
|
|
362
|
+
provider,
|
|
363
|
+
enabled,
|
|
364
|
+
autoSwitchThreshold: threshold,
|
|
365
|
+
strategy: normalizeAccountPoolStrategy(strategy),
|
|
366
|
+
stickyLimit: normalizeAccountPoolStickyLimit(stickyLimit),
|
|
367
|
+
experimental: true,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
if (url.pathname === "/api/oauth/accounts/clear-cooldown" && req.method === "POST") {
|
|
371
|
+
const body = await readManagementJsonBodyOr(req, {}) as { provider?: unknown; accountId?: unknown };
|
|
372
|
+
const provider = typeof body.provider === "string" ? body.provider.trim().toLowerCase() : "";
|
|
373
|
+
const accountId = typeof body.accountId === "string" ? body.accountId.trim() : "";
|
|
374
|
+
if (provider !== "anthropic") return jsonResponse({ error: "clear-cooldown is only supported for anthropic" }, 400);
|
|
375
|
+
if (!accountId) return jsonResponse({ error: "missing accountId" }, 400);
|
|
376
|
+
const { clearAnthropicAccountCooldown } = await import("../../oauth/anthropic-routing");
|
|
377
|
+
const cleared = clearAnthropicAccountCooldown(accountId);
|
|
378
|
+
return jsonResponse({ ok: true, cleared });
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (url.pathname === "/api/oauth/accounts/alias" && req.method === "PUT") {
|
|
382
|
+
const body = await readManagementJsonBodyOr(req, {}) as { provider?: unknown; accountId?: unknown; alias?: unknown };
|
|
383
|
+
const provider = typeof body.provider === "string" ? body.provider.trim().toLowerCase() : "";
|
|
384
|
+
const accountId = typeof body.accountId === "string" ? body.accountId.trim() : "";
|
|
385
|
+
const alias = typeof body.alias === "string" ? body.alias.trim() : "";
|
|
386
|
+
if (!isPublicOAuthProvider(provider)) return jsonResponse({ error: "unknown oauth provider" }, 400);
|
|
387
|
+
if (!accountId) return jsonResponse({ error: "missing accountId" }, 400);
|
|
388
|
+
if (typeof body.alias !== "string" || alias.length > 80 || /[\x00-\x1f\x7f]/.test(alias)) {
|
|
389
|
+
return jsonResponse({ error: "alias must be at most 80 printable characters" }, 400);
|
|
390
|
+
}
|
|
391
|
+
const { setAccountAlias } = await import("../../oauth/store");
|
|
392
|
+
if (!(await setAccountAlias(provider, accountId, alias || undefined))) return jsonResponse({ error: "account not found" }, 404);
|
|
393
|
+
return jsonResponse({ ok: true, provider, accountId, alias: alias || null });
|
|
394
|
+
}
|
|
395
|
+
if (url.pathname === "/api/oauth/accounts" && req.method === "DELETE") {
|
|
396
|
+
const provider = (url.searchParams.get("provider") ?? "").trim().toLowerCase();
|
|
397
|
+
const id = url.searchParams.get("id") ?? "";
|
|
398
|
+
if (!isPublicOAuthProvider(provider)) return jsonResponse({ error: "unknown oauth provider" }, 400);
|
|
399
|
+
if (!id) return jsonResponse({ error: "missing id" }, 400);
|
|
400
|
+
const { removeAccount, getAccountSet } = await import("../../oauth/store");
|
|
401
|
+
if (!(await removeAccount(provider, id))) return jsonResponse({ error: "account not found" }, 404);
|
|
402
|
+
reconcileLiveStateStores();
|
|
403
|
+
if (provider === "anthropic") {
|
|
404
|
+
const { clearAnthropicAccountCooldown, clearAnthropicSessionAffinityForAccount } = await import("../../oauth/anthropic-routing");
|
|
405
|
+
clearAnthropicAccountCooldown(id);
|
|
406
|
+
clearAnthropicSessionAffinityForAccount(id);
|
|
407
|
+
}
|
|
408
|
+
if (!getAccountSet(provider)) clearLoginState(provider);
|
|
409
|
+
const { clearProviderQuotaCache, clearAccountQuotaCache } = await import("../../providers/quota");
|
|
410
|
+
clearProviderQuotaCache();
|
|
411
|
+
clearAccountQuotaCache(provider);
|
|
412
|
+
return jsonResponse({ ok: true });
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Multi-key pool for API-key providers (same GUI dropdown as OAuth multiauth): list masked
|
|
416
|
+
// keys, add one (upserts + activates), switch the active key, or remove one. `apiKey` always
|
|
417
|
+
// mirrors the active entry so routing is untouched.
|
|
418
|
+
if (url.pathname === "/api/providers/keys" && req.method === "GET") {
|
|
419
|
+
const name = (url.searchParams.get("name") ?? "").trim();
|
|
420
|
+
if (!name || !isValidProviderName(name) || !hasOwnProvider(config.providers, name)) return jsonResponse({ error: "unknown provider" }, 404);
|
|
421
|
+
const { listProviderApiKeys } = await import("../../providers/api-keys");
|
|
422
|
+
return jsonResponse(listProviderApiKeys(config, name));
|
|
423
|
+
}
|
|
424
|
+
if (url.pathname === "/api/providers/keys" && req.method === "POST") {
|
|
425
|
+
const body = await readManagementJsonBodyOr(req, {}) as { name?: string; key?: string; label?: string };
|
|
426
|
+
const name = (body.name ?? "").trim();
|
|
427
|
+
if (!name || !isValidProviderName(name) || !hasOwnProvider(config.providers, name)) return jsonResponse({ error: "unknown provider" }, 404);
|
|
428
|
+
if (typeof body.key !== "string" || !body.key.trim()) return jsonResponse({ error: "key is required" }, 400);
|
|
429
|
+
const { addProviderApiKey } = await import("../../providers/api-keys");
|
|
430
|
+
const result = addProviderApiKey(config, name, body.key, body.label);
|
|
431
|
+
if ("error" in result) return jsonResponse({ error: result.error }, 400);
|
|
432
|
+
const { clearModelCache } = await import("../../codex/model-cache");
|
|
433
|
+
clearModelCache(name);
|
|
434
|
+
const { clearProviderQuotaCache } = await import("../../providers/quota");
|
|
435
|
+
clearProviderQuotaCache();
|
|
436
|
+
const { clearKeyCooldowns } = await import("../../providers/key-failover");
|
|
437
|
+
clearKeyCooldowns(name); // manual key management resets 429 cooldown state
|
|
438
|
+
return jsonResponse({ ok: true, id: result.id }, 201);
|
|
439
|
+
}
|
|
440
|
+
if (url.pathname === "/api/providers/keys/active" && req.method === "PUT") {
|
|
441
|
+
const body = await readManagementJsonBodyOr(req, {}) as { name?: string; id?: string };
|
|
442
|
+
const name = (body.name ?? "").trim();
|
|
443
|
+
if (!name || !isValidProviderName(name) || !hasOwnProvider(config.providers, name)) return jsonResponse({ error: "unknown provider" }, 404);
|
|
444
|
+
if (!body.id) return jsonResponse({ error: "missing id" }, 400);
|
|
445
|
+
const { setActiveProviderApiKey } = await import("../../providers/api-keys");
|
|
446
|
+
if (!setActiveProviderApiKey(config, name, body.id)) return jsonResponse({ error: "key not found" }, 404);
|
|
447
|
+
const { clearModelCache } = await import("../../codex/model-cache");
|
|
448
|
+
clearModelCache(name);
|
|
449
|
+
const { clearProviderQuotaCache } = await import("../../providers/quota");
|
|
450
|
+
clearProviderQuotaCache();
|
|
451
|
+
const { clearKeyCooldowns } = await import("../../providers/key-failover");
|
|
452
|
+
clearKeyCooldowns(name); // manual key management resets 429 cooldown state
|
|
453
|
+
return jsonResponse({ ok: true, name, activeId: body.id });
|
|
454
|
+
}
|
|
455
|
+
if (url.pathname === "/api/providers/keys/alias" && req.method === "PUT") {
|
|
456
|
+
const body = await readManagementJsonBodyOr(req, {}) as { name?: unknown; id?: unknown; alias?: unknown };
|
|
457
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
458
|
+
const id = typeof body.id === "string" ? body.id.trim() : "";
|
|
459
|
+
const alias = typeof body.alias === "string" ? body.alias.trim() : "";
|
|
460
|
+
if (!name || !isValidProviderName(name) || !hasOwnProvider(config.providers, name)) return jsonResponse({ error: "unknown provider" }, 404);
|
|
461
|
+
if (!id) return jsonResponse({ error: "missing id" }, 400);
|
|
462
|
+
if (typeof body.alias !== "string" || alias.length > 80 || /[\x00-\x1f\x7f]/.test(alias)) {
|
|
463
|
+
return jsonResponse({ error: "alias must be at most 80 printable characters" }, 400);
|
|
464
|
+
}
|
|
465
|
+
const { setProviderApiKeyLabel } = await import("../../providers/api-keys");
|
|
466
|
+
if (!setProviderApiKeyLabel(config, name, id, alias || undefined)) return jsonResponse({ error: "key not found" }, 404);
|
|
467
|
+
return jsonResponse({ ok: true, name, id, alias: alias || null });
|
|
468
|
+
}
|
|
469
|
+
if (url.pathname === "/api/providers/keys" && req.method === "DELETE") {
|
|
470
|
+
const name = (url.searchParams.get("name") ?? "").trim();
|
|
471
|
+
const id = url.searchParams.get("id") ?? "";
|
|
472
|
+
if (!name || !isValidProviderName(name) || !hasOwnProvider(config.providers, name)) return jsonResponse({ error: "unknown provider" }, 404);
|
|
473
|
+
if (!id) return jsonResponse({ error: "missing id" }, 400);
|
|
474
|
+
const { removeProviderApiKey } = await import("../../providers/api-keys");
|
|
475
|
+
if (!removeProviderApiKey(config, name, id)) return jsonResponse({ error: "key not found" }, 404);
|
|
476
|
+
const { clearModelCache } = await import("../../codex/model-cache");
|
|
477
|
+
clearModelCache(name);
|
|
478
|
+
const { clearProviderQuotaCache } = await import("../../providers/quota");
|
|
479
|
+
clearProviderQuotaCache();
|
|
480
|
+
const { clearKeyCooldowns } = await import("../../providers/key-failover");
|
|
481
|
+
clearKeyCooldowns(name); // manual key management resets 429 cooldown state
|
|
482
|
+
return jsonResponse({ ok: true });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ---------------------------------------------------------------------------
|
|
486
|
+
// API Keys management
|
|
487
|
+
// ---------------------------------------------------------------------------
|
|
488
|
+
if (url.pathname === "/api/keys" && req.method === "GET") {
|
|
489
|
+
const keys = config.apiKeys ?? [];
|
|
490
|
+
const endpoints = buildApiAccessEndpoints(config, {
|
|
491
|
+
requestUrl: req.url,
|
|
492
|
+
requestHost: req.headers.get("host"),
|
|
493
|
+
requestOrigin: req.headers.get("origin"),
|
|
494
|
+
});
|
|
495
|
+
const { readApiKeyUsageRollup } = await import("./api-key-usage");
|
|
496
|
+
const { rollup, attributionSince, historyTruncated } = await readApiKeyUsageRollup(keys.map(k => k.id), config.managementUsageMaxReadBytes);
|
|
497
|
+
return jsonResponse({
|
|
498
|
+
// 8 random hex past the fixed `ocx_data_` literal: enough to tell two keys
|
|
499
|
+
// apart in a list, with 128 bits of the tail still unrevealed. Masking only
|
|
500
|
+
// 8 characters showed `ocx_data...` for every key ever generated.
|
|
501
|
+
keys: keys.map(k => ({
|
|
502
|
+
id: k.id,
|
|
503
|
+
name: k.name,
|
|
504
|
+
prefix: k.key.slice(0, 17) + "...",
|
|
505
|
+
createdAt: k.createdAt,
|
|
506
|
+
usage: rollup.get(k.id) ?? { requests7d: 0, totalRequests: 0 },
|
|
507
|
+
})),
|
|
508
|
+
// Dataset-level and singular: it describes the usage log, not any one key.
|
|
509
|
+
...(attributionSince ? { attributionSince } : {}),
|
|
510
|
+
...(historyTruncated ? { historyTruncated: true } : {}),
|
|
511
|
+
authMatrix: AUTH_MATRIX,
|
|
512
|
+
...endpoints,
|
|
513
|
+
}, 200, req, config);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (url.pathname === "/api/keys" && req.method === "POST") {
|
|
517
|
+
const body = await readJsonBody(req);
|
|
518
|
+
if (!body) return jsonResponse({ error: "invalid body" }, 400, req, config);
|
|
519
|
+
const nameField = validateKeyName(body.name, { required: false });
|
|
520
|
+
if ("error" in nameField) return jsonResponse({ error: nameField.error }, 400, req, config);
|
|
521
|
+
const name = nameField.value || "default";
|
|
522
|
+
// A direct random draw. The previous derivation hashed every configured
|
|
523
|
+
// provider API key into the input, which was never needed for uniqueness and
|
|
524
|
+
// made this secret's safety argument depend on string concatenation rather
|
|
525
|
+
// than the RNG. 20 bytes is the same 40 hex characters as before, so nothing
|
|
526
|
+
// that pattern-matches the key shape changes.
|
|
527
|
+
const key = "ocx_data_" + randomBytes(20).toString("hex");
|
|
528
|
+
const entry = { id: randomUUID(), name, key, createdAt: new Date().toISOString() };
|
|
529
|
+
config.apiKeys = [...(config.apiKeys ?? []), entry];
|
|
530
|
+
saveConfigPreservingClaudeCode(config);
|
|
531
|
+
reconcileLiveStateStores();
|
|
532
|
+
return jsonResponse({ id: entry.id, name: entry.name, key: entry.key, createdAt: entry.createdAt }, 201, req, config);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (url.pathname === "/api/keys" && req.method === "PATCH") {
|
|
536
|
+
const body = await readJsonBody(req);
|
|
537
|
+
if (!body) return jsonResponse({ error: "invalid body" }, 400, req, config);
|
|
538
|
+
if (typeof body.id !== "string" || !body.id) return jsonResponse({ error: "id required" }, 400, req, config);
|
|
539
|
+
const nameField = validateKeyName(body.name, { required: true });
|
|
540
|
+
if ("error" in nameField) return jsonResponse({ error: nameField.error }, 400, req, config);
|
|
541
|
+
const entry = (config.apiKeys ?? []).find(k => k.id === body.id);
|
|
542
|
+
if (!entry) return jsonResponse({ error: "key not found" }, 404, req, config);
|
|
543
|
+
entry.name = nameField.value;
|
|
544
|
+
saveConfigPreservingClaudeCode(config);
|
|
545
|
+
reconcileLiveStateStores();
|
|
546
|
+
// Never echo key material from a rename.
|
|
547
|
+
return jsonResponse({ id: entry.id, name: entry.name, createdAt: entry.createdAt }, 200, req, config);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (url.pathname === "/api/keys" && req.method === "DELETE") {
|
|
551
|
+
const body = await readJsonBody(req);
|
|
552
|
+
if (!body) return jsonResponse({ error: "invalid body" }, 400, req, config);
|
|
553
|
+
if (typeof body.id !== "string" || !body.id) return jsonResponse({ error: "id required" }, 400, req, config);
|
|
554
|
+
const before = (config.apiKeys ?? []).length;
|
|
555
|
+
config.apiKeys = (config.apiKeys ?? []).filter(k => k.id !== body.id);
|
|
556
|
+
// A stale id must not read as a successful revocation.
|
|
557
|
+
if (config.apiKeys.length === before) return jsonResponse({ error: "key not found" }, 404, req, config);
|
|
558
|
+
saveConfigPreservingClaudeCode(config);
|
|
559
|
+
reconcileLiveStateStores();
|
|
560
|
+
return jsonResponse({ success: true }, 200, req, config);
|
|
561
|
+
}
|
|
562
|
+
return null;
|
|
563
|
+
}
|