@remodex/rmx 1.0.2
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/AGENTS_INSTALL.md +91 -0
- package/LICENSE +21 -0
- package/README.md +242 -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 +584 -0
- package/bin/package-main.mjs +9 -0
- package/gui/dist/assets/index-CZqebSPQ.css +1 -0
- package/gui/dist/assets/index-CkETtt7P.js +71 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/fonts/google-sans-cyrillic.woff2 +0 -0
- package/gui/dist/fonts/google-sans-latin.woff2 +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/claude-color.svg +1 -0
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -0
- package/gui/dist/provider-icons/cursor-color.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/github-copilot-color.svg +1 -0
- package/gui/dist/provider-icons/gitlab-duo-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/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 +2 -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 +118 -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 +1205 -0
- package/src/adapters/azure.ts +36 -0
- package/src/adapters/base.ts +83 -0
- package/src/adapters/client-fingerprint.ts +59 -0
- package/src/adapters/command-code.ts +453 -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 +139 -0
- package/src/adapters/cursor/exec-policy.ts +88 -0
- package/src/adapters/cursor/framing.ts +250 -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 +1235 -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 +59 -0
- package/src/adapters/cursor/native-exec-desktop.ts +184 -0
- package/src/adapters/cursor/native-exec-fs.ts +332 -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 +604 -0
- package/src/adapters/cursor/protobuf-events.ts +735 -0
- package/src/adapters/cursor/protobuf-request.ts +719 -0
- package/src/adapters/cursor/request-builder.ts +280 -0
- package/src/adapters/cursor/thread-continuity.ts +67 -0
- package/src/adapters/cursor/tool-definitions.ts +621 -0
- package/src/adapters/cursor/transport-retry.ts +132 -0
- package/src/adapters/cursor/transport.ts +57 -0
- package/src/adapters/cursor/types.ts +59 -0
- package/src/adapters/cursor.ts +196 -0
- package/src/adapters/google-antigravity-replay.ts +520 -0
- package/src/adapters/google-antigravity-wire.ts +140 -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 +24 -0
- package/src/adapters/google-wire-compiler.ts +232 -0
- package/src/adapters/google.ts +859 -0
- package/src/adapters/identity.ts +77 -0
- package/src/adapters/image.ts +23 -0
- package/src/adapters/kiro-constants.ts +16 -0
- package/src/adapters/kiro-errors.ts +208 -0
- package/src/adapters/kiro-events.ts +197 -0
- package/src/adapters/kiro-images.ts +129 -0
- package/src/adapters/kiro-retry.ts +312 -0
- package/src/adapters/kiro-thinking.ts +104 -0
- package/src/adapters/kiro-tool-fallback.ts +36 -0
- package/src/adapters/kiro-tools.ts +224 -0
- package/src/adapters/kiro-truncation.ts +33 -0
- package/src/adapters/kiro-wire.ts +129 -0
- package/src/adapters/kiro.ts +1924 -0
- package/src/adapters/mimo-free.ts +263 -0
- package/src/adapters/openai-chat.ts +1265 -0
- package/src/adapters/openai-responses.ts +1309 -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/android-remote/assets.ts +218 -0
- package/src/android-remote/attachments.ts +168 -0
- package/src/android-remote/auth.ts +237 -0
- package/src/android-remote/cloudflare-provisioning.ts +409 -0
- package/src/android-remote/cloudflare-secret.ts +106 -0
- package/src/android-remote/cloudflare-tunnel.ts +488 -0
- package/src/android-remote/cloudflared.ts +286 -0
- package/src/android-remote/codex-app-server.ts +565 -0
- package/src/android-remote/desktop-history-page.ts +936 -0
- package/src/android-remote/desktop-ipc.ts +3643 -0
- package/src/android-remote/desktop-ownership-store.ts +98 -0
- package/src/android-remote/desktop-project-registration.ts +129 -0
- package/src/android-remote/desktop-session-stream.ts +1110 -0
- package/src/android-remote/desktop-workspace-state.ts +443 -0
- package/src/android-remote/file-change-parser.ts +112 -0
- package/src/android-remote/gateway.ts +9108 -0
- package/src/android-remote/mutation-store.ts +299 -0
- package/src/android-remote/projection.ts +1780 -0
- package/src/android-remote/queued-turn-store.ts +248 -0
- package/src/android-remote/session-command-recovery.ts +1384 -0
- package/src/android-remote/store.ts +466 -0
- package/src/android-remote/thread-reconciliation.ts +133 -0
- package/src/android-remote/thread-source-paths.ts +307 -0
- package/src/android-remote/thread-stream.ts +546 -0
- package/src/android-remote/turn-activity.ts +235 -0
- package/src/android-remote/user-message-identity.ts +94 -0
- package/src/bridge.ts +1793 -0
- package/src/chat/inbound.ts +295 -0
- package/src/chat/outbound.ts +821 -0
- package/src/claude/agents-inject.ts +267 -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 +601 -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 +519 -0
- package/src/claude/model-info.ts +154 -0
- package/src/claude/outbound.ts +898 -0
- package/src/cli/access.ts +108 -0
- package/src/cli/account-api.ts +296 -0
- package/src/cli/account-auth.ts +250 -0
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +476 -0
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +297 -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 +211 -0
- package/src/cli/claude.ts +302 -0
- package/src/cli/codex-shim-autorestore.ts +45 -0
- package/src/cli/codex-shim-readiness.ts +69 -0
- package/src/cli/combo.ts +124 -0
- package/src/cli/config-command.ts +183 -0
- package/src/cli/debug.ts +228 -0
- package/src/cli/desktop-first-run.ts +25 -0
- package/src/cli/doctor.ts +1022 -0
- package/src/cli/export-command.ts +201 -0
- package/src/cli/help.ts +370 -0
- package/src/cli/index.ts +1565 -0
- package/src/cli/init.ts +224 -0
- package/src/cli/integrations.ts +225 -0
- package/src/cli/interactive-confirm.ts +133 -0
- package/src/cli/internal-dispatch.ts +35 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/models-runtime.ts +224 -0
- package/src/cli/models.ts +340 -0
- package/src/cli/observe.ts +170 -0
- package/src/cli/opencode.ts +587 -0
- package/src/cli/provider-runtime.ts +179 -0
- package/src/cli/provider.ts +476 -0
- package/src/cli/ready.ts +301 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +328 -0
- package/src/cli/star-prompt.ts +211 -0
- package/src/cli/status-oauth.ts +78 -0
- package/src/cli/status.ts +321 -0
- package/src/cli/system-command.ts +196 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +205 -0
- package/src/cli/v2.ts +200 -0
- package/src/cli.ts +10 -0
- package/src/clients/config-export.ts +1109 -0
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-label.ts +34 -0
- package/src/codex/account-lifecycle.ts +172 -0
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +195 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-runtime-state.ts +31 -0
- package/src/codex/account-store.ts +517 -0
- package/src/codex/account-usability.ts +40 -0
- package/src/codex/admission.ts +263 -0
- package/src/codex/app-server-processes.ts +799 -0
- package/src/codex/auth-api.ts +2098 -0
- package/src/codex/auth-collision.ts +107 -0
- package/src/codex/auth-context.ts +480 -0
- package/src/codex/autostart-health.ts +156 -0
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +471 -0
- package/src/codex/catalog/bundled.ts +533 -0
- package/src/codex/catalog/effort.ts +432 -0
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +287 -0
- package/src/codex/catalog/native-models.ts +7 -0
- package/src/codex/catalog/parsing.ts +503 -0
- package/src/codex/catalog/provider-fetch.ts +2267 -0
- package/src/codex/catalog/sync.ts +1606 -0
- package/src/codex/catalog-admission.ts +197 -0
- package/src/codex/catalog-refresh-status.ts +87 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +15 -0
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +593 -0
- package/src/codex/convergence.ts +580 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/data/upstream-models.json +830 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/desktop-client-processes.ts +521 -0
- package/src/codex/exec-invocation.ts +22 -0
- package/src/codex/features.ts +1091 -0
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +347 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +115 -0
- package/src/codex/history-provider.ts +1075 -0
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +204 -0
- package/src/codex/home.ts +206 -0
- package/src/codex/inject-coordination.ts +257 -0
- package/src/codex/inject.ts +1857 -0
- package/src/codex/injected-marker.ts +79 -0
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +105 -0
- package/src/codex/journal.ts +172 -0
- package/src/codex/main-account-cache.ts +56 -0
- package/src/codex/main-account.ts +40 -0
- package/src/codex/management-convergence.ts +114 -0
- package/src/codex/model-cache.ts +267 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +167 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +348 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +691 -0
- package/src/codex/paths.ts +78 -0
- package/src/codex/plugins-doctor.ts +242 -0
- package/src/codex/pool-rotation.ts +295 -0
- package/src/codex/project-config-warnings.ts +426 -0
- package/src/codex/prompt-journal.ts +311 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/provider-adoption.ts +242 -0
- package/src/codex/quota-rejection.ts +224 -0
- package/src/codex/quota.ts +494 -0
- package/src/codex/refresh.ts +60 -0
- package/src/codex/routing.ts +1855 -0
- package/src/codex/runtime.ts +659 -0
- package/src/codex/shim.ts +1215 -0
- package/src/codex/subagent-defaults.ts +557 -0
- package/src/codex/subagent-model-fallback.ts +560 -0
- package/src/codex/sync.ts +238 -0
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +374 -0
- package/src/codex/warmup.ts +192 -0
- package/src/codex/websocket-registry.ts +100 -0
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +140 -0
- package/src/combos/index.ts +44 -0
- package/src/combos/request.ts +64 -0
- package/src/combos/resolve.ts +232 -0
- package/src/combos/types.ts +392 -0
- package/src/config.ts +3270 -0
- package/src/generated/model-metadata.ts +144 -0
- package/src/github/star-state.ts +203 -0
- package/src/grok/inject.ts +540 -0
- package/src/grok/inspect.ts +45 -0
- package/src/grok/status.ts +127 -0
- package/src/grok/sync.ts +66 -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 +922 -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/integrations/config-io.ts +151 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +108 -0
- package/src/integrations/serialize.ts +235 -0
- package/src/integrations/state.ts +290 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer.ts +492 -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 +242 -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 +184 -0
- package/src/lib/bun-stream-caps.ts +127 -0
- package/src/lib/config-ownership.ts +438 -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/local-management-attestation.ts +51 -0
- package/src/lib/open-url.ts +25 -0
- package/src/lib/pinned-http.ts +182 -0
- package/src/lib/privacy.ts +20 -0
- package/src/lib/process-control.ts +168 -0
- package/src/lib/provider-environment.ts +470 -0
- package/src/lib/provider-outbound.ts +203 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/redact.ts +510 -0
- package/src/lib/remodex-home.ts +616 -0
- package/src/lib/retry-after.ts +55 -0
- package/src/lib/service-secrets.ts +178 -0
- package/src/lib/shadow-call.ts +54 -0
- package/src/lib/sidecar-tracker.ts +52 -0
- package/src/lib/sse-decoder.ts +364 -0
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/test-home-guard.ts +98 -0
- package/src/lib/token-estimate.ts +69 -0
- package/src/lib/translator-budget.ts +366 -0
- package/src/lib/upstream-reachability.ts +91 -0
- package/src/lib/upstream-retry.ts +508 -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 +817 -0
- package/src/lib/windows-user-principal.ts +283 -0
- package/src/lib/winsw.ts +402 -0
- package/src/model-sources.ts +73 -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/command-code.ts +239 -0
- package/src/oauth/cursor.ts +231 -0
- package/src/oauth/github-copilot.ts +428 -0
- package/src/oauth/google-antigravity.ts +230 -0
- package/src/oauth/health.ts +443 -0
- package/src/oauth/index.ts +1280 -0
- package/src/oauth/key-providers.ts +128 -0
- package/src/oauth/kimi.ts +213 -0
- package/src/oauth/kiro-credentials.ts +726 -0
- package/src/oauth/kiro.ts +621 -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 +655 -0
- package/src/oauth/token-guardian.ts +309 -0
- package/src/oauth/types.ts +62 -0
- package/src/oauth/xai.ts +241 -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 +317 -0
- package/src/providers/api-keys.ts +140 -0
- package/src/providers/base-url-choices.ts +64 -0
- package/src/providers/codex-capacity.ts +288 -0
- package/src/providers/command-code-efforts.ts +85 -0
- package/src/providers/context-cap.ts +73 -0
- package/src/providers/derive.ts +451 -0
- package/src/providers/free-directory.ts +187 -0
- package/src/providers/github-copilot-transport.ts +56 -0
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +271 -0
- package/src/providers/kiro-models.ts +67 -0
- package/src/providers/label.ts +19 -0
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +361 -0
- package/src/providers/openai-sidecar.ts +235 -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 +83 -0
- package/src/providers/openrouter-routing.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +179 -0
- package/src/providers/quota.ts +1942 -0
- package/src/providers/reasoning-capabilities.ts +336 -0
- package/src/providers/registry.ts +2375 -0
- package/src/providers/slug-codec.ts +74 -0
- package/src/providers/xai-transport.ts +149 -0
- package/src/reasoning-effort.ts +243 -0
- package/src/responses/compaction.ts +124 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/parser.ts +714 -0
- package/src/responses/reasoning-envelope.ts +60 -0
- package/src/responses/reasoning-replay-cache.ts +106 -0
- package/src/responses/schema.ts +159 -0
- package/src/responses/spill-store.ts +431 -0
- package/src/responses/state.ts +1039 -0
- package/src/responses/tool-groups.ts +19 -0
- package/src/router.ts +802 -0
- package/src/routing/analytics.ts +377 -0
- package/src/routing/capability.ts +205 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +444 -0
- package/src/routing/health.ts +401 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +424 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +686 -0
- package/src/server/adapter-resolve.ts +83 -0
- package/src/server/auth-cors.ts +606 -0
- package/src/server/chat-completions.ts +379 -0
- package/src/server/claude-messages.ts +980 -0
- package/src/server/effort-policy.ts +251 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +152 -0
- package/src/server/image-retry.ts +42 -0
- package/src/server/images.ts +485 -0
- package/src/server/index.ts +1633 -0
- package/src/server/lifecycle.ts +482 -0
- package/src/server/live.ts +609 -0
- package/src/server/management/agent-settings-routes.ts +1180 -0
- package/src/server/management/android-remote-routes.ts +390 -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/combo-routes.ts +244 -0
- package/src/server/management/config-routes.ts +602 -0
- package/src/server/management/context.ts +88 -0
- package/src/server/management/integration-routes.ts +538 -0
- package/src/server/management/logs-usage-routes.ts +516 -0
- package/src/server/management/model-routes.ts +519 -0
- package/src/server/management/model-rows.ts +143 -0
- package/src/server/management/native-integration-routes.ts +781 -0
- package/src/server/management/oauth-account-routes.ts +573 -0
- package/src/server/management/provider-routes.ts +781 -0
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +384 -0
- package/src/server/management/shared.ts +277 -0
- package/src/server/management/sidebar-routes.ts +106 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +433 -0
- package/src/server/management/system-routes.ts +141 -0
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +269 -0
- package/src/server/management-auth.ts +353 -0
- package/src/server/memory-watchdog.ts +156 -0
- package/src/server/port-reclaim.ts +307 -0
- package/src/server/ports.ts +156 -0
- package/src/server/proxy-liveness.ts +326 -0
- package/src/server/proxy-stop.ts +92 -0
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +353 -0
- package/src/server/relay.ts +1179 -0
- package/src/server/request-decompress.ts +132 -0
- package/src/server/request-log-conversation.ts +168 -0
- package/src/server/request-log.ts +1072 -0
- package/src/server/responses/collaboration.ts +409 -0
- package/src/server/responses/compact.ts +710 -0
- package/src/server/responses/core.ts +3561 -0
- package/src/server/responses/encrypted-payload.ts +308 -0
- package/src/server/responses/fetch-helpers.ts +171 -0
- package/src/server/responses/passthrough-error.ts +78 -0
- package/src/server/responses/policy-fallback.ts +152 -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 +272 -0
- package/src/server/responses-json-events.ts +52 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses.ts +10 -0
- package/src/server/search.ts +181 -0
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +263 -0
- package/src/server/startup-action-control.ts +308 -0
- package/src/server/startup-health-cache.ts +119 -0
- package/src/server/system-env.ts +418 -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 +470 -0
- package/src/service-manager-probe.ts +824 -0
- package/src/service.ts +3011 -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 +364 -0
- package/src/tray/windows.ts +738 -0
- package/src/types.ts +1531 -0
- package/src/update/badge.ts +72 -0
- package/src/update/desktop-release.ts +1620 -0
- package/src/update/index.ts +402 -0
- package/src/update/job.ts +1906 -0
- package/src/update/notify.ts +261 -0
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -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 +283 -0
- package/src/usage/log.ts +695 -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 +127 -0
- package/src/vision/index.ts +558 -0
- package/src/vision/reasoning.ts +55 -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 +791 -0
- package/src/web-search/parse.ts +235 -0
- package/src/web-search/progress-stream.ts +342 -0
- package/src/web-search/synthetic-tool.ts +47 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "../types";
|
|
2
|
+
import { OPENAI_PROVIDER_TIER_VERSION } from "../types";
|
|
3
|
+
|
|
4
|
+
export const OPENAI_CODEX_PROVIDER_ID = "openai";
|
|
5
|
+
export const LEGACY_OPENAI_MULTI_PROVIDER_ID = "openai-multi";
|
|
6
|
+
export const OPENAI_API_PROVIDER_ID = "openai-apikey";
|
|
7
|
+
export const LEGACY_CHATGPT_PROVIDER_ID = "chatgpt";
|
|
8
|
+
|
|
9
|
+
export const CODEX_FORWARD_BASE_URL = "https://chatgpt.com/backend-api/codex";
|
|
10
|
+
const LEGACY_OPENAI_MULTI_PREFIX = `${LEGACY_OPENAI_MULTI_PROVIDER_ID}/`;
|
|
11
|
+
|
|
12
|
+
function canonicalCodexForwardProvider(mode: CodexAccountMode): OcxProviderConfig {
|
|
13
|
+
return {
|
|
14
|
+
adapter: "openai-responses",
|
|
15
|
+
baseUrl: CODEX_FORWARD_BASE_URL,
|
|
16
|
+
authMode: "forward",
|
|
17
|
+
codexAccountMode: mode,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizedBaseUrl(value: string): string | undefined {
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL(value.trim());
|
|
24
|
+
if (url.username || url.password || url.search || url.hash) return undefined;
|
|
25
|
+
const path = url.pathname.replace(/\/+$/, "");
|
|
26
|
+
return `${url.origin}${path}`;
|
|
27
|
+
} catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isCanonicalOpenAiForwardProvider(provider: OcxProviderConfig): boolean {
|
|
33
|
+
return provider.adapter === "openai-responses"
|
|
34
|
+
&& provider.authMode === "forward"
|
|
35
|
+
&& normalizedBaseUrl(provider.baseUrl) === CODEX_FORWARD_BASE_URL;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const OPENAI_API_BASE_URL = "https://api.openai.com/v1";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Whether this provider can serve `POST /responses/compact`. The canonical ChatGPT
|
|
42
|
+
* backend can, and so can the official OpenAI API — but an arbitrary gateway that
|
|
43
|
+
* merely speaks the Responses wire cannot, and calling it there fails compaction
|
|
44
|
+
* with an unhelpful error instead of falling back to a routed summary (#422).
|
|
45
|
+
*/
|
|
46
|
+
export function supportsNativeResponsesCompactEndpoint(
|
|
47
|
+
providerName: string,
|
|
48
|
+
provider: OcxProviderConfig,
|
|
49
|
+
): boolean {
|
|
50
|
+
if (isCanonicalOpenAiForwardProvider(provider)) return true;
|
|
51
|
+
return providerName === OPENAI_API_PROVIDER_ID
|
|
52
|
+
&& provider.adapter === "openai-responses"
|
|
53
|
+
&& normalizedBaseUrl(provider.baseUrl) === OPENAI_API_BASE_URL;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface OpenAiTierMigrationProjection {
|
|
57
|
+
config: OcxConfig;
|
|
58
|
+
changed: boolean;
|
|
59
|
+
resolvedMode: CodexAccountMode;
|
|
60
|
+
warnings: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class OpenAiTierMigrationCollisionError extends Error {
|
|
64
|
+
readonly providerName = LEGACY_OPENAI_MULTI_PROVIDER_ID;
|
|
65
|
+
|
|
66
|
+
constructor() {
|
|
67
|
+
super(`Reserved provider id "${LEGACY_OPENAI_MULTI_PROVIDER_ID}" is already configured with a noncanonical shape`);
|
|
68
|
+
this.name = "OpenAiTierMigrationCollisionError";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function managedLegacyMultiOverlay(provider: OcxProviderConfig): Pick<OcxProviderConfig, "disabled" | "selectedModels"> | null {
|
|
73
|
+
const allowed = new Set(["adapter", "authMode", "baseUrl", "disabled", "selectedModels"]);
|
|
74
|
+
if (!Object.keys(provider).every(key => allowed.has(key))) return null;
|
|
75
|
+
if (!isCanonicalOpenAiForwardProvider(provider)) return null;
|
|
76
|
+
if (provider.disabled !== undefined && typeof provider.disabled !== "boolean") return null;
|
|
77
|
+
if (provider.selectedModels !== undefined && (
|
|
78
|
+
!Array.isArray(provider.selectedModels)
|
|
79
|
+
|| provider.selectedModels.some(model => typeof model !== "string")
|
|
80
|
+
)) return null;
|
|
81
|
+
return {
|
|
82
|
+
...(provider.disabled !== undefined ? { disabled: provider.disabled } : {}),
|
|
83
|
+
...(provider.selectedModels !== undefined ? { selectedModels: [...provider.selectedModels] } : {}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function rewriteLegacyOpenAiSelectedId(value: string): string {
|
|
88
|
+
return value.startsWith(LEGACY_OPENAI_MULTI_PREFIX)
|
|
89
|
+
? value.slice(LEGACY_OPENAI_MULTI_PREFIX.length)
|
|
90
|
+
: value;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function rewriteLegacyOpenAiModelList(values: string[] | undefined): string[] | undefined {
|
|
94
|
+
if (!values) return undefined;
|
|
95
|
+
return [...new Set(values.map(rewriteLegacyOpenAiSelectedId))];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function mergeLegacyOpenAiProviderRows(
|
|
99
|
+
openai: OcxProviderConfig | undefined,
|
|
100
|
+
legacyMulti: OcxProviderConfig | undefined,
|
|
101
|
+
mode: CodexAccountMode,
|
|
102
|
+
): OcxProviderConfig {
|
|
103
|
+
const selectedModels = rewriteLegacyOpenAiModelList([
|
|
104
|
+
...(openai?.selectedModels ?? []),
|
|
105
|
+
...(legacyMulti?.selectedModels ?? []),
|
|
106
|
+
]);
|
|
107
|
+
const formerRows = [openai, legacyMulti].filter((row): row is OcxProviderConfig => row !== undefined);
|
|
108
|
+
const disabled = formerRows.length > 0 && formerRows.every(row => row.disabled === true);
|
|
109
|
+
return {
|
|
110
|
+
...canonicalCodexForwardProvider(mode),
|
|
111
|
+
...(disabled ? { disabled: true } : {}),
|
|
112
|
+
...(selectedModels && selectedModels.length > 0 ? { selectedModels } : {}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function hasKnownLegacyOpenAiReference(config: OcxConfig): boolean {
|
|
117
|
+
const matches = (value: unknown): boolean => typeof value === "string" && value.startsWith(LEGACY_OPENAI_MULTI_PREFIX);
|
|
118
|
+
const matchesList = (value: unknown): boolean => Array.isArray(value) && value.some(matches);
|
|
119
|
+
const claude = config.claudeCode;
|
|
120
|
+
return config.defaultProvider === LEGACY_OPENAI_MULTI_PROVIDER_ID
|
|
121
|
+
|| matchesList(config.disabledModels)
|
|
122
|
+
|| matchesList(config.subagentModels)
|
|
123
|
+
|| matches(config.injectionModel)
|
|
124
|
+
|| matches(config.shadowCallIntercept?.model)
|
|
125
|
+
|| matches(config.webSearchSidecar?.model)
|
|
126
|
+
|| matches(config.visionSidecar?.model)
|
|
127
|
+
|| matches(claude?.webSearchSidecar?.model)
|
|
128
|
+
|| matches(claude?.visionSidecar?.model)
|
|
129
|
+
|| matches(claude?.model)
|
|
130
|
+
|| matches(claude?.smallFastModel)
|
|
131
|
+
|| Object.values(claude?.tierModels ?? {}).some(matches)
|
|
132
|
+
|| Object.values(claude?.modelMap ?? {}).some(matches)
|
|
133
|
+
|| matchesList(config.providers[OPENAI_CODEX_PROVIDER_ID]?.selectedModels)
|
|
134
|
+
|| matchesList(config.providers[LEGACY_OPENAI_MULTI_PROVIDER_ID]?.selectedModels)
|
|
135
|
+
|| config.providerContextCaps?.[LEGACY_OPENAI_MULTI_PROVIDER_ID] !== undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function rewriteLegacyOpenAiReferences(config: OcxConfig, warnings: string[]): void {
|
|
139
|
+
config.disabledModels = rewriteLegacyOpenAiModelList(config.disabledModels);
|
|
140
|
+
config.subagentModels = rewriteLegacyOpenAiModelList(config.subagentModels);
|
|
141
|
+
if (config.injectionModel) config.injectionModel = rewriteLegacyOpenAiSelectedId(config.injectionModel);
|
|
142
|
+
if (config.shadowCallIntercept?.model) {
|
|
143
|
+
config.shadowCallIntercept.model = rewriteLegacyOpenAiSelectedId(config.shadowCallIntercept.model);
|
|
144
|
+
}
|
|
145
|
+
if (config.webSearchSidecar?.model) config.webSearchSidecar.model = rewriteLegacyOpenAiSelectedId(config.webSearchSidecar.model);
|
|
146
|
+
if (config.visionSidecar?.model) config.visionSidecar.model = rewriteLegacyOpenAiSelectedId(config.visionSidecar.model);
|
|
147
|
+
|
|
148
|
+
const claude = config.claudeCode;
|
|
149
|
+
if (claude?.webSearchSidecar?.model) claude.webSearchSidecar.model = rewriteLegacyOpenAiSelectedId(claude.webSearchSidecar.model);
|
|
150
|
+
if (claude?.visionSidecar?.model) claude.visionSidecar.model = rewriteLegacyOpenAiSelectedId(claude.visionSidecar.model);
|
|
151
|
+
if (claude?.model) claude.model = rewriteLegacyOpenAiSelectedId(claude.model);
|
|
152
|
+
if (claude?.smallFastModel) claude.smallFastModel = rewriteLegacyOpenAiSelectedId(claude.smallFastModel);
|
|
153
|
+
if (claude?.tierModels) {
|
|
154
|
+
for (const tier of ["opus", "sonnet", "haiku", "fable"] as const) {
|
|
155
|
+
const value = claude.tierModels[tier];
|
|
156
|
+
if (value) claude.tierModels[tier] = rewriteLegacyOpenAiSelectedId(value);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (claude?.modelMap) {
|
|
160
|
+
for (const key of Object.keys(claude.modelMap)) {
|
|
161
|
+
claude.modelMap[key] = rewriteLegacyOpenAiSelectedId(claude.modelMap[key]!);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const caps = config.providerContextCaps;
|
|
166
|
+
const legacyCap = caps?.[LEGACY_OPENAI_MULTI_PROVIDER_ID];
|
|
167
|
+
if (caps && legacyCap !== undefined) {
|
|
168
|
+
const currentCap = caps[OPENAI_CODEX_PROVIDER_ID];
|
|
169
|
+
if (currentCap !== undefined) {
|
|
170
|
+
caps[OPENAI_CODEX_PROVIDER_ID] = Math.min(currentCap, legacyCap);
|
|
171
|
+
warnings.push("providerContextCaps.openai + providerContextCaps.openai-multi: kept lower positive cap");
|
|
172
|
+
} else {
|
|
173
|
+
caps[OPENAI_CODEX_PROVIDER_ID] = legacyCap;
|
|
174
|
+
}
|
|
175
|
+
delete caps[LEGACY_OPENAI_MULTI_PROVIDER_ID];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function isKnownLegacyValuePath(path: readonly string[]): boolean {
|
|
180
|
+
const joined = path.join(".");
|
|
181
|
+
if (joined === "defaultProvider") return true;
|
|
182
|
+
if (/^(disabledModels|subagentModels)\.\d+$/.test(joined)) return true;
|
|
183
|
+
if (/^providers\.(openai|openai-multi)\.selectedModels\.\d+$/.test(joined)) return true;
|
|
184
|
+
return new Set([
|
|
185
|
+
"injectionModel",
|
|
186
|
+
"shadowCallIntercept.model",
|
|
187
|
+
"webSearchSidecar.model",
|
|
188
|
+
"visionSidecar.model",
|
|
189
|
+
"claudeCode.webSearchSidecar.model",
|
|
190
|
+
"claudeCode.visionSidecar.model",
|
|
191
|
+
"claudeCode.model",
|
|
192
|
+
"claudeCode.smallFastModel",
|
|
193
|
+
"claudeCode.tierModels.opus",
|
|
194
|
+
"claudeCode.tierModels.sonnet",
|
|
195
|
+
"claudeCode.tierModels.haiku",
|
|
196
|
+
"claudeCode.tierModels.fable",
|
|
197
|
+
]).has(joined) || /^claudeCode\.modelMap\..+$/.test(joined);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function unknownLegacyOpenAiWarnings(config: OcxConfig): string[] {
|
|
201
|
+
const warnings = new Set<string>();
|
|
202
|
+
const visit = (value: unknown, path: string[]): void => {
|
|
203
|
+
if (typeof value === "string") {
|
|
204
|
+
if (value.includes(LEGACY_OPENAI_MULTI_PROVIDER_ID) && !isKnownLegacyValuePath(path)) {
|
|
205
|
+
warnings.add(`${path.join(".")}: legacy OpenAI provider id left unchanged`);
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (!value || typeof value !== "object") return;
|
|
210
|
+
if (Array.isArray(value)) {
|
|
211
|
+
value.forEach((item, index) => visit(item, [...path, String(index)]));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
|
|
215
|
+
visit(item, [...path, key]);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
visit(config, []);
|
|
219
|
+
return [...warnings];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function resolvedOpenAiMode(
|
|
223
|
+
config: OcxConfig,
|
|
224
|
+
openai: OcxProviderConfig | undefined,
|
|
225
|
+
legacyMulti: OcxProviderConfig | undefined,
|
|
226
|
+
knownLegacyReference: boolean,
|
|
227
|
+
): CodexAccountMode {
|
|
228
|
+
if (openai?.codexAccountMode === "pool" || openai?.codexAccountMode === "direct") {
|
|
229
|
+
return openai.codexAccountMode;
|
|
230
|
+
}
|
|
231
|
+
if (
|
|
232
|
+
(legacyMulti && legacyMulti.disabled !== true)
|
|
233
|
+
|| config.defaultProvider === LEGACY_OPENAI_MULTI_PROVIDER_ID
|
|
234
|
+
|| knownLegacyReference
|
|
235
|
+
) return "pool";
|
|
236
|
+
if (config.openaiProviderTierVersion === 1 && openai) return "direct";
|
|
237
|
+
if (config.openaiProviderTierVersion === 1 && legacyMulti) return "pool";
|
|
238
|
+
return "pool";
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function projectOpenAiTierMigration(config: OcxConfig): OpenAiTierMigrationProjection {
|
|
242
|
+
const projected = structuredClone(config);
|
|
243
|
+
const legacyMulti = projected.providers[LEGACY_OPENAI_MULTI_PROVIDER_ID];
|
|
244
|
+
if (legacyMulti && !managedLegacyMultiOverlay(legacyMulti)) {
|
|
245
|
+
throw new OpenAiTierMigrationCollisionError();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const openai = projected.providers[OPENAI_CODEX_PROVIDER_ID];
|
|
249
|
+
const knownLegacyReference = hasKnownLegacyOpenAiReference(projected);
|
|
250
|
+
const resolvedMode = resolvedOpenAiMode(projected, openai, legacyMulti, knownLegacyReference);
|
|
251
|
+
if (
|
|
252
|
+
projected.openaiProviderTierVersion === OPENAI_PROVIDER_TIER_VERSION
|
|
253
|
+
&& !legacyMulti
|
|
254
|
+
&& !Object.hasOwn(projected.providers, LEGACY_CHATGPT_PROVIDER_ID)
|
|
255
|
+
&& projected.defaultProvider !== LEGACY_OPENAI_MULTI_PROVIDER_ID
|
|
256
|
+
&& !knownLegacyReference
|
|
257
|
+
) {
|
|
258
|
+
return { config: projected, changed: false, resolvedMode, warnings: [] };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const warnings = unknownLegacyOpenAiWarnings(projected);
|
|
262
|
+
const referencesCodexForward = !!openai
|
|
263
|
+
|| !!legacyMulti
|
|
264
|
+
|| Object.hasOwn(projected.providers, LEGACY_CHATGPT_PROVIDER_ID)
|
|
265
|
+
|| projected.defaultProvider === OPENAI_CODEX_PROVIDER_ID
|
|
266
|
+
|| projected.defaultProvider === LEGACY_OPENAI_MULTI_PROVIDER_ID
|
|
267
|
+
|| projected.defaultProvider === LEGACY_CHATGPT_PROVIDER_ID
|
|
268
|
+
|| knownLegacyReference;
|
|
269
|
+
const mergedOpenAi = referencesCodexForward
|
|
270
|
+
? mergeLegacyOpenAiProviderRows(openai, legacyMulti, resolvedMode)
|
|
271
|
+
: undefined;
|
|
272
|
+
|
|
273
|
+
const nextProviders: Array<[string, OcxProviderConfig]> = [];
|
|
274
|
+
let inserted = false;
|
|
275
|
+
for (const [name, provider] of Object.entries(projected.providers)) {
|
|
276
|
+
if (name === OPENAI_CODEX_PROVIDER_ID) {
|
|
277
|
+
if (mergedOpenAi && !inserted) nextProviders.push([OPENAI_CODEX_PROVIDER_ID, mergedOpenAi]);
|
|
278
|
+
inserted = true;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (name === LEGACY_OPENAI_MULTI_PROVIDER_ID || name === LEGACY_CHATGPT_PROVIDER_ID) {
|
|
282
|
+
if (mergedOpenAi && !inserted && !openai) {
|
|
283
|
+
nextProviders.push([OPENAI_CODEX_PROVIDER_ID, mergedOpenAi]);
|
|
284
|
+
inserted = true;
|
|
285
|
+
}
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
nextProviders.push([name, provider]);
|
|
289
|
+
}
|
|
290
|
+
if (mergedOpenAi && !inserted) nextProviders.push([OPENAI_CODEX_PROVIDER_ID, mergedOpenAi]);
|
|
291
|
+
projected.providers = Object.fromEntries(nextProviders);
|
|
292
|
+
|
|
293
|
+
if (
|
|
294
|
+
projected.defaultProvider === LEGACY_OPENAI_MULTI_PROVIDER_ID
|
|
295
|
+
|| projected.defaultProvider === LEGACY_CHATGPT_PROVIDER_ID
|
|
296
|
+
) projected.defaultProvider = OPENAI_CODEX_PROVIDER_ID;
|
|
297
|
+
|
|
298
|
+
rewriteLegacyOpenAiReferences(projected, warnings);
|
|
299
|
+
projected.openaiProviderTierVersion = OPENAI_PROVIDER_TIER_VERSION;
|
|
300
|
+
return { config: projected, changed: true, resolvedMode, warnings: [...new Set(warnings)] };
|
|
301
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { PROVIDER_REGISTRY } from "./registry";
|
|
2
|
+
import { OPENAI_API_PROVIDER_ID } from "./openai-tiers";
|
|
3
|
+
import type { OcxParsedRequest } from "../types";
|
|
4
|
+
import type { RouteResult } from "../router";
|
|
5
|
+
import type { RequestLogContext } from "../server/request-log";
|
|
6
|
+
|
|
7
|
+
export interface OpenAiVirtualModelResolution {
|
|
8
|
+
selectedModelId: string;
|
|
9
|
+
wireModelId: string;
|
|
10
|
+
reasoningMode: "pro";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class InvalidOpenAiVirtualModelRegistryError extends Error {
|
|
14
|
+
constructor(selectedModelId: string) {
|
|
15
|
+
super(`Invalid OpenAI virtual model registry definition: ${selectedModelId}`);
|
|
16
|
+
this.name = "InvalidOpenAiVirtualModelRegistryError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function validateOpenAiVirtualModelDefinition(
|
|
21
|
+
selectedModelId: string,
|
|
22
|
+
definition: unknown,
|
|
23
|
+
): OpenAiVirtualModelResolution {
|
|
24
|
+
if (!definition || typeof definition !== "object" || Array.isArray(definition)) {
|
|
25
|
+
throw new InvalidOpenAiVirtualModelRegistryError(selectedModelId);
|
|
26
|
+
}
|
|
27
|
+
const raw = definition as { wireModelId?: unknown; reasoningMode?: unknown };
|
|
28
|
+
if (
|
|
29
|
+
typeof raw.wireModelId !== "string"
|
|
30
|
+
|| raw.wireModelId.trim() !== raw.wireModelId
|
|
31
|
+
|| raw.wireModelId.length === 0
|
|
32
|
+
|| raw.wireModelId.includes("/")
|
|
33
|
+
|| raw.wireModelId === selectedModelId
|
|
34
|
+
|| raw.reasoningMode !== "pro"
|
|
35
|
+
) {
|
|
36
|
+
throw new InvalidOpenAiVirtualModelRegistryError(selectedModelId);
|
|
37
|
+
}
|
|
38
|
+
return { selectedModelId, wireModelId: raw.wireModelId, reasoningMode: "pro" };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function resolveOpenAiVirtualModel(
|
|
42
|
+
providerName: string,
|
|
43
|
+
selectedModelId: string,
|
|
44
|
+
): OpenAiVirtualModelResolution | undefined {
|
|
45
|
+
if (providerName !== OPENAI_API_PROVIDER_ID) return undefined;
|
|
46
|
+
const entry = PROVIDER_REGISTRY.find(row => row.id === OPENAI_API_PROVIDER_ID);
|
|
47
|
+
if (!entry?.virtualModels || !Object.hasOwn(entry.virtualModels, selectedModelId)) return undefined;
|
|
48
|
+
const definition = entry.virtualModels[selectedModelId];
|
|
49
|
+
return validateOpenAiVirtualModelDefinition(selectedModelId, definition);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function applyOpenAiVirtualModel(
|
|
53
|
+
parsed: OcxParsedRequest,
|
|
54
|
+
route: RouteResult,
|
|
55
|
+
logCtx: RequestLogContext,
|
|
56
|
+
): OpenAiVirtualModelResolution | undefined {
|
|
57
|
+
const selectedModelId = logCtx.model && logCtx.model !== route.modelId ? logCtx.model : route.modelId;
|
|
58
|
+
const resolution = resolveOpenAiVirtualModel(route.providerName, selectedModelId);
|
|
59
|
+
if (!resolution) return undefined;
|
|
60
|
+
|
|
61
|
+
logCtx.model = resolution.selectedModelId;
|
|
62
|
+
logCtx.resolvedModel = resolution.wireModelId;
|
|
63
|
+
route.modelId = resolution.wireModelId;
|
|
64
|
+
parsed.modelId = resolution.wireModelId;
|
|
65
|
+
parsed._openAiVirtualSelectedModelId = resolution.selectedModelId;
|
|
66
|
+
|
|
67
|
+
if (parsed._rawBody && typeof parsed._rawBody === "object" && !Array.isArray(parsed._rawBody)) {
|
|
68
|
+
const raw = parsed._rawBody as Record<string, unknown>;
|
|
69
|
+
raw.model = resolution.wireModelId;
|
|
70
|
+
const existing = raw.reasoning;
|
|
71
|
+
raw.reasoning = existing && typeof existing === "object" && !Array.isArray(existing)
|
|
72
|
+
? { ...(existing as Record<string, unknown>), mode: resolution.reasoningMode }
|
|
73
|
+
: { mode: resolution.reasoningMode };
|
|
74
|
+
}
|
|
75
|
+
return resolution;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function resolveOpenAiCompactModel(
|
|
79
|
+
providerName: string,
|
|
80
|
+
selectedModelId: string,
|
|
81
|
+
): OpenAiVirtualModelResolution | undefined {
|
|
82
|
+
return resolveOpenAiVirtualModel(providerName, selectedModelId);
|
|
83
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { OcxProviderConfig, OpenRouterProviderRouting } from "../types";
|
|
2
|
+
|
|
3
|
+
const ROUTING_KEYS = new Set(["order", "only", "allowFallbacks"]);
|
|
4
|
+
const MAX_PROVIDER_SLUGS = 64;
|
|
5
|
+
|
|
6
|
+
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
7
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
8
|
+
const prototype = Object.getPrototypeOf(value);
|
|
9
|
+
return prototype === Object.prototype || prototype === null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function isCanonicalOpenRouterTarget(baseUrl: string): boolean {
|
|
13
|
+
try {
|
|
14
|
+
const url = new URL(baseUrl);
|
|
15
|
+
return url.origin === "https://openrouter.ai"
|
|
16
|
+
&& !url.username
|
|
17
|
+
&& !url.password
|
|
18
|
+
&& !url.search
|
|
19
|
+
&& !url.hash
|
|
20
|
+
&& url.pathname.replace(/\/+$/, "") === "/api/v1";
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function routingPreferenceError(value: unknown, field: string): string | null {
|
|
27
|
+
if (!isPlainRecord(value)) return `${field} must be a plain object`;
|
|
28
|
+
const unknown = Object.keys(value).find(key => !ROUTING_KEYS.has(key));
|
|
29
|
+
if (unknown) return `${field} contains unknown field "${unknown}"`;
|
|
30
|
+
|
|
31
|
+
for (const listField of ["order", "only"] as const) {
|
|
32
|
+
const list = value[listField];
|
|
33
|
+
if (list === undefined) continue;
|
|
34
|
+
if (!Array.isArray(list) || list.length === 0 || list.length > MAX_PROVIDER_SLUGS) {
|
|
35
|
+
return `${field}.${listField} must contain 1-${MAX_PROVIDER_SLUGS} provider slugs`;
|
|
36
|
+
}
|
|
37
|
+
const seen = new Set<string>();
|
|
38
|
+
for (const slug of list) {
|
|
39
|
+
if (typeof slug !== "string" || !slug.trim() || slug !== slug.trim() || slug.length > 128) {
|
|
40
|
+
return `${field}.${listField} must contain nonblank trimmed provider slugs up to 128 characters`;
|
|
41
|
+
}
|
|
42
|
+
if (seen.has(slug)) return `${field}.${listField} must not contain duplicate provider slugs`;
|
|
43
|
+
seen.add(slug);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (value.allowFallbacks !== undefined && typeof value.allowFallbacks !== "boolean") {
|
|
47
|
+
return `${field}.allowFallbacks must be a boolean`;
|
|
48
|
+
}
|
|
49
|
+
if (value.order === undefined && value.only === undefined && value.allowFallbacks === undefined) {
|
|
50
|
+
return `${field} must define order, only, or allowFallbacks`;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function openRouterRoutingConfigError(provider: OcxProviderConfig): string | null {
|
|
56
|
+
const hasDefault = provider.openRouterRouting !== undefined;
|
|
57
|
+
const hasModels = provider.modelOpenRouterRouting !== undefined;
|
|
58
|
+
if (!hasDefault && !hasModels) return null;
|
|
59
|
+
if (provider.adapter !== "openai-chat") {
|
|
60
|
+
return "OpenRouter routing preferences require the openai-chat adapter";
|
|
61
|
+
}
|
|
62
|
+
if (!isCanonicalOpenRouterTarget(provider.baseUrl)) {
|
|
63
|
+
return "OpenRouter routing preferences require the canonical https://openrouter.ai/api/v1 baseUrl";
|
|
64
|
+
}
|
|
65
|
+
if (hasDefault) {
|
|
66
|
+
const error = routingPreferenceError(provider.openRouterRouting, "openRouterRouting");
|
|
67
|
+
if (error) return error;
|
|
68
|
+
}
|
|
69
|
+
if (hasModels) {
|
|
70
|
+
const routes = provider.modelOpenRouterRouting;
|
|
71
|
+
if (!isPlainRecord(routes)) return "modelOpenRouterRouting must be a plain object";
|
|
72
|
+
for (const [modelId, preference] of Object.entries(routes)) {
|
|
73
|
+
if (!modelId.trim() || modelId !== modelId.trim()) {
|
|
74
|
+
return "modelOpenRouterRouting keys must be nonblank trimmed model ids";
|
|
75
|
+
}
|
|
76
|
+
const error = routingPreferenceError(preference, `modelOpenRouterRouting.${modelId}`);
|
|
77
|
+
if (error) return error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function resolveOpenRouterRouting(
|
|
84
|
+
provider: OcxProviderConfig,
|
|
85
|
+
modelId: string,
|
|
86
|
+
): OpenRouterProviderRouting | undefined {
|
|
87
|
+
if (!isCanonicalOpenRouterTarget(provider.baseUrl)) return undefined;
|
|
88
|
+
const modelRoutes = provider.modelOpenRouterRouting;
|
|
89
|
+
return modelRoutes && Object.hasOwn(modelRoutes, modelId)
|
|
90
|
+
? modelRoutes[modelId]
|
|
91
|
+
: provider.openRouterRouting;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function openRouterProviderPayload(
|
|
95
|
+
preference: OpenRouterProviderRouting,
|
|
96
|
+
): Record<string, unknown> {
|
|
97
|
+
return {
|
|
98
|
+
...(preference.order ? { order: [...preference.order] } : {}),
|
|
99
|
+
...(preference.only ? { only: [...preference.only] } : {}),
|
|
100
|
+
...(preference.allowFallbacks !== undefined ? { allow_fallbacks: preference.allowFallbacks } : {}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { OcxConfig } from "../types";
|
|
2
|
+
|
|
3
|
+
export interface ProviderRewriteResult {
|
|
4
|
+
/** Number of references re-pointed. */
|
|
5
|
+
changed: number;
|
|
6
|
+
/**
|
|
7
|
+
* Sites where the destination key already held a value, left untouched. A
|
|
8
|
+
* non-empty list means the caller must not treat the rewrite as complete —
|
|
9
|
+
* merging two settings is not a decision this function can make. The function
|
|
10
|
+
* is NOT transactional: earlier sites are already rewritten by the time a
|
|
11
|
+
* collision is found, so a caller that receives collisions must discard the
|
|
12
|
+
* config it passed in.
|
|
13
|
+
*/
|
|
14
|
+
collisions: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Re-point every config reference from one provider id to another.
|
|
19
|
+
*
|
|
20
|
+
* Three shapes exist and the difference matters: routed model strings
|
|
21
|
+
* (`"<provider>/<model>"`), bare provider ids (`customModels[].provider`,
|
|
22
|
+
* `combos[*].targets[].provider`), and keys that ARE provider ids or routes
|
|
23
|
+
* (`providerContextCaps`, `claudeCode.desktopProfile.assignments`). A rewrite
|
|
24
|
+
* that handles only the first leaves an orphaned context cap and — worse — a
|
|
25
|
+
* combo target naming a provider that no longer exists, which fails validation
|
|
26
|
+
* in `src/combos/types.ts` and makes `loadConfig` discard the whole config.
|
|
27
|
+
*
|
|
28
|
+
* `providers[*].selectedModels` is deliberately NOT rewritten: those are
|
|
29
|
+
* per-provider native model ids, and upstream ids may themselves contain a
|
|
30
|
+
* slash, so a prefix rewrite could mangle an unrelated provider's allowlist. A
|
|
31
|
+
* caller that moves a provider row handles its own allowlist, where the
|
|
32
|
+
* destination catalog is known.
|
|
33
|
+
*/
|
|
34
|
+
export function rewriteProviderReferences(config: OcxConfig, from: string, to: string): ProviderRewriteResult {
|
|
35
|
+
const prefix = `${from}/`;
|
|
36
|
+
const collisions: string[] = [];
|
|
37
|
+
let changed = 0;
|
|
38
|
+
|
|
39
|
+
/** Routed string: rewrite only an exact `<from>/…` prefix, never a longer id. */
|
|
40
|
+
const route = (value: unknown): string | undefined => {
|
|
41
|
+
if (typeof value !== "string" || !value.startsWith(prefix)) return undefined;
|
|
42
|
+
changed += 1;
|
|
43
|
+
return `${to}/${value.slice(prefix.length)}`;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Rewrite a routed-string list in place. Assigning the result unconditionally
|
|
48
|
+
* would add an own property with value `undefined` where the field was absent,
|
|
49
|
+
* which breaks the no-op contract. The key type is an explicit union rather
|
|
50
|
+
* than `keyof OcxConfig`: the latter also admits `customModels` and friends, so
|
|
51
|
+
* `map` would infer a union array that is not assignable back.
|
|
52
|
+
*/
|
|
53
|
+
type RoutedListKey = "disabledModels" | "subagentModels" | "subagentModelFallback";
|
|
54
|
+
const routeListAt = (key: RoutedListKey): void => {
|
|
55
|
+
const list = config[key];
|
|
56
|
+
if (!list) return;
|
|
57
|
+
config[key] = list.map(id => route(id) ?? id);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const routeRecordValues = (record: Record<string, string> | undefined): void => {
|
|
61
|
+
if (!record) return;
|
|
62
|
+
for (const [key, value] of Object.entries(record)) {
|
|
63
|
+
const next = route(value);
|
|
64
|
+
if (next) record[key] = next;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
if (config.defaultProvider === from) {
|
|
69
|
+
config.defaultProvider = to;
|
|
70
|
+
changed += 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
routeListAt("disabledModels");
|
|
74
|
+
routeListAt("subagentModels");
|
|
75
|
+
routeListAt("subagentModelFallback");
|
|
76
|
+
|
|
77
|
+
const scalarOwners: Array<[Record<string, unknown> | undefined, string]> = [
|
|
78
|
+
[config as unknown as Record<string, unknown>, "injectionModel"],
|
|
79
|
+
[config.shadowCallIntercept as Record<string, unknown> | undefined, "model"],
|
|
80
|
+
[config.webSearchSidecar as Record<string, unknown> | undefined, "model"],
|
|
81
|
+
[config.visionSidecar as Record<string, unknown> | undefined, "model"],
|
|
82
|
+
[config.claudeCode as unknown as Record<string, unknown> | undefined, "model"],
|
|
83
|
+
[config.claudeCode as unknown as Record<string, unknown> | undefined, "smallFastModel"],
|
|
84
|
+
[config.claudeCode?.webSearchSidecar as Record<string, unknown> | undefined, "model"],
|
|
85
|
+
[config.claudeCode?.visionSidecar as Record<string, unknown> | undefined, "model"],
|
|
86
|
+
];
|
|
87
|
+
for (const [owner, key] of scalarOwners) {
|
|
88
|
+
if (!owner) continue;
|
|
89
|
+
const next = route(owner[key]);
|
|
90
|
+
if (next) owner[key] = next;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
routeRecordValues(config.claudeCode?.tierModels as Record<string, string> | undefined);
|
|
94
|
+
routeRecordValues(config.claudeCode?.modelMap as Record<string, string> | undefined);
|
|
95
|
+
|
|
96
|
+
// Bare provider ids.
|
|
97
|
+
for (const model of config.customModels ?? []) {
|
|
98
|
+
if (model.provider === from) {
|
|
99
|
+
model.provider = to;
|
|
100
|
+
changed += 1;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for (const combo of Object.values(config.combos ?? {})) {
|
|
104
|
+
for (const target of combo.targets ?? []) {
|
|
105
|
+
if (target.provider === from) {
|
|
106
|
+
target.provider = to;
|
|
107
|
+
changed += 1;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Keys. `providerContextCaps` is KEYED by provider id — a prefix rewrite would
|
|
113
|
+
// silently orphan the cap — and a destination key may already be occupied.
|
|
114
|
+
const caps = config.providerContextCaps;
|
|
115
|
+
if (caps && Object.hasOwn(caps, from)) {
|
|
116
|
+
if (Object.hasOwn(caps, to)) {
|
|
117
|
+
collisions.push(`providerContextCaps.${to}`);
|
|
118
|
+
} else {
|
|
119
|
+
caps[to] = caps[from]!;
|
|
120
|
+
delete caps[from];
|
|
121
|
+
changed += 1;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// `desktopProfile` is nested and asymmetric: `assignments` is KEYED by route
|
|
126
|
+
// while `defaults` holds routes as VALUES.
|
|
127
|
+
const profile = config.claudeCode?.desktopProfile;
|
|
128
|
+
if (profile) {
|
|
129
|
+
for (const key of Object.keys(profile.assignments ?? {})) {
|
|
130
|
+
const next = route(key);
|
|
131
|
+
if (!next) continue;
|
|
132
|
+
if (Object.hasOwn(profile.assignments, next)) {
|
|
133
|
+
changed -= 1; // `route` counted it; the move did not happen.
|
|
134
|
+
collisions.push(`claudeCode.desktopProfile.assignments.${next}`);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
profile.assignments[next] = profile.assignments[key]!;
|
|
138
|
+
delete profile.assignments[key];
|
|
139
|
+
}
|
|
140
|
+
const defaults = profile.defaults as Record<string, string | null> | undefined;
|
|
141
|
+
if (defaults) {
|
|
142
|
+
for (const [family, value] of Object.entries(defaults)) {
|
|
143
|
+
const next = route(value);
|
|
144
|
+
if (next) defaults[family] = next;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return { changed, collisions };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Drop the custom-model rows that belonged to a provider being removed.
|
|
154
|
+
*
|
|
155
|
+
* The sibling of the rename pass above. `rewriteProviderReferences` already
|
|
156
|
+
* carries `customModels[].provider` across a rename, so the array tracks the
|
|
157
|
+
* provider lifecycle — but removal used to delete only `config.providers[name]`
|
|
158
|
+
* and leave the rows behind. Those orphans still reach `/api/models` and the
|
|
159
|
+
* generated Codex catalog, which key on the row rather than on provider
|
|
160
|
+
* existence, so they surface as models that resolve to nothing (#1273).
|
|
161
|
+
*
|
|
162
|
+
* Only the rows are touched: the `customModelCatalogMigration` marker records
|
|
163
|
+
* one-time ownership of pre-marker rows and must survive removal unchanged, or
|
|
164
|
+
* an older binary's view of that ownership silently changes.
|
|
165
|
+
*
|
|
166
|
+
* Returns the number of rows dropped so callers can report it.
|
|
167
|
+
*/
|
|
168
|
+
export function dropProviderCustomModels(config: OcxConfig, provider: string): number {
|
|
169
|
+
const existing = config.customModels;
|
|
170
|
+
if (!Array.isArray(existing) || existing.length === 0) return 0;
|
|
171
|
+
const kept = existing.filter(model => model.provider !== provider);
|
|
172
|
+
if (kept.length === existing.length) return 0;
|
|
173
|
+
// Match the add/remove routes: an emptied list is dropped rather than left as
|
|
174
|
+
// `[]`, so the `customModels` field is absent either way. Only that field —
|
|
175
|
+
// the `customModelCatalogMigration` marker is deliberately left in place.
|
|
176
|
+
if (kept.length > 0) config.customModels = kept;
|
|
177
|
+
else delete config.customModels;
|
|
178
|
+
return existing.length - kept.length;
|
|
179
|
+
}
|