@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,686 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route decision trace: bounded, versioned, privacy-safe evidence of WHY a
|
|
3
|
+
* provider/model/account was selected for a request (RI-01).
|
|
4
|
+
*
|
|
5
|
+
* Contract rules (devlog/_plan/260804_router_intelligence/000_master_plan.md):
|
|
6
|
+
* - One trace per routing decision; fallback EXECUTION attempts stay in the
|
|
7
|
+
* usage entry's existing `attempts[]` array, never in this trace.
|
|
8
|
+
* - Never persists prompts, message bodies, tool payloads, credentials,
|
|
9
|
+
* authorization headers, raw quota responses, or hidden reasoning.
|
|
10
|
+
* - Stable wire values only; no localized strings.
|
|
11
|
+
* - Deterministic bounds: candidates <= 8, exclusions per candidate <= 16,
|
|
12
|
+
* requirements <= 16, strings <= 128 chars, serialized trace <= 16 KiB.
|
|
13
|
+
* - Truncation is explicit: `truncated` flags are set whenever a limit was
|
|
14
|
+
* enforced.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { randomBytes } from "node:crypto";
|
|
18
|
+
|
|
19
|
+
export type RouteDecisionKind =
|
|
20
|
+
| "explicit-account"
|
|
21
|
+
| "explicit-provider"
|
|
22
|
+
| "native"
|
|
23
|
+
| "combo"
|
|
24
|
+
| "policy"
|
|
25
|
+
| "default-provider";
|
|
26
|
+
|
|
27
|
+
export type Unknownable = number | boolean | "unknown";
|
|
28
|
+
|
|
29
|
+
export interface RouteRequirementEvidence {
|
|
30
|
+
/** Stable wire code, e.g. "min-context-window" | "tools" | "image-input". */
|
|
31
|
+
id: string;
|
|
32
|
+
expected?: string | number | boolean;
|
|
33
|
+
actual?: Unknownable | string;
|
|
34
|
+
outcome: "satisfied" | "unsatisfied" | "unknown";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface RouteExclusionReason {
|
|
38
|
+
/** Stable wire code, e.g. "cooldown" | "disabled" | "cost-limit". */
|
|
39
|
+
code: string;
|
|
40
|
+
detail?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface RouteCapabilityEvidence {
|
|
44
|
+
contextWindow?: number;
|
|
45
|
+
tools?: Unknownable;
|
|
46
|
+
image?: Unknownable;
|
|
47
|
+
structuredOutput?: Unknownable;
|
|
48
|
+
reasoningEfforts?: string[];
|
|
49
|
+
/** `"unknown"` is reserved: it encodes missing evidence, never a real tier. */
|
|
50
|
+
serviceTier?: string | "unknown";
|
|
51
|
+
localOnly?: Unknownable;
|
|
52
|
+
remoteAllowed?: Unknownable;
|
|
53
|
+
encryptedCodexTasks?: Unknownable;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface RouteHealthEvidence {
|
|
57
|
+
cooldownUntilMs?: number;
|
|
58
|
+
softAvoidUntilMs?: number;
|
|
59
|
+
successRate?: number;
|
|
60
|
+
failures?: number;
|
|
61
|
+
incompleteStreamRate?: number;
|
|
62
|
+
recentLatencyMs?: number;
|
|
63
|
+
sampleCount?: number;
|
|
64
|
+
recencyWeight?: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface RouteQuotaEvidence {
|
|
68
|
+
known: boolean;
|
|
69
|
+
/** Remaining headroom as a fraction 0..1 (larger is better). */
|
|
70
|
+
headroom?: number;
|
|
71
|
+
headroomTokens?: number;
|
|
72
|
+
exhausted?: boolean;
|
|
73
|
+
resetAtMs?: number;
|
|
74
|
+
reauthOrCooling?: boolean;
|
|
75
|
+
reservedHeadroomTokens?: number;
|
|
76
|
+
/** Stable wire code for the evidence source (e.g. "provider-report"). */
|
|
77
|
+
source?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface RouteCostEvidence {
|
|
81
|
+
estimatedUsd?: number;
|
|
82
|
+
/** Stable wire code for the price source (e.g. "registry" | "expected"). */
|
|
83
|
+
priceSource?: string;
|
|
84
|
+
incomplete?: boolean;
|
|
85
|
+
limitUsd?: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface RouteScoreEvidence {
|
|
89
|
+
total: number;
|
|
90
|
+
components: {
|
|
91
|
+
capability?: number;
|
|
92
|
+
health?: number;
|
|
93
|
+
quota?: number;
|
|
94
|
+
cost?: number;
|
|
95
|
+
latency?: number;
|
|
96
|
+
configuredPriority?: number;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface RouteCandidateTrace {
|
|
101
|
+
provider: string;
|
|
102
|
+
model: string;
|
|
103
|
+
accountRef?: string;
|
|
104
|
+
eligible: boolean;
|
|
105
|
+
exclusions: RouteExclusionReason[];
|
|
106
|
+
capability?: RouteCapabilityEvidence;
|
|
107
|
+
health?: RouteHealthEvidence;
|
|
108
|
+
quota?: RouteQuotaEvidence;
|
|
109
|
+
cost?: RouteCostEvidence;
|
|
110
|
+
score?: RouteScoreEvidence;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface RouteDecisionTraceV1 {
|
|
114
|
+
version: 1;
|
|
115
|
+
decisionId: string;
|
|
116
|
+
createdAt: number;
|
|
117
|
+
requestedModel: string;
|
|
118
|
+
routeKind: RouteDecisionKind;
|
|
119
|
+
profile?: { id: string; revision: string };
|
|
120
|
+
requirements: RouteRequirementEvidence[];
|
|
121
|
+
candidates: RouteCandidateTrace[];
|
|
122
|
+
selected: {
|
|
123
|
+
candidateIndex: number;
|
|
124
|
+
provider: string;
|
|
125
|
+
model: string;
|
|
126
|
+
accountRef?: string;
|
|
127
|
+
reason: string;
|
|
128
|
+
tieBreak?: string;
|
|
129
|
+
};
|
|
130
|
+
truncated?: {
|
|
131
|
+
candidates?: true;
|
|
132
|
+
exclusions?: true;
|
|
133
|
+
requirements?: true;
|
|
134
|
+
strings?: true;
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const MAX_TRACE_CANDIDATES = 8;
|
|
139
|
+
export const MAX_EXCLUSIONS_PER_CANDIDATE = 16;
|
|
140
|
+
export const MAX_REQUIREMENTS = 16;
|
|
141
|
+
export const MAX_TRACE_STRING = 128;
|
|
142
|
+
export const MAX_TRACE_BYTES = 16 * 1024;
|
|
143
|
+
|
|
144
|
+
const ROUTE_KINDS = new Set<RouteDecisionKind>([
|
|
145
|
+
"explicit-account",
|
|
146
|
+
"explicit-provider",
|
|
147
|
+
"native",
|
|
148
|
+
"combo",
|
|
149
|
+
"policy",
|
|
150
|
+
"default-provider",
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
const REQUIREMENT_OUTCOMES = new Set(["satisfied", "unsatisfied", "unknown"]);
|
|
154
|
+
|
|
155
|
+
/** Cap a string at MAX_TRACE_STRING and record the truncation flag. */
|
|
156
|
+
function capString(value: string, budget: { strings?: true }): string {
|
|
157
|
+
if (value.length <= MAX_TRACE_STRING) return value;
|
|
158
|
+
budget.strings = true;
|
|
159
|
+
return value.slice(0, MAX_TRACE_STRING);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
163
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function finiteNumber(value: unknown): value is number {
|
|
167
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function unknownable(value: unknown): Unknownable | undefined {
|
|
171
|
+
if (typeof value === "boolean") return value;
|
|
172
|
+
if (finiteNumber(value)) return value;
|
|
173
|
+
if (value === "unknown") return "unknown";
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface TraceCandidateInput {
|
|
178
|
+
provider: string;
|
|
179
|
+
model: string;
|
|
180
|
+
accountRef?: string;
|
|
181
|
+
eligible: boolean;
|
|
182
|
+
exclusions: RouteExclusionReason[];
|
|
183
|
+
score?: RouteScoreEvidence;
|
|
184
|
+
capability?: RouteCapabilityEvidence;
|
|
185
|
+
health?: RouteHealthEvidence;
|
|
186
|
+
quota?: RouteQuotaEvidence;
|
|
187
|
+
cost?: RouteCostEvidence;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface TraceBuildInput {
|
|
191
|
+
requestedModel: string;
|
|
192
|
+
routeKind: RouteDecisionKind;
|
|
193
|
+
selected: {
|
|
194
|
+
provider: string;
|
|
195
|
+
model: string;
|
|
196
|
+
accountRef?: string;
|
|
197
|
+
reason: string;
|
|
198
|
+
tieBreak?: string;
|
|
199
|
+
/** Index into `candidates`; defaults to 0. */
|
|
200
|
+
candidateIndex?: number;
|
|
201
|
+
};
|
|
202
|
+
profile?: { id: string; revision: string };
|
|
203
|
+
requirements?: RouteRequirementEvidence[];
|
|
204
|
+
candidates?: TraceCandidateInput[];
|
|
205
|
+
now?: number;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Bounded candidate copy: strings capped, exclusions sliced, score/evidence kept. */
|
|
209
|
+
function buildCandidate(input: TraceCandidateInput, budget: { strings?: true; exclusions?: true }): RouteCandidateTrace {
|
|
210
|
+
const exclusions = input.exclusions.slice(0, MAX_EXCLUSIONS_PER_CANDIDATE);
|
|
211
|
+
if (exclusions.length < input.exclusions.length) budget.exclusions = true;
|
|
212
|
+
// Evidence reaches the builder from internal producers (bounded) or from
|
|
213
|
+
// caller-supplied dry-run input (unbounded). Whitelist + bound it through
|
|
214
|
+
// the same parsers the persisted-row normalizer uses so no unknown nested
|
|
215
|
+
// field or oversized string survives into the trace.
|
|
216
|
+
const capability = input.capability ? parseCapability(input.capability, budget) : undefined;
|
|
217
|
+
const health = input.health ? parseHealth(input.health) : undefined;
|
|
218
|
+
const quota = input.quota ? parseQuota(input.quota, budget) : undefined;
|
|
219
|
+
const cost = input.cost ? parseCost(input.cost, budget) : undefined;
|
|
220
|
+
return {
|
|
221
|
+
provider: capString(input.provider, budget),
|
|
222
|
+
model: capString(input.model, budget),
|
|
223
|
+
...(input.accountRef !== undefined
|
|
224
|
+
? { accountRef: capString(input.accountRef, budget) }
|
|
225
|
+
: {}),
|
|
226
|
+
eligible: input.eligible,
|
|
227
|
+
exclusions: exclusions.map(exclusion => ({
|
|
228
|
+
code: capString(exclusion.code, budget),
|
|
229
|
+
...(exclusion.detail !== undefined
|
|
230
|
+
? { detail: capString(exclusion.detail, budget) }
|
|
231
|
+
: {}),
|
|
232
|
+
})),
|
|
233
|
+
...(input.score ? { score: input.score } : {}),
|
|
234
|
+
...(capability ? { capability } : {}),
|
|
235
|
+
...(health ? { health } : {}),
|
|
236
|
+
...(quota ? { quota } : {}),
|
|
237
|
+
...(cost ? { cost } : {}),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Bounded requirement copy with capped strings. */
|
|
242
|
+
function buildRequirement(requirement: RouteRequirementEvidence, budget: { strings?: true }): RouteRequirementEvidence {
|
|
243
|
+
return {
|
|
244
|
+
id: capString(requirement.id, budget),
|
|
245
|
+
...(requirement.expected !== undefined
|
|
246
|
+
? { expected: typeof requirement.expected === "string"
|
|
247
|
+
? capString(requirement.expected, budget)
|
|
248
|
+
: requirement.expected }
|
|
249
|
+
: {}),
|
|
250
|
+
...(requirement.actual !== undefined
|
|
251
|
+
? { actual: typeof requirement.actual === "string"
|
|
252
|
+
? capString(requirement.actual, budget)
|
|
253
|
+
: requirement.actual }
|
|
254
|
+
: {}),
|
|
255
|
+
outcome: requirement.outcome,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Build a bounded decision trace. The builder never receives credentials: callers
|
|
261
|
+
* pass provider/model NAME strings and opaque account references only.
|
|
262
|
+
*/
|
|
263
|
+
export function buildRouteDecisionTrace(input: TraceBuildInput): RouteDecisionTraceV1 {
|
|
264
|
+
const budget: { strings?: true; exclusions?: true; candidates?: true } = {};
|
|
265
|
+
const now = input.now ?? Date.now();
|
|
266
|
+
const truncated: RouteDecisionTraceV1["truncated"] = {};
|
|
267
|
+
let selectedIndex = Number.isInteger(input.selected.candidateIndex ?? 0)
|
|
268
|
+
? (input.selected.candidateIndex ?? 0)
|
|
269
|
+
: 0;
|
|
270
|
+
|
|
271
|
+
let candidates = (input.candidates ?? []).map(candidate => buildCandidate(candidate, budget));
|
|
272
|
+
if (candidates.length > MAX_TRACE_CANDIDATES) {
|
|
273
|
+
// Keep the selected candidate even when it sits beyond the slice: a trace
|
|
274
|
+
// whose selected candidate vanished would contradict the decision.
|
|
275
|
+
candidates = selectedIndex < MAX_TRACE_CANDIDATES
|
|
276
|
+
? candidates.slice(0, MAX_TRACE_CANDIDATES)
|
|
277
|
+
: [...candidates.slice(0, MAX_TRACE_CANDIDATES - 1), candidates[selectedIndex]!];
|
|
278
|
+
selectedIndex = Math.min(selectedIndex, candidates.length - 1);
|
|
279
|
+
truncated.candidates = true;
|
|
280
|
+
}
|
|
281
|
+
if (candidates.length === 0) {
|
|
282
|
+
// Invariant: every decision names at least the selected route as a candidate.
|
|
283
|
+
candidates = [{
|
|
284
|
+
provider: capString(input.selected.provider, budget),
|
|
285
|
+
model: capString(input.selected.model, budget),
|
|
286
|
+
...(input.selected.accountRef !== undefined
|
|
287
|
+
? { accountRef: capString(input.selected.accountRef, budget) }
|
|
288
|
+
: {}),
|
|
289
|
+
eligible: true,
|
|
290
|
+
exclusions: [],
|
|
291
|
+
}];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let requirements = (input.requirements ?? []).map(requirement => buildRequirement(requirement, budget));
|
|
295
|
+
if (requirements.length > MAX_REQUIREMENTS) {
|
|
296
|
+
requirements = requirements.slice(0, MAX_REQUIREMENTS);
|
|
297
|
+
truncated.requirements = true;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (selectedIndex < 0 || selectedIndex >= candidates.length) selectedIndex = 0;
|
|
301
|
+
|
|
302
|
+
const trace: RouteDecisionTraceV1 = {
|
|
303
|
+
version: 1,
|
|
304
|
+
decisionId: randomBytes(6).toString("hex"),
|
|
305
|
+
createdAt: now,
|
|
306
|
+
requestedModel: capString(input.requestedModel, budget),
|
|
307
|
+
routeKind: input.routeKind,
|
|
308
|
+
...(input.profile
|
|
309
|
+
? {
|
|
310
|
+
profile: {
|
|
311
|
+
id: capString(input.profile.id, budget),
|
|
312
|
+
revision: capString(input.profile.revision, budget),
|
|
313
|
+
},
|
|
314
|
+
}
|
|
315
|
+
: {}),
|
|
316
|
+
requirements,
|
|
317
|
+
candidates,
|
|
318
|
+
selected: {
|
|
319
|
+
candidateIndex: selectedIndex,
|
|
320
|
+
provider: capString(input.selected.provider, budget),
|
|
321
|
+
model: capString(input.selected.model, budget),
|
|
322
|
+
...(input.selected.accountRef !== undefined
|
|
323
|
+
? { accountRef: capString(input.selected.accountRef, budget) }
|
|
324
|
+
: {}),
|
|
325
|
+
reason: capString(input.selected.reason, budget),
|
|
326
|
+
...(input.selected.tieBreak !== undefined
|
|
327
|
+
? { tieBreak: capString(input.selected.tieBreak, budget) }
|
|
328
|
+
: {}),
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
if (budget.strings) truncated.strings = true;
|
|
333
|
+
if (budget.exclusions) truncated.exclusions = true;
|
|
334
|
+
if (budget.candidates) truncated.candidates = true;
|
|
335
|
+
if (Object.keys(truncated).length > 0) trace.truncated = truncated;
|
|
336
|
+
|
|
337
|
+
return enforceByteBudget(trace);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Serialized UTF-8 length of a value (JSON is measured in bytes, not code units). */
|
|
341
|
+
function serializedByteLength(value: unknown): number {
|
|
342
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** Deterministic byte-budget enforcement: drop details, then shrink candidates. */
|
|
346
|
+
function enforceByteBudget(trace: RouteDecisionTraceV1): RouteDecisionTraceV1 {
|
|
347
|
+
if (serializedByteLength(trace) <= MAX_TRACE_BYTES) return trace;
|
|
348
|
+
const truncated = { ...trace.truncated, strings: true as const };
|
|
349
|
+
const candidates = trace.candidates.map(candidate => ({
|
|
350
|
+
...candidate,
|
|
351
|
+
exclusions: candidate.exclusions.map(exclusion => ({ code: exclusion.code })),
|
|
352
|
+
}));
|
|
353
|
+
const slimmed: RouteDecisionTraceV1 = { ...trace, truncated, candidates };
|
|
354
|
+
if (serializedByteLength(slimmed) <= MAX_TRACE_BYTES) return slimmed;
|
|
355
|
+
// Second stage: shrink candidates. The selected candidate must survive and
|
|
356
|
+
// `selected.candidateIndex` must keep pointing at it (same invariant as the
|
|
357
|
+
// candidate-cap branch above).
|
|
358
|
+
const half = Math.max(1, Math.floor(MAX_TRACE_CANDIDATES / 2));
|
|
359
|
+
const selectedIndex = trace.selected.candidateIndex;
|
|
360
|
+
const kept = selectedIndex < half
|
|
361
|
+
? slimmed.candidates.slice(0, half)
|
|
362
|
+
: [...slimmed.candidates.slice(0, half - 1), slimmed.candidates[selectedIndex]!];
|
|
363
|
+
let result: RouteDecisionTraceV1 = {
|
|
364
|
+
...slimmed,
|
|
365
|
+
truncated: { ...truncated, candidates: true as const },
|
|
366
|
+
candidates: kept,
|
|
367
|
+
selected: { ...slimmed.selected, candidateIndex: Math.min(selectedIndex, kept.length - 1) },
|
|
368
|
+
};
|
|
369
|
+
// Last resort: keep shrinking deterministically until the byte budget holds.
|
|
370
|
+
// Each stage reduces a bounded dimension, and a single candidate with no
|
|
371
|
+
// exclusions is provably below MAX_TRACE_BYTES given the string cap, so the
|
|
372
|
+
// loop terminates.
|
|
373
|
+
for (let stage = 0; stage < 4 && serializedByteLength(result) > MAX_TRACE_BYTES; stage++) {
|
|
374
|
+
if (stage === 0) {
|
|
375
|
+
result = {
|
|
376
|
+
...result,
|
|
377
|
+
truncated: { ...result.truncated, exclusions: true as const },
|
|
378
|
+
candidates: result.candidates.map(candidate => ({
|
|
379
|
+
...candidate,
|
|
380
|
+
exclusions: candidate.exclusions.slice(0, 8),
|
|
381
|
+
})),
|
|
382
|
+
};
|
|
383
|
+
} else if (stage === 1) {
|
|
384
|
+
result = {
|
|
385
|
+
...result,
|
|
386
|
+
truncated: { ...result.truncated, exclusions: true as const },
|
|
387
|
+
candidates: result.candidates.map(candidate => ({ ...candidate, exclusions: [] })),
|
|
388
|
+
};
|
|
389
|
+
} else {
|
|
390
|
+
const half = Math.max(1, Math.ceil(result.candidates.length / 2));
|
|
391
|
+
const index = result.selected.candidateIndex;
|
|
392
|
+
const shrinkKept = index < half
|
|
393
|
+
? result.candidates.slice(0, half)
|
|
394
|
+
: [...result.candidates.slice(0, half - 1), result.candidates[index]!];
|
|
395
|
+
result = {
|
|
396
|
+
...result,
|
|
397
|
+
truncated: { ...result.truncated, candidates: true as const },
|
|
398
|
+
candidates: shrinkKept,
|
|
399
|
+
selected: { ...result.selected, candidateIndex: Math.min(index, shrinkKept.length - 1) },
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return result;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ---- defensive parsing of persisted rows --------------------------------------
|
|
407
|
+
|
|
408
|
+
/** Caps applied by the normalizer; unioned into `truncated` on the result. */
|
|
409
|
+
interface ParseCaps {
|
|
410
|
+
candidates?: true;
|
|
411
|
+
exclusions?: true;
|
|
412
|
+
requirements?: true;
|
|
413
|
+
strings?: true;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** Defensive parse of one persisted exclusion reason. */
|
|
417
|
+
function parseExclusion(raw: unknown, caps: ParseCaps): RouteExclusionReason | null {
|
|
418
|
+
if (!isPlainRecord(raw)) return null;
|
|
419
|
+
const code = raw.code;
|
|
420
|
+
if (typeof code !== "string" || code.length === 0) return null;
|
|
421
|
+
if (code.length > MAX_TRACE_STRING) caps.strings = true;
|
|
422
|
+
const out: RouteExclusionReason = { code: code.slice(0, MAX_TRACE_STRING) };
|
|
423
|
+
if (typeof raw.detail === "string") {
|
|
424
|
+
if (raw.detail.length > MAX_TRACE_STRING) caps.strings = true;
|
|
425
|
+
out.detail = raw.detail.slice(0, MAX_TRACE_STRING);
|
|
426
|
+
}
|
|
427
|
+
return out;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** Defensive parse of one persisted requirement; rejects unknown outcomes. */
|
|
431
|
+
function parseRequirement(raw: unknown, caps: ParseCaps): RouteRequirementEvidence | null {
|
|
432
|
+
if (!isPlainRecord(raw)) return null;
|
|
433
|
+
const id = raw.id;
|
|
434
|
+
const outcome = raw.outcome;
|
|
435
|
+
if (typeof id !== "string" || id.length === 0) return null;
|
|
436
|
+
if (typeof outcome !== "string" || !REQUIREMENT_OUTCOMES.has(outcome)) return null;
|
|
437
|
+
if (id.length > MAX_TRACE_STRING) caps.strings = true;
|
|
438
|
+
const out: RouteRequirementEvidence = {
|
|
439
|
+
id: id.slice(0, MAX_TRACE_STRING),
|
|
440
|
+
outcome: outcome as RouteRequirementEvidence["outcome"],
|
|
441
|
+
};
|
|
442
|
+
if (typeof raw.expected === "string") {
|
|
443
|
+
if (raw.expected.length > MAX_TRACE_STRING) caps.strings = true;
|
|
444
|
+
out.expected = raw.expected.slice(0, MAX_TRACE_STRING);
|
|
445
|
+
} else if (typeof raw.expected === "number" || typeof raw.expected === "boolean") {
|
|
446
|
+
out.expected = raw.expected;
|
|
447
|
+
}
|
|
448
|
+
if (typeof raw.actual === "string") {
|
|
449
|
+
if (raw.actual.length > MAX_TRACE_STRING) caps.strings = true;
|
|
450
|
+
out.actual = raw.actual.slice(0, MAX_TRACE_STRING);
|
|
451
|
+
} else if (unknownable(raw.actual) !== undefined) {
|
|
452
|
+
out.actual = unknownable(raw.actual);
|
|
453
|
+
}
|
|
454
|
+
return out;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/** Whitelisted capability-evidence parse; unknown fields are dropped. */
|
|
458
|
+
function parseCapability(raw: unknown, caps: ParseCaps): RouteCapabilityEvidence | undefined {
|
|
459
|
+
if (!isPlainRecord(raw)) return undefined;
|
|
460
|
+
const out: RouteCapabilityEvidence = {};
|
|
461
|
+
if (finiteNumber(raw.contextWindow)) out.contextWindow = raw.contextWindow;
|
|
462
|
+
const tools = unknownable(raw.tools);
|
|
463
|
+
if (tools !== undefined) out.tools = tools;
|
|
464
|
+
const image = unknownable(raw.image);
|
|
465
|
+
if (image !== undefined) out.image = image;
|
|
466
|
+
const structuredOutput = unknownable(raw.structuredOutput);
|
|
467
|
+
if (structuredOutput !== undefined) out.structuredOutput = structuredOutput;
|
|
468
|
+
if (Array.isArray(raw.reasoningEfforts)
|
|
469
|
+
&& raw.reasoningEfforts.slice(0, 8).every((value): value is string => typeof value === "string")) {
|
|
470
|
+
if (raw.reasoningEfforts.some((value: unknown) => typeof value === "string"
|
|
471
|
+
&& value.length > MAX_TRACE_STRING)) caps.strings = true;
|
|
472
|
+
out.reasoningEfforts = raw.reasoningEfforts
|
|
473
|
+
.slice(0, 8)
|
|
474
|
+
.map(value => value.slice(0, MAX_TRACE_STRING));
|
|
475
|
+
}
|
|
476
|
+
if (raw.serviceTier === "unknown") {
|
|
477
|
+
out.serviceTier = "unknown";
|
|
478
|
+
} else if (typeof raw.serviceTier === "string" && raw.serviceTier) {
|
|
479
|
+
if (raw.serviceTier.length > MAX_TRACE_STRING) caps.strings = true;
|
|
480
|
+
out.serviceTier = raw.serviceTier.slice(0, MAX_TRACE_STRING);
|
|
481
|
+
}
|
|
482
|
+
const localOnly = unknownable(raw.localOnly);
|
|
483
|
+
if (localOnly !== undefined) out.localOnly = localOnly;
|
|
484
|
+
const remoteAllowed = unknownable(raw.remoteAllowed);
|
|
485
|
+
if (remoteAllowed !== undefined) out.remoteAllowed = remoteAllowed;
|
|
486
|
+
const encryptedCodexTasks = unknownable(raw.encryptedCodexTasks);
|
|
487
|
+
if (encryptedCodexTasks !== undefined) out.encryptedCodexTasks = encryptedCodexTasks;
|
|
488
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/** Whitelisted health-evidence parse; non-numeric fields are dropped. */
|
|
492
|
+
function parseHealth(raw: unknown): RouteHealthEvidence | undefined {
|
|
493
|
+
if (!isPlainRecord(raw)) return undefined;
|
|
494
|
+
const out: RouteHealthEvidence = {};
|
|
495
|
+
if (finiteNumber(raw.cooldownUntilMs)) out.cooldownUntilMs = raw.cooldownUntilMs;
|
|
496
|
+
if (finiteNumber(raw.softAvoidUntilMs)) out.softAvoidUntilMs = raw.softAvoidUntilMs;
|
|
497
|
+
if (finiteNumber(raw.successRate)) out.successRate = raw.successRate;
|
|
498
|
+
if (finiteNumber(raw.failures)) out.failures = raw.failures;
|
|
499
|
+
if (finiteNumber(raw.incompleteStreamRate)) out.incompleteStreamRate = raw.incompleteStreamRate;
|
|
500
|
+
if (finiteNumber(raw.recentLatencyMs)) out.recentLatencyMs = raw.recentLatencyMs;
|
|
501
|
+
if (finiteNumber(raw.sampleCount)) out.sampleCount = raw.sampleCount;
|
|
502
|
+
if (finiteNumber(raw.recencyWeight)) out.recencyWeight = raw.recencyWeight;
|
|
503
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/** Whitelisted quota-evidence parse; requires a boolean `known`. */
|
|
507
|
+
function parseQuota(raw: unknown, caps: ParseCaps): RouteQuotaEvidence | undefined {
|
|
508
|
+
if (!isPlainRecord(raw)) return undefined;
|
|
509
|
+
if (typeof raw.known !== "boolean") return undefined;
|
|
510
|
+
const out: RouteQuotaEvidence = { known: raw.known };
|
|
511
|
+
if (finiteNumber(raw.headroom)) out.headroom = Math.max(0, Math.min(1, raw.headroom));
|
|
512
|
+
if (finiteNumber(raw.headroomTokens)) out.headroomTokens = raw.headroomTokens;
|
|
513
|
+
if (typeof raw.exhausted === "boolean") out.exhausted = raw.exhausted;
|
|
514
|
+
if (finiteNumber(raw.resetAtMs)) out.resetAtMs = raw.resetAtMs;
|
|
515
|
+
if (typeof raw.reauthOrCooling === "boolean") out.reauthOrCooling = raw.reauthOrCooling;
|
|
516
|
+
if (finiteNumber(raw.reservedHeadroomTokens)) out.reservedHeadroomTokens = raw.reservedHeadroomTokens;
|
|
517
|
+
if (typeof raw.source === "string" && raw.source) {
|
|
518
|
+
if (raw.source.length > MAX_TRACE_STRING) caps.strings = true;
|
|
519
|
+
out.source = raw.source.slice(0, MAX_TRACE_STRING);
|
|
520
|
+
}
|
|
521
|
+
return out;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/** Whitelisted cost-evidence parse. */
|
|
525
|
+
function parseCost(raw: unknown, caps: ParseCaps): RouteCostEvidence | undefined {
|
|
526
|
+
if (!isPlainRecord(raw)) return undefined;
|
|
527
|
+
const out: RouteCostEvidence = {};
|
|
528
|
+
if (finiteNumber(raw.estimatedUsd)) out.estimatedUsd = raw.estimatedUsd;
|
|
529
|
+
if (typeof raw.priceSource === "string" && raw.priceSource) {
|
|
530
|
+
if (raw.priceSource.length > MAX_TRACE_STRING) caps.strings = true;
|
|
531
|
+
out.priceSource = raw.priceSource.slice(0, MAX_TRACE_STRING);
|
|
532
|
+
}
|
|
533
|
+
if (typeof raw.incomplete === "boolean") out.incomplete = raw.incomplete;
|
|
534
|
+
if (finiteNumber(raw.limitUsd)) out.limitUsd = raw.limitUsd;
|
|
535
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** Whitelisted score parse; requires a finite `total` and bounded components. */
|
|
539
|
+
function parseScore(raw: unknown): RouteScoreEvidence | undefined {
|
|
540
|
+
if (!isPlainRecord(raw)) return undefined;
|
|
541
|
+
if (!finiteNumber(raw.total)) return undefined;
|
|
542
|
+
const components = isPlainRecord(raw.components) ? raw.components : {};
|
|
543
|
+
const parsedComponents: RouteScoreEvidence["components"] = {};
|
|
544
|
+
for (const key of ["capability", "health", "quota", "cost", "latency", "configuredPriority"] as const) {
|
|
545
|
+
if (finiteNumber(components[key])) parsedComponents[key] = components[key];
|
|
546
|
+
}
|
|
547
|
+
return { total: raw.total, components: parsedComponents };
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** Defensive parse of one persisted candidate with bounded evidence blocks. */
|
|
551
|
+
function parseCandidate(raw: unknown, caps: ParseCaps): RouteCandidateTrace | null {
|
|
552
|
+
if (!isPlainRecord(raw)) return null;
|
|
553
|
+
const provider = raw.provider;
|
|
554
|
+
const model = raw.model;
|
|
555
|
+
if (typeof provider !== "string" || provider.length === 0) return null;
|
|
556
|
+
if (typeof model !== "string" || model.length === 0) return null;
|
|
557
|
+
if (provider.length > MAX_TRACE_STRING) caps.strings = true;
|
|
558
|
+
if (model.length > MAX_TRACE_STRING) caps.strings = true;
|
|
559
|
+
if (typeof raw.accountRef === "string" && raw.accountRef.length > MAX_TRACE_STRING) caps.strings = true;
|
|
560
|
+
if (typeof raw.eligible !== "boolean") return null;
|
|
561
|
+
if (Array.isArray(raw.exclusions) && raw.exclusions.length > MAX_EXCLUSIONS_PER_CANDIDATE) caps.exclusions = true;
|
|
562
|
+
const exclusions = Array.isArray(raw.exclusions)
|
|
563
|
+
? raw.exclusions.slice(0, MAX_EXCLUSIONS_PER_CANDIDATE)
|
|
564
|
+
.map(value => parseExclusion(value, caps))
|
|
565
|
+
.filter((value): value is RouteExclusionReason => value !== null)
|
|
566
|
+
: [];
|
|
567
|
+
const capability = parseCapability(raw.capability, caps);
|
|
568
|
+
const health = parseHealth(raw.health);
|
|
569
|
+
const quota = parseQuota(raw.quota, caps);
|
|
570
|
+
const cost = parseCost(raw.cost, caps);
|
|
571
|
+
const score = parseScore(raw.score);
|
|
572
|
+
return {
|
|
573
|
+
provider: provider.slice(0, MAX_TRACE_STRING),
|
|
574
|
+
model: model.slice(0, MAX_TRACE_STRING),
|
|
575
|
+
...(typeof raw.accountRef === "string"
|
|
576
|
+
? { accountRef: raw.accountRef.slice(0, MAX_TRACE_STRING) }
|
|
577
|
+
: {}),
|
|
578
|
+
eligible: raw.eligible,
|
|
579
|
+
exclusions: exclusions.slice(0, MAX_EXCLUSIONS_PER_CANDIDATE),
|
|
580
|
+
...(capability ? { capability } : {}),
|
|
581
|
+
...(health ? { health } : {}),
|
|
582
|
+
...(quota ? { quota } : {}),
|
|
583
|
+
...(cost ? { cost } : {}),
|
|
584
|
+
...(score ? { score } : {}),
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Defensive parse of a persisted trace. Returns null when the row is not a
|
|
590
|
+
* version-1 trace; otherwise returns a bounded, whitelisted copy. Invalid
|
|
591
|
+
* evidence objects are dropped rather than poisoning the DTO.
|
|
592
|
+
*/
|
|
593
|
+
export function normalizeRouteDecisionTrace(raw: unknown): RouteDecisionTraceV1 | null {
|
|
594
|
+
if (!isPlainRecord(raw)) return null;
|
|
595
|
+
if (raw.version !== 1) return null;
|
|
596
|
+
const caps: ParseCaps = {};
|
|
597
|
+
const decisionId = raw.decisionId;
|
|
598
|
+
const createdAt = raw.createdAt;
|
|
599
|
+
const requestedModel = raw.requestedModel;
|
|
600
|
+
const routeKind = raw.routeKind;
|
|
601
|
+
if (typeof decisionId !== "string" || decisionId.length === 0) return null;
|
|
602
|
+
if (!/^[0-9a-f]{12}$/.test(decisionId)) return null;
|
|
603
|
+
if (decisionId.length > MAX_TRACE_STRING) caps.strings = true;
|
|
604
|
+
if (!finiteNumber(createdAt)) return null;
|
|
605
|
+
if (typeof requestedModel !== "string" || requestedModel.length === 0) return null;
|
|
606
|
+
if (requestedModel.length > MAX_TRACE_STRING) caps.strings = true;
|
|
607
|
+
if (typeof routeKind !== "string" || !ROUTE_KINDS.has(routeKind as RouteDecisionKind)) return null;
|
|
608
|
+
if (!Array.isArray(raw.candidates) || raw.candidates.length === 0) return null;
|
|
609
|
+
if (raw.candidates.length > MAX_TRACE_CANDIDATES) caps.candidates = true;
|
|
610
|
+
|
|
611
|
+
const candidates = raw.candidates
|
|
612
|
+
.slice(0, MAX_TRACE_CANDIDATES)
|
|
613
|
+
.map(value => parseCandidate(value, caps))
|
|
614
|
+
.filter((value): value is RouteCandidateTrace => value !== null)
|
|
615
|
+
.slice(0, MAX_TRACE_CANDIDATES);
|
|
616
|
+
if (candidates.length === 0) return null;
|
|
617
|
+
|
|
618
|
+
const selected = isPlainRecord(raw.selected) ? raw.selected : null;
|
|
619
|
+
if (!selected) return null;
|
|
620
|
+
const selectedProvider = selected.provider;
|
|
621
|
+
const selectedModel = selected.model;
|
|
622
|
+
const selectedReason = selected.reason;
|
|
623
|
+
if (typeof selectedProvider !== "string" || typeof selectedModel !== "string") return null;
|
|
624
|
+
if (typeof selectedReason !== "string") return null;
|
|
625
|
+
if (selectedProvider.length > MAX_TRACE_STRING) caps.strings = true;
|
|
626
|
+
if (selectedModel.length > MAX_TRACE_STRING) caps.strings = true;
|
|
627
|
+
if (selectedReason.length > MAX_TRACE_STRING) caps.strings = true;
|
|
628
|
+
if (typeof selected.accountRef === "string" && selected.accountRef.length > MAX_TRACE_STRING) caps.strings = true;
|
|
629
|
+
if (typeof selected.tieBreak === "string" && selected.tieBreak.length > MAX_TRACE_STRING) caps.strings = true;
|
|
630
|
+
const candidateIndex = Number.isInteger(selected.candidateIndex)
|
|
631
|
+
? selected.candidateIndex as number
|
|
632
|
+
: 0;
|
|
633
|
+
if (candidateIndex < 0 || candidateIndex >= candidates.length) return null;
|
|
634
|
+
|
|
635
|
+
const rawRequirements = Array.isArray(raw.requirements) ? raw.requirements : [];
|
|
636
|
+
if (rawRequirements.length > MAX_REQUIREMENTS) caps.requirements = true;
|
|
637
|
+
const requirements = rawRequirements
|
|
638
|
+
.slice(0, MAX_REQUIREMENTS)
|
|
639
|
+
.map(value => parseRequirement(value, caps))
|
|
640
|
+
.filter((value): value is RouteRequirementEvidence => value !== null)
|
|
641
|
+
.slice(0, MAX_REQUIREMENTS);
|
|
642
|
+
|
|
643
|
+
const profile = isPlainRecord(raw.profile)
|
|
644
|
+
&& typeof raw.profile.id === "string"
|
|
645
|
+
&& typeof raw.profile.revision === "string"
|
|
646
|
+
? (() => {
|
|
647
|
+
if (raw.profile.id.length > MAX_TRACE_STRING) caps.strings = true;
|
|
648
|
+
if (raw.profile.revision.length > MAX_TRACE_STRING) caps.strings = true;
|
|
649
|
+
return {
|
|
650
|
+
id: raw.profile.id.slice(0, MAX_TRACE_STRING),
|
|
651
|
+
revision: raw.profile.revision.slice(0, MAX_TRACE_STRING),
|
|
652
|
+
};
|
|
653
|
+
})()
|
|
654
|
+
: undefined;
|
|
655
|
+
|
|
656
|
+
const incoming = isPlainRecord(raw.truncated) ? raw.truncated : {};
|
|
657
|
+
const truncated: RouteDecisionTraceV1["truncated"] = {};
|
|
658
|
+
if (incoming.candidates === true || caps.candidates) truncated.candidates = true;
|
|
659
|
+
if (incoming.exclusions === true || caps.exclusions) truncated.exclusions = true;
|
|
660
|
+
if (incoming.requirements === true || caps.requirements) truncated.requirements = true;
|
|
661
|
+
if (incoming.strings === true || caps.strings) truncated.strings = true;
|
|
662
|
+
|
|
663
|
+
return {
|
|
664
|
+
version: 1,
|
|
665
|
+
decisionId: decisionId.slice(0, MAX_TRACE_STRING),
|
|
666
|
+
createdAt,
|
|
667
|
+
requestedModel: requestedModel.slice(0, MAX_TRACE_STRING),
|
|
668
|
+
routeKind: routeKind as RouteDecisionKind,
|
|
669
|
+
...(profile ? { profile } : {}),
|
|
670
|
+
requirements,
|
|
671
|
+
candidates,
|
|
672
|
+
selected: {
|
|
673
|
+
candidateIndex,
|
|
674
|
+
provider: selectedProvider.slice(0, MAX_TRACE_STRING),
|
|
675
|
+
model: selectedModel.slice(0, MAX_TRACE_STRING),
|
|
676
|
+
...(typeof selected.accountRef === "string"
|
|
677
|
+
? { accountRef: selected.accountRef.slice(0, MAX_TRACE_STRING) }
|
|
678
|
+
: {}),
|
|
679
|
+
reason: selectedReason.slice(0, MAX_TRACE_STRING),
|
|
680
|
+
...(typeof selected.tieBreak === "string"
|
|
681
|
+
? { tieBreak: selected.tieBreak.slice(0, MAX_TRACE_STRING) }
|
|
682
|
+
: {}),
|
|
683
|
+
},
|
|
684
|
+
...(truncated && Object.keys(truncated).length > 0 ? { truncated } : {}),
|
|
685
|
+
};
|
|
686
|
+
}
|