@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.
Files changed (588) hide show
  1. package/AGENTS_INSTALL.md +91 -0
  2. package/LICENSE +21 -0
  3. package/README.md +242 -0
  4. package/assets/architecture.png +0 -0
  5. package/assets/banner.png +0 -0
  6. package/assets/claude-code-models.gif +0 -0
  7. package/assets/codex-app-picker.png +0 -0
  8. package/bin/ocx.mjs +584 -0
  9. package/bin/package-main.mjs +9 -0
  10. package/gui/dist/assets/index-CZqebSPQ.css +1 -0
  11. package/gui/dist/assets/index-CkETtt7P.js +71 -0
  12. package/gui/dist/favicon.png +0 -0
  13. package/gui/dist/fonts/google-sans-cyrillic.woff2 +0 -0
  14. package/gui/dist/fonts/google-sans-latin.woff2 +0 -0
  15. package/gui/dist/icons.svg +24 -0
  16. package/gui/dist/index.html +25 -0
  17. package/gui/dist/logo.png +0 -0
  18. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  19. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  20. package/gui/dist/provider-icons/claude-color.svg +1 -0
  21. package/gui/dist/provider-icons/cline-color.svg +16 -0
  22. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  23. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  24. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  25. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  26. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  27. package/gui/dist/provider-icons/discord.svg +1 -0
  28. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  29. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  30. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  31. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  32. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  33. package/gui/dist/provider-icons/grok.svg +1 -0
  34. package/gui/dist/provider-icons/groq-color.svg +1 -0
  35. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  36. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  37. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  38. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  39. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  40. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  41. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  42. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  43. package/gui/dist/provider-icons/openai.svg +1 -0
  44. package/gui/dist/provider-icons/opencode.svg +2 -0
  45. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  46. package/gui/dist/provider-icons/pi.svg +21 -0
  47. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  48. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  49. package/gui/dist/provider-icons/telegram.svg +1 -0
  50. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  51. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  52. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  53. package/package.json +118 -0
  54. package/src/AGENTS.md +28 -0
  55. package/src/adapters/anthropic-image-guard.ts +251 -0
  56. package/src/adapters/anthropic-image-normalize.ts +518 -0
  57. package/src/adapters/anthropic.ts +1205 -0
  58. package/src/adapters/azure.ts +36 -0
  59. package/src/adapters/base.ts +83 -0
  60. package/src/adapters/client-fingerprint.ts +59 -0
  61. package/src/adapters/command-code.ts +453 -0
  62. package/src/adapters/cursor/arg-codec.ts +38 -0
  63. package/src/adapters/cursor/arg-normalize.ts +104 -0
  64. package/src/adapters/cursor/cursor-errors.ts +165 -0
  65. package/src/adapters/cursor/discovery.ts +276 -0
  66. package/src/adapters/cursor/effort-map.ts +139 -0
  67. package/src/adapters/cursor/exec-policy.ts +88 -0
  68. package/src/adapters/cursor/framing.ts +250 -0
  69. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  70. package/src/adapters/cursor/kv-store.ts +52 -0
  71. package/src/adapters/cursor/live-models.ts +153 -0
  72. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  73. package/src/adapters/cursor/live-transport.ts +1235 -0
  74. package/src/adapters/cursor/mcp-config.ts +42 -0
  75. package/src/adapters/cursor/mcp-manager.ts +333 -0
  76. package/src/adapters/cursor/message-mapper.ts +49 -0
  77. package/src/adapters/cursor/native-exec-common.ts +59 -0
  78. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  79. package/src/adapters/cursor/native-exec-fs.ts +332 -0
  80. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  81. package/src/adapters/cursor/native-exec-network.ts +43 -0
  82. package/src/adapters/cursor/native-exec-shell.ts +548 -0
  83. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  84. package/src/adapters/cursor/native-exec.ts +604 -0
  85. package/src/adapters/cursor/protobuf-events.ts +735 -0
  86. package/src/adapters/cursor/protobuf-request.ts +719 -0
  87. package/src/adapters/cursor/request-builder.ts +280 -0
  88. package/src/adapters/cursor/thread-continuity.ts +67 -0
  89. package/src/adapters/cursor/tool-definitions.ts +621 -0
  90. package/src/adapters/cursor/transport-retry.ts +132 -0
  91. package/src/adapters/cursor/transport.ts +57 -0
  92. package/src/adapters/cursor/types.ts +59 -0
  93. package/src/adapters/cursor.ts +196 -0
  94. package/src/adapters/google-antigravity-replay.ts +520 -0
  95. package/src/adapters/google-antigravity-wire.ts +140 -0
  96. package/src/adapters/google-errors.ts +85 -0
  97. package/src/adapters/google-http.ts +100 -0
  98. package/src/adapters/google-tool-schema.ts +173 -0
  99. package/src/adapters/google-truncation.ts +24 -0
  100. package/src/adapters/google-wire-compiler.ts +232 -0
  101. package/src/adapters/google.ts +859 -0
  102. package/src/adapters/identity.ts +77 -0
  103. package/src/adapters/image.ts +23 -0
  104. package/src/adapters/kiro-constants.ts +16 -0
  105. package/src/adapters/kiro-errors.ts +208 -0
  106. package/src/adapters/kiro-events.ts +197 -0
  107. package/src/adapters/kiro-images.ts +129 -0
  108. package/src/adapters/kiro-retry.ts +312 -0
  109. package/src/adapters/kiro-thinking.ts +104 -0
  110. package/src/adapters/kiro-tool-fallback.ts +36 -0
  111. package/src/adapters/kiro-tools.ts +224 -0
  112. package/src/adapters/kiro-truncation.ts +33 -0
  113. package/src/adapters/kiro-wire.ts +129 -0
  114. package/src/adapters/kiro.ts +1924 -0
  115. package/src/adapters/mimo-free.ts +263 -0
  116. package/src/adapters/openai-chat.ts +1265 -0
  117. package/src/adapters/openai-responses.ts +1309 -0
  118. package/src/adapters/run-turn-queue.ts +114 -0
  119. package/src/adapters/tool-catalog-nudge.ts +71 -0
  120. package/src/adapters/upstream-http-error.ts +48 -0
  121. package/src/android-remote/assets.ts +218 -0
  122. package/src/android-remote/attachments.ts +168 -0
  123. package/src/android-remote/auth.ts +237 -0
  124. package/src/android-remote/cloudflare-provisioning.ts +409 -0
  125. package/src/android-remote/cloudflare-secret.ts +106 -0
  126. package/src/android-remote/cloudflare-tunnel.ts +488 -0
  127. package/src/android-remote/cloudflared.ts +286 -0
  128. package/src/android-remote/codex-app-server.ts +565 -0
  129. package/src/android-remote/desktop-history-page.ts +936 -0
  130. package/src/android-remote/desktop-ipc.ts +3643 -0
  131. package/src/android-remote/desktop-ownership-store.ts +98 -0
  132. package/src/android-remote/desktop-project-registration.ts +129 -0
  133. package/src/android-remote/desktop-session-stream.ts +1110 -0
  134. package/src/android-remote/desktop-workspace-state.ts +443 -0
  135. package/src/android-remote/file-change-parser.ts +112 -0
  136. package/src/android-remote/gateway.ts +9108 -0
  137. package/src/android-remote/mutation-store.ts +299 -0
  138. package/src/android-remote/projection.ts +1780 -0
  139. package/src/android-remote/queued-turn-store.ts +248 -0
  140. package/src/android-remote/session-command-recovery.ts +1384 -0
  141. package/src/android-remote/store.ts +466 -0
  142. package/src/android-remote/thread-reconciliation.ts +133 -0
  143. package/src/android-remote/thread-source-paths.ts +307 -0
  144. package/src/android-remote/thread-stream.ts +546 -0
  145. package/src/android-remote/turn-activity.ts +235 -0
  146. package/src/android-remote/user-message-identity.ts +94 -0
  147. package/src/bridge.ts +1793 -0
  148. package/src/chat/inbound.ts +295 -0
  149. package/src/chat/outbound.ts +821 -0
  150. package/src/claude/agents-inject.ts +267 -0
  151. package/src/claude/alias.ts +149 -0
  152. package/src/claude/auth-detect.ts +229 -0
  153. package/src/claude/auth-mode-migration.ts +32 -0
  154. package/src/claude/auth-mode.ts +62 -0
  155. package/src/claude/context-windows.ts +189 -0
  156. package/src/claude/desktop-3p-guard.ts +35 -0
  157. package/src/claude/desktop-3p-paths.ts +84 -0
  158. package/src/claude/desktop-3p.ts +601 -0
  159. package/src/claude/desktop-health.ts +26 -0
  160. package/src/claude/desktop-profile.ts +263 -0
  161. package/src/claude/gateway-cache.ts +70 -0
  162. package/src/claude/inbound-debug.ts +163 -0
  163. package/src/claude/inbound.ts +519 -0
  164. package/src/claude/model-info.ts +154 -0
  165. package/src/claude/outbound.ts +898 -0
  166. package/src/cli/access.ts +108 -0
  167. package/src/cli/account-api.ts +296 -0
  168. package/src/cli/account-auth.ts +250 -0
  169. package/src/cli/account-catalog-refresh.ts +14 -0
  170. package/src/cli/account-extended.ts +476 -0
  171. package/src/cli/account-main.ts +317 -0
  172. package/src/cli/account.ts +297 -0
  173. package/src/cli/agent-driven.ts +70 -0
  174. package/src/cli/agent.ts +184 -0
  175. package/src/cli/catalog-prewarm.ts +27 -0
  176. package/src/cli/claude-desktop.ts +211 -0
  177. package/src/cli/claude.ts +302 -0
  178. package/src/cli/codex-shim-autorestore.ts +45 -0
  179. package/src/cli/codex-shim-readiness.ts +69 -0
  180. package/src/cli/combo.ts +124 -0
  181. package/src/cli/config-command.ts +183 -0
  182. package/src/cli/debug.ts +228 -0
  183. package/src/cli/desktop-first-run.ts +25 -0
  184. package/src/cli/doctor.ts +1022 -0
  185. package/src/cli/export-command.ts +201 -0
  186. package/src/cli/help.ts +370 -0
  187. package/src/cli/index.ts +1565 -0
  188. package/src/cli/init.ts +224 -0
  189. package/src/cli/integrations.ts +225 -0
  190. package/src/cli/interactive-confirm.ts +133 -0
  191. package/src/cli/internal-dispatch.ts +35 -0
  192. package/src/cli/launcher-context.ts +77 -0
  193. package/src/cli/models-runtime.ts +224 -0
  194. package/src/cli/models.ts +340 -0
  195. package/src/cli/observe.ts +170 -0
  196. package/src/cli/opencode.ts +587 -0
  197. package/src/cli/provider-runtime.ts +179 -0
  198. package/src/cli/provider.ts +476 -0
  199. package/src/cli/ready.ts +301 -0
  200. package/src/cli/route-policy.ts +92 -0
  201. package/src/cli/runtime-api.ts +328 -0
  202. package/src/cli/star-prompt.ts +211 -0
  203. package/src/cli/status-oauth.ts +78 -0
  204. package/src/cli/status.ts +321 -0
  205. package/src/cli/system-command.ts +196 -0
  206. package/src/cli/system-restart-client.ts +146 -0
  207. package/src/cli/tray-proxy.ts +205 -0
  208. package/src/cli/v2.ts +200 -0
  209. package/src/cli.ts +10 -0
  210. package/src/clients/config-export.ts +1109 -0
  211. package/src/codex/account-id.ts +34 -0
  212. package/src/codex/account-label.ts +34 -0
  213. package/src/codex/account-lifecycle.ts +172 -0
  214. package/src/codex/account-namespace-match.ts +63 -0
  215. package/src/codex/account-namespaces.ts +195 -0
  216. package/src/codex/account-pause.ts +20 -0
  217. package/src/codex/account-priority.ts +83 -0
  218. package/src/codex/account-runtime-state.ts +31 -0
  219. package/src/codex/account-store.ts +517 -0
  220. package/src/codex/account-usability.ts +40 -0
  221. package/src/codex/admission.ts +263 -0
  222. package/src/codex/app-server-processes.ts +799 -0
  223. package/src/codex/auth-api.ts +2098 -0
  224. package/src/codex/auth-collision.ts +107 -0
  225. package/src/codex/auth-context.ts +480 -0
  226. package/src/codex/autostart-health.ts +156 -0
  227. package/src/codex/catalog/account-models.ts +67 -0
  228. package/src/codex/catalog/aggregation.ts +471 -0
  229. package/src/codex/catalog/bundled.ts +533 -0
  230. package/src/codex/catalog/effort.ts +432 -0
  231. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  232. package/src/codex/catalog/kinds.ts +2 -0
  233. package/src/codex/catalog/metadata.ts +287 -0
  234. package/src/codex/catalog/native-models.ts +7 -0
  235. package/src/codex/catalog/parsing.ts +503 -0
  236. package/src/codex/catalog/provider-fetch.ts +2267 -0
  237. package/src/codex/catalog/sync.ts +1606 -0
  238. package/src/codex/catalog-admission.ts +197 -0
  239. package/src/codex/catalog-refresh-status.ts +87 -0
  240. package/src/codex/catalog-write-serialization.ts +242 -0
  241. package/src/codex/catalog.ts +15 -0
  242. package/src/codex/codex-write-lock.ts +384 -0
  243. package/src/codex/convergence-types.ts +593 -0
  244. package/src/codex/convergence.ts +580 -0
  245. package/src/codex/custom-model-catalog-migration.ts +176 -0
  246. package/src/codex/data/upstream-models.json +830 -0
  247. package/src/codex/desired-state.ts +230 -0
  248. package/src/codex/desktop-client-processes.ts +521 -0
  249. package/src/codex/exec-invocation.ts +22 -0
  250. package/src/codex/features.ts +1091 -0
  251. package/src/codex/generation.ts +202 -0
  252. package/src/codex/history-job.ts +347 -0
  253. package/src/codex/history-lock.ts +242 -0
  254. package/src/codex/history-migration-guardian.ts +115 -0
  255. package/src/codex/history-provider.ts +1075 -0
  256. package/src/codex/history-transition.ts +105 -0
  257. package/src/codex/history-worker.ts +204 -0
  258. package/src/codex/home.ts +206 -0
  259. package/src/codex/inject-coordination.ts +257 -0
  260. package/src/codex/inject.ts +1857 -0
  261. package/src/codex/injected-marker.ts +79 -0
  262. package/src/codex/integration-record.ts +266 -0
  263. package/src/codex/internal/catalog-writer.ts +203 -0
  264. package/src/codex/internal/history-writer.ts +105 -0
  265. package/src/codex/journal.ts +172 -0
  266. package/src/codex/main-account-cache.ts +56 -0
  267. package/src/codex/main-account.ts +40 -0
  268. package/src/codex/management-convergence.ts +114 -0
  269. package/src/codex/model-cache.ts +267 -0
  270. package/src/codex/native-main-admission.ts +47 -0
  271. package/src/codex/native-main-auth-temp.ts +187 -0
  272. package/src/codex/native-main-claim.ts +167 -0
  273. package/src/codex/native-main-lock-file.ts +162 -0
  274. package/src/codex/native-main-owner.ts +329 -0
  275. package/src/codex/native-profile-api.ts +247 -0
  276. package/src/codex/native-profile-manager.ts +1531 -0
  277. package/src/codex/native-profile-processes.ts +121 -0
  278. package/src/codex/native-profile-recovery.ts +99 -0
  279. package/src/codex/native-profile-stage-store.ts +387 -0
  280. package/src/codex/native-profile-startup.ts +348 -0
  281. package/src/codex/native-profile-store.ts +855 -0
  282. package/src/codex/native-profile-types.ts +120 -0
  283. package/src/codex/native-residue.ts +691 -0
  284. package/src/codex/paths.ts +78 -0
  285. package/src/codex/plugins-doctor.ts +242 -0
  286. package/src/codex/pool-rotation.ts +295 -0
  287. package/src/codex/project-config-warnings.ts +426 -0
  288. package/src/codex/prompt-journal.ts +311 -0
  289. package/src/codex/prompt-layers.ts +967 -0
  290. package/src/codex/prompt-lock.ts +143 -0
  291. package/src/codex/provider-adoption.ts +242 -0
  292. package/src/codex/quota-rejection.ts +224 -0
  293. package/src/codex/quota.ts +494 -0
  294. package/src/codex/refresh.ts +60 -0
  295. package/src/codex/routing.ts +1855 -0
  296. package/src/codex/runtime.ts +659 -0
  297. package/src/codex/shim.ts +1215 -0
  298. package/src/codex/subagent-defaults.ts +557 -0
  299. package/src/codex/subagent-model-fallback.ts +560 -0
  300. package/src/codex/sync.ts +238 -0
  301. package/src/codex/transition-state.ts +612 -0
  302. package/src/codex/upstream-host-health.ts +368 -0
  303. package/src/codex/user-identity.ts +374 -0
  304. package/src/codex/warmup.ts +192 -0
  305. package/src/codex/websocket-registry.ts +100 -0
  306. package/src/codex/write-coordination.ts +114 -0
  307. package/src/combos/failover.ts +140 -0
  308. package/src/combos/index.ts +44 -0
  309. package/src/combos/request.ts +64 -0
  310. package/src/combos/resolve.ts +232 -0
  311. package/src/combos/types.ts +392 -0
  312. package/src/config.ts +3270 -0
  313. package/src/generated/model-metadata.ts +144 -0
  314. package/src/github/star-state.ts +203 -0
  315. package/src/grok/inject.ts +540 -0
  316. package/src/grok/inspect.ts +45 -0
  317. package/src/grok/status.ts +127 -0
  318. package/src/grok/sync.ts +66 -0
  319. package/src/images/artifacts.ts +516 -0
  320. package/src/images/fulfill-video.ts +163 -0
  321. package/src/images/fulfill.ts +149 -0
  322. package/src/images/index.ts +4 -0
  323. package/src/images/loop.ts +922 -0
  324. package/src/images/plan.ts +133 -0
  325. package/src/images/synthetic-tool.ts +133 -0
  326. package/src/images/types.ts +41 -0
  327. package/src/images/xai-client.ts +141 -0
  328. package/src/images/xai-video-client.ts +163 -0
  329. package/src/index.ts +22 -0
  330. package/src/integrations/config-io.ts +151 -0
  331. package/src/integrations/journal.ts +315 -0
  332. package/src/integrations/merge.ts +135 -0
  333. package/src/integrations/native/ownership-preflight.ts +202 -0
  334. package/src/integrations/ownership.ts +111 -0
  335. package/src/integrations/registry.ts +108 -0
  336. package/src/integrations/serialize.ts +235 -0
  337. package/src/integrations/state.ts +290 -0
  338. package/src/integrations/store.ts +103 -0
  339. package/src/integrations/writer.ts +492 -0
  340. package/src/lib/abort.ts +146 -0
  341. package/src/lib/admin-secrets.ts +25 -0
  342. package/src/lib/admission.ts +83 -0
  343. package/src/lib/app-owned-memory-stores.ts +173 -0
  344. package/src/lib/app-owned-memory.ts +265 -0
  345. package/src/lib/bounded-body.ts +242 -0
  346. package/src/lib/bun-binary-validator.d.mts +3 -0
  347. package/src/lib/bun-binary-validator.mjs +18 -0
  348. package/src/lib/bun-runtime.ts +184 -0
  349. package/src/lib/bun-stream-caps.ts +127 -0
  350. package/src/lib/config-ownership.ts +438 -0
  351. package/src/lib/crash-guard.ts +344 -0
  352. package/src/lib/debug-log-buffer.ts +83 -0
  353. package/src/lib/debug-settings.ts +108 -0
  354. package/src/lib/debug.ts +31 -0
  355. package/src/lib/destination-policy.ts +316 -0
  356. package/src/lib/errors.ts +364 -0
  357. package/src/lib/eventstream-decoder.ts +253 -0
  358. package/src/lib/gcp-adc.ts +341 -0
  359. package/src/lib/injection-debug-log.ts +58 -0
  360. package/src/lib/local-management-attestation.ts +51 -0
  361. package/src/lib/open-url.ts +25 -0
  362. package/src/lib/pinned-http.ts +182 -0
  363. package/src/lib/privacy.ts +20 -0
  364. package/src/lib/process-control.ts +168 -0
  365. package/src/lib/provider-environment.ts +470 -0
  366. package/src/lib/provider-outbound.ts +203 -0
  367. package/src/lib/provider-url.ts +14 -0
  368. package/src/lib/proxy-env.ts +18 -0
  369. package/src/lib/redact.ts +510 -0
  370. package/src/lib/remodex-home.ts +616 -0
  371. package/src/lib/retry-after.ts +55 -0
  372. package/src/lib/service-secrets.ts +178 -0
  373. package/src/lib/shadow-call.ts +54 -0
  374. package/src/lib/sidecar-tracker.ts +52 -0
  375. package/src/lib/sse-decoder.ts +364 -0
  376. package/src/lib/state-store-registrations.ts +109 -0
  377. package/src/lib/state-store-sweeper.ts +184 -0
  378. package/src/lib/system-restart-contract.ts +73 -0
  379. package/src/lib/test-home-guard.ts +98 -0
  380. package/src/lib/token-estimate.ts +69 -0
  381. package/src/lib/translator-budget.ts +366 -0
  382. package/src/lib/upstream-reachability.ts +91 -0
  383. package/src/lib/upstream-retry.ts +508 -0
  384. package/src/lib/win-exec.ts +115 -0
  385. package/src/lib/win-paths.ts +68 -0
  386. package/src/lib/windows-elevation.ts +705 -0
  387. package/src/lib/windows-secret-acl.ts +817 -0
  388. package/src/lib/windows-user-principal.ts +283 -0
  389. package/src/lib/winsw.ts +402 -0
  390. package/src/model-sources.ts +73 -0
  391. package/src/oauth/anthropic-routing.ts +594 -0
  392. package/src/oauth/anthropic.ts +177 -0
  393. package/src/oauth/callback-server.ts +294 -0
  394. package/src/oauth/chatgpt.ts +150 -0
  395. package/src/oauth/command-code.ts +239 -0
  396. package/src/oauth/cursor.ts +231 -0
  397. package/src/oauth/github-copilot.ts +428 -0
  398. package/src/oauth/google-antigravity.ts +230 -0
  399. package/src/oauth/health.ts +443 -0
  400. package/src/oauth/index.ts +1280 -0
  401. package/src/oauth/key-providers.ts +128 -0
  402. package/src/oauth/kimi.ts +213 -0
  403. package/src/oauth/kiro-credentials.ts +726 -0
  404. package/src/oauth/kiro.ts +621 -0
  405. package/src/oauth/local-token-detect.ts +121 -0
  406. package/src/oauth/log.ts +48 -0
  407. package/src/oauth/login-cli.ts +163 -0
  408. package/src/oauth/pkce.ts +15 -0
  409. package/src/oauth/store.ts +655 -0
  410. package/src/oauth/token-guardian.ts +309 -0
  411. package/src/oauth/types.ts +62 -0
  412. package/src/oauth/xai.ts +241 -0
  413. package/src/providers/alibaba-region-backup.ts +75 -0
  414. package/src/providers/alibaba-region-migration.ts +156 -0
  415. package/src/providers/alibaba-region-startup.ts +36 -0
  416. package/src/providers/antigravity-models.ts +317 -0
  417. package/src/providers/api-keys.ts +140 -0
  418. package/src/providers/base-url-choices.ts +64 -0
  419. package/src/providers/codex-capacity.ts +288 -0
  420. package/src/providers/command-code-efforts.ts +85 -0
  421. package/src/providers/context-cap.ts +73 -0
  422. package/src/providers/derive.ts +451 -0
  423. package/src/providers/free-directory.ts +187 -0
  424. package/src/providers/github-copilot-transport.ts +56 -0
  425. package/src/providers/google-vertex-location.ts +14 -0
  426. package/src/providers/key-failover.ts +271 -0
  427. package/src/providers/kiro-models.ts +67 -0
  428. package/src/providers/label.ts +19 -0
  429. package/src/providers/model-discovery-limits.ts +16 -0
  430. package/src/providers/model-discovery.ts +361 -0
  431. package/src/providers/openai-sidecar.ts +235 -0
  432. package/src/providers/openai-tier-startup.ts +27 -0
  433. package/src/providers/openai-tiers.ts +301 -0
  434. package/src/providers/openai-virtual-models.ts +83 -0
  435. package/src/providers/openrouter-routing.ts +102 -0
  436. package/src/providers/provider-id-rewrite.ts +179 -0
  437. package/src/providers/quota.ts +1942 -0
  438. package/src/providers/reasoning-capabilities.ts +336 -0
  439. package/src/providers/registry.ts +2375 -0
  440. package/src/providers/slug-codec.ts +74 -0
  441. package/src/providers/xai-transport.ts +149 -0
  442. package/src/reasoning-effort.ts +243 -0
  443. package/src/responses/compaction.ts +124 -0
  444. package/src/responses/hosted-tool-policy.ts +9 -0
  445. package/src/responses/parser.ts +714 -0
  446. package/src/responses/reasoning-envelope.ts +60 -0
  447. package/src/responses/reasoning-replay-cache.ts +106 -0
  448. package/src/responses/schema.ts +159 -0
  449. package/src/responses/spill-store.ts +431 -0
  450. package/src/responses/state.ts +1039 -0
  451. package/src/responses/tool-groups.ts +19 -0
  452. package/src/router.ts +802 -0
  453. package/src/routing/analytics.ts +377 -0
  454. package/src/routing/capability.ts +205 -0
  455. package/src/routing/cost.ts +77 -0
  456. package/src/routing/evaluator.ts +444 -0
  457. package/src/routing/health.ts +401 -0
  458. package/src/routing/history/cursor.ts +43 -0
  459. package/src/routing/history/indexer.ts +605 -0
  460. package/src/routing/history/schema.ts +72 -0
  461. package/src/routing/profile-namespace.ts +15 -0
  462. package/src/routing/profile.ts +424 -0
  463. package/src/routing/quota.ts +145 -0
  464. package/src/routing/request-evidence.ts +45 -0
  465. package/src/routing/trace.ts +686 -0
  466. package/src/server/adapter-resolve.ts +83 -0
  467. package/src/server/auth-cors.ts +606 -0
  468. package/src/server/chat-completions.ts +379 -0
  469. package/src/server/claude-messages.ts +980 -0
  470. package/src/server/effort-policy.ts +251 -0
  471. package/src/server/github-copilot-responses-repair.ts +338 -0
  472. package/src/server/gui-static.ts +152 -0
  473. package/src/server/image-retry.ts +42 -0
  474. package/src/server/images.ts +485 -0
  475. package/src/server/index.ts +1633 -0
  476. package/src/server/lifecycle.ts +482 -0
  477. package/src/server/live.ts +609 -0
  478. package/src/server/management/agent-settings-routes.ts +1180 -0
  479. package/src/server/management/android-remote-routes.ts +390 -0
  480. package/src/server/management/api-access.ts +141 -0
  481. package/src/server/management/api-key-usage.ts +167 -0
  482. package/src/server/management/body.ts +35 -0
  483. package/src/server/management/combo-routes.ts +244 -0
  484. package/src/server/management/config-routes.ts +602 -0
  485. package/src/server/management/context.ts +88 -0
  486. package/src/server/management/integration-routes.ts +538 -0
  487. package/src/server/management/logs-usage-routes.ts +516 -0
  488. package/src/server/management/model-routes.ts +519 -0
  489. package/src/server/management/model-rows.ts +143 -0
  490. package/src/server/management/native-integration-routes.ts +781 -0
  491. package/src/server/management/oauth-account-routes.ts +573 -0
  492. package/src/server/management/provider-routes.ts +781 -0
  493. package/src/server/management/request-history-routes.ts +191 -0
  494. package/src/server/management/routing-analytics-routes.ts +74 -0
  495. package/src/server/management/routing-profile-routes.ts +384 -0
  496. package/src/server/management/shared.ts +277 -0
  497. package/src/server/management/sidebar-routes.ts +106 -0
  498. package/src/server/management/sync-response.ts +69 -0
  499. package/src/server/management/system-restart.ts +433 -0
  500. package/src/server/management/system-routes.ts +141 -0
  501. package/src/server/management/usage-summary-cache.ts +86 -0
  502. package/src/server/management-api.ts +269 -0
  503. package/src/server/management-auth.ts +353 -0
  504. package/src/server/memory-watchdog.ts +156 -0
  505. package/src/server/port-reclaim.ts +307 -0
  506. package/src/server/ports.ts +156 -0
  507. package/src/server/proxy-liveness.ts +326 -0
  508. package/src/server/proxy-stop.ts +92 -0
  509. package/src/server/readiness.ts +99 -0
  510. package/src/server/relay-eager.ts +353 -0
  511. package/src/server/relay.ts +1179 -0
  512. package/src/server/request-decompress.ts +132 -0
  513. package/src/server/request-log-conversation.ts +168 -0
  514. package/src/server/request-log.ts +1072 -0
  515. package/src/server/responses/collaboration.ts +409 -0
  516. package/src/server/responses/compact.ts +710 -0
  517. package/src/server/responses/core.ts +3561 -0
  518. package/src/server/responses/encrypted-payload.ts +308 -0
  519. package/src/server/responses/fetch-helpers.ts +171 -0
  520. package/src/server/responses/passthrough-error.ts +78 -0
  521. package/src/server/responses/policy-fallback.ts +152 -0
  522. package/src/server/responses/terminal-guard.ts +230 -0
  523. package/src/server/responses/upstream-error.ts +48 -0
  524. package/src/server/responses-image-gen-repair.ts +132 -0
  525. package/src/server/responses-item-id-repair.ts +272 -0
  526. package/src/server/responses-json-events.ts +52 -0
  527. package/src/server/responses-model-rewrite.ts +29 -0
  528. package/src/server/responses-snapshot-repair.ts +621 -0
  529. package/src/server/responses.ts +10 -0
  530. package/src/server/search.ts +181 -0
  531. package/src/server/sse-frame-buffer.ts +292 -0
  532. package/src/server/sse-payload-rewrite.ts +263 -0
  533. package/src/server/startup-action-control.ts +308 -0
  534. package/src/server/startup-health-cache.ts +119 -0
  535. package/src/server/system-env.ts +418 -0
  536. package/src/server/windows-tcp-drop.ts +184 -0
  537. package/src/server/windows-tray-control.ts +41 -0
  538. package/src/server/ws-bridge.ts +470 -0
  539. package/src/service-manager-probe.ts +824 -0
  540. package/src/service.ts +3011 -0
  541. package/src/stall-timeout.ts +20 -0
  542. package/src/storage/cleanup-job.ts +57 -0
  543. package/src/storage/cleanup.ts +3085 -0
  544. package/src/storage/policy-job.ts +457 -0
  545. package/src/storage/policy-scheduler.ts +40 -0
  546. package/src/storage/policy-worker.ts +59 -0
  547. package/src/storage/policy.ts +527 -0
  548. package/src/storage/restore-job.ts +299 -0
  549. package/src/storage/restore-worker.ts +58 -0
  550. package/src/storage/scanner.ts +238 -0
  551. package/src/storage/storage-mutation-coordinator.ts +139 -0
  552. package/src/storage/worker-lifecycle.ts +215 -0
  553. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  554. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  555. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  556. package/src/tray/assets/opencodex-tray.png +0 -0
  557. package/src/tray/windows-tray.ps1 +364 -0
  558. package/src/tray/windows.ts +738 -0
  559. package/src/types.ts +1531 -0
  560. package/src/update/badge.ts +72 -0
  561. package/src/update/desktop-release.ts +1620 -0
  562. package/src/update/index.ts +402 -0
  563. package/src/update/job.ts +1906 -0
  564. package/src/update/notify.ts +261 -0
  565. package/src/update/npm-cache-preflight.d.mts +47 -0
  566. package/src/update/npm-cache-preflight.mjs +201 -0
  567. package/src/update/npm-invocation.d.mts +23 -0
  568. package/src/update/npm-invocation.mjs +94 -0
  569. package/src/update/tray-update-plan.d.mts +18 -0
  570. package/src/update/tray-update-plan.mjs +38 -0
  571. package/src/usage/cost.ts +0 -0
  572. package/src/usage/debug.ts +97 -0
  573. package/src/usage/expected-prices.ts +283 -0
  574. package/src/usage/log.ts +695 -0
  575. package/src/usage/summary.ts +585 -0
  576. package/src/usage/totals.ts +14 -0
  577. package/src/vision/anthropic-describe.ts +185 -0
  578. package/src/vision/describe.ts +127 -0
  579. package/src/vision/index.ts +558 -0
  580. package/src/vision/reasoning.ts +55 -0
  581. package/src/web-search/anthropic-executor.ts +189 -0
  582. package/src/web-search/executor.ts +105 -0
  583. package/src/web-search/format-result.ts +89 -0
  584. package/src/web-search/index.ts +196 -0
  585. package/src/web-search/loop.ts +791 -0
  586. package/src/web-search/parse.ts +235 -0
  587. package/src/web-search/progress-stream.ts +342 -0
  588. 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
+ }