@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,16 @@
|
|
|
1
|
+
/** Hard process-wide limits shared by all live model-discovery parsers. */
|
|
2
|
+
export const MODEL_DISCOVERY_MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
3
|
+
export const MODEL_DISCOVERY_MAX_MODELS = 2_000;
|
|
4
|
+
export const MODEL_DISCOVERY_MAX_MODEL_ID_LENGTH = 1_024;
|
|
5
|
+
|
|
6
|
+
const MODEL_DISCOVERY_MODEL_ID_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;
|
|
7
|
+
|
|
8
|
+
/** Reject model IDs that cannot safely be published as callable catalog selectors. */
|
|
9
|
+
export function isValidModelDiscoveryModelId(value: unknown): value is string {
|
|
10
|
+
if (typeof value !== "string") return false;
|
|
11
|
+
const normalized = value.trim();
|
|
12
|
+
return Boolean(normalized)
|
|
13
|
+
&& normalized === value
|
|
14
|
+
&& normalized.length <= MODEL_DISCOVERY_MAX_MODEL_ID_LENGTH
|
|
15
|
+
&& !MODEL_DISCOVERY_MODEL_ID_CONTROL_CHARS.test(normalized);
|
|
16
|
+
}
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import type { OcxProviderConfig } from "../types";
|
|
2
|
+
import {
|
|
3
|
+
isValidModelDiscoveryModelId,
|
|
4
|
+
MODEL_DISCOVERY_MAX_MODEL_ID_LENGTH,
|
|
5
|
+
MODEL_DISCOVERY_MAX_MODELS,
|
|
6
|
+
MODEL_DISCOVERY_MAX_RESPONSE_BYTES,
|
|
7
|
+
} from "./model-discovery-limits";
|
|
8
|
+
export {
|
|
9
|
+
isValidModelDiscoveryModelId,
|
|
10
|
+
MODEL_DISCOVERY_MAX_MODEL_ID_LENGTH,
|
|
11
|
+
MODEL_DISCOVERY_MAX_MODELS,
|
|
12
|
+
MODEL_DISCOVERY_MAX_RESPONSE_BYTES,
|
|
13
|
+
} from "./model-discovery-limits";
|
|
14
|
+
import {
|
|
15
|
+
getProviderRegistryEntry,
|
|
16
|
+
providerMatchesRegistryTransport,
|
|
17
|
+
registryEntryForProviderDestination,
|
|
18
|
+
type ProviderModelDiscoveryFilter,
|
|
19
|
+
type ProviderModelDiscoveryPredicate,
|
|
20
|
+
type ProviderModelDiscoveryScalar,
|
|
21
|
+
type ProviderModelDiscoverySpec,
|
|
22
|
+
} from "./registry";
|
|
23
|
+
|
|
24
|
+
const MODEL_DISCOVERY_MAX_FILTER_VALUES = 256;
|
|
25
|
+
const MODEL_DISCOVERY_MAX_FILTER_STRING_LENGTH = 1_024;
|
|
26
|
+
|
|
27
|
+
export interface ResolvedProviderModelDiscovery {
|
|
28
|
+
spec?: ProviderModelDiscoverySpec;
|
|
29
|
+
maxResponseBytes: number;
|
|
30
|
+
maxModels: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type ProviderModelsApiItem = Record<string, unknown> & { id: string };
|
|
34
|
+
|
|
35
|
+
export type ModelDiscoveryResponseFailure =
|
|
36
|
+
| "response_too_large"
|
|
37
|
+
| "invalid_json"
|
|
38
|
+
| "invalid_shape"
|
|
39
|
+
| "too_many_models";
|
|
40
|
+
|
|
41
|
+
export type BoundedDiscoveryJsonResult =
|
|
42
|
+
| { ok: true; value: unknown }
|
|
43
|
+
| { ok: false; reason: "response_too_large" | "invalid_json" };
|
|
44
|
+
|
|
45
|
+
export type ProviderModelItemsResult =
|
|
46
|
+
| { ok: true; items: ProviderModelsApiItem[]; rawCount: number }
|
|
47
|
+
| { ok: false; reason: "invalid_shape" | "too_many_models" };
|
|
48
|
+
|
|
49
|
+
export type ModelEnvelopeRowsResult =
|
|
50
|
+
| { ok: true; rows: unknown[] }
|
|
51
|
+
| { ok: false; reason: "invalid_shape" | "too_many_models" };
|
|
52
|
+
|
|
53
|
+
function positiveIntegerAtMost(value: number | undefined, hardLimit: number): number {
|
|
54
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return hardLimit;
|
|
55
|
+
return Math.min(Math.floor(value), hardLimit);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function discoveryPredicateError(predicate: ProviderModelDiscoveryPredicate): string | null {
|
|
59
|
+
if (!Array.isArray(predicate.path) || predicate.path.length === 0 || predicate.path.length > 8) {
|
|
60
|
+
return "predicate path must contain 1-8 segments";
|
|
61
|
+
}
|
|
62
|
+
if (predicate.path.some(segment => typeof segment !== "string" || !segment.trim() || segment.length > 64)) {
|
|
63
|
+
return "predicate path segments must be nonblank strings up to 64 characters";
|
|
64
|
+
}
|
|
65
|
+
const values = "equalsAny" in predicate
|
|
66
|
+
? predicate.equalsAny
|
|
67
|
+
: "containsAny" in predicate
|
|
68
|
+
? predicate.containsAny
|
|
69
|
+
: predicate.containsAll;
|
|
70
|
+
if (!Array.isArray(values) || values.length === 0 || values.length > 32) {
|
|
71
|
+
return "predicate values must contain 1-32 scalars";
|
|
72
|
+
}
|
|
73
|
+
if (values.some(value => (
|
|
74
|
+
(typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean")
|
|
75
|
+
|| (typeof value === "string" && (!value.trim() || value.length > 128))
|
|
76
|
+
|| (typeof value === "number" && !Number.isFinite(value))
|
|
77
|
+
))) {
|
|
78
|
+
return "predicate values must be finite booleans/numbers or nonblank strings up to 128 characters";
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Static registry validation used by parity tests; discovery metadata never comes from config. */
|
|
84
|
+
export function providerModelDiscoverySpecError(spec: ProviderModelDiscoverySpec): string | null {
|
|
85
|
+
if (spec.url && spec.path) return "url and path are mutually exclusive";
|
|
86
|
+
if (spec.url !== undefined) {
|
|
87
|
+
try {
|
|
88
|
+
const parsed = new URL(spec.url);
|
|
89
|
+
if (parsed.protocol !== "https:") return "absolute discovery url must use https";
|
|
90
|
+
if (parsed.username || parsed.password || parsed.hash) return "absolute discovery url must not contain credentials or a fragment";
|
|
91
|
+
} catch {
|
|
92
|
+
return "absolute discovery url must be valid";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (spec.path !== undefined) {
|
|
96
|
+
const path = spec.path.trim();
|
|
97
|
+
if (!path || path.length > 512) return "discovery path must be 1-512 characters";
|
|
98
|
+
if (/^[a-z][a-z\d+.-]*:/i.test(path) || path.startsWith("//") || path.includes("?") || path.includes("#")) {
|
|
99
|
+
return "discovery path must be a query-free relative/origin path";
|
|
100
|
+
}
|
|
101
|
+
if (path.includes("\\")) return "discovery path must use forward slashes";
|
|
102
|
+
if (path.split("/").some(segment => segment.replace(/%2e/gi, ".") === "..")) {
|
|
103
|
+
return "discovery path must not contain parent-directory segments";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const queryEntries = Object.entries(spec.query ?? {});
|
|
107
|
+
if (queryEntries.length > 32) return "discovery query may contain at most 32 entries";
|
|
108
|
+
if (queryEntries.some(([key, value]) => !key.trim() || key.length > 128 || typeof value !== "string" || value.length > 512)) {
|
|
109
|
+
return "discovery query keys/values exceed their bounds";
|
|
110
|
+
}
|
|
111
|
+
for (const [field, value, hardLimit] of [
|
|
112
|
+
["maxResponseBytes", spec.maxResponseBytes, MODEL_DISCOVERY_MAX_RESPONSE_BYTES],
|
|
113
|
+
["maxModels", spec.maxModels, MODEL_DISCOVERY_MAX_MODELS],
|
|
114
|
+
] as const) {
|
|
115
|
+
if (value !== undefined && (!Number.isInteger(value) || value <= 0 || value > hardLimit)) {
|
|
116
|
+
return `${field} must be a positive integer no greater than ${hardLimit}`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const [group, predicates] of Object.entries(spec.filter ?? {})) {
|
|
120
|
+
if (!Array.isArray(predicates) || predicates.length === 0 || predicates.length > 32) {
|
|
121
|
+
return `${group} must contain 1-32 predicates`;
|
|
122
|
+
}
|
|
123
|
+
for (const predicate of predicates) {
|
|
124
|
+
const error = discoveryPredicateError(predicate);
|
|
125
|
+
if (error) return `${group}: ${error}`;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function resolveProviderModelDiscovery(
|
|
132
|
+
providerName: string,
|
|
133
|
+
provider: Pick<OcxProviderConfig, "baseUrl" | "adapter"> & Partial<Pick<OcxProviderConfig, "authMode">>,
|
|
134
|
+
): ResolvedProviderModelDiscovery {
|
|
135
|
+
// The dashboard permits a canonical preset to be saved under a different name. Recover its
|
|
136
|
+
// registry-owned discovery policy by transport in that case. The destination helper is limited
|
|
137
|
+
// to exact fixed-key baseUrl + adapter matches, so custom endpoints, OAuth rows, templates, and
|
|
138
|
+
// overridable destinations cannot acquire another provider's discovery URL or filter.
|
|
139
|
+
const namedEntry = getProviderRegistryEntry(providerName);
|
|
140
|
+
const entry = namedEntry
|
|
141
|
+
? (providerMatchesRegistryTransport(providerName, provider) ? namedEntry : undefined)
|
|
142
|
+
: registryEntryForProviderDestination(provider);
|
|
143
|
+
const spec = entry?.modelDiscovery;
|
|
144
|
+
return {
|
|
145
|
+
...(spec ? { spec } : {}),
|
|
146
|
+
maxResponseBytes: positiveIntegerAtMost(spec?.maxResponseBytes, MODEL_DISCOVERY_MAX_RESPONSE_BYTES),
|
|
147
|
+
maxModels: positiveIntegerAtMost(spec?.maxModels, MODEL_DISCOVERY_MAX_MODELS),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function appendDiscoveryQuery(url: URL, query: Readonly<Record<string, string>> | undefined): URL {
|
|
152
|
+
for (const [key, value] of Object.entries(query ?? {})) url.searchParams.set(key, value);
|
|
153
|
+
return url;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Apply a registry-owned URL/path/query policy to the adapter's normal discovery endpoint. */
|
|
157
|
+
export function resolveProviderModelDiscoveryUrl(
|
|
158
|
+
providerName: string,
|
|
159
|
+
configuredProvider: Pick<OcxProviderConfig, "baseUrl" | "adapter"> & Partial<Pick<OcxProviderConfig, "authMode">>,
|
|
160
|
+
effectiveBaseUrl: string,
|
|
161
|
+
defaultUrl: string,
|
|
162
|
+
): string {
|
|
163
|
+
const { spec } = resolveProviderModelDiscovery(providerName, configuredProvider);
|
|
164
|
+
if (!spec) return defaultUrl;
|
|
165
|
+
|
|
166
|
+
let resolved: URL;
|
|
167
|
+
if (spec.url) {
|
|
168
|
+
resolved = new URL(spec.url);
|
|
169
|
+
} else if (spec.path) {
|
|
170
|
+
const base = new URL(effectiveBaseUrl.endsWith("/") ? effectiveBaseUrl : `${effectiveBaseUrl}/`);
|
|
171
|
+
resolved = spec.path.startsWith("/")
|
|
172
|
+
? new URL(spec.path, base.origin)
|
|
173
|
+
: new URL(spec.path, base);
|
|
174
|
+
} else {
|
|
175
|
+
resolved = new URL(defaultUrl);
|
|
176
|
+
}
|
|
177
|
+
return appendDiscoveryQuery(resolved, spec.query).toString();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function cancelWithoutWaiting(reader: ReadableStreamDefaultReader<Uint8Array>, reason: unknown): void {
|
|
181
|
+
try {
|
|
182
|
+
void reader.cancel(reason).catch(() => undefined);
|
|
183
|
+
} catch {
|
|
184
|
+
// A non-conforming stream may throw synchronously from cancel().
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Read a discovery response under a strict byte ceiling before JSON.parse can allocate freely. */
|
|
189
|
+
export async function readBoundedDiscoveryJson(
|
|
190
|
+
response: Response,
|
|
191
|
+
maxResponseBytes: number,
|
|
192
|
+
): Promise<BoundedDiscoveryJsonResult> {
|
|
193
|
+
const limit = positiveIntegerAtMost(maxResponseBytes, MODEL_DISCOVERY_MAX_RESPONSE_BYTES);
|
|
194
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
195
|
+
if (Number.isFinite(declaredLength) && declaredLength > limit) {
|
|
196
|
+
try {
|
|
197
|
+
void response.body?.cancel(new DOMException("Model discovery response is too large", "QuotaExceededError"))
|
|
198
|
+
.catch(() => undefined);
|
|
199
|
+
} catch {
|
|
200
|
+
// Best-effort cancellation only.
|
|
201
|
+
}
|
|
202
|
+
return { ok: false, reason: "response_too_large" };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!response.body) return { ok: false, reason: "invalid_json" };
|
|
206
|
+
const reader = response.body.getReader();
|
|
207
|
+
const chunks: Uint8Array[] = [];
|
|
208
|
+
let total = 0;
|
|
209
|
+
try {
|
|
210
|
+
while (true) {
|
|
211
|
+
const { value, done } = await reader.read();
|
|
212
|
+
if (done) break;
|
|
213
|
+
if (!value || value.byteLength === 0) continue;
|
|
214
|
+
if (value.byteLength > limit - total) {
|
|
215
|
+
cancelWithoutWaiting(
|
|
216
|
+
reader,
|
|
217
|
+
new DOMException("Model discovery response is too large", "QuotaExceededError"),
|
|
218
|
+
);
|
|
219
|
+
return { ok: false, reason: "response_too_large" };
|
|
220
|
+
}
|
|
221
|
+
chunks.push(value);
|
|
222
|
+
total += value.byteLength;
|
|
223
|
+
}
|
|
224
|
+
} finally {
|
|
225
|
+
try {
|
|
226
|
+
reader.releaseLock();
|
|
227
|
+
} catch {
|
|
228
|
+
// Cancellation may keep the lock briefly.
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const bytes = new Uint8Array(total);
|
|
233
|
+
let offset = 0;
|
|
234
|
+
for (const chunk of chunks) {
|
|
235
|
+
bytes.set(chunk, offset);
|
|
236
|
+
offset += chunk.byteLength;
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
240
|
+
return { ok: true, value: JSON.parse(text) as unknown };
|
|
241
|
+
} catch {
|
|
242
|
+
return { ok: false, reason: "invalid_json" };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function valueAtPath(item: Record<string, unknown>, path: readonly string[]): unknown {
|
|
247
|
+
let current: unknown = item;
|
|
248
|
+
for (const segment of path) {
|
|
249
|
+
if (current === null || typeof current !== "object" || Array.isArray(current)) return undefined;
|
|
250
|
+
current = (current as Record<string, unknown>)[segment];
|
|
251
|
+
}
|
|
252
|
+
return current;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function comparableScalar(value: unknown, caseInsensitive: boolean): ProviderModelDiscoveryScalar | undefined {
|
|
256
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") return undefined;
|
|
257
|
+
if (typeof value === "string" && value.length > MODEL_DISCOVERY_MAX_FILTER_STRING_LENGTH) return undefined;
|
|
258
|
+
return caseInsensitive && typeof value === "string" ? value.toLowerCase() : value;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function comparableNeedles(
|
|
262
|
+
values: readonly ProviderModelDiscoveryScalar[],
|
|
263
|
+
caseInsensitive: boolean,
|
|
264
|
+
): ProviderModelDiscoveryScalar[] {
|
|
265
|
+
return values.map(value => caseInsensitive && typeof value === "string" ? value.toLowerCase() : value);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function predicateMatches(item: ProviderModelsApiItem, predicate: ProviderModelDiscoveryPredicate): boolean {
|
|
269
|
+
const caseInsensitive = predicate.caseInsensitive === true;
|
|
270
|
+
const raw = valueAtPath(item, predicate.path);
|
|
271
|
+
if ("equalsAny" in predicate) {
|
|
272
|
+
const value = comparableScalar(raw, caseInsensitive);
|
|
273
|
+
return value !== undefined && comparableNeedles(predicate.equalsAny, caseInsensitive).includes(value);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const collection = Array.isArray(raw);
|
|
277
|
+
const values: ProviderModelDiscoveryScalar[] = [];
|
|
278
|
+
if (collection) {
|
|
279
|
+
for (let i = 0; i < raw.length && i < MODEL_DISCOVERY_MAX_FILTER_VALUES; i += 1) {
|
|
280
|
+
const value = comparableScalar(raw[i], caseInsensitive);
|
|
281
|
+
if (value !== undefined) values.push(value);
|
|
282
|
+
}
|
|
283
|
+
} else if (typeof raw === "string") {
|
|
284
|
+
values.push(caseInsensitive ? raw.toLowerCase() : raw);
|
|
285
|
+
}
|
|
286
|
+
const needles = comparableNeedles(
|
|
287
|
+
"containsAny" in predicate ? predicate.containsAny : predicate.containsAll,
|
|
288
|
+
caseInsensitive,
|
|
289
|
+
);
|
|
290
|
+
if ("containsAny" in predicate) {
|
|
291
|
+
return needles.some(needle => values.some(value => (
|
|
292
|
+
!collection && typeof value === "string" && typeof needle === "string" ? value.includes(needle) : value === needle
|
|
293
|
+
)));
|
|
294
|
+
}
|
|
295
|
+
return needles.every(needle => values.some(value => (
|
|
296
|
+
!collection && typeof value === "string" && typeof needle === "string" ? value.includes(needle) : value === needle
|
|
297
|
+
)));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function providerModelMatchesDiscoveryFilter(
|
|
301
|
+
item: ProviderModelsApiItem,
|
|
302
|
+
filter: ProviderModelDiscoveryFilter | undefined,
|
|
303
|
+
): boolean {
|
|
304
|
+
if (!filter) return true;
|
|
305
|
+
if (filter.allOf && !filter.allOf.every(predicate => predicateMatches(item, predicate))) return false;
|
|
306
|
+
if (filter.anyOf && filter.anyOf.length > 0 && !filter.anyOf.some(predicate => predicateMatches(item, predicate))) return false;
|
|
307
|
+
if (filter.noneOf?.some(predicate => predicateMatches(item, predicate))) return false;
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Extract one allowlisted array envelope while enforcing the raw-row ceiling. */
|
|
312
|
+
export function extractModelEnvelopeRows(
|
|
313
|
+
value: unknown,
|
|
314
|
+
maxModels: number,
|
|
315
|
+
envelopeKeys: readonly string[],
|
|
316
|
+
): ModelEnvelopeRowsResult {
|
|
317
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
318
|
+
return { ok: false, reason: "invalid_shape" };
|
|
319
|
+
}
|
|
320
|
+
const record = value as Record<string, unknown>;
|
|
321
|
+
const rows = envelopeKeys.map(key => record[key]).find(Array.isArray);
|
|
322
|
+
if (!rows) return { ok: false, reason: "invalid_shape" };
|
|
323
|
+
const limit = positiveIntegerAtMost(maxModels, MODEL_DISCOVERY_MAX_MODELS);
|
|
324
|
+
if (rows.length > limit) return { ok: false, reason: "too_many_models" };
|
|
325
|
+
return { ok: true, rows };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Validate, bound, deduplicate, and declaratively filter OpenAI `{data:[...]}` or top-level arrays (Together `#617`). */
|
|
329
|
+
export function extractProviderModelItems(
|
|
330
|
+
value: unknown,
|
|
331
|
+
discovery: ResolvedProviderModelDiscovery,
|
|
332
|
+
): ProviderModelItemsResult {
|
|
333
|
+
const limit = positiveIntegerAtMost(discovery.maxModels, MODEL_DISCOVERY_MAX_MODELS);
|
|
334
|
+
let data: unknown[];
|
|
335
|
+
if (Array.isArray(value)) {
|
|
336
|
+
// Together-style top-level /models arrays. Catalog discovery must not treat a stray
|
|
337
|
+
// `models` key on openai-chat responses as valid — only `data` envelopes or top-level arrays.
|
|
338
|
+
if (value.length > limit) return { ok: false, reason: "too_many_models" };
|
|
339
|
+
data = value;
|
|
340
|
+
} else {
|
|
341
|
+
const envelope = extractModelEnvelopeRows(value, discovery.maxModels, ["data"]);
|
|
342
|
+
if (!envelope.ok) return envelope;
|
|
343
|
+
data = envelope.rows;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const items: ProviderModelsApiItem[] = [];
|
|
347
|
+
const seen = new Set<string>();
|
|
348
|
+
for (const raw of data) {
|
|
349
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
350
|
+
return { ok: false, reason: "invalid_shape" };
|
|
351
|
+
}
|
|
352
|
+
const id = (raw as { id?: unknown }).id;
|
|
353
|
+
if (!isValidModelDiscoveryModelId(id)) return { ok: false, reason: "invalid_shape" };
|
|
354
|
+
const normalizedId = id;
|
|
355
|
+
const item = raw as ProviderModelsApiItem;
|
|
356
|
+
if (!providerModelMatchesDiscoveryFilter(item, discovery.spec?.filter) || seen.has(normalizedId)) continue;
|
|
357
|
+
seen.add(normalizedId);
|
|
358
|
+
items.push(item);
|
|
359
|
+
}
|
|
360
|
+
return { ok: true, items, rawCount: data.length };
|
|
361
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { resolveEnvValue } from "../config";
|
|
2
|
+
import {
|
|
3
|
+
CodexPoolAuthenticationError,
|
|
4
|
+
headersForCodexAuthContext,
|
|
5
|
+
hasCallerCodexBearer,
|
|
6
|
+
isCodexAuthContextUsable,
|
|
7
|
+
resolveCodexAuthContext,
|
|
8
|
+
type CodexAccountSelectionAdmission,
|
|
9
|
+
type CodexAuthContext,
|
|
10
|
+
} from "../codex/auth-context";
|
|
11
|
+
import { recordCodexUpstreamOutcome, type CodexUpstreamOutcome } from "../codex/routing";
|
|
12
|
+
import { extractAccountId } from "../oauth/chatgpt";
|
|
13
|
+
import { ForwardAdmissionCredentialError, validateForwardAdmissionCredential } from "../server/auth-cors";
|
|
14
|
+
import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "../types";
|
|
15
|
+
import {
|
|
16
|
+
isCanonicalOpenAiForwardProvider,
|
|
17
|
+
OPENAI_API_PROVIDER_ID,
|
|
18
|
+
OPENAI_CODEX_PROVIDER_ID,
|
|
19
|
+
} from "./openai-tiers";
|
|
20
|
+
import { getProviderRegistryEntry, providerCodexAccountMode } from "./registry";
|
|
21
|
+
|
|
22
|
+
export interface OpenAiForwardSidecarCandidate {
|
|
23
|
+
providerName: typeof OPENAI_CODEX_PROVIDER_ID;
|
|
24
|
+
provider: OcxProviderConfig;
|
|
25
|
+
accountMode: CodexAccountMode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ResolvedOpenAiForwardSidecar extends OpenAiForwardSidecarCandidate {
|
|
29
|
+
authContext: CodexAuthContext;
|
|
30
|
+
headers: Headers;
|
|
31
|
+
recordOutcome?: (outcome: CodexUpstreamOutcome) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Server-resolved exact account selection for a ChatGPT sidecar call.
|
|
36
|
+
* `accountId` must come from a validated account-qualified route; request headers
|
|
37
|
+
* are never trusted as account ids. `modelId` is the sidecar's actual upstream
|
|
38
|
+
* model so cooldown admission and outcome recording use the correct quota scope.
|
|
39
|
+
*/
|
|
40
|
+
export interface ExactOpenAiSidecarAccount {
|
|
41
|
+
accountId: string;
|
|
42
|
+
modelId: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface OpenAiImagesProviderSelection {
|
|
46
|
+
forwardCandidates: OpenAiForwardSidecarCandidate[];
|
|
47
|
+
keyed?: {
|
|
48
|
+
providerName: string;
|
|
49
|
+
provider: OcxProviderConfig;
|
|
50
|
+
apiKey: string;
|
|
51
|
+
};
|
|
52
|
+
error?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function listOpenAiForwardSidecarCandidates(config: OcxConfig): OpenAiForwardSidecarCandidate[] {
|
|
56
|
+
const provider = config.providers[OPENAI_CODEX_PROVIDER_ID];
|
|
57
|
+
if (!provider || provider.disabled === true) return [];
|
|
58
|
+
// The built-in registry defaults an omitted authMode to forward. Normalize only that
|
|
59
|
+
// missing field before the strict adapter/destination check; explicit key mode and
|
|
60
|
+
// noncanonical destinations remain ineligible for ChatGPT credential injection.
|
|
61
|
+
const canonicalProvider = provider.authMode === undefined
|
|
62
|
+
? { ...provider, authMode: "forward" as const }
|
|
63
|
+
: provider;
|
|
64
|
+
if (!isCanonicalOpenAiForwardProvider(canonicalProvider)) return [];
|
|
65
|
+
return [{
|
|
66
|
+
providerName: OPENAI_CODEX_PROVIDER_ID,
|
|
67
|
+
provider: canonicalProvider,
|
|
68
|
+
accountMode: providerCodexAccountMode(OPENAI_CODEX_PROVIDER_ID, canonicalProvider) ?? "pool",
|
|
69
|
+
}];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function directSidecarHeaders(
|
|
73
|
+
incomingHeaders: Headers,
|
|
74
|
+
): Headers | undefined {
|
|
75
|
+
const bearer = incomingHeaders.get("authorization")?.replace(/^Bearer\s+/i, "").trim();
|
|
76
|
+
if (!bearer) return undefined;
|
|
77
|
+
const derivedAccountId = extractAccountId(undefined, bearer);
|
|
78
|
+
if (!derivedAccountId) return undefined;
|
|
79
|
+
const requestedAccountId = incomingHeaders.get("chatgpt-account-id")?.trim();
|
|
80
|
+
// JWT payloads are decoded locally but not signature-verified. Requiring the caller's
|
|
81
|
+
// explicit account header, and checking it against the token claim, makes forwarding an
|
|
82
|
+
// intentional ChatGPT-auth operation instead of silently reclassifying any JWT-shaped
|
|
83
|
+
// provider credential as a Codex bearer.
|
|
84
|
+
if (!requestedAccountId || requestedAccountId !== derivedAccountId) return undefined;
|
|
85
|
+
const selected = headersForCodexAuthContext(incomingHeaders, { kind: "main", accountId: null });
|
|
86
|
+
return selected;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function resolveFirstUsableOpenAiSidecar(
|
|
90
|
+
candidates: readonly OpenAiForwardSidecarCandidate[],
|
|
91
|
+
incomingHeaders: Headers,
|
|
92
|
+
config: OcxConfig,
|
|
93
|
+
options: {
|
|
94
|
+
exactAccount?: ExactOpenAiSidecarAccount;
|
|
95
|
+
beginCodexAccountSelection?: () => CodexAccountSelectionAdmission | undefined;
|
|
96
|
+
} = {},
|
|
97
|
+
): Promise<ResolvedOpenAiForwardSidecar | undefined> {
|
|
98
|
+
const { exactAccount } = options;
|
|
99
|
+
let callerBearerMayBeForwarded = true;
|
|
100
|
+
try {
|
|
101
|
+
validateForwardAdmissionCredential(incomingHeaders, config);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (!(error instanceof ForwardAdmissionCredentialError)) throw error;
|
|
104
|
+
callerBearerMayBeForwarded = false;
|
|
105
|
+
}
|
|
106
|
+
for (const candidate of candidates) {
|
|
107
|
+
if (exactAccount) {
|
|
108
|
+
// An account-qualified model is an explicit user choice. Resolve the stored
|
|
109
|
+
// credential directly even when the provider is globally Direct, and never
|
|
110
|
+
// consult Pool active state, affinity, probes, or alternates.
|
|
111
|
+
const authContext = await resolveCodexAuthContext(incomingHeaders, config, "pool", {
|
|
112
|
+
accountId: exactAccount.accountId,
|
|
113
|
+
modelId: exactAccount.modelId,
|
|
114
|
+
beginCodexAccountSelection: options.beginCodexAccountSelection,
|
|
115
|
+
});
|
|
116
|
+
if ((authContext.kind !== "pool" && authContext.kind !== "main-pool")
|
|
117
|
+
|| !isCodexAuthContextUsable(authContext, config)) {
|
|
118
|
+
// Exact selection is fail-closed. A generation/runtime-state race must not fall through
|
|
119
|
+
// to the caller-bearer error or let a later candidate select another account.
|
|
120
|
+
throw new CodexPoolAuthenticationError("Selected Codex account is unavailable");
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
...candidate,
|
|
124
|
+
authContext,
|
|
125
|
+
headers: headersForCodexAuthContext(incomingHeaders, authContext),
|
|
126
|
+
recordOutcome: (outcome: CodexUpstreamOutcome) => recordCodexUpstreamOutcome(
|
|
127
|
+
config,
|
|
128
|
+
authContext.accountId,
|
|
129
|
+
outcome,
|
|
130
|
+
{
|
|
131
|
+
modelId: exactAccount.modelId,
|
|
132
|
+
fixedAccount: true,
|
|
133
|
+
probeLeaseId: authContext.probeLeaseId,
|
|
134
|
+
probeQuotaScope: authContext.probeQuotaScope,
|
|
135
|
+
writerGeneration: authContext.writerGeneration,
|
|
136
|
+
},
|
|
137
|
+
),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (candidate.accountMode === "direct") {
|
|
141
|
+
if (!callerBearerMayBeForwarded || !hasCallerCodexBearer(incomingHeaders)) continue;
|
|
142
|
+
const headers = directSidecarHeaders(incomingHeaders);
|
|
143
|
+
if (!headers) continue;
|
|
144
|
+
return {
|
|
145
|
+
...candidate,
|
|
146
|
+
authContext: { kind: "main", accountId: null },
|
|
147
|
+
headers,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const authContext = await resolveCodexAuthContext(incomingHeaders, config, candidate.accountMode, {
|
|
151
|
+
beginCodexAccountSelection: options.beginCodexAccountSelection,
|
|
152
|
+
});
|
|
153
|
+
if (!isCodexAuthContextUsable(authContext, config)) continue;
|
|
154
|
+
return {
|
|
155
|
+
...candidate,
|
|
156
|
+
authContext,
|
|
157
|
+
headers: headersForCodexAuthContext(incomingHeaders, authContext),
|
|
158
|
+
...(authContext.kind === "pool" || authContext.kind === "main-pool"
|
|
159
|
+
? {
|
|
160
|
+
recordOutcome: (outcome: CodexUpstreamOutcome) => recordCodexUpstreamOutcome(
|
|
161
|
+
config,
|
|
162
|
+
authContext.accountId,
|
|
163
|
+
outcome,
|
|
164
|
+
{
|
|
165
|
+
probeLeaseId: authContext.probeLeaseId,
|
|
166
|
+
writerGeneration: authContext.writerGeneration,
|
|
167
|
+
},
|
|
168
|
+
),
|
|
169
|
+
}
|
|
170
|
+
: {}),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function selectOpenAiImagesProvider(config: OcxConfig): OpenAiImagesProviderSelection {
|
|
177
|
+
const selection: OpenAiImagesProviderSelection = {
|
|
178
|
+
forwardCandidates: listOpenAiForwardSidecarCandidates(config),
|
|
179
|
+
};
|
|
180
|
+
const provider = config.providers[OPENAI_API_PROVIDER_ID];
|
|
181
|
+
if (
|
|
182
|
+
provider
|
|
183
|
+
&& provider.disabled !== true
|
|
184
|
+
&& provider.adapter === "openai-responses"
|
|
185
|
+
&& provider.authMode !== "forward"
|
|
186
|
+
&& provider.baseUrl.replace(/\/+$/, "") === "https://api.openai.com/v1"
|
|
187
|
+
) {
|
|
188
|
+
const apiKey = resolveEnvValue(provider.apiKey)?.trim();
|
|
189
|
+
if (apiKey) selection.keyed = { providerName: OPENAI_API_PROVIDER_ID, provider, apiKey };
|
|
190
|
+
}
|
|
191
|
+
return selection;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Resolve an explicit custom Images provider, otherwise preserve the existing OpenAI fallback. */
|
|
195
|
+
export function selectImagesProvider(config: OcxConfig): OpenAiImagesProviderSelection {
|
|
196
|
+
const configuredProvider = config.images?.provider;
|
|
197
|
+
if (configuredProvider === undefined) return selectOpenAiImagesProvider(config);
|
|
198
|
+
if (typeof configuredProvider !== "string" || !configuredProvider.trim()) {
|
|
199
|
+
return { forwardCandidates: [], error: "images.provider must be a nonblank provider name" };
|
|
200
|
+
}
|
|
201
|
+
const providerName = configuredProvider.trim();
|
|
202
|
+
|
|
203
|
+
if (getProviderRegistryEntry(providerName)) {
|
|
204
|
+
return {
|
|
205
|
+
forwardCandidates: [],
|
|
206
|
+
error: `images.provider "${providerName}" must name a custom provider; omit it to use built-in OpenAI tiers`,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const provider = Object.prototype.hasOwnProperty.call(config.providers, providerName)
|
|
211
|
+
? config.providers[providerName]
|
|
212
|
+
: undefined;
|
|
213
|
+
if (!provider) {
|
|
214
|
+
return { forwardCandidates: [], error: `images.provider "${providerName}" is not configured` };
|
|
215
|
+
}
|
|
216
|
+
if (provider.disabled === true) {
|
|
217
|
+
return { forwardCandidates: [], error: `images.provider "${providerName}" is disabled` };
|
|
218
|
+
}
|
|
219
|
+
if (provider.adapter !== "openai-responses" || (provider.authMode !== undefined && provider.authMode !== "key")) {
|
|
220
|
+
return {
|
|
221
|
+
forwardCandidates: [],
|
|
222
|
+
error: `images.provider "${providerName}" must be an API-key openai-responses provider`,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const apiKey = resolveEnvValue(provider.apiKey)?.trim();
|
|
227
|
+
if (!apiKey) {
|
|
228
|
+
return { forwardCandidates: [], error: `images.provider "${providerName}" has no usable API key` };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
forwardCandidates: [],
|
|
233
|
+
keyed: { providerName, provider, apiKey },
|
|
234
|
+
};
|
|
235
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { backupConfigBeforeOpenAiTierMigration, saveConfig } from "../config";
|
|
2
|
+
import type { OcxConfig } from "../types";
|
|
3
|
+
import { projectOpenAiTierMigration } from "./openai-tiers";
|
|
4
|
+
|
|
5
|
+
export interface OpenAiTierStartupDeps {
|
|
6
|
+
project: typeof projectOpenAiTierMigration;
|
|
7
|
+
backup: () => void;
|
|
8
|
+
save: (config: OcxConfig) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const DEFAULT_DEPS: OpenAiTierStartupDeps = {
|
|
12
|
+
project: projectOpenAiTierMigration,
|
|
13
|
+
backup: backupConfigBeforeOpenAiTierMigration,
|
|
14
|
+
save: saveConfig,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function runOpenAiTierStartupMigration(
|
|
18
|
+
config: OcxConfig,
|
|
19
|
+
deps: OpenAiTierStartupDeps = DEFAULT_DEPS,
|
|
20
|
+
): OcxConfig {
|
|
21
|
+
const projection = deps.project(config);
|
|
22
|
+
if (!projection.changed) return projection.config;
|
|
23
|
+
deps.backup();
|
|
24
|
+
deps.save(projection.config);
|
|
25
|
+
for (const warning of projection.warnings) console.warn(`[openai-provider-migration] ${warning}`);
|
|
26
|
+
return projection.config;
|
|
27
|
+
}
|