@iislee/opencodex 2.11.0 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (733) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +117 -22
  3. package/bin/ocx.mjs +245 -88
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  6. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +21 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +475 -62
  44. package/src/adapters/base.ts +82 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +637 -0
  48. package/src/adapters/cursor/call-id.ts +44 -0
  49. package/src/adapters/cursor/catalog.ts +541 -0
  50. package/src/adapters/cursor/checkpoint-store.ts +308 -0
  51. package/src/adapters/cursor/cursor-errors.ts +144 -6
  52. package/src/adapters/cursor/discovery.ts +122 -14
  53. package/src/adapters/cursor/effort-map.ts +106 -4
  54. package/src/adapters/cursor/envelope-echo.ts +290 -0
  55. package/src/adapters/cursor/framing.ts +39 -0
  56. package/src/adapters/cursor/h2-pool.ts +123 -0
  57. package/src/adapters/cursor/http1-bidi.ts +361 -0
  58. package/src/adapters/cursor/images.ts +704 -0
  59. package/src/adapters/cursor/live-models.ts +180 -59
  60. package/src/adapters/cursor/live-transport.ts +623 -170
  61. package/src/adapters/cursor/message-mapper.ts +4 -1
  62. package/src/adapters/cursor/native-exec-common.ts +23 -2
  63. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  64. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  65. package/src/adapters/cursor/native-exec-network.ts +1 -1
  66. package/src/adapters/cursor/native-exec-shell.ts +5 -3
  67. package/src/adapters/cursor/native-exec.ts +121 -14
  68. package/src/adapters/cursor/protobuf-events.ts +829 -11
  69. package/src/adapters/cursor/protobuf-request.ts +447 -75
  70. package/src/adapters/cursor/request-builder.ts +271 -35
  71. package/src/adapters/cursor/tool-definitions.ts +252 -12
  72. package/src/adapters/cursor/tool-result-normalize.ts +115 -0
  73. package/src/adapters/cursor/transport.ts +22 -0
  74. package/src/adapters/cursor/types.ts +48 -1
  75. package/src/adapters/cursor.ts +336 -39
  76. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  77. package/src/adapters/google-antigravity-replay.ts +713 -41
  78. package/src/adapters/google-antigravity-wire.ts +48 -10
  79. package/src/adapters/google-errors.ts +44 -12
  80. package/src/adapters/google-http.ts +38 -10
  81. package/src/adapters/google-tool-schema.ts +80 -15
  82. package/src/adapters/google-truncation.ts +11 -0
  83. package/src/adapters/google.ts +742 -86
  84. package/src/adapters/identity.ts +39 -6
  85. package/src/adapters/image.ts +1 -1
  86. package/src/adapters/kiro-constants.ts +27 -0
  87. package/src/adapters/kiro-errors.ts +11 -0
  88. package/src/adapters/kiro-events.ts +19 -1
  89. package/src/adapters/kiro-thinking.ts +18 -2
  90. package/src/adapters/kiro-tools.ts +65 -17
  91. package/src/adapters/kiro.ts +270 -73
  92. package/src/adapters/mimo-free.ts +17 -0
  93. package/src/adapters/openai-chat-url.ts +11 -0
  94. package/src/adapters/openai-chat.ts +1377 -344
  95. package/src/adapters/openai-responses-url.ts +16 -0
  96. package/src/adapters/openai-responses.ts +1164 -64
  97. package/src/adapters/registry.ts +175 -0
  98. package/src/adapters/responses-tool-schema.ts +67 -0
  99. package/src/adapters/run-turn-queue.ts +36 -1
  100. package/src/adapters/tool-call-id.ts +119 -0
  101. package/src/adapters/tool-catalog-nudge.ts +105 -21
  102. package/src/adapters/xai-tool-schema.ts +436 -0
  103. package/src/adapters/xai-web-search.ts +186 -0
  104. package/src/bridge.ts +512 -92
  105. package/src/chat/inbound.ts +57 -20
  106. package/src/chat/outbound.ts +97 -33
  107. package/src/claude/agents-inject.ts +40 -10
  108. package/src/claude/context-windows.ts +37 -14
  109. package/src/claude/desktop-3p.ts +243 -9
  110. package/src/claude/gateway-cache.ts +41 -4
  111. package/src/claude/inbound.ts +72 -3
  112. package/src/claude/model-info.ts +38 -15
  113. package/src/claude/outbound.ts +88 -21
  114. package/src/cli/access.ts +46 -3
  115. package/src/cli/account-api.ts +150 -19
  116. package/src/cli/account-auth.ts +33 -6
  117. package/src/cli/account-catalog-refresh.ts +14 -0
  118. package/src/cli/account-extended.ts +648 -28
  119. package/src/cli/account-main.ts +317 -0
  120. package/src/cli/account.ts +97 -19
  121. package/src/cli/agent.ts +53 -2
  122. package/src/cli/alias.ts +66 -0
  123. package/src/cli/capabilities-command.ts +94 -0
  124. package/src/cli/capabilities.ts +496 -0
  125. package/src/cli/claude-agent-startup-sync.ts +73 -0
  126. package/src/cli/claude-desktop.ts +60 -15
  127. package/src/cli/claude.ts +129 -35
  128. package/src/cli/codex-log-guard-doctor.ts +103 -0
  129. package/src/cli/codex-shim-autorestore.ts +2 -0
  130. package/src/cli/codex-shim-readiness.ts +76 -0
  131. package/src/cli/combo.ts +8 -0
  132. package/src/cli/config-command.ts +74 -10
  133. package/src/cli/dispatch.ts +821 -0
  134. package/src/cli/doctor.ts +440 -56
  135. package/src/cli/ensure-desired-integrations.ts +152 -0
  136. package/src/cli/export-command.ts +46 -20
  137. package/src/cli/help.ts +34 -274
  138. package/src/cli/index.ts +417 -528
  139. package/src/cli/init.ts +5 -17
  140. package/src/cli/inspect.ts +230 -0
  141. package/src/cli/integrations.ts +120 -2
  142. package/src/cli/lab.ts +607 -0
  143. package/src/cli/launcher-context.ts +77 -0
  144. package/src/cli/minimax.ts +497 -0
  145. package/src/cli/models-runtime.ts +130 -2
  146. package/src/cli/models.ts +118 -16
  147. package/src/cli/observe.ts +123 -11
  148. package/src/cli/opencode.ts +4 -2
  149. package/src/cli/provider-runtime.ts +34 -3
  150. package/src/cli/provider.ts +25 -3
  151. package/src/cli/ready.ts +301 -0
  152. package/src/cli/registry.ts +476 -0
  153. package/src/cli/root.ts +86 -0
  154. package/src/cli/route-policy.ts +92 -0
  155. package/src/cli/runtime-api.ts +57 -10
  156. package/src/cli/star-prompt.ts +71 -15
  157. package/src/cli/status.ts +51 -4
  158. package/src/cli/storage.ts +234 -0
  159. package/src/cli/system-command.ts +16 -0
  160. package/src/cli/system-restart-client.ts +146 -0
  161. package/src/cli/tray-proxy.ts +153 -6
  162. package/src/cli/usage-report.ts +184 -0
  163. package/src/cli/v2.ts +137 -18
  164. package/src/cli/version-skew.ts +46 -0
  165. package/src/cli.ts +1 -1
  166. package/src/clients/config-export.ts +1442 -23
  167. package/src/clients/effective-status.ts +30 -2
  168. package/src/clients/probes/cline.ts +81 -0
  169. package/src/codex/account-label.ts +35 -1
  170. package/src/codex/account-lifecycle.ts +130 -13
  171. package/src/codex/account-namespaces.ts +49 -3
  172. package/src/codex/account-pause.ts +2 -1
  173. package/src/codex/account-priority.ts +84 -0
  174. package/src/codex/account-store.ts +29 -2
  175. package/src/codex/account-usability.ts +25 -2
  176. package/src/codex/admission.ts +256 -0
  177. package/src/codex/affinity-debug.ts +162 -0
  178. package/src/codex/app-server-processes.ts +570 -107
  179. package/src/codex/app-server-restart-service.ts +232 -0
  180. package/src/codex/auth-api.ts +893 -246
  181. package/src/codex/auth-collision.ts +5 -3
  182. package/src/codex/auth-context.ts +348 -32
  183. package/src/codex/autostart-health.ts +24 -1
  184. package/src/codex/catalog/account-models.ts +67 -0
  185. package/src/codex/catalog/aggregation.ts +86 -10
  186. package/src/codex/catalog/bundled.ts +331 -33
  187. package/src/codex/catalog/effort.ts +138 -32
  188. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  189. package/src/codex/catalog/kinds.ts +2 -0
  190. package/src/codex/catalog/metadata.ts +555 -45
  191. package/src/codex/catalog/native-models.ts +75 -0
  192. package/src/codex/catalog/parsing.ts +269 -30
  193. package/src/codex/catalog/provider-fetch.ts +1626 -144
  194. package/src/codex/catalog/sync.ts +1576 -187
  195. package/src/codex/catalog-admission.ts +199 -0
  196. package/src/codex/catalog-refresh-status.ts +105 -0
  197. package/src/codex/catalog-write-serialization.ts +242 -0
  198. package/src/codex/catalog.ts +6 -3
  199. package/src/codex/codex-write-lock.ts +386 -0
  200. package/src/codex/convergence-types.ts +614 -0
  201. package/src/codex/convergence.ts +676 -0
  202. package/src/codex/coordinator-doctor.ts +332 -0
  203. package/src/codex/custom-model-catalog-migration.ts +176 -0
  204. package/src/codex/desired-state.ts +230 -0
  205. package/src/codex/desktop-app-restart.ts +355 -0
  206. package/src/codex/features.ts +636 -39
  207. package/src/codex/generation.ts +202 -0
  208. package/src/codex/history-job.ts +436 -0
  209. package/src/codex/history-lock.ts +242 -0
  210. package/src/codex/history-manifest.ts +112 -0
  211. package/src/codex/history-migration-guardian.ts +30 -24
  212. package/src/codex/history-provider.ts +1016 -235
  213. package/src/codex/history-transition.ts +105 -0
  214. package/src/codex/history-worker.ts +223 -0
  215. package/src/codex/inject-coordination.ts +373 -0
  216. package/src/codex/inject.ts +1114 -152
  217. package/src/codex/injected-marker.ts +37 -3
  218. package/src/codex/integration-record.ts +266 -0
  219. package/src/codex/internal/catalog-writer.ts +203 -0
  220. package/src/codex/internal/history-writer.ts +81 -0
  221. package/src/codex/journal.ts +66 -4
  222. package/src/codex/log-guard/inspect.ts +524 -0
  223. package/src/codex/log-guard/lock.ts +150 -0
  224. package/src/codex/log-guard/maintenance.ts +403 -0
  225. package/src/codex/log-guard/path-safety.ts +88 -0
  226. package/src/codex/log-guard/policy.ts +44 -0
  227. package/src/codex/log-guard/processes.ts +205 -0
  228. package/src/codex/log-guard/protection.ts +489 -0
  229. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  230. package/src/codex/main-account-cache.ts +24 -0
  231. package/src/codex/main-account.ts +29 -1
  232. package/src/codex/management-convergence.ts +167 -0
  233. package/src/codex/model-cache.ts +56 -10
  234. package/src/codex/model-entitlements.ts +360 -0
  235. package/src/codex/native-main-admission.ts +47 -0
  236. package/src/codex/native-main-auth-temp.ts +187 -0
  237. package/src/codex/native-main-claim.ts +178 -0
  238. package/src/codex/native-main-lock-file.ts +162 -0
  239. package/src/codex/native-main-owner.ts +329 -0
  240. package/src/codex/native-profile-api.ts +247 -0
  241. package/src/codex/native-profile-manager.ts +1531 -0
  242. package/src/codex/native-profile-processes.ts +121 -0
  243. package/src/codex/native-profile-recovery.ts +99 -0
  244. package/src/codex/native-profile-stage-store.ts +387 -0
  245. package/src/codex/native-profile-startup.ts +622 -0
  246. package/src/codex/native-profile-store.ts +855 -0
  247. package/src/codex/native-profile-types.ts +120 -0
  248. package/src/codex/native-residue.ts +675 -0
  249. package/src/codex/paths.ts +80 -1
  250. package/src/codex/plan-from-token.ts +140 -0
  251. package/src/codex/plan.ts +40 -0
  252. package/src/codex/plugins-doctor.ts +1 -1
  253. package/src/codex/pool-rotation.ts +74 -4
  254. package/src/codex/project-config-warnings.ts +20 -6
  255. package/src/codex/prompt-journal.ts +352 -0
  256. package/src/codex/prompt-layers.ts +1313 -0
  257. package/src/codex/prompt-lock.ts +143 -0
  258. package/src/codex/prompt-text-probe.ts +238 -0
  259. package/src/codex/quota-rejection.ts +298 -0
  260. package/src/codex/quota.ts +264 -21
  261. package/src/codex/refresh.ts +11 -2
  262. package/src/codex/reset-credit-recovery.ts +1044 -0
  263. package/src/codex/routing.ts +514 -94
  264. package/src/codex/runtime.ts +159 -38
  265. package/src/codex/shim.ts +1065 -31
  266. package/src/codex/subagent-model-fallback.ts +437 -43
  267. package/src/codex/sync.ts +191 -2
  268. package/src/codex/transition-state.ts +720 -0
  269. package/src/codex/upstream-host-health.ts +368 -0
  270. package/src/codex/user-identity.ts +557 -0
  271. package/src/codex/warmup.ts +187 -81
  272. package/src/codex/write-coordination.ts +114 -0
  273. package/src/combos/failover.ts +47 -0
  274. package/src/combos/index.ts +4 -0
  275. package/src/combos/request.ts +32 -0
  276. package/src/combos/types.ts +91 -9
  277. package/src/compatibility/index.ts +26 -0
  278. package/src/compatibility/manifest.ts +253 -0
  279. package/src/compatibility/openai-responses.ts +121 -0
  280. package/src/config/atomic-write.ts +219 -0
  281. package/src/config/paths.ts +40 -0
  282. package/src/config/process-state.ts +309 -0
  283. package/src/config/provider-name.ts +24 -0
  284. package/src/config/provider-validation.ts +177 -0
  285. package/src/config/rebase-provenance.ts +68 -0
  286. package/src/config.ts +1713 -709
  287. package/src/generated/compatibility-version.json +3324 -0
  288. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  289. package/src/grok/inject.ts +16 -5
  290. package/src/grok/inspect.ts +45 -0
  291. package/src/grok/sync.ts +2 -2
  292. package/src/images/loop.ts +157 -33
  293. package/src/images/plan.ts +24 -13
  294. package/src/integrations/config-io.ts +269 -0
  295. package/src/integrations/journal.ts +315 -0
  296. package/src/integrations/merge.ts +135 -0
  297. package/src/integrations/mutation-flight.ts +71 -0
  298. package/src/integrations/native/ownership-preflight.ts +202 -0
  299. package/src/integrations/omp-yaml-source.ts +358 -0
  300. package/src/integrations/owned-refresh.ts +74 -0
  301. package/src/integrations/ownership-policy.ts +160 -0
  302. package/src/integrations/ownership.ts +155 -0
  303. package/src/integrations/registry.ts +166 -0
  304. package/src/integrations/serialize.ts +314 -0
  305. package/src/integrations/state.ts +433 -0
  306. package/src/integrations/store.ts +103 -0
  307. package/src/integrations/writer-lock.ts +98 -0
  308. package/src/integrations/writer.ts +715 -0
  309. package/src/lab/artifacts/sanitize.ts +586 -0
  310. package/src/lab/artifacts/secure-fs.ts +475 -0
  311. package/src/lab/artifacts/store.ts +310 -0
  312. package/src/lab/automation/budgets.ts +78 -0
  313. package/src/lab/automation/config-persistence.ts +256 -0
  314. package/src/lab/automation/constants.ts +39 -0
  315. package/src/lab/automation/cooldown.ts +103 -0
  316. package/src/lab/automation/dispatch.ts +211 -0
  317. package/src/lab/automation/index.ts +13 -0
  318. package/src/lab/automation/orchestrator.ts +499 -0
  319. package/src/lab/automation/persistence.ts +512 -0
  320. package/src/lab/automation/planner.ts +371 -0
  321. package/src/lab/automation/policy.ts +136 -0
  322. package/src/lab/automation/queue.ts +191 -0
  323. package/src/lab/automation/recovery.ts +24 -0
  324. package/src/lab/automation/route-context.ts +21 -0
  325. package/src/lab/automation/run-key.ts +44 -0
  326. package/src/lab/automation/runs-query.ts +34 -0
  327. package/src/lab/automation/types.ts +160 -0
  328. package/src/lab/conformance/assertion.ts +325 -0
  329. package/src/lab/conformance/digest.ts +22 -0
  330. package/src/lab/conformance/executor.ts +741 -0
  331. package/src/lab/conformance/fixture-provider.ts +27 -0
  332. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  333. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  334. package/src/lab/conformance/harness-budget.ts +47 -0
  335. package/src/lab/conformance/index.ts +5 -0
  336. package/src/lab/conformance/jcs.ts +64 -0
  337. package/src/lab/conformance/json-pointer.ts +39 -0
  338. package/src/lab/conformance/manifest.ts +180 -0
  339. package/src/lab/conformance/mcp-stub.ts +179 -0
  340. package/src/lab/conformance/negative-controls.ts +164 -0
  341. package/src/lab/conformance/observation.ts +355 -0
  342. package/src/lab/conformance/runner.ts +68 -0
  343. package/src/lab/conformance/sse-normalize.ts +59 -0
  344. package/src/lab/conformance/suite-manifest.ts +78 -0
  345. package/src/lab/conformance/types.ts +214 -0
  346. package/src/lab/constants.ts +126 -0
  347. package/src/lab/digest.ts +64 -0
  348. package/src/lab/events/errors.ts +9 -0
  349. package/src/lab/events/limits.ts +117 -0
  350. package/src/lab/events/types.ts +229 -0
  351. package/src/lab/events/validate.ts +781 -0
  352. package/src/lab/fabric/constants.ts +40 -0
  353. package/src/lab/fabric/executor.ts +492 -0
  354. package/src/lab/fabric/index.ts +80 -0
  355. package/src/lab/fabric/manifest.ts +222 -0
  356. package/src/lab/fabric/observe.ts +489 -0
  357. package/src/lab/fabric/patch.ts +79 -0
  358. package/src/lab/fabric/producer-child.ts +139 -0
  359. package/src/lab/fabric/producer-isolate.ts +276 -0
  360. package/src/lab/fabric/producer-protocol.ts +61 -0
  361. package/src/lab/fabric/scratch.ts +439 -0
  362. package/src/lab/fabric/subject.ts +106 -0
  363. package/src/lab/fabric/types.ts +134 -0
  364. package/src/lab/fabric/verifier.ts +98 -0
  365. package/src/lab/index.ts +54 -0
  366. package/src/lab/ledger/artifact-refs.ts +127 -0
  367. package/src/lab/ledger/invalidation.ts +136 -0
  368. package/src/lab/ledger/purge.ts +310 -0
  369. package/src/lab/ledger/store.ts +532 -0
  370. package/src/lab/live/credential-lease.ts +53 -0
  371. package/src/lab/live/destination.ts +155 -0
  372. package/src/lab/live/executor.ts +336 -0
  373. package/src/lab/live/inert-tools.ts +56 -0
  374. package/src/lab/live/manifest.ts +85 -0
  375. package/src/lab/live/mcp-loopback.ts +57 -0
  376. package/src/lab/live/runner.ts +19 -0
  377. package/src/lab/live/sandbox.ts +61 -0
  378. package/src/lab/live/suite-manifest.ts +41 -0
  379. package/src/lab/live/transport.ts +118 -0
  380. package/src/lab/live/types.ts +197 -0
  381. package/src/lab/observe/from-conformance.ts +301 -0
  382. package/src/lab/observe/from-live.ts +117 -0
  383. package/src/lab/paths.ts +153 -0
  384. package/src/lab/projection/rebuild.ts +495 -0
  385. package/src/lab/projection/schema.ts +135 -0
  386. package/src/lab/projection/verdicts.ts +474 -0
  387. package/src/lab/projection/verification.ts +412 -0
  388. package/src/lab/public/bundle.ts +217 -0
  389. package/src/lab/public/community-authority.ts +175 -0
  390. package/src/lab/public/community-files.ts +29 -0
  391. package/src/lab/public/community.ts +479 -0
  392. package/src/lab/public/file-safety.ts +155 -0
  393. package/src/lab/public/ids.ts +26 -0
  394. package/src/lab/public/index.ts +16 -0
  395. package/src/lab/public/mutation-lock.ts +424 -0
  396. package/src/lab/public/operator.ts +353 -0
  397. package/src/lab/public/origin-purge.ts +79 -0
  398. package/src/lab/public/origin.ts +203 -0
  399. package/src/lab/public/privacy.ts +143 -0
  400. package/src/lab/public/private-file.ts +261 -0
  401. package/src/lab/public/project.ts +124 -0
  402. package/src/lab/public/purge-test-fault.ts +21 -0
  403. package/src/lab/public/purge.ts +223 -0
  404. package/src/lab/public/registry.ts +44 -0
  405. package/src/lab/public/revocation.ts +252 -0
  406. package/src/lab/public/signature.ts +243 -0
  407. package/src/lab/public/storage.ts +105 -0
  408. package/src/lab/public/strict-json.ts +206 -0
  409. package/src/lab/public/time.ts +26 -0
  410. package/src/lab/public/types.ts +172 -0
  411. package/src/lab/public/validate.ts +391 -0
  412. package/src/lab/query/catalog.ts +101 -0
  413. package/src/lab/query/connection.ts +107 -0
  414. package/src/lab/query/constants.ts +4 -0
  415. package/src/lab/query/cursor.ts +132 -0
  416. package/src/lab/query/dto-map.ts +277 -0
  417. package/src/lab/query/errors.ts +22 -0
  418. package/src/lab/query/freshness.ts +53 -0
  419. package/src/lab/query/index.ts +45 -0
  420. package/src/lab/query/latest-observation.ts +59 -0
  421. package/src/lab/query/passive-production.ts +159 -0
  422. package/src/lab/query/queries.ts +444 -0
  423. package/src/lab/query/types.ts +266 -0
  424. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  425. package/src/lab/subject/installation-salt.ts +112 -0
  426. package/src/lab/subject/protocol-subject.ts +80 -0
  427. package/src/lab/subject/route-subject.ts +74 -0
  428. package/src/lib/admin-secrets.ts +24 -0
  429. package/src/lib/app-owned-memory-stores.ts +22 -0
  430. package/src/lib/bounded-body.ts +167 -11
  431. package/src/lib/bun-runtime.ts +125 -12
  432. package/src/lib/bun-stream-caps.ts +13 -9
  433. package/src/lib/codex-restart-contract.ts +120 -0
  434. package/src/lib/config-ownership.ts +26 -2
  435. package/src/lib/destination-policy.ts +65 -1
  436. package/src/lib/errors.ts +80 -5
  437. package/src/lib/fabric-task-execution-authority.ts +7 -0
  438. package/src/lib/fabric-task-host.ts +29 -0
  439. package/src/lib/lab-activation.ts +223 -0
  440. package/src/lib/lab-live-execution-authority.ts +13 -0
  441. package/src/lib/lab-live-host.ts +30 -0
  442. package/src/lib/lab-live-pinned-sender.ts +56 -0
  443. package/src/lib/lab-live-route-production.ts +130 -0
  444. package/src/lib/lab-passive-linker-registration.ts +26 -0
  445. package/src/lib/local-management-attestation.ts +51 -0
  446. package/src/lib/local-management-capability.ts +100 -0
  447. package/src/lib/local-provider-reload-contract.ts +100 -0
  448. package/src/lib/optional-shutdown-hooks.ts +57 -0
  449. package/src/lib/package-tree-integrity.ts +101 -0
  450. package/src/lib/pinned-http.ts +145 -26
  451. package/src/lib/process-control.ts +6 -2
  452. package/src/lib/provider-outbound.ts +49 -9
  453. package/src/lib/redact.ts +419 -3
  454. package/src/lib/self-launch-argv.ts +15 -0
  455. package/src/lib/server-resource-ownership.ts +71 -0
  456. package/src/lib/service-secrets.ts +15 -0
  457. package/src/lib/shadow-call.ts +35 -4
  458. package/src/lib/sse-decoder.ts +41 -0
  459. package/src/lib/state-store-registrations.ts +10 -2
  460. package/src/lib/system-restart-contract.ts +73 -0
  461. package/src/lib/token-estimate.ts +19 -2
  462. package/src/lib/tool-argument-integers.ts +253 -0
  463. package/src/lib/translator-budget.ts +44 -0
  464. package/src/lib/upstream-http-version.ts +57 -0
  465. package/src/lib/upstream-reachability.ts +95 -0
  466. package/src/lib/upstream-retry.ts +156 -3
  467. package/src/lib/windows-atomic-replace.ts +155 -0
  468. package/src/lib/windows-elevation.ts +70 -2
  469. package/src/lib/windows-secret-acl.ts +409 -69
  470. package/src/lib/windows-service-wrappers.ts +72 -0
  471. package/src/lib/windows-text.ts +106 -0
  472. package/src/lib/windows-user-principal.ts +341 -0
  473. package/src/lib/winsw.ts +33 -5
  474. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  475. package/src/oauth/account-import/index.ts +15 -0
  476. package/src/oauth/account-import/parser.ts +83 -0
  477. package/src/oauth/account-import/registry.ts +18 -0
  478. package/src/oauth/account-import/service.ts +75 -0
  479. package/src/oauth/account-import/types.ts +91 -0
  480. package/src/oauth/anthropic.ts +12 -1
  481. package/src/oauth/callback-server.ts +30 -4
  482. package/src/oauth/chatgpt.ts +12 -1
  483. package/src/oauth/cline.ts +203 -0
  484. package/src/oauth/command-code.ts +239 -0
  485. package/src/oauth/cursor.ts +46 -5
  486. package/src/oauth/generic-account-failover.ts +231 -0
  487. package/src/oauth/google-antigravity.ts +35 -3
  488. package/src/oauth/health.ts +20 -12
  489. package/src/oauth/index.ts +462 -71
  490. package/src/oauth/key-providers.ts +25 -0
  491. package/src/oauth/kimi.ts +25 -3
  492. package/src/oauth/kiro.ts +95 -6
  493. package/src/oauth/local-token-detect.ts +13 -2
  494. package/src/oauth/log.ts +3 -1
  495. package/src/oauth/login-cli.ts +88 -28
  496. package/src/oauth/nous.ts +798 -0
  497. package/src/oauth/open-browser-choice.ts +26 -0
  498. package/src/oauth/store.ts +133 -26
  499. package/src/oauth/token-guardian.ts +9 -3
  500. package/src/oauth/types.ts +15 -0
  501. package/src/pi/models.ts +2 -2
  502. package/src/providers/alibaba-region-migration.ts +1 -1
  503. package/src/providers/antigravity-models.ts +521 -31
  504. package/src/providers/auto-compact-budget.ts +65 -0
  505. package/src/providers/base-url-choices.ts +10 -0
  506. package/src/providers/codex-capacity.ts +292 -0
  507. package/src/providers/command-code-efforts.ts +176 -0
  508. package/src/providers/context-cap.ts +26 -8
  509. package/src/providers/cursor-pool.ts +72 -0
  510. package/src/providers/default-aliases.ts +65 -0
  511. package/src/providers/derive.ts +281 -6
  512. package/src/providers/fastwire.ts +507 -0
  513. package/src/providers/free-directory.ts +10 -7
  514. package/src/providers/google-vertex-location.ts +14 -0
  515. package/src/providers/key-failover.ts +71 -3
  516. package/src/providers/label.ts +35 -2
  517. package/src/providers/model-discovery-limits.ts +16 -0
  518. package/src/providers/model-discovery.ts +115 -22
  519. package/src/providers/model-presets.ts +119 -0
  520. package/src/providers/model-rename-migration.ts +255 -0
  521. package/src/providers/model-rename-startup.ts +28 -0
  522. package/src/providers/new-model-policy.ts +146 -0
  523. package/src/providers/openai-sidecar.ts +72 -4
  524. package/src/providers/openai-tier-startup.ts +31 -2
  525. package/src/providers/openai-tiers.ts +119 -4
  526. package/src/providers/openai-virtual-models.ts +1 -0
  527. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  528. package/src/providers/provider-id-rewrite.ts +30 -0
  529. package/src/providers/quota.ts +1379 -40
  530. package/src/providers/registry.ts +1545 -112
  531. package/src/providers/request-pacing.ts +310 -0
  532. package/src/providers/service-tier.ts +277 -0
  533. package/src/providers/slug-codec.ts +94 -6
  534. package/src/providers/static-model-discovery.ts +86 -0
  535. package/src/providers/xai-responses-opt-in.ts +15 -0
  536. package/src/providers/xai-transport.ts +32 -4
  537. package/src/reasoning-effort.ts +68 -3
  538. package/src/responses/apply-patch-envelope.ts +63 -0
  539. package/src/responses/code-mode-helper-compat.ts +50 -0
  540. package/src/responses/compaction.ts +26 -1
  541. package/src/responses/custom-tool-compat.ts +384 -0
  542. package/src/responses/hosted-tool-policy.ts +9 -0
  543. package/src/responses/namespace-tool-compat.ts +435 -0
  544. package/src/responses/parser.ts +225 -38
  545. package/src/responses/provider-continuation.ts +98 -0
  546. package/src/responses/provider-opaque-metadata.ts +73 -0
  547. package/src/responses/reasoning-envelope.ts +9 -1
  548. package/src/responses/reasoning-replay-cache.ts +504 -0
  549. package/src/responses/schema.ts +12 -2
  550. package/src/responses/spill-store.ts +75 -10
  551. package/src/responses/state.ts +659 -31
  552. package/src/responses/thought-signature-replay.ts +364 -0
  553. package/src/responses/tool-search-compat.ts +301 -0
  554. package/src/responses/truncated-stop-reason.ts +60 -0
  555. package/src/responses/turn-termination.ts +107 -0
  556. package/src/router.ts +405 -31
  557. package/src/routing/analytics.ts +379 -0
  558. package/src/routing/capability.ts +244 -0
  559. package/src/routing/compatibility/assemble.ts +73 -0
  560. package/src/routing/compatibility/behavior.ts +278 -0
  561. package/src/routing/compatibility/catalog.ts +99 -0
  562. package/src/routing/compatibility/endpoint.ts +52 -0
  563. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  564. package/src/routing/compatibility/policy.ts +181 -0
  565. package/src/routing/compatibility/provider-slot.ts +56 -0
  566. package/src/routing/compatibility/reader.ts +110 -0
  567. package/src/routing/compatibility/subject.ts +191 -0
  568. package/src/routing/compatibility/types.ts +64 -0
  569. package/src/routing/compatibility/version.ts +104 -0
  570. package/src/routing/cost.ts +77 -0
  571. package/src/routing/evaluator.ts +495 -0
  572. package/src/routing/health.ts +412 -0
  573. package/src/routing/history/cursor.ts +43 -0
  574. package/src/routing/history/indexer.ts +605 -0
  575. package/src/routing/history/schema.ts +72 -0
  576. package/src/routing/profile-namespace.ts +15 -0
  577. package/src/routing/profile.ts +547 -0
  578. package/src/routing/quota.ts +155 -0
  579. package/src/routing/request-evidence.ts +45 -0
  580. package/src/routing/trace.ts +776 -0
  581. package/src/server/adapter-resolve.ts +2 -29
  582. package/src/server/auth-cors.ts +289 -48
  583. package/src/server/background-lifecycle.ts +182 -0
  584. package/src/server/chat-completions.ts +154 -72
  585. package/src/server/chat-native-sse.ts +331 -0
  586. package/src/server/chat-native.ts +445 -0
  587. package/src/server/claude-messages.ts +160 -44
  588. package/src/server/direct-local-http.ts +347 -0
  589. package/src/server/effort-policy.ts +18 -0
  590. package/src/server/github-copilot-responses-repair.ts +338 -0
  591. package/src/server/gui-static.ts +39 -10
  592. package/src/server/images.ts +94 -12
  593. package/src/server/index.ts +1021 -190
  594. package/src/server/lifecycle.ts +336 -14
  595. package/src/server/live.ts +136 -17
  596. package/src/server/local-management-read-client.ts +90 -0
  597. package/src/server/local-provider-reload-client.ts +137 -0
  598. package/src/server/management/agent-settings-routes.ts +433 -116
  599. package/src/server/management/api-key-usage.ts +31 -5
  600. package/src/server/management/body.ts +6 -0
  601. package/src/server/management/codex-prompt-routes.ts +570 -0
  602. package/src/server/management/combo-routes.ts +64 -25
  603. package/src/server/management/config-routes.ts +511 -51
  604. package/src/server/management/context.ts +89 -2
  605. package/src/server/management/integration-routes.ts +498 -0
  606. package/src/server/management/lab-automation-routes.ts +206 -0
  607. package/src/server/management/lab-routes.ts +563 -0
  608. package/src/server/management/logs-usage-routes.ts +127 -52
  609. package/src/server/management/model-routes.ts +461 -133
  610. package/src/server/management/model-rows.ts +167 -0
  611. package/src/server/management/native-integration-routes.ts +770 -0
  612. package/src/server/management/oauth-account-routes.ts +90 -8
  613. package/src/server/management/provider-capability-config.ts +48 -0
  614. package/src/server/management/provider-routes.ts +836 -159
  615. package/src/server/management/request-history-routes.ts +191 -0
  616. package/src/server/management/route-registry.ts +332 -0
  617. package/src/server/management/routing-analytics-routes.ts +74 -0
  618. package/src/server/management/routing-profile-routes.ts +380 -0
  619. package/src/server/management/shared.ts +27 -11
  620. package/src/server/management/sidebar-routes.ts +47 -31
  621. package/src/server/management/storage-log-guard-routes.ts +186 -0
  622. package/src/server/management/sync-response.ts +69 -0
  623. package/src/server/management/system-restart.ts +289 -33
  624. package/src/server/management/system-routes.ts +77 -0
  625. package/src/server/management/usage-summary-cache.ts +9 -1
  626. package/src/server/management/vision-sidecar-options.ts +167 -0
  627. package/src/server/management/web-search-sidecar-options.ts +120 -0
  628. package/src/server/management-api.ts +126 -23
  629. package/src/server/management-auth.ts +220 -5
  630. package/src/server/passive-route-linker.ts +66 -0
  631. package/src/server/port-reclaim.ts +1 -1
  632. package/src/server/ports.ts +41 -1
  633. package/src/server/proxy-liveness.ts +161 -10
  634. package/src/server/readiness.ts +99 -0
  635. package/src/server/relay-eager.ts +221 -54
  636. package/src/server/relay.ts +479 -87
  637. package/src/server/request-decompress.ts +113 -6
  638. package/src/server/request-log-conversation.ts +51 -0
  639. package/src/server/request-log.ts +298 -31
  640. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  641. package/src/server/responses/agent-task-recovery.ts +465 -0
  642. package/src/server/responses/codex-auth-error.ts +55 -0
  643. package/src/server/responses/collaboration.ts +239 -36
  644. package/src/server/responses/combo-stream-preflight.ts +178 -0
  645. package/src/server/responses/compact.ts +444 -72
  646. package/src/server/responses/core.ts +3579 -436
  647. package/src/server/responses/empty-completion-guard.ts +333 -0
  648. package/src/server/responses/encrypted-payload.ts +62 -39
  649. package/src/server/responses/fetch-helpers.ts +89 -99
  650. package/src/server/responses/input-admission.ts +187 -0
  651. package/src/server/responses/pacing-overload.ts +13 -0
  652. package/src/server/responses/passthrough-error.ts +33 -9
  653. package/src/server/responses/policy-fallback.ts +178 -0
  654. package/src/server/responses/responses-field-backfill.ts +343 -0
  655. package/src/server/responses/terminal-guard.ts +26 -5
  656. package/src/server/responses/upstream-error.ts +5 -0
  657. package/src/server/responses/ws-upstream.ts +381 -0
  658. package/src/server/responses-custom-tool-repair.ts +363 -0
  659. package/src/server/responses-item-id-repair.ts +54 -6
  660. package/src/server/responses-json-events.ts +90 -0
  661. package/src/server/responses-model-rewrite.ts +29 -0
  662. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  663. package/src/server/responses-snapshot-repair.ts +621 -0
  664. package/src/server/responses-terminal-repair.ts +363 -0
  665. package/src/server/responses-tool-search-repair.ts +267 -0
  666. package/src/server/responses-undeclared-tool-guard.ts +376 -0
  667. package/src/server/responses.ts +18 -2
  668. package/src/server/search.ts +78 -13
  669. package/src/server/sse-frame-buffer.ts +319 -0
  670. package/src/server/sse-payload-rewrite.ts +110 -22
  671. package/src/server/startup-action-control.ts +8 -1
  672. package/src/server/startup-health-cache.ts +19 -1
  673. package/src/server/system-env.ts +80 -9
  674. package/src/server/ws-bridge.ts +53 -40
  675. package/src/service-manager-probe.ts +892 -0
  676. package/src/service.ts +1153 -94
  677. package/src/sidecar/auth.ts +92 -0
  678. package/src/sidecar/candidates.ts +83 -0
  679. package/src/storage/cleanup.ts +2 -2
  680. package/src/storage/policy-job.ts +14 -4
  681. package/src/storage/policy.ts +88 -23
  682. package/src/storage/scanner.ts +1 -1
  683. package/src/storage/worker-lifecycle.ts +14 -14
  684. package/src/tray/windows-tray.ps1 +83 -9
  685. package/src/tray/windows.ts +43 -16
  686. package/src/types/accounts.ts +37 -0
  687. package/src/types/config.ts +911 -0
  688. package/src/types/provider.ts +614 -0
  689. package/src/types/request.ts +397 -0
  690. package/src/types/tools.ts +236 -0
  691. package/src/types/wire.ts +80 -0
  692. package/src/types.ts +107 -1237
  693. package/src/update/index.ts +37 -23
  694. package/src/update/job.ts +445 -68
  695. package/src/update/notify.ts +12 -6
  696. package/src/update/npm-cache-preflight.d.mts +47 -0
  697. package/src/update/npm-cache-preflight.mjs +201 -0
  698. package/src/update/transactional-install.d.mts +22 -0
  699. package/src/update/transactional-install.mjs +266 -0
  700. package/src/usage/cost.ts +0 -0
  701. package/src/usage/expected-prices.ts +287 -16
  702. package/src/usage/log.ts +666 -50
  703. package/src/usage/summary.ts +388 -17
  704. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  705. package/src/usage/user-cost-overlays.ts +314 -0
  706. package/src/vision/anthropic-describe.ts +10 -6
  707. package/src/vision/backends.ts +97 -0
  708. package/src/vision/describe.ts +27 -16
  709. package/src/vision/eligibility.ts +250 -0
  710. package/src/vision/index.ts +238 -24
  711. package/src/vision/reasoning.ts +55 -0
  712. package/src/vision/routed-describe.ts +175 -0
  713. package/src/vision/timeout-bounds.ts +9 -0
  714. package/src/web-search/anthropic-executor.ts +13 -7
  715. package/src/web-search/backends.ts +108 -0
  716. package/src/web-search/exa-executor.ts +119 -0
  717. package/src/web-search/executor.ts +21 -6
  718. package/src/web-search/gemini-executor.ts +141 -0
  719. package/src/web-search/index.ts +150 -15
  720. package/src/web-search/loop.ts +284 -54
  721. package/src/web-search/parse.ts +125 -30
  722. package/src/web-search/sources.ts +60 -0
  723. package/src/web-search/xai-executor.ts +219 -0
  724. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  725. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  726. package/gui/dist/provider-icons/antigravity.svg +0 -1
  727. package/gui/dist/provider-icons/claude.svg +0 -1
  728. package/gui/dist/provider-icons/copilot.svg +0 -1
  729. package/gui/dist/provider-icons/cursor.svg +0 -2
  730. package/gui/dist/provider-icons/gemini.svg +0 -1
  731. package/gui/dist/provider-icons/grok-color.svg +0 -1
  732. package/gui/dist/provider-icons/kiro.svg +0 -14
  733. package/src/cli/internal-dispatch.ts +0 -20
@@ -0,0 +1,379 @@
1
+ /**
2
+ * Source-backed routing analytics (RI-03).
3
+ *
4
+ * All metrics derive from the rebuildable request-history index
5
+ * (`routing-history.sqlite`), never from repeated full JSONL scans. The
6
+ * analysis is read-only: no routing decision, profile, or weight changes here
7
+ * (ADR-10 - no automatic self-tuning).
8
+ *
9
+ * Bounds: at most ANALYTICS_MAX_ROWS matching rows are analyzed per call; a
10
+ * larger population sets `historyTruncated: true` so readers never mistake a
11
+ * sample for the full history.
12
+ */
13
+
14
+ import type { PersistedUsageEntry } from "../usage/log";
15
+ import { estimateRequestCost, serviceTierContext } from "../usage/cost";
16
+ import { openRequestHistoryIndex, requestHistoryDb } from "./history/indexer";
17
+
18
+ export const ANALYTICS_MAX_ROWS = 50_000;
19
+ /** Default row cap for the management API (full cap remains available via `limit`). */
20
+ export const ANALYTICS_API_DEFAULT_ROWS = 5_000;
21
+
22
+ export interface RoutingAnalyticsFilters {
23
+ provider?: string;
24
+ model?: string;
25
+ profileId?: string;
26
+ surface?: string;
27
+ from?: number;
28
+ to?: number;
29
+ }
30
+
31
+ export type AnalyticsConfidence = "high" | "medium" | "low";
32
+
33
+ export interface AnalyticsBreakdownRow {
34
+ provider: string;
35
+ model: string;
36
+ accountRef?: string;
37
+ profileId?: string;
38
+ requests: number;
39
+ successes: number;
40
+ failures: number;
41
+ cancelled: number;
42
+ successRate: number | null;
43
+ p50DurationMs?: number;
44
+ estimatedCostUsdPerSuccessfulRequest?: number | null;
45
+ }
46
+
47
+ export interface AnalyticsProfileRow {
48
+ profileId: string;
49
+ profileRevision?: string;
50
+ requests: number;
51
+ successes: number;
52
+ failures: number;
53
+ fallbacks: number;
54
+ successRate: number | null;
55
+ }
56
+
57
+ export interface RoutingAnalyticsResult {
58
+ generatedAt: number;
59
+ totalRequests: number;
60
+ scannedRows: number;
61
+ historyTruncated: boolean;
62
+ confidence: AnalyticsConfidence | null;
63
+ successRate: number | null;
64
+ failureRate: number | null;
65
+ cancelledRate: number | null;
66
+ fallbackRate: number | null;
67
+ totalAttempts: number;
68
+ averageAttemptsPerRequest: number | null;
69
+ incompleteStreamRate: number | null;
70
+ cooldownTriggeringFailures: number;
71
+ durationMs: {
72
+ p50?: number;
73
+ p95?: number;
74
+ p99?: number;
75
+ sampleCount: number;
76
+ };
77
+ firstOutputMs: {
78
+ p50?: number;
79
+ p95?: number;
80
+ p99?: number;
81
+ sampleCount: number;
82
+ /** Share of scanned requests with a TTFT measurement (0..1). */
83
+ coverage: number | null;
84
+ };
85
+ estimatedCostUsdPerSuccessfulRequest: number | null;
86
+ estimatedCostUsdTotalSuccessful: number | null;
87
+ usageCoverage: number | null;
88
+ priceCoverage: number | null;
89
+ breakdown: AnalyticsBreakdownRow[];
90
+ profileBreakdown: AnalyticsProfileRow[];
91
+ }
92
+
93
+ interface ScannedRow {
94
+ provider: string;
95
+ model: string;
96
+ apiKeyId?: string | null;
97
+ profileId?: string | null;
98
+ profileRevision?: string | null;
99
+ status: number;
100
+ durationMs: number;
101
+ firstOutputMs?: number | null;
102
+ closeReason?: string | null;
103
+ terminalStatus?: string | null;
104
+ usageStatus: string;
105
+ usageJson?: string | null;
106
+ attemptCount: number;
107
+ fallback: number;
108
+ rowJson: string;
109
+ }
110
+
111
+ interface Bucket extends AnalyticsBreakdownRow {
112
+ durations: number[];
113
+ costUsdSum: number;
114
+ costRows: number;
115
+ }
116
+
117
+ const COOLDOWN_RECOVERY_KINDS = new Set([
118
+ "rate-limit-429",
119
+ "key-429",
120
+ "oauth-401",
121
+ "anthropic-oauth-429",
122
+ "oauth-account-429",
123
+ ]);
124
+
125
+ function percentile(sorted: number[], p: number): number | undefined {
126
+ if (sorted.length === 0) return undefined;
127
+ const index = Math.max(0, Math.ceil((p / 100) * sorted.length) - 1);
128
+ return sorted[Math.min(index, sorted.length - 1)];
129
+ }
130
+
131
+ function classifyRow(row: ScannedRow): "success" | "failure" | "cancelled" {
132
+ if (row.closeReason === "client_cancel" || row.status === 499) return "cancelled";
133
+ if (row.terminalStatus === "incomplete") return "failure";
134
+ if (row.terminalStatus && row.terminalStatus !== "completed") return "failure";
135
+ if (row.status >= 400) return "failure";
136
+ return "success";
137
+ }
138
+
139
+ function parseEntry(rowJson: string): PersistedUsageEntry | null {
140
+ try {
141
+ const parsed = JSON.parse(rowJson) as PersistedUsageEntry;
142
+ return parsed && typeof parsed === "object" && typeof parsed.requestId === "string" ? parsed : null;
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+
148
+ function cooldownTriggering(entry: PersistedUsageEntry | null, status: number): boolean {
149
+ if (status === 429) return true;
150
+ if (!Array.isArray(entry?.attempts)) return false;
151
+ return entry.attempts.some((attempt: unknown) => {
152
+ if (!attempt || typeof attempt !== "object") return false;
153
+ const recoveryKinds = (attempt as { recoveryKinds?: unknown }).recoveryKinds;
154
+ return Array.isArray(recoveryKinds)
155
+ && recoveryKinds.some(kind => typeof kind === "string" && COOLDOWN_RECOVERY_KINDS.has(kind));
156
+ });
157
+ }
158
+
159
+ function successCostUsd(
160
+ row: Pick<ScannedRow, "provider" | "model">,
161
+ entry: PersistedUsageEntry,
162
+ ): number | null {
163
+ if (!entry.usage) return null;
164
+ const estimate = estimateRequestCost({
165
+ provider: row.provider,
166
+ model: row.model,
167
+ usage: entry.usage,
168
+ usageStatus: entry.usageStatus,
169
+ serviceTier: serviceTierContext(entry),
170
+ });
171
+ return estimate ? estimate.cost.total : null;
172
+ }
173
+
174
+ export async function computeRoutingAnalytics(
175
+ filters: RoutingAnalyticsFilters,
176
+ options: { maxRows?: number } = {},
177
+ ): Promise<RoutingAnalyticsResult> {
178
+ await openRequestHistoryIndex();
179
+ const handle = requestHistoryDb();
180
+ const maxRows = Math.min(
181
+ Math.max(1, Math.trunc(options.maxRows ?? ANALYTICS_MAX_ROWS)),
182
+ ANALYTICS_MAX_ROWS,
183
+ );
184
+
185
+ const where: string[] = [];
186
+ const values: Array<string | number> = [];
187
+ const add = (clause: string, value: string | number) => {
188
+ where.push(clause);
189
+ values.push(value);
190
+ };
191
+ if (filters.provider !== undefined) add("provider = ?", filters.provider);
192
+ if (filters.model !== undefined) add("model = ?", filters.model);
193
+ if (filters.profileId !== undefined) add("profile_id = ?", filters.profileId);
194
+ if (filters.surface !== undefined) add("surface = ?", filters.surface);
195
+ if (filters.from !== undefined) add("timestamp >= ?", filters.from);
196
+ if (filters.to !== undefined) add("timestamp <= ?", filters.to);
197
+ const whereSql = where.length > 0 ? ` WHERE ${where.join(" AND ")}` : "";
198
+
199
+ const rows = handle.query(
200
+ `SELECT provider, model, api_key_id AS apiKeyId, profile_id AS profileId,
201
+ profile_revision AS profileRevision, status,
202
+ duration_ms AS durationMs, first_output_ms AS firstOutputMs,
203
+ close_reason AS closeReason, terminal_status AS terminalStatus,
204
+ usage_status AS usageStatus, usage_json AS usageJson,
205
+ attempt_count AS attemptCount, fallback, row_json AS rowJson
206
+ FROM requests${whereSql} ORDER BY timestamp DESC LIMIT ?`,
207
+ ).all(...values, maxRows + 1) as ScannedRow[];
208
+
209
+ const scanned = rows.slice(0, maxRows);
210
+ const historyTruncated = rows.length > maxRows;
211
+
212
+ let successes = 0;
213
+ let failures = 0;
214
+ let cancelled = 0;
215
+ let fallbacks = 0;
216
+ let totalAttempts = 0;
217
+ let incompleteStreams = 0;
218
+ let cooldownFailures = 0;
219
+ let usageReported = 0;
220
+ const durations: number[] = [];
221
+ const firstOutputs: number[] = [];
222
+ let costTotalUsd = 0;
223
+ let costCount = 0;
224
+
225
+ const byKey = new Map<string, Bucket>();
226
+ const byProfile = new Map<string, AnalyticsProfileRow>();
227
+
228
+ for (const row of scanned) {
229
+ const kind = classifyRow(row);
230
+ if (kind === "success") successes += 1;
231
+ else if (kind === "failure") failures += 1;
232
+ else cancelled += 1;
233
+ if (row.fallback === 1) fallbacks += 1;
234
+ totalAttempts += row.attemptCount;
235
+ if (row.terminalStatus === "incomplete") incompleteStreams += 1;
236
+ durations.push(row.durationMs);
237
+ if (row.firstOutputMs !== null && row.firstOutputMs !== undefined && row.firstOutputMs >= 0) {
238
+ firstOutputs.push(row.firstOutputMs);
239
+ }
240
+ if (row.usageStatus !== "unreported") usageReported += 1;
241
+
242
+ let rowCostUsd: number | null = null;
243
+ if (kind === "success") {
244
+ const entry = parseEntry(row.rowJson);
245
+ if (entry) {
246
+ rowCostUsd = successCostUsd(row, entry);
247
+ if (rowCostUsd !== null) {
248
+ costTotalUsd += rowCostUsd;
249
+ costCount += 1;
250
+ }
251
+ }
252
+ }
253
+
254
+ if (kind === "failure") {
255
+ const failureEntry = parseEntry(row.rowJson);
256
+ if (cooldownTriggering(failureEntry, row.status)) cooldownFailures += 1;
257
+ }
258
+
259
+ const key = `${row.provider}\0${row.model}\0${row.apiKeyId ?? ""}\0${row.profileId ?? ""}`;
260
+ let bucket: Bucket | undefined = byKey.get(key);
261
+ if (!bucket) {
262
+ bucket = {
263
+ provider: row.provider,
264
+ model: row.model,
265
+ ...(row.apiKeyId ? { accountRef: row.apiKeyId } : {}),
266
+ ...(row.profileId ? { profileId: row.profileId } : {}),
267
+ requests: 0,
268
+ successes: 0,
269
+ failures: 0,
270
+ cancelled: 0,
271
+ successRate: null,
272
+ durations: [],
273
+ costUsdSum: 0,
274
+ costRows: 0,
275
+ };
276
+ byKey.set(key, bucket);
277
+ }
278
+ bucket.requests += 1;
279
+ if (kind === "success") bucket.successes += 1;
280
+ else if (kind === "failure") bucket.failures += 1;
281
+ else bucket.cancelled += 1;
282
+ bucket.durations.push(row.durationMs);
283
+ if (rowCostUsd !== null) {
284
+ bucket.costUsdSum += rowCostUsd;
285
+ bucket.costRows += 1;
286
+ }
287
+
288
+ if (row.profileId) {
289
+ let profile = byProfile.get(row.profileId);
290
+ if (!profile) {
291
+ profile = {
292
+ profileId: row.profileId,
293
+ ...(row.profileRevision ? { profileRevision: row.profileRevision } : {}),
294
+ requests: 0,
295
+ successes: 0,
296
+ failures: 0,
297
+ fallbacks: 0,
298
+ successRate: null,
299
+ };
300
+ byProfile.set(row.profileId, profile);
301
+ }
302
+ profile.requests += 1;
303
+ if (kind === "success") profile.successes += 1;
304
+ else if (kind === "failure") profile.failures += 1;
305
+ if (row.fallback === 1) profile.fallbacks += 1;
306
+ }
307
+ }
308
+
309
+ durations.sort((a, b) => a - b);
310
+ firstOutputs.sort((a, b) => a - b);
311
+ const total = scanned.length;
312
+ const rate = (count: number): number | null => (total > 0 ? count / total : null);
313
+
314
+ const breakdown: AnalyticsBreakdownRow[] = [...byKey.values()].map(bucket => {
315
+ const sorted = bucket.durations.sort((a, b) => a - b);
316
+ const p50DurationMs = percentile(sorted, 50);
317
+ return {
318
+ provider: bucket.provider,
319
+ model: bucket.model,
320
+ ...(bucket.accountRef ? { accountRef: bucket.accountRef } : {}),
321
+ ...(bucket.profileId ? { profileId: bucket.profileId } : {}),
322
+ requests: bucket.requests,
323
+ successes: bucket.successes,
324
+ failures: bucket.failures,
325
+ cancelled: bucket.cancelled,
326
+ successRate: bucket.requests > 0 ? bucket.successes / bucket.requests : null,
327
+ ...(p50DurationMs !== undefined ? { p50DurationMs } : {}),
328
+ ...(bucket.requests > 0
329
+ ? { estimatedCostUsdPerSuccessfulRequest: bucket.costRows > 0
330
+ ? bucket.costUsdSum / bucket.costRows
331
+ : null }
332
+ : {}),
333
+ };
334
+ }).sort((a, b) => b.requests - a.requests);
335
+
336
+ const profileBreakdown: AnalyticsProfileRow[] = [...byProfile.values()].map(profile => ({
337
+ ...profile,
338
+ successRate: profile.requests > 0 ? profile.successes / profile.requests : null,
339
+ })).sort((a, b) => b.requests - a.requests);
340
+
341
+ const confidence: AnalyticsConfidence | null = total === 0
342
+ ? null
343
+ : total >= 100 ? "high" : total >= 20 ? "medium" : "low";
344
+
345
+ return {
346
+ generatedAt: Date.now(),
347
+ totalRequests: total,
348
+ scannedRows: scanned.length,
349
+ historyTruncated,
350
+ confidence,
351
+ successRate: rate(successes),
352
+ failureRate: rate(failures),
353
+ cancelledRate: rate(cancelled),
354
+ fallbackRate: rate(fallbacks),
355
+ totalAttempts,
356
+ averageAttemptsPerRequest: total > 0 ? totalAttempts / total : null,
357
+ incompleteStreamRate: rate(incompleteStreams),
358
+ cooldownTriggeringFailures: cooldownFailures,
359
+ durationMs: {
360
+ ...(percentile(durations, 50) !== undefined ? { p50: percentile(durations, 50) } : {}),
361
+ ...(percentile(durations, 95) !== undefined ? { p95: percentile(durations, 95) } : {}),
362
+ ...(percentile(durations, 99) !== undefined ? { p99: percentile(durations, 99) } : {}),
363
+ sampleCount: durations.length,
364
+ },
365
+ firstOutputMs: {
366
+ ...(percentile(firstOutputs, 50) !== undefined ? { p50: percentile(firstOutputs, 50) } : {}),
367
+ ...(percentile(firstOutputs, 95) !== undefined ? { p95: percentile(firstOutputs, 95) } : {}),
368
+ ...(percentile(firstOutputs, 99) !== undefined ? { p99: percentile(firstOutputs, 99) } : {}),
369
+ sampleCount: firstOutputs.length,
370
+ coverage: total > 0 ? firstOutputs.length / total : null,
371
+ },
372
+ estimatedCostUsdPerSuccessfulRequest: costCount > 0 ? costTotalUsd / costCount : null,
373
+ estimatedCostUsdTotalSuccessful: costCount > 0 ? costTotalUsd : null,
374
+ usageCoverage: total > 0 ? usageReported / total : null,
375
+ priceCoverage: successes > 0 ? costCount / successes : null,
376
+ breakdown,
377
+ profileBreakdown,
378
+ };
379
+ }
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Candidate capability evidence for policy routing (RI-05).
3
+ *
4
+ * Evidence comes from canonical local sources only - provider config maps,
5
+ * the provider registry, the cached Codex catalog file, and the native-model
6
+ * metadata helpers. No live network fetch happens at routing time.
7
+ *
8
+ * "Unknown is not zero": any dimension without canonical evidence stays
9
+ * `undefined` (unknown) and the profile's `unknownEvidence` policy decides
10
+ * how that affects eligibility.
11
+ */
12
+
13
+ import { modelInList, type OcxConfig } from "../types";
14
+ import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
15
+ import { serviceTierSupportForModel } from "../providers/service-tier";
16
+ import { PROVIDER_REGISTRY } from "../providers/registry";
17
+ import {
18
+ nativeInputModalities,
19
+ nativeContextLimits, nativeOpenAiContextWindow,
20
+ nativeParallelToolCalls,
21
+ nativeReasoningEfforts,
22
+ } from "../codex/catalog/metadata";
23
+ import { readCatalog, readCodexCatalogPath } from "../codex/catalog/parsing";
24
+ import { modelRecordValue } from "../reasoning-effort";
25
+ import { statSync } from "node:fs";
26
+ import type { RouteCapabilityEvidence } from "./trace";
27
+
28
+ type CatalogModelRow = {
29
+ /** Exact provider/native-id identity, from the provenance block. */
30
+ provider: string;
31
+ id: string;
32
+ /** Only values a real source asserted; never a strict-parser default. */
33
+ contextWindow?: number;
34
+ inputModalities?: string[];
35
+ capabilities?: string[];
36
+ };
37
+
38
+ /**
39
+ * Catalog rows memoized by path + mtime: the cached Codex catalog is stable
40
+ * between refreshes, and re-reading/parsing the whole file per candidate on
41
+ * the request path would multiply a synchronous disk + JSON cost by the
42
+ * profile candidate count for every policy-routed request.
43
+ */
44
+ let catalogCache: { path: string; mtimeMs: number; rows: CatalogModelRow[] } | null = null;
45
+
46
+ function cachedCatalogModels(): CatalogModelRow[] {
47
+ try {
48
+ const path = readCodexCatalogPath();
49
+ const mtimeMs = statSync(path).mtimeMs;
50
+ if (catalogCache && catalogCache.path === path && catalogCache.mtimeMs === mtimeMs) {
51
+ return catalogCache.rows;
52
+ }
53
+ const catalog = readCatalog(path);
54
+ const models = catalog?.models;
55
+ if (!Array.isArray(models)) return [];
56
+ // Read ONLY `opencodex_capability_provenance` (written by
57
+ // applyCatalogModelMetadata). The row's own `context_window` and
58
+ // `input_modalities` always exist because ensureStrictCatalogFields fills them
59
+ // with compatibility defaults for Codex's strict parser, so reading them would
60
+ // turn "nobody asserted anything" into a confident `image: false` and a
61
+ // fabricated 128000 — the opposite of this module's contract. A row without
62
+ // provenance contributes nothing.
63
+ const rows = models.flatMap((model): CatalogModelRow[] => {
64
+ if (typeof model !== "object" || model === null) return [];
65
+ const provenance = (model as Record<string, unknown>).opencodex_capability_provenance;
66
+ if (typeof provenance !== "object" || provenance === null) return [];
67
+ const source = provenance as Record<string, unknown>;
68
+ if (typeof source.provider !== "string" || typeof source.model_id !== "string") return [];
69
+ return [{
70
+ provider: source.provider,
71
+ id: source.model_id,
72
+ ...(typeof source.context_window === "number" && source.context_window > 0
73
+ ? { contextWindow: source.context_window }
74
+ : {}),
75
+ ...(Array.isArray(source.input_modalities)
76
+ ? { inputModalities: source.input_modalities.filter((value): value is string => typeof value === "string") }
77
+ : {}),
78
+ ...(Array.isArray(source.capabilities)
79
+ ? { capabilities: source.capabilities.filter((value): value is string => typeof value === "string") }
80
+ : {}),
81
+ }];
82
+ });
83
+ catalogCache = { path, mtimeMs, rows };
84
+ return rows;
85
+ } catch {
86
+ return [];
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Classify a hostname for locality evidence. `URL.hostname` keeps IPv6
92
+ * literals bracketed (`[::1]`), so strip the brackets before matching.
93
+ * Anything not positively local or private stays unknown: "unknown is not
94
+ * zero", so an unrecognized host must never assert `remoteAllowed`.
95
+ */
96
+ function classifyHostname(hostname: string): "local" | "private" | null {
97
+ const host = hostname.trim().toLowerCase().replace(/\.$/, "").replace(/^\[|\]$/g, "");
98
+ if (host === "localhost" || host.endsWith(".localhost") || host === "0.0.0.0") return "local";
99
+ if (host === "::1" || /^127\./.test(host)) return "local";
100
+ if (/^10\./.test(host)
101
+ || /^192\.168\./.test(host)
102
+ || /^169\.254\./.test(host)
103
+ || /^172\.(1[6-9]|2\d|3[01])\./.test(host)
104
+ || /^f[cd][0-9a-f]{2}:/.test(host)
105
+ || /^fe80:/.test(host)
106
+ || /^::ffff:(?:10\.|127\.|192\.168\.|169\.254\.|172\.(?:1[6-9]|2\d|3[01])\.)/.test(host)) {
107
+ return "private";
108
+ }
109
+ return null;
110
+ }
111
+
112
+ /**
113
+ * Adapters whose upstream protocol supports function/tool calling. Mirrors
114
+ * the adapter ids the resolver accepts (including the `azure` alias for
115
+ * `azure-openai`); `kiro` and `mimo-free` send/delegate tool calls.
116
+ */
117
+ const TOOL_CAPABLE_ADAPTERS = new Set([
118
+ "openai-chat",
119
+ "openai-responses",
120
+ "anthropic",
121
+ "cursor",
122
+ "google",
123
+ "azure-openai",
124
+ "azure",
125
+ "kiro",
126
+ "mimo-free",
127
+ "command-code",
128
+ ]);
129
+
130
+ function localRemoteEvidence(baseUrl: string | undefined): Pick<RouteCapabilityEvidence, "localOnly" | "remoteAllowed"> {
131
+ if (typeof baseUrl !== "string" || baseUrl.length === 0) return {};
132
+ try {
133
+ const hostname = new URL(baseUrl).hostname;
134
+ if (!hostname) return {};
135
+ const kind = classifyHostname(hostname);
136
+ if (kind === null) return {};
137
+ // Both booleans are emitted once classified: definitive negative evidence,
138
+ // so a local host cannot satisfy `require.remoteAllowed` (or vice versa)
139
+ // under `unknownEvidence.capability: "allow"`/`"penalize"`.
140
+ return kind === "local" || kind === "private"
141
+ ? { localOnly: true, remoteAllowed: false }
142
+ : { remoteAllowed: true, localOnly: false };
143
+ } catch {
144
+ return {};
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Assemble canonical capability evidence for one `provider/model` candidate.
150
+ * Sources (in priority order): provider config maps, provider registry hints,
151
+ * cached Codex catalog row, native-model metadata.
152
+ */
153
+ export function candidateCapabilityEvidence(
154
+ config: OcxConfig,
155
+ providerName: string,
156
+ modelId: string,
157
+ ): RouteCapabilityEvidence {
158
+ const provider = config.providers[providerName];
159
+ const registryEntry = PROVIDER_REGISTRY.find(entry => entry.id === providerName);
160
+ const catalogRow = cachedCatalogModels().find(model => model.provider === providerName && model.id === modelId);
161
+ const isNative = providerName === OPENAI_CODEX_PROVIDER_ID && !modelId.includes("/");
162
+
163
+ // `modelRecordValue`, not a bare lookup: every runtime reader of these three maps
164
+ // resolves them that way, so a `gpt-oss` entry covers `gpt-oss:120b`. Reading raw
165
+ // made the evidence disagree with the resolver it claims to describe — and for the
166
+ // window it did not even degrade to unknown, it fell through to the provider-wide
167
+ // value, which is a definite wrong answer rather than an absent one.
168
+ const rawContextWindow = modelRecordValue(provider?.modelContextWindows, modelId)
169
+ ?? provider?.contextWindow
170
+ ?? modelRecordValue(registryEntry?.modelContextWindows, modelId)
171
+ ?? catalogRow?.contextWindow
172
+ ?? (isNative ? nativeOpenAiContextWindow(modelId, nativeContextLimits(config)) : undefined);
173
+ // Native rows go through the accessor (raise-to-ceiling + opt-in). Routed rows keep
174
+ // the raw value; a provider cap on openai must not invent a window they do not have.
175
+ const contextWindow = isNative
176
+ ? (nativeOpenAiContextWindow(modelId, nativeContextLimits(config)) ?? rawContextWindow)
177
+ : rawContextWindow;
178
+
179
+ // `noVisionModels` is checked before the modality chain because that is the order
180
+ // `isModelTextOnly` uses: it matches the no-vision list and returns true before it
181
+ // ever reads `modelInputModalities` (`src/vision/index.ts:32`). So a `gpt-oss`
182
+ // no-vision entry beats an exact `gpt-oss:120b` entry that lists "image", and
183
+ // deriving `image` from the modality chain alone reported vision on a model the
184
+ // runtime refuses it for. That matters more here than on the CLI surface fixed in
185
+ // #2086: routing *acts* on this evidence, so it would select the candidate for image
186
+ // work that execution then rejects.
187
+ const noVision = modelInList(provider?.noVisionModels, modelId);
188
+ const modalities = noVision
189
+ ? ["text"]
190
+ : (modelRecordValue(provider?.modelInputModalities, modelId)
191
+ ?? modelRecordValue(registryEntry?.modelInputModalities, modelId)
192
+ ?? catalogRow?.inputModalities
193
+ ?? (isNative ? nativeInputModalities(modelId) : undefined));
194
+ const image = Array.isArray(modalities)
195
+ ? modalities.includes("image")
196
+ : undefined;
197
+
198
+ const capabilities = catalogRow?.capabilities ?? [];
199
+ // The catalog `capabilities` list is a positive per-model signal; a row
200
+ // without "tools" is treated as unknown, never as a negative. Without a
201
+ // catalog row the adapter protocol itself is the signal: tool-capable
202
+ // adapters run single tool calls even when the parallel-call opt-in is
203
+ // unset or false. `parallelToolCalls` stays a positive provider-level
204
+ // override.
205
+ const tools = capabilities.includes("tools")
206
+ || isNative
207
+ // The adapter protocol is positive evidence on its own. This was once gated
208
+ // on `catalogRow === undefined`, which was only safe while the catalog lookup
209
+ // never matched anything: once it matches, a row that simply does not
210
+ // enumerate "tools" would silently revoke tool support for every openai-chat
211
+ // and anthropic candidate.
212
+ || (provider !== undefined && TOOL_CAPABLE_ADAPTERS.has(provider.adapter))
213
+ || provider?.parallelToolCalls === true
214
+ || undefined;
215
+
216
+ const reasoningEfforts = modelRecordValue(provider?.modelReasoningEfforts, modelId)
217
+ ?? modelRecordValue(registryEntry?.modelReasoningEfforts, modelId)
218
+ ?? (isNative ? nativeReasoningEfforts(modelId) : undefined);
219
+
220
+ const tierSupport = provider
221
+ ? serviceTierSupportForModel(provider, modelId, providerName)
222
+ : registryEntry ? serviceTierSupportForModel(registryEntry, modelId, providerName) : undefined;
223
+ const serviceTier = tierSupport === true
224
+ ? "supported"
225
+ : tierSupport === false ? "unsupported" : "unknown";
226
+
227
+ const localRemote = localRemoteEvidence(provider?.baseUrl);
228
+ // Only emit a definitive encryptedCodexTasks value when the provider is
229
+ // present. An absent/unconfigured provider must stay unknown so
230
+ // require.encryptedCodexTasks does not fail closed on missing config.
231
+ const encryptedCodexTasks = provider === undefined
232
+ ? undefined
233
+ : isCanonicalOpenAiForwardProvider(provider);
234
+
235
+ return {
236
+ ...(typeof contextWindow === "number" ? { contextWindow } : {}),
237
+ ...(typeof image === "boolean" ? { image } : {}),
238
+ ...(typeof tools === "boolean" ? { tools } : {}),
239
+ ...(reasoningEfforts !== undefined && reasoningEfforts.length > 0 ? { reasoningEfforts } : {}),
240
+ ...(serviceTier !== "unknown" ? { serviceTier } : {}),
241
+ ...localRemote,
242
+ ...(typeof encryptedCodexTasks === "boolean" ? { encryptedCodexTasks } : {}),
243
+ };
244
+ }
@@ -0,0 +1,73 @@
1
+ import type { OcxConfig, OcxProviderConfig } from "../../types";
2
+ import { candidateCapabilityEvidence } from "../capability";
3
+ import { costEvidenceForCandidate } from "../cost";
4
+ import type { PolicyCandidateEvidence } from "../evaluator";
5
+ import { policyCandidateHealthEvidence } from "../health";
6
+ import type { NormalizedRoutingProfile } from "../profile";
7
+ import { quotaEvidenceForCandidate } from "../quota";
8
+ import { resolveCompatibilityEvidenceProvider, type CoreEvidenceOptions } from "./provider-slot";
9
+
10
+ export type RoutedProviderResolver = (
11
+ providerName: string,
12
+ provider: OcxProviderConfig,
13
+ ) => OcxProviderConfig;
14
+
15
+ /**
16
+ * Options the core assembler needs. Provider-specific test seams (subject resolution,
17
+ * catalog and projection loading) belong to the compatibility provider, not here -- keeping
18
+ * them out is what stops the core assembler from naming Lab-backed contracts.
19
+ *
20
+ * The provider reads its own seams off the same object, so callers may still pass a
21
+ * `LabCompatibilityProviderOptions`; that type extends this one.
22
+ */
23
+ export type AssemblePolicyEvidenceOptions = CoreEvidenceOptions;
24
+
25
+ /**
26
+ * Assemble production policy candidate evidence including compatibility snapshots.
27
+ * Compatibility work is completely skipped for legacy/no-requirement profiles.
28
+ * Active compatibility profiles use one catalog snapshot and one bounded SQLite
29
+ * read for all candidate subject IDs.
30
+ */
31
+ export function assemblePolicyCandidateEvidence(
32
+ config: OcxConfig,
33
+ profile: NormalizedRoutingProfile,
34
+ now: number,
35
+ options: AssemblePolicyEvidenceOptions,
36
+ ): PolicyCandidateEvidence[] {
37
+ const compatibilityPolicy = profile.compatibility;
38
+ const hasCompatibilityRequirements = Boolean(
39
+ compatibilityPolicy && compatibilityPolicy.requiredSuites.length > 0,
40
+ );
41
+ // Compatibility evidence is supplied by an opt-in subsystem. With no provider registered
42
+ // -- every install without compatibility-gated profiles -- the evaluator sees no
43
+ // compatibility evidence and scores on capability, health, quota, and cost exactly as it
44
+ // did before compatibility policy existed.
45
+ const compatibilityProvider = hasCompatibilityRequirements && compatibilityPolicy
46
+ ? resolveCompatibilityEvidenceProvider()
47
+ : null;
48
+ const compatibilityByCandidate = compatibilityProvider && compatibilityPolicy
49
+ ? compatibilityProvider(config, profile, compatibilityPolicy, options)
50
+ : null;
51
+
52
+ return profile.candidates.map(candidate => {
53
+ const key = `${candidate.provider}/${candidate.model}`;
54
+ const compatibility = compatibilityByCandidate?.get(key);
55
+
56
+ return {
57
+ provider: candidate.provider,
58
+ model: candidate.model,
59
+ capability: candidateCapabilityEvidence(config, candidate.provider, candidate.model),
60
+ health: policyCandidateHealthEvidence(config, candidate, now),
61
+ quota: quotaEvidenceForCandidate({
62
+ provider: candidate.provider,
63
+ model: candidate.model,
64
+ }),
65
+ cost: costEvidenceForCandidate({
66
+ provider: candidate.provider,
67
+ model: candidate.model,
68
+ limitUsd: profile.limits.maxEstimatedCostUsd,
69
+ }),
70
+ ...(compatibility ? { compatibility } : {}),
71
+ };
72
+ });
73
+ }