@iislee/opencodex 2.11.0 → 2.31.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 (675) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +114 -19
  3. package/bin/ocx.mjs +164 -36
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-BF38heuV.js +104 -0
  6. package/gui/dist/assets/index-DMiI18Kv.css +1 -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 +19 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +376 -52
  44. package/src/adapters/base.ts +54 -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 +601 -0
  48. package/src/adapters/cursor/checkpoint-store.ts +303 -0
  49. package/src/adapters/cursor/cursor-errors.ts +129 -6
  50. package/src/adapters/cursor/discovery.ts +61 -4
  51. package/src/adapters/cursor/effort-map.ts +27 -3
  52. package/src/adapters/cursor/framing.ts +39 -0
  53. package/src/adapters/cursor/h2-pool.ts +123 -0
  54. package/src/adapters/cursor/http1-bidi.ts +361 -0
  55. package/src/adapters/cursor/images.ts +704 -0
  56. package/src/adapters/cursor/live-models.ts +174 -58
  57. package/src/adapters/cursor/live-transport.ts +609 -170
  58. package/src/adapters/cursor/native-exec-common.ts +23 -2
  59. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  60. package/src/adapters/cursor/native-exec-network.ts +1 -1
  61. package/src/adapters/cursor/native-exec-shell.ts +0 -1
  62. package/src/adapters/cursor/native-exec.ts +101 -14
  63. package/src/adapters/cursor/protobuf-events.ts +829 -11
  64. package/src/adapters/cursor/protobuf-request.ts +383 -65
  65. package/src/adapters/cursor/request-builder.ts +237 -31
  66. package/src/adapters/cursor/tool-definitions.ts +242 -12
  67. package/src/adapters/cursor/tool-result-normalize.ts +92 -0
  68. package/src/adapters/cursor/transport.ts +22 -0
  69. package/src/adapters/cursor/types.ts +28 -1
  70. package/src/adapters/cursor.ts +133 -7
  71. package/src/adapters/google-antigravity-replay.ts +558 -34
  72. package/src/adapters/google-antigravity-wire.ts +43 -10
  73. package/src/adapters/google-http.ts +38 -10
  74. package/src/adapters/google-tool-schema.ts +80 -15
  75. package/src/adapters/google-truncation.ts +11 -0
  76. package/src/adapters/google.ts +618 -74
  77. package/src/adapters/identity.ts +39 -6
  78. package/src/adapters/kiro-errors.ts +11 -0
  79. package/src/adapters/kiro-events.ts +19 -1
  80. package/src/adapters/kiro-thinking.ts +18 -2
  81. package/src/adapters/kiro-tools.ts +10 -1
  82. package/src/adapters/kiro.ts +92 -54
  83. package/src/adapters/mimo-free.ts +17 -0
  84. package/src/adapters/openai-chat-url.ts +11 -0
  85. package/src/adapters/openai-chat.ts +1311 -336
  86. package/src/adapters/openai-responses-url.ts +16 -0
  87. package/src/adapters/openai-responses.ts +830 -56
  88. package/src/adapters/registry.ts +175 -0
  89. package/src/adapters/responses-tool-schema.ts +67 -0
  90. package/src/adapters/tool-call-id.ts +119 -0
  91. package/src/adapters/tool-catalog-nudge.ts +104 -21
  92. package/src/adapters/xai-web-search.ts +185 -0
  93. package/src/bridge.ts +428 -61
  94. package/src/chat/inbound.ts +43 -19
  95. package/src/chat/outbound.ts +82 -26
  96. package/src/claude/agents-inject.ts +32 -9
  97. package/src/claude/context-windows.ts +21 -5
  98. package/src/claude/desktop-3p.ts +243 -9
  99. package/src/claude/gateway-cache.ts +41 -4
  100. package/src/claude/inbound.ts +72 -3
  101. package/src/claude/model-info.ts +38 -15
  102. package/src/claude/outbound.ts +70 -16
  103. package/src/cli/account-api.ts +35 -1
  104. package/src/cli/account-auth.ts +33 -6
  105. package/src/cli/account-catalog-refresh.ts +14 -0
  106. package/src/cli/account-extended.ts +389 -2
  107. package/src/cli/account-main.ts +317 -0
  108. package/src/cli/account.ts +30 -6
  109. package/src/cli/agent.ts +45 -1
  110. package/src/cli/claude-agent-startup-sync.ts +73 -0
  111. package/src/cli/claude-desktop.ts +29 -4
  112. package/src/cli/claude.ts +103 -34
  113. package/src/cli/codex-log-guard-doctor.ts +103 -0
  114. package/src/cli/codex-shim-autorestore.ts +2 -0
  115. package/src/cli/codex-shim-readiness.ts +76 -0
  116. package/src/cli/combo.ts +8 -0
  117. package/src/cli/config-command.ts +74 -10
  118. package/src/cli/dispatch.ts +593 -0
  119. package/src/cli/doctor.ts +315 -43
  120. package/src/cli/ensure-desired-integrations.ts +152 -0
  121. package/src/cli/export-command.ts +46 -20
  122. package/src/cli/help.ts +20 -271
  123. package/src/cli/index.ts +378 -519
  124. package/src/cli/init.ts +4 -17
  125. package/src/cli/integrations.ts +120 -2
  126. package/src/cli/lab.ts +607 -0
  127. package/src/cli/launcher-context.ts +77 -0
  128. package/src/cli/minimax.ts +497 -0
  129. package/src/cli/models-runtime.ts +35 -2
  130. package/src/cli/models.ts +100 -14
  131. package/src/cli/observe.ts +92 -3
  132. package/src/cli/opencode.ts +4 -2
  133. package/src/cli/provider-runtime.ts +18 -1
  134. package/src/cli/provider.ts +24 -3
  135. package/src/cli/ready.ts +301 -0
  136. package/src/cli/registry.ts +437 -0
  137. package/src/cli/root.ts +86 -0
  138. package/src/cli/route-policy.ts +92 -0
  139. package/src/cli/runtime-api.ts +6 -3
  140. package/src/cli/star-prompt.ts +71 -15
  141. package/src/cli/status.ts +10 -3
  142. package/src/cli/system-restart-client.ts +146 -0
  143. package/src/cli/tray-proxy.ts +153 -6
  144. package/src/cli/v2.ts +105 -10
  145. package/src/cli.ts +1 -1
  146. package/src/clients/config-export.ts +1358 -21
  147. package/src/codex/account-label.ts +14 -1
  148. package/src/codex/account-lifecycle.ts +130 -13
  149. package/src/codex/account-namespaces.ts +49 -3
  150. package/src/codex/account-priority.ts +83 -0
  151. package/src/codex/account-store.ts +29 -2
  152. package/src/codex/account-usability.ts +25 -2
  153. package/src/codex/admission.ts +256 -0
  154. package/src/codex/affinity-debug.ts +162 -0
  155. package/src/codex/app-server-processes.ts +493 -106
  156. package/src/codex/app-server-restart-service.ts +232 -0
  157. package/src/codex/auth-api.ts +849 -242
  158. package/src/codex/auth-collision.ts +5 -3
  159. package/src/codex/auth-context.ts +345 -32
  160. package/src/codex/autostart-health.ts +8 -1
  161. package/src/codex/catalog/account-models.ts +67 -0
  162. package/src/codex/catalog/aggregation.ts +68 -10
  163. package/src/codex/catalog/bundled.ts +331 -33
  164. package/src/codex/catalog/effort.ts +121 -30
  165. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  166. package/src/codex/catalog/kinds.ts +2 -0
  167. package/src/codex/catalog/metadata.ts +529 -45
  168. package/src/codex/catalog/native-models.ts +72 -0
  169. package/src/codex/catalog/parsing.ts +224 -30
  170. package/src/codex/catalog/provider-fetch.ts +1460 -134
  171. package/src/codex/catalog/sync.ts +1449 -186
  172. package/src/codex/catalog-admission.ts +199 -0
  173. package/src/codex/catalog-refresh-status.ts +105 -0
  174. package/src/codex/catalog-write-serialization.ts +242 -0
  175. package/src/codex/catalog.ts +6 -3
  176. package/src/codex/codex-write-lock.ts +384 -0
  177. package/src/codex/convergence-types.ts +614 -0
  178. package/src/codex/convergence.ts +651 -0
  179. package/src/codex/coordinator-doctor.ts +332 -0
  180. package/src/codex/custom-model-catalog-migration.ts +176 -0
  181. package/src/codex/desired-state.ts +230 -0
  182. package/src/codex/features.ts +636 -39
  183. package/src/codex/generation.ts +202 -0
  184. package/src/codex/history-job.ts +407 -0
  185. package/src/codex/history-lock.ts +242 -0
  186. package/src/codex/history-migration-guardian.ts +26 -20
  187. package/src/codex/history-provider.ts +231 -28
  188. package/src/codex/history-transition.ts +105 -0
  189. package/src/codex/history-worker.ts +220 -0
  190. package/src/codex/inject-coordination.ts +290 -0
  191. package/src/codex/inject.ts +1073 -152
  192. package/src/codex/injected-marker.ts +37 -3
  193. package/src/codex/integration-record.ts +266 -0
  194. package/src/codex/internal/catalog-writer.ts +203 -0
  195. package/src/codex/internal/history-writer.ts +80 -0
  196. package/src/codex/journal.ts +66 -4
  197. package/src/codex/log-guard/inspect.ts +506 -0
  198. package/src/codex/log-guard/lock.ts +150 -0
  199. package/src/codex/log-guard/maintenance.ts +403 -0
  200. package/src/codex/log-guard/path-safety.ts +88 -0
  201. package/src/codex/log-guard/policy.ts +44 -0
  202. package/src/codex/log-guard/processes.ts +205 -0
  203. package/src/codex/log-guard/protection.ts +489 -0
  204. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  205. package/src/codex/main-account-cache.ts +24 -0
  206. package/src/codex/main-account.ts +29 -1
  207. package/src/codex/management-convergence.ts +167 -0
  208. package/src/codex/model-cache.ts +56 -10
  209. package/src/codex/model-entitlements.ts +353 -0
  210. package/src/codex/native-main-admission.ts +47 -0
  211. package/src/codex/native-main-auth-temp.ts +187 -0
  212. package/src/codex/native-main-claim.ts +178 -0
  213. package/src/codex/native-main-lock-file.ts +162 -0
  214. package/src/codex/native-main-owner.ts +329 -0
  215. package/src/codex/native-profile-api.ts +247 -0
  216. package/src/codex/native-profile-manager.ts +1531 -0
  217. package/src/codex/native-profile-processes.ts +121 -0
  218. package/src/codex/native-profile-recovery.ts +99 -0
  219. package/src/codex/native-profile-stage-store.ts +387 -0
  220. package/src/codex/native-profile-startup.ts +492 -0
  221. package/src/codex/native-profile-store.ts +855 -0
  222. package/src/codex/native-profile-types.ts +120 -0
  223. package/src/codex/native-residue.ts +682 -0
  224. package/src/codex/paths.ts +80 -1
  225. package/src/codex/plan-from-token.ts +140 -0
  226. package/src/codex/plan.ts +40 -0
  227. package/src/codex/plugins-doctor.ts +1 -1
  228. package/src/codex/pool-rotation.ts +74 -4
  229. package/src/codex/project-config-warnings.ts +20 -6
  230. package/src/codex/prompt-journal.ts +352 -0
  231. package/src/codex/prompt-layers.ts +967 -0
  232. package/src/codex/prompt-lock.ts +143 -0
  233. package/src/codex/quota-rejection.ts +298 -0
  234. package/src/codex/quota.ts +175 -13
  235. package/src/codex/refresh.ts +11 -2
  236. package/src/codex/reset-credit-recovery.ts +1044 -0
  237. package/src/codex/routing.ts +505 -94
  238. package/src/codex/runtime.ts +159 -38
  239. package/src/codex/shim.ts +1009 -28
  240. package/src/codex/subagent-model-fallback.ts +350 -35
  241. package/src/codex/sync.ts +191 -2
  242. package/src/codex/transition-state.ts +612 -0
  243. package/src/codex/upstream-host-health.ts +368 -0
  244. package/src/codex/user-identity.ts +557 -0
  245. package/src/codex/warmup.ts +187 -81
  246. package/src/codex/write-coordination.ts +114 -0
  247. package/src/combos/failover.ts +20 -0
  248. package/src/combos/index.ts +4 -0
  249. package/src/combos/request.ts +32 -0
  250. package/src/combos/types.ts +81 -9
  251. package/src/config/provider-name.ts +24 -0
  252. package/src/config.ts +1762 -140
  253. package/src/generated/compatibility-version.json +3164 -0
  254. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  255. package/src/grok/inject.ts +16 -5
  256. package/src/grok/inspect.ts +45 -0
  257. package/src/grok/sync.ts +2 -2
  258. package/src/images/loop.ts +152 -29
  259. package/src/images/plan.ts +23 -13
  260. package/src/integrations/config-io.ts +269 -0
  261. package/src/integrations/journal.ts +315 -0
  262. package/src/integrations/merge.ts +135 -0
  263. package/src/integrations/mutation-flight.ts +71 -0
  264. package/src/integrations/native/ownership-preflight.ts +202 -0
  265. package/src/integrations/omp-yaml-source.ts +358 -0
  266. package/src/integrations/owned-refresh.ts +74 -0
  267. package/src/integrations/ownership.ts +111 -0
  268. package/src/integrations/registry.ts +159 -0
  269. package/src/integrations/serialize.ts +314 -0
  270. package/src/integrations/state.ts +361 -0
  271. package/src/integrations/store.ts +103 -0
  272. package/src/integrations/writer-lock.ts +98 -0
  273. package/src/integrations/writer.ts +691 -0
  274. package/src/lab/artifacts/sanitize.ts +586 -0
  275. package/src/lab/artifacts/secure-fs.ts +475 -0
  276. package/src/lab/artifacts/store.ts +310 -0
  277. package/src/lab/automation/budgets.ts +78 -0
  278. package/src/lab/automation/config-persistence.ts +256 -0
  279. package/src/lab/automation/constants.ts +39 -0
  280. package/src/lab/automation/cooldown.ts +103 -0
  281. package/src/lab/automation/dispatch.ts +211 -0
  282. package/src/lab/automation/index.ts +13 -0
  283. package/src/lab/automation/orchestrator.ts +499 -0
  284. package/src/lab/automation/persistence.ts +512 -0
  285. package/src/lab/automation/planner.ts +371 -0
  286. package/src/lab/automation/policy.ts +136 -0
  287. package/src/lab/automation/queue.ts +191 -0
  288. package/src/lab/automation/recovery.ts +24 -0
  289. package/src/lab/automation/route-context.ts +21 -0
  290. package/src/lab/automation/run-key.ts +44 -0
  291. package/src/lab/automation/runs-query.ts +34 -0
  292. package/src/lab/automation/types.ts +160 -0
  293. package/src/lab/conformance/assertion.ts +325 -0
  294. package/src/lab/conformance/digest.ts +22 -0
  295. package/src/lab/conformance/executor.ts +741 -0
  296. package/src/lab/conformance/fixture-provider.ts +27 -0
  297. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  298. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  299. package/src/lab/conformance/harness-budget.ts +47 -0
  300. package/src/lab/conformance/index.ts +5 -0
  301. package/src/lab/conformance/jcs.ts +64 -0
  302. package/src/lab/conformance/json-pointer.ts +39 -0
  303. package/src/lab/conformance/manifest.ts +180 -0
  304. package/src/lab/conformance/mcp-stub.ts +179 -0
  305. package/src/lab/conformance/negative-controls.ts +164 -0
  306. package/src/lab/conformance/observation.ts +355 -0
  307. package/src/lab/conformance/runner.ts +68 -0
  308. package/src/lab/conformance/sse-normalize.ts +59 -0
  309. package/src/lab/conformance/suite-manifest.ts +78 -0
  310. package/src/lab/conformance/types.ts +214 -0
  311. package/src/lab/constants.ts +126 -0
  312. package/src/lab/digest.ts +64 -0
  313. package/src/lab/events/errors.ts +9 -0
  314. package/src/lab/events/limits.ts +117 -0
  315. package/src/lab/events/types.ts +229 -0
  316. package/src/lab/events/validate.ts +781 -0
  317. package/src/lab/fabric/constants.ts +40 -0
  318. package/src/lab/fabric/executor.ts +492 -0
  319. package/src/lab/fabric/index.ts +80 -0
  320. package/src/lab/fabric/manifest.ts +222 -0
  321. package/src/lab/fabric/observe.ts +489 -0
  322. package/src/lab/fabric/patch.ts +79 -0
  323. package/src/lab/fabric/producer-child.ts +139 -0
  324. package/src/lab/fabric/producer-isolate.ts +276 -0
  325. package/src/lab/fabric/producer-protocol.ts +61 -0
  326. package/src/lab/fabric/scratch.ts +439 -0
  327. package/src/lab/fabric/subject.ts +106 -0
  328. package/src/lab/fabric/types.ts +134 -0
  329. package/src/lab/fabric/verifier.ts +98 -0
  330. package/src/lab/index.ts +54 -0
  331. package/src/lab/ledger/artifact-refs.ts +127 -0
  332. package/src/lab/ledger/invalidation.ts +136 -0
  333. package/src/lab/ledger/purge.ts +310 -0
  334. package/src/lab/ledger/store.ts +532 -0
  335. package/src/lab/live/credential-lease.ts +53 -0
  336. package/src/lab/live/destination.ts +155 -0
  337. package/src/lab/live/executor.ts +336 -0
  338. package/src/lab/live/inert-tools.ts +56 -0
  339. package/src/lab/live/manifest.ts +85 -0
  340. package/src/lab/live/mcp-loopback.ts +57 -0
  341. package/src/lab/live/runner.ts +19 -0
  342. package/src/lab/live/sandbox.ts +61 -0
  343. package/src/lab/live/suite-manifest.ts +41 -0
  344. package/src/lab/live/transport.ts +118 -0
  345. package/src/lab/live/types.ts +197 -0
  346. package/src/lab/observe/from-conformance.ts +301 -0
  347. package/src/lab/observe/from-live.ts +117 -0
  348. package/src/lab/paths.ts +153 -0
  349. package/src/lab/projection/rebuild.ts +495 -0
  350. package/src/lab/projection/schema.ts +135 -0
  351. package/src/lab/projection/verdicts.ts +474 -0
  352. package/src/lab/projection/verification.ts +412 -0
  353. package/src/lab/public/bundle.ts +217 -0
  354. package/src/lab/public/community-authority.ts +175 -0
  355. package/src/lab/public/community-files.ts +29 -0
  356. package/src/lab/public/community.ts +479 -0
  357. package/src/lab/public/file-safety.ts +155 -0
  358. package/src/lab/public/ids.ts +26 -0
  359. package/src/lab/public/index.ts +16 -0
  360. package/src/lab/public/mutation-lock.ts +424 -0
  361. package/src/lab/public/operator.ts +353 -0
  362. package/src/lab/public/origin-purge.ts +79 -0
  363. package/src/lab/public/origin.ts +203 -0
  364. package/src/lab/public/privacy.ts +143 -0
  365. package/src/lab/public/private-file.ts +261 -0
  366. package/src/lab/public/project.ts +124 -0
  367. package/src/lab/public/purge-test-fault.ts +21 -0
  368. package/src/lab/public/purge.ts +223 -0
  369. package/src/lab/public/registry.ts +44 -0
  370. package/src/lab/public/revocation.ts +252 -0
  371. package/src/lab/public/signature.ts +243 -0
  372. package/src/lab/public/storage.ts +105 -0
  373. package/src/lab/public/strict-json.ts +206 -0
  374. package/src/lab/public/time.ts +26 -0
  375. package/src/lab/public/types.ts +172 -0
  376. package/src/lab/public/validate.ts +391 -0
  377. package/src/lab/query/catalog.ts +101 -0
  378. package/src/lab/query/connection.ts +107 -0
  379. package/src/lab/query/constants.ts +4 -0
  380. package/src/lab/query/cursor.ts +132 -0
  381. package/src/lab/query/dto-map.ts +277 -0
  382. package/src/lab/query/errors.ts +22 -0
  383. package/src/lab/query/freshness.ts +53 -0
  384. package/src/lab/query/index.ts +45 -0
  385. package/src/lab/query/latest-observation.ts +59 -0
  386. package/src/lab/query/passive-production.ts +159 -0
  387. package/src/lab/query/queries.ts +444 -0
  388. package/src/lab/query/types.ts +266 -0
  389. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  390. package/src/lab/subject/installation-salt.ts +112 -0
  391. package/src/lab/subject/protocol-subject.ts +80 -0
  392. package/src/lab/subject/route-subject.ts +74 -0
  393. package/src/lib/app-owned-memory-stores.ts +22 -0
  394. package/src/lib/bounded-body.ts +153 -9
  395. package/src/lib/bun-runtime.ts +125 -12
  396. package/src/lib/bun-stream-caps.ts +13 -9
  397. package/src/lib/codex-restart-contract.ts +120 -0
  398. package/src/lib/config-ownership.ts +6 -2
  399. package/src/lib/destination-policy.ts +65 -1
  400. package/src/lib/errors.ts +44 -2
  401. package/src/lib/fabric-task-execution-authority.ts +7 -0
  402. package/src/lib/fabric-task-host.ts +29 -0
  403. package/src/lib/lab-activation.ts +223 -0
  404. package/src/lib/lab-live-execution-authority.ts +13 -0
  405. package/src/lib/lab-live-host.ts +30 -0
  406. package/src/lib/lab-live-pinned-sender.ts +56 -0
  407. package/src/lib/lab-live-route-production.ts +130 -0
  408. package/src/lib/lab-passive-linker-registration.ts +26 -0
  409. package/src/lib/local-management-attestation.ts +51 -0
  410. package/src/lib/local-management-capability.ts +100 -0
  411. package/src/lib/local-provider-reload-contract.ts +100 -0
  412. package/src/lib/optional-shutdown-hooks.ts +57 -0
  413. package/src/lib/pinned-http.ts +145 -26
  414. package/src/lib/process-control.ts +4 -1
  415. package/src/lib/provider-outbound.ts +49 -9
  416. package/src/lib/redact.ts +419 -3
  417. package/src/lib/self-launch-argv.ts +15 -0
  418. package/src/lib/server-resource-ownership.ts +71 -0
  419. package/src/lib/shadow-call.ts +35 -4
  420. package/src/lib/sse-decoder.ts +41 -0
  421. package/src/lib/state-store-registrations.ts +10 -2
  422. package/src/lib/system-restart-contract.ts +73 -0
  423. package/src/lib/token-estimate.ts +19 -2
  424. package/src/lib/tool-argument-integers.ts +202 -0
  425. package/src/lib/translator-budget.ts +44 -0
  426. package/src/lib/upstream-http-version.ts +57 -0
  427. package/src/lib/upstream-reachability.ts +95 -0
  428. package/src/lib/upstream-retry.ts +156 -3
  429. package/src/lib/windows-atomic-replace.ts +155 -0
  430. package/src/lib/windows-elevation.ts +70 -2
  431. package/src/lib/windows-secret-acl.ts +409 -69
  432. package/src/lib/windows-service-wrappers.ts +72 -0
  433. package/src/lib/windows-text.ts +106 -0
  434. package/src/lib/windows-user-principal.ts +341 -0
  435. package/src/lib/winsw.ts +33 -5
  436. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  437. package/src/oauth/account-import/index.ts +15 -0
  438. package/src/oauth/account-import/parser.ts +83 -0
  439. package/src/oauth/account-import/registry.ts +18 -0
  440. package/src/oauth/account-import/service.ts +75 -0
  441. package/src/oauth/account-import/types.ts +91 -0
  442. package/src/oauth/anthropic.ts +12 -1
  443. package/src/oauth/callback-server.ts +8 -2
  444. package/src/oauth/chatgpt.ts +12 -1
  445. package/src/oauth/command-code.ts +239 -0
  446. package/src/oauth/cursor.ts +46 -5
  447. package/src/oauth/google-antigravity.ts +35 -3
  448. package/src/oauth/health.ts +20 -12
  449. package/src/oauth/index.ts +398 -66
  450. package/src/oauth/key-providers.ts +16 -0
  451. package/src/oauth/kimi.ts +16 -2
  452. package/src/oauth/kiro.ts +50 -6
  453. package/src/oauth/local-token-detect.ts +11 -2
  454. package/src/oauth/log.ts +3 -1
  455. package/src/oauth/login-cli.ts +88 -28
  456. package/src/oauth/nous.ts +798 -0
  457. package/src/oauth/store.ts +119 -21
  458. package/src/oauth/token-guardian.ts +9 -3
  459. package/src/pi/models.ts +2 -2
  460. package/src/providers/alibaba-region-migration.ts +1 -1
  461. package/src/providers/antigravity-models.ts +521 -31
  462. package/src/providers/base-url-choices.ts +10 -0
  463. package/src/providers/codex-capacity.ts +292 -0
  464. package/src/providers/command-code-efforts.ts +144 -0
  465. package/src/providers/context-cap.ts +22 -5
  466. package/src/providers/cursor-pool.ts +72 -0
  467. package/src/providers/derive.ts +253 -6
  468. package/src/providers/fastwire.ts +501 -0
  469. package/src/providers/free-directory.ts +10 -7
  470. package/src/providers/google-vertex-location.ts +14 -0
  471. package/src/providers/key-failover.ts +71 -3
  472. package/src/providers/label.ts +1 -1
  473. package/src/providers/model-discovery-limits.ts +16 -0
  474. package/src/providers/model-discovery.ts +115 -22
  475. package/src/providers/model-rename-migration.ts +255 -0
  476. package/src/providers/model-rename-startup.ts +28 -0
  477. package/src/providers/openai-sidecar.ts +72 -4
  478. package/src/providers/openai-tier-startup.ts +31 -2
  479. package/src/providers/openai-tiers.ts +119 -4
  480. package/src/providers/openai-virtual-models.ts +1 -0
  481. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  482. package/src/providers/provider-id-rewrite.ts +29 -0
  483. package/src/providers/quota.ts +1319 -38
  484. package/src/providers/registry.ts +1429 -111
  485. package/src/providers/request-pacing.ts +310 -0
  486. package/src/providers/service-tier.ts +277 -0
  487. package/src/providers/slug-codec.ts +42 -6
  488. package/src/providers/static-model-discovery.ts +86 -0
  489. package/src/providers/xai-responses-opt-in.ts +15 -0
  490. package/src/providers/xai-transport.ts +11 -4
  491. package/src/reasoning-effort.ts +49 -1
  492. package/src/responses/compaction.ts +26 -1
  493. package/src/responses/custom-tool-compat.ts +266 -0
  494. package/src/responses/hosted-tool-policy.ts +9 -0
  495. package/src/responses/namespace-tool-compat.ts +355 -0
  496. package/src/responses/parser.ts +220 -38
  497. package/src/responses/provider-continuation.ts +98 -0
  498. package/src/responses/provider-opaque-metadata.ts +73 -0
  499. package/src/responses/reasoning-envelope.ts +9 -1
  500. package/src/responses/reasoning-replay-cache.ts +426 -0
  501. package/src/responses/schema.ts +7 -1
  502. package/src/responses/spill-store.ts +75 -10
  503. package/src/responses/state.ts +565 -27
  504. package/src/responses/thought-signature-replay.ts +347 -0
  505. package/src/responses/tool-search-compat.ts +301 -0
  506. package/src/responses/truncated-stop-reason.ts +60 -0
  507. package/src/router.ts +366 -30
  508. package/src/routing/analytics.ts +378 -0
  509. package/src/routing/capability.ts +244 -0
  510. package/src/routing/compatibility/assemble.ts +73 -0
  511. package/src/routing/compatibility/behavior.ts +278 -0
  512. package/src/routing/compatibility/catalog.ts +99 -0
  513. package/src/routing/compatibility/endpoint.ts +52 -0
  514. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  515. package/src/routing/compatibility/policy.ts +181 -0
  516. package/src/routing/compatibility/provider-slot.ts +56 -0
  517. package/src/routing/compatibility/reader.ts +110 -0
  518. package/src/routing/compatibility/subject.ts +191 -0
  519. package/src/routing/compatibility/types.ts +64 -0
  520. package/src/routing/compatibility/version.ts +104 -0
  521. package/src/routing/cost.ts +77 -0
  522. package/src/routing/evaluator.ts +495 -0
  523. package/src/routing/health.ts +412 -0
  524. package/src/routing/history/cursor.ts +43 -0
  525. package/src/routing/history/indexer.ts +605 -0
  526. package/src/routing/history/schema.ts +72 -0
  527. package/src/routing/profile-namespace.ts +15 -0
  528. package/src/routing/profile.ts +547 -0
  529. package/src/routing/quota.ts +145 -0
  530. package/src/routing/request-evidence.ts +45 -0
  531. package/src/routing/trace.ts +776 -0
  532. package/src/server/adapter-resolve.ts +2 -29
  533. package/src/server/auth-cors.ts +267 -46
  534. package/src/server/background-lifecycle.ts +182 -0
  535. package/src/server/chat-completions.ts +130 -56
  536. package/src/server/chat-native-sse.ts +331 -0
  537. package/src/server/chat-native.ts +426 -0
  538. package/src/server/claude-messages.ts +159 -43
  539. package/src/server/direct-local-http.ts +347 -0
  540. package/src/server/effort-policy.ts +18 -0
  541. package/src/server/github-copilot-responses-repair.ts +338 -0
  542. package/src/server/gui-static.ts +39 -10
  543. package/src/server/images.ts +94 -12
  544. package/src/server/index.ts +865 -181
  545. package/src/server/lifecycle.ts +284 -13
  546. package/src/server/live.ts +136 -17
  547. package/src/server/local-management-read-client.ts +90 -0
  548. package/src/server/local-provider-reload-client.ts +137 -0
  549. package/src/server/management/agent-settings-routes.ts +398 -103
  550. package/src/server/management/api-key-usage.ts +31 -5
  551. package/src/server/management/body.ts +6 -0
  552. package/src/server/management/combo-routes.ts +62 -24
  553. package/src/server/management/config-routes.ts +464 -51
  554. package/src/server/management/context.ts +80 -2
  555. package/src/server/management/integration-routes.ts +498 -0
  556. package/src/server/management/lab-automation-routes.ts +206 -0
  557. package/src/server/management/lab-routes.ts +563 -0
  558. package/src/server/management/logs-usage-routes.ts +101 -32
  559. package/src/server/management/model-routes.ts +189 -131
  560. package/src/server/management/model-rows.ts +163 -0
  561. package/src/server/management/native-integration-routes.ts +769 -0
  562. package/src/server/management/oauth-account-routes.ts +80 -4
  563. package/src/server/management/provider-capability-config.ts +48 -0
  564. package/src/server/management/provider-routes.ts +764 -157
  565. package/src/server/management/request-history-routes.ts +191 -0
  566. package/src/server/management/routing-analytics-routes.ts +74 -0
  567. package/src/server/management/routing-profile-routes.ts +380 -0
  568. package/src/server/management/shared.ts +27 -11
  569. package/src/server/management/sidebar-routes.ts +47 -31
  570. package/src/server/management/storage-log-guard-routes.ts +186 -0
  571. package/src/server/management/sync-response.ts +69 -0
  572. package/src/server/management/system-restart.ts +288 -32
  573. package/src/server/management/system-routes.ts +77 -0
  574. package/src/server/management/usage-summary-cache.ts +9 -1
  575. package/src/server/management/vision-sidecar-options.ts +167 -0
  576. package/src/server/management/web-search-sidecar-options.ts +120 -0
  577. package/src/server/management-api.ts +115 -14
  578. package/src/server/management-auth.ts +220 -5
  579. package/src/server/passive-route-linker.ts +66 -0
  580. package/src/server/ports.ts +41 -1
  581. package/src/server/proxy-liveness.ts +132 -5
  582. package/src/server/readiness.ts +99 -0
  583. package/src/server/relay-eager.ts +82 -42
  584. package/src/server/relay.ts +236 -76
  585. package/src/server/request-decompress.ts +113 -6
  586. package/src/server/request-log.ts +235 -22
  587. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  588. package/src/server/responses/agent-task-recovery.ts +465 -0
  589. package/src/server/responses/collaboration.ts +204 -35
  590. package/src/server/responses/compact.ts +442 -55
  591. package/src/server/responses/core.ts +2872 -331
  592. package/src/server/responses/empty-completion-guard.ts +276 -0
  593. package/src/server/responses/encrypted-payload.ts +62 -39
  594. package/src/server/responses/fetch-helpers.ts +79 -4
  595. package/src/server/responses/input-admission.ts +185 -0
  596. package/src/server/responses/pacing-overload.ts +13 -0
  597. package/src/server/responses/policy-fallback.ts +178 -0
  598. package/src/server/responses/responses-field-backfill.ts +251 -0
  599. package/src/server/responses/terminal-guard.ts +26 -5
  600. package/src/server/responses/upstream-error.ts +5 -0
  601. package/src/server/responses/ws-upstream.ts +308 -0
  602. package/src/server/responses-custom-tool-repair.ts +282 -0
  603. package/src/server/responses-item-id-repair.ts +54 -6
  604. package/src/server/responses-json-events.ts +90 -0
  605. package/src/server/responses-model-rewrite.ts +29 -0
  606. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  607. package/src/server/responses-snapshot-repair.ts +621 -0
  608. package/src/server/responses-terminal-repair.ts +342 -0
  609. package/src/server/responses-tool-search-repair.ts +267 -0
  610. package/src/server/responses-undeclared-tool-guard.ts +153 -0
  611. package/src/server/responses.ts +18 -2
  612. package/src/server/search.ts +78 -13
  613. package/src/server/sse-frame-buffer.ts +292 -0
  614. package/src/server/sse-payload-rewrite.ts +110 -22
  615. package/src/server/startup-action-control.ts +8 -1
  616. package/src/server/startup-health-cache.ts +19 -1
  617. package/src/server/system-env.ts +80 -9
  618. package/src/server/ws-bridge.ts +39 -38
  619. package/src/service-manager-probe.ts +892 -0
  620. package/src/service.ts +1111 -90
  621. package/src/sidecar/auth.ts +92 -0
  622. package/src/sidecar/candidates.ts +83 -0
  623. package/src/storage/cleanup.ts +2 -2
  624. package/src/storage/scanner.ts +1 -1
  625. package/src/storage/worker-lifecycle.ts +14 -14
  626. package/src/tray/windows-tray.ps1 +83 -9
  627. package/src/tray/windows.ts +43 -16
  628. package/src/types/accounts.ts +37 -0
  629. package/src/types/config.ts +845 -0
  630. package/src/types/provider.ts +545 -0
  631. package/src/types/request.ts +384 -0
  632. package/src/types/tools.ts +131 -0
  633. package/src/types/wire.ts +80 -0
  634. package/src/types.ts +104 -1236
  635. package/src/update/index.ts +32 -19
  636. package/src/update/job.ts +442 -67
  637. package/src/update/notify.ts +12 -6
  638. package/src/update/npm-cache-preflight.d.mts +47 -0
  639. package/src/update/npm-cache-preflight.mjs +201 -0
  640. package/src/update/transactional-install.d.mts +22 -0
  641. package/src/update/transactional-install.mjs +259 -0
  642. package/src/usage/cost.ts +0 -0
  643. package/src/usage/expected-prices.ts +268 -16
  644. package/src/usage/log.ts +606 -41
  645. package/src/usage/summary.ts +177 -9
  646. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  647. package/src/usage/user-cost-overlays.ts +314 -0
  648. package/src/vision/anthropic-describe.ts +10 -6
  649. package/src/vision/backends.ts +97 -0
  650. package/src/vision/describe.ts +9 -3
  651. package/src/vision/eligibility.ts +250 -0
  652. package/src/vision/index.ts +238 -24
  653. package/src/vision/reasoning.ts +55 -0
  654. package/src/vision/routed-describe.ts +175 -0
  655. package/src/vision/timeout-bounds.ts +9 -0
  656. package/src/web-search/anthropic-executor.ts +13 -7
  657. package/src/web-search/backends.ts +108 -0
  658. package/src/web-search/exa-executor.ts +88 -0
  659. package/src/web-search/executor.ts +11 -3
  660. package/src/web-search/gemini-executor.ts +141 -0
  661. package/src/web-search/index.ts +150 -15
  662. package/src/web-search/loop.ts +279 -50
  663. package/src/web-search/parse.ts +125 -30
  664. package/src/web-search/sources.ts +60 -0
  665. package/src/web-search/xai-executor.ts +219 -0
  666. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  667. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  668. package/gui/dist/provider-icons/antigravity.svg +0 -1
  669. package/gui/dist/provider-icons/claude.svg +0 -1
  670. package/gui/dist/provider-icons/copilot.svg +0 -1
  671. package/gui/dist/provider-icons/cursor.svg +0 -2
  672. package/gui/dist/provider-icons/gemini.svg +0 -1
  673. package/gui/dist/provider-icons/grok-color.svg +0 -1
  674. package/gui/dist/provider-icons/kiro.svg +0 -14
  675. package/src/cli/internal-dispatch.ts +0 -20
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Cursor-paginated request-history API (RI-02).
3
+ *
4
+ * - `GET /api/request-history` - keyset-paginated rows with filters
5
+ * - `GET /api/request-history/:requestId` - one canonical row
6
+ * - `GET /api/request-history/:requestId/route-decision` - why-this-route
7
+ * explanation (RI-09): durable trace + attempts + outcome
8
+ *
9
+ * The index is a derived projection of `usage.jsonl`; every response carries
10
+ * an `index` status block so callers can see schema version, indexed rows and
11
+ * any repair the indexer performed.
12
+ */
13
+
14
+ import {
15
+ queryRequestHistory,
16
+ requestHistoryRowById,
17
+ REQUEST_HISTORY_MAX_PAGE_SIZE,
18
+ } from "../../routing/history/indexer";
19
+ import { InvalidCursorError } from "../../routing/history/cursor";
20
+ import { requestLogEntryFromPersistedUsage } from "../request-log";
21
+ import { requestLogDto } from "./shared";
22
+ import { jsonResponse } from "../auth-cors";
23
+ import type { ManagementContext } from "./context";
24
+ import type { PersistedUsageEntry } from "../../usage/log";
25
+
26
+ function parseQueryInt(raw: string | null): number | undefined | "invalid" {
27
+ if (raw === null) return undefined;
28
+ const trimmed = raw.trim();
29
+ if (trimmed.length === 0) return "invalid";
30
+ const value = Number(trimmed);
31
+ return Number.isInteger(value) ? value : "invalid";
32
+ }
33
+
34
+ function finalAttemptTarget(entry: PersistedUsageEntry): { provider: string; model: string } {
35
+ const attempts = entry.attempts;
36
+ if (Array.isArray(attempts) && attempts.length > 0) {
37
+ const last = attempts[attempts.length - 1];
38
+ if (last && typeof last.provider === "string" && typeof last.model === "string") {
39
+ return { provider: last.provider, model: last.model };
40
+ }
41
+ }
42
+ return { provider: entry.provider, model: entry.model };
43
+ }
44
+
45
+ export async function handleRequestHistoryRoutes(ctx: ManagementContext): Promise<Response | null> {
46
+ const { url, req, config } = ctx;
47
+ if (!url.pathname.startsWith("/api/request-history")) return null;
48
+
49
+ if (url.pathname === "/api/request-history" && req.method === "GET") {
50
+ const statusRaw = parseQueryInt(url.searchParams.get("status"));
51
+ if (statusRaw === "invalid") {
52
+ return jsonResponse({ error: { code: "invalid_status", message: "status must be an integer from 100 to 599" } }, 400, req, config);
53
+ }
54
+ const status = statusRaw;
55
+ if (status !== undefined && (status < 100 || status > 599)) {
56
+ return jsonResponse({ error: { code: "invalid_status", message: "status must be an integer from 100 to 599" } }, 400, req, config);
57
+ }
58
+ const fromRaw = parseQueryInt(url.searchParams.get("from"));
59
+ if (fromRaw === "invalid") {
60
+ return jsonResponse({ error: { code: "invalid_from", message: "from must be an integer timestamp" } }, 400, req, config);
61
+ }
62
+ const toRaw = parseQueryInt(url.searchParams.get("to"));
63
+ if (toRaw === "invalid") {
64
+ return jsonResponse({ error: { code: "invalid_to", message: "to must be an integer timestamp" } }, 400, req, config);
65
+ }
66
+ const from = fromRaw;
67
+ const to = toRaw;
68
+ if (from !== undefined && to !== undefined && from > to) {
69
+ return jsonResponse({ error: { code: "invalid_range", message: "from must not be after to" } }, 400, req, config);
70
+ }
71
+ const limitRaw = url.searchParams.get("limit");
72
+ const limitParsed = limitRaw === null ? undefined : parseQueryInt(limitRaw);
73
+ if (limitParsed === "invalid") {
74
+ return jsonResponse(
75
+ { error: { code: "invalid_limit", message: `limit must be an integer from 1 to ${REQUEST_HISTORY_MAX_PAGE_SIZE}` } },
76
+ 400,
77
+ req,
78
+ config,
79
+ );
80
+ }
81
+ const limit = limitParsed;
82
+ if (limit !== undefined && (limit < 1 || limit > REQUEST_HISTORY_MAX_PAGE_SIZE)) {
83
+ return jsonResponse(
84
+ { error: { code: "invalid_limit", message: `limit must be an integer from 1 to ${REQUEST_HISTORY_MAX_PAGE_SIZE}` } },
85
+ 400,
86
+ req,
87
+ config,
88
+ );
89
+ }
90
+ const cursor = url.searchParams.get("cursor");
91
+ try {
92
+ const page = await queryRequestHistory({
93
+ provider: url.searchParams.get("provider")?.trim() || undefined,
94
+ model: url.searchParams.get("model")?.trim() || undefined,
95
+ requestedModel: url.searchParams.get("requestedModel")?.trim() || undefined,
96
+ status,
97
+ conversationId: url.searchParams.get("conversationId")?.trim() || undefined,
98
+ surface: url.searchParams.get("surface")?.trim() || undefined,
99
+ inboundProtocol: url.searchParams.get("inboundProtocol")?.trim() || undefined,
100
+ apiKeyId: url.searchParams.get("apiKeyId")?.trim() || undefined,
101
+ profileId: url.searchParams.get("profileId")?.trim() || undefined,
102
+ fallback: url.searchParams.get("fallback") === "true"
103
+ ? true
104
+ : url.searchParams.get("fallback") === "false" ? false : undefined,
105
+ from,
106
+ to,
107
+ }, cursor, limit);
108
+ return jsonResponse({
109
+ entries: page.rows.map(row => requestLogDto(requestLogEntryFromPersistedUsage(row))),
110
+ ...(page.nextCursor ? { nextCursor: page.nextCursor } : {}),
111
+ hasMore: page.hasMore,
112
+ index: {
113
+ schemaVersion: page.meta.schemaVersion,
114
+ indexedRows: page.meta.indexedRows,
115
+ sourceSize: page.meta.sourceSize,
116
+ sourceMtimeMs: page.meta.sourceMtimeMs,
117
+ builtAtMs: page.meta.builtAtMs,
118
+ lastError: page.meta.lastError,
119
+ },
120
+ }, 200, req, config);
121
+ } catch (err) {
122
+ if (err instanceof InvalidCursorError) {
123
+ return jsonResponse({ error: { code: "invalid_cursor", message: "invalid cursor" } }, 400, req, config);
124
+ }
125
+ throw err;
126
+ }
127
+ }
128
+
129
+ if (url.pathname.startsWith("/api/request-history/") && req.method === "GET") {
130
+ // Why-this-route explanation (RI-09): trace + attempts + outcome.
131
+ if (url.pathname.endsWith("/route-decision")) {
132
+ let requestId: string;
133
+ try {
134
+ requestId = decodeURIComponent(
135
+ url.pathname.slice("/api/request-history/".length, -"/route-decision".length),
136
+ );
137
+ } catch {
138
+ return jsonResponse({ error: { code: "not_found", message: "unknown request" } }, 404, req, config);
139
+ }
140
+ if (!requestId || requestId.includes("/")) {
141
+ return jsonResponse({ error: { code: "not_found", message: "unknown request" } }, 404, req, config);
142
+ }
143
+ const entry = await requestHistoryRowById(requestId);
144
+ if (!entry) {
145
+ return jsonResponse({ error: { code: "not_found", message: "unknown request" } }, 404, req, config);
146
+ }
147
+ const trace = entry.routeDecision ?? null;
148
+ const final = finalAttemptTarget(entry);
149
+ return jsonResponse({
150
+ requestId,
151
+ routeDecision: trace,
152
+ attemptSequence: entry.attempts ?? [],
153
+ outcome: {
154
+ status: entry.status,
155
+ ...(entry.terminalStatus ? { terminalStatus: entry.terminalStatus } : {}),
156
+ ...(entry.closeReason ? { closeReason: entry.closeReason } : {}),
157
+ ...(entry.errorCode ? { errorCode: entry.errorCode } : {}),
158
+ ...(entry.durationMs !== undefined ? { durationMs: entry.durationMs } : {}),
159
+ ...(entry.usageStatus ? { usageStatus: entry.usageStatus } : {}),
160
+ },
161
+ summary: {
162
+ requestedModel: entry.requestedModel ?? entry.model,
163
+ routeKind: trace?.routeKind ?? null,
164
+ ...(trace?.profile ? { profileId: trace.profile.id, revision: trace.profile.revision } : {}),
165
+ selected: trace?.selected
166
+ ? { provider: trace.selected.provider, model: trace.selected.model }
167
+ : null,
168
+ finalProvider: final.provider,
169
+ finalModel: final.model,
170
+ },
171
+ }, 200, req, config);
172
+ }
173
+
174
+ let requestId: string;
175
+ try {
176
+ requestId = decodeURIComponent(url.pathname.slice("/api/request-history/".length));
177
+ } catch {
178
+ return jsonResponse({ error: { code: "not_found", message: "unknown request" } }, 404, req, config);
179
+ }
180
+ if (!requestId || requestId.includes("/")) {
181
+ return jsonResponse({ error: { code: "not_found", message: "unknown request" } }, 404, req, config);
182
+ }
183
+ const entry = await requestHistoryRowById(requestId);
184
+ if (!entry) {
185
+ return jsonResponse({ error: { code: "not_found", message: "unknown request" } }, 404, req, config);
186
+ }
187
+ return jsonResponse(requestLogDto(requestLogEntryFromPersistedUsage(entry)), 200, req, config);
188
+ }
189
+
190
+ return null;
191
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Routing analytics API (RI-03): `GET /api/routing-analytics`.
3
+ *
4
+ * Returns source-backed reliability/latency/cost metrics over the
5
+ * request-history index. Read-only; never changes routing behavior.
6
+ */
7
+
8
+ import {
9
+ ANALYTICS_API_DEFAULT_ROWS,
10
+ ANALYTICS_MAX_ROWS,
11
+ computeRoutingAnalytics,
12
+ } from "../../routing/analytics";
13
+ import { jsonResponse } from "../auth-cors";
14
+ import type { ManagementContext } from "./context";
15
+
16
+ function parseQueryInt(raw: string | null): number | undefined | "invalid" {
17
+ if (raw === null) return undefined;
18
+ const trimmed = raw.trim();
19
+ if (trimmed.length === 0) return "invalid";
20
+ const value = Number(trimmed);
21
+ return Number.isInteger(value) ? value : "invalid";
22
+ }
23
+
24
+ export async function handleRoutingAnalyticsRoutes(ctx: ManagementContext): Promise<Response | null> {
25
+ const { url, req, config } = ctx;
26
+ if (url.pathname !== "/api/routing-analytics" || req.method !== "GET") return null;
27
+
28
+ const fromParsed = parseQueryInt(url.searchParams.get("from"));
29
+ if (fromParsed === "invalid") {
30
+ return jsonResponse({ error: { code: "invalid_from", message: "from must be an integer timestamp" } }, 400, req, config);
31
+ }
32
+ const toParsed = parseQueryInt(url.searchParams.get("to"));
33
+ if (toParsed === "invalid") {
34
+ return jsonResponse({ error: { code: "invalid_to", message: "to must be an integer timestamp" } }, 400, req, config);
35
+ }
36
+ const from = fromParsed;
37
+ const to = toParsed;
38
+ if (from !== undefined && to !== undefined && from > to) {
39
+ return jsonResponse({ error: { code: "invalid_range", message: "from must not be after to" } }, 400, req, config);
40
+ }
41
+ const limitParsed = parseQueryInt(url.searchParams.get("limit"));
42
+ if (limitParsed === "invalid") {
43
+ return jsonResponse(
44
+ { error: { code: "invalid_limit", message: "limit must be an integer" } },
45
+ 400,
46
+ req,
47
+ config,
48
+ );
49
+ }
50
+ const maxRows = limitParsed ?? ANALYTICS_API_DEFAULT_ROWS;
51
+ if (maxRows < 1 || maxRows > ANALYTICS_MAX_ROWS) {
52
+ return jsonResponse(
53
+ {
54
+ error: {
55
+ code: "invalid_limit",
56
+ message: `limit must be between 1 and ${ANALYTICS_MAX_ROWS}`,
57
+ },
58
+ },
59
+ 400,
60
+ req,
61
+ config,
62
+ );
63
+ }
64
+
65
+ const result = await computeRoutingAnalytics({
66
+ provider: url.searchParams.get("provider")?.trim() || undefined,
67
+ model: url.searchParams.get("model")?.trim() || undefined,
68
+ profileId: url.searchParams.get("profileId")?.trim() || undefined,
69
+ surface: url.searchParams.get("surface")?.trim() || undefined,
70
+ from,
71
+ to,
72
+ }, { maxRows });
73
+ return jsonResponse(result, 200, req, config);
74
+ }
@@ -0,0 +1,380 @@
1
+ /**
2
+ * Routing-profile management API (RI-04).
3
+ *
4
+ * - `GET /api/routing-profiles` - normalized profiles with revisions
5
+ * - `PUT /api/routing-profiles` - create or replace one validated profile
6
+ * - `DELETE /api/routing-profiles?id=<id>` - remove one profile
7
+ * - `POST /api/routing-profiles/dry-run` - deterministic dry-run evaluation
8
+ * (never dispatches an upstream request)
9
+ */
10
+
11
+ import {
12
+ getRoutingProfile,
13
+ listRoutingProfileIds,
14
+ normalizeRoutingProfile,
15
+ policyPublicModelId,
16
+ routingProfileIssues,
17
+ } from "../../routing/profile";
18
+ import { evaluatePolicyProfile, type PolicyCandidateEvidence, type PolicyRequestEvidence } from "../../routing/evaluator";
19
+ import { assemblePolicyCandidateEvidence } from "../../routing/compatibility/assemble";
20
+ import { activateLab, labActivationRequired } from "../../lib/lab-activation";
21
+ import { quotaEvidenceForCandidate } from "../../routing/quota";
22
+ import { routedProviderConfig } from "../../router";
23
+ import { saveConfigPreservingClaudeCode, getConfigDir } from "../../config";
24
+ import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
25
+ import { isPlainRecord } from "./shared";
26
+ import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
27
+ import { jsonResponse } from "../auth-cors";
28
+ import type { ManagementContext } from "./context";
29
+ import type { OcxConfig, OcxRoutingProfileConfig } from "../../types";
30
+
31
+ function profileDto(config: Parameters<typeof getRoutingProfile>[0], id: string): Record<string, unknown> | null {
32
+ const profile = getRoutingProfile(config, id);
33
+ if (!profile) return null;
34
+ return {
35
+ id,
36
+ alias: profile.alias,
37
+ model: policyPublicModelId(id, profile),
38
+ revision: profile.revision,
39
+ candidates: profile.candidates,
40
+ require: profile.require,
41
+ optimize: profile.optimize,
42
+ limits: profile.limits,
43
+ unknownEvidence: profile.unknownEvidence,
44
+ ...(profile.compatibility ? { compatibility: profile.compatibility } : {}),
45
+ };
46
+ }
47
+
48
+ function parseEvidence(raw: unknown): { evidence: PolicyRequestEvidence; ok: boolean } {
49
+ // Absent evidence is empty evidence, mirroring the absent-candidates case.
50
+ if (raw === undefined) return { evidence: {}, ok: true };
51
+ if (!isPlainRecord(raw)) return { evidence: {}, ok: false };
52
+ const record = raw as Record<string, unknown>;
53
+ const evidence: PolicyRequestEvidence = {};
54
+ if (typeof record.contextWindow === "number" && Number.isFinite(record.contextWindow) && record.contextWindow >= 0) {
55
+ evidence.contextWindow = record.contextWindow;
56
+ }
57
+ for (const key of ["toolsRequired", "imageInputRequired", "structuredOutputRequired", "encryptedCodexTask"] as const) {
58
+ if (typeof record[key] === "boolean") evidence[key] = record[key];
59
+ }
60
+ if (typeof record.reasoningEffort === "string") evidence.reasoningEffort = record.reasoningEffort;
61
+ if (typeof record.serviceTier === "string") evidence.serviceTier = record.serviceTier;
62
+ return { evidence, ok: true };
63
+ }
64
+
65
+ function parseCandidateEvidence(raw: unknown): PolicyCandidateEvidence[] | null {
66
+ if (!Array.isArray(raw)) return null;
67
+ const out: PolicyCandidateEvidence[] = [];
68
+ for (const item of raw) {
69
+ if (!isPlainRecord(item)) return null;
70
+ const provider = item.provider;
71
+ const model = item.model;
72
+ if (typeof provider !== "string" || typeof model !== "string") return null;
73
+ out.push({
74
+ provider,
75
+ model,
76
+ ...(typeof item.accountRef === "string" ? { accountRef: item.accountRef } : {}),
77
+ ...(typeof item.codexAccountId === "string" ? { codexAccountId: item.codexAccountId } : {}),
78
+ // Dry-run evidence is caller-supplied and echoed back in the result as
79
+ // given; the trace's candidate rows carry only score/exclusions, which
80
+ // the trace builder bounds. Structural casts keep the API permissive.
81
+ ...(isPlainRecord(item.capability) ? { capability: item.capability as unknown as PolicyCandidateEvidence["capability"] } : {}),
82
+ ...(isPlainRecord(item.health) ? { health: item.health as unknown as PolicyCandidateEvidence["health"] } : {}),
83
+ ...(isPlainRecord(item.quota) ? { quota: item.quota as unknown as PolicyCandidateEvidence["quota"] } : {}),
84
+ ...(isPlainRecord(item.cost) ? { cost: item.cost as unknown as PolicyCandidateEvidence["cost"] } : {}),
85
+ // Derive account-scoped quota evidence from the documented refs when the
86
+ // caller does not supply an explicit quota object, so a dry-run following
87
+ // the documented shape reports the same cached account quota as routing.
88
+ ...(item.quota === undefined && typeof item.codexAccountId === "string"
89
+ ? { quota: quotaEvidenceForCandidate({ provider, model, codexAccountId: item.codexAccountId }) }
90
+ : {}),
91
+ ...(item.quota === undefined && typeof item.accountRef === "string" && typeof item.codexAccountId !== "string"
92
+ ? { quota: quotaEvidenceForCandidate({ provider, model, accountRef: item.accountRef }) }
93
+ : {}),
94
+ });
95
+ }
96
+ return out;
97
+ }
98
+
99
+ function assembleCandidateEvidence(
100
+ config: OcxConfig,
101
+ profile: NonNullable<ReturnType<typeof getRoutingProfile>>,
102
+ now: number,
103
+ ): PolicyCandidateEvidence[] {
104
+ return assemblePolicyCandidateEvidence(config, profile, now, {
105
+ routedProviderConfig,
106
+ });
107
+ }
108
+
109
+ function storedProfile(
110
+ id: string,
111
+ raw: OcxRoutingProfileConfig,
112
+ ): OcxRoutingProfileConfig {
113
+ const normalized = normalizeRoutingProfile(id, raw);
114
+ const {
115
+ id: _id,
116
+ revision: _revision,
117
+ alias,
118
+ ...profile
119
+ } = normalized;
120
+ return alias === null ? profile : { ...profile, alias };
121
+ }
122
+
123
+ /**
124
+ * Rewrite config references from one public model id to another (alias change
125
+ * on update). Mirrors the /api/combos migration: model-valued config that
126
+ * still names the old alias must follow it, or requests fall through to
127
+ * ordinary routing and send the obsolete alias upstream.
128
+ */
129
+ /**
130
+ * Detect a modelMap key collision that the alias migration would silently
131
+ * resolve by dropping one mapping: the map already contains the new public
132
+ * model as a key with a different target than the old-alias key's target.
133
+ */
134
+ function modelMapMigrationCollision(
135
+ config: OcxConfig,
136
+ oldPublicModel: string,
137
+ newPublicModel: string,
138
+ ): string | null {
139
+ const map = config.claudeCode?.modelMap;
140
+ if (!map) return null;
141
+ if (oldPublicModel === newPublicModel) return null;
142
+ const oldTarget = map[oldPublicModel];
143
+ if (oldTarget === undefined) return null;
144
+ const newTarget = map[newPublicModel];
145
+ if (newTarget === undefined) return null;
146
+ if (oldTarget === newTarget) return null;
147
+ return `modelMap already maps \"${newPublicModel}\" to \"${newTarget}\"; renaming \"${oldPublicModel}\" (→ \"${newTarget}\") would drop one mapping. Resolve the conflict and retry.`;
148
+ }
149
+
150
+ /**
151
+ * Rewrite config references from one public model id to another (alias change
152
+ * on update). Mirrors the /api/combos migration: model-valued config that
153
+ * still names the old alias must follow it, or requests fall through to
154
+ * ordinary routing and send the obsolete alias upstream.
155
+ */
156
+ function migrateProfileModelReferences(
157
+ config: OcxConfig,
158
+ oldPublicModel: string,
159
+ newPublicModel: string,
160
+ ): boolean {
161
+ if (oldPublicModel === newPublicModel) return false;
162
+ const migrateReference = (model: string): string => (
163
+ model === oldPublicModel ? newPublicModel : model
164
+ );
165
+ let shouldSyncClaudeAgentDefs = false;
166
+ const migrateAgentReference = (model: string): string => {
167
+ const migrated = migrateReference(model);
168
+ if (migrated !== model) shouldSyncClaudeAgentDefs = true;
169
+ return migrated;
170
+ };
171
+ const migrateReferences = (models: string[]): string[] => [
172
+ ...new Set(models.map(migrateReference)),
173
+ ];
174
+ if (config.disabledModels) {
175
+ config.disabledModels = migrateReferences(config.disabledModels);
176
+ }
177
+ if (config.subagentModels) {
178
+ config.subagentModels = [...new Set(config.subagentModels.map(migrateAgentReference))];
179
+ }
180
+ if (config.subagentModelFallback) {
181
+ config.subagentModelFallback = [...new Set(config.subagentModelFallback.map(migrateAgentReference))];
182
+ }
183
+ if (config.injectionModel && config.injectionModel === oldPublicModel) {
184
+ config.injectionModel = newPublicModel;
185
+ }
186
+ if (config.shadowCallIntercept?.model && config.shadowCallIntercept.model === oldPublicModel) {
187
+ config.shadowCallIntercept = { ...config.shadowCallIntercept, model: newPublicModel };
188
+ }
189
+ if (config.claudeCode) {
190
+ const claudeCode = { ...config.claudeCode };
191
+ for (const field of ["model", "smallFastModel"] as const) {
192
+ if (claudeCode[field]) claudeCode[field] = migrateAgentReference(claudeCode[field]);
193
+ }
194
+ if (claudeCode.tierModels) {
195
+ claudeCode.tierModels = Object.fromEntries(
196
+ Object.entries(claudeCode.tierModels).map(([tier, model]) => [tier, migrateAgentReference(model)]),
197
+ );
198
+ }
199
+ if (claudeCode.modelMap) {
200
+ // Keys are the inbound ids matched for reroute (src/claude/inbound.ts);
201
+ // an old-alias key must follow the rename or that request stops intercepting.
202
+ claudeCode.modelMap = Object.fromEntries(
203
+ Object.entries(claudeCode.modelMap).map(([source, model]) => [
204
+ migrateAgentReference(source),
205
+ migrateAgentReference(model),
206
+ ]),
207
+ );
208
+ }
209
+ config.claudeCode = claudeCode;
210
+ }
211
+ return shouldSyncClaudeAgentDefs;
212
+ }
213
+ export async function handleRoutingProfileRoutes(ctx: ManagementContext): Promise<Response | null> {
214
+ const { req, url, config, deps, convergeCodexCatalog, syncClaudeAgentDefsBestEffort } = ctx;
215
+
216
+ if (url.pathname === "/api/routing-profiles" && req.method === "GET") {
217
+ const profiles = listRoutingProfileIds(config).map(id => profileDto(config, id)).filter(
218
+ (profile): profile is Record<string, unknown> => profile !== null,
219
+ );
220
+ return jsonResponse({ profiles }, 200, req, config);
221
+ }
222
+
223
+ if (url.pathname === "/api/routing-profiles" && req.method === "PUT") {
224
+ let rawBody: unknown;
225
+ try {
226
+ rawBody = await readManagementJsonBody(req);
227
+ } catch (error) {
228
+ rethrowManagementBodyTooLarge(error);
229
+ return jsonResponse({ error: "invalid JSON body" }, 400, req, config);
230
+ }
231
+ if (!isPlainRecord(rawBody)) {
232
+ return jsonResponse({ error: "request body must be an object" }, 400, req, config);
233
+ }
234
+ const body = rawBody as Record<string, unknown>;
235
+ const id = typeof body.id === "string" ? body.id.trim() : "";
236
+ if (!id) {
237
+ return jsonResponse({ error: { code: "missing_profile_id", message: "id is required" } }, 400, req, config);
238
+ }
239
+ const mode = body.mode === "create" || body.mode === "update" ? body.mode : null;
240
+ if (!mode) {
241
+ return jsonResponse({ error: { code: "invalid_profile_mode", message: "mode must be create or update" } }, 400, req, config);
242
+ }
243
+ const exists = Object.hasOwn(config.routingProfiles ?? {}, id);
244
+ if (mode === "create" && exists) {
245
+ return jsonResponse({ error: { code: "profile_exists", message: `routing profile already exists: ${id}` } }, 409, req, config);
246
+ }
247
+ if (mode === "update" && !exists) {
248
+ return jsonResponse({ error: { code: "unknown_profile", message: `unknown routing profile: ${id}` } }, 404, req, config);
249
+ }
250
+ if (mode === "update") {
251
+ const expectedRevision = typeof body.expectedRevision === "string" && body.expectedRevision.trim()
252
+ ? body.expectedRevision.trim()
253
+ : undefined;
254
+ if (expectedRevision) {
255
+ const current = getRoutingProfile(config, id);
256
+ if (current && current.revision !== expectedRevision) {
257
+ return jsonResponse({
258
+ error: {
259
+ code: "profile_revision_conflict",
260
+ message: `routing profile ${id} changed since it was loaded; reload and retry`,
261
+ currentRevision: current.revision,
262
+ },
263
+ }, 409, req, config);
264
+ }
265
+ }
266
+ }
267
+ const issues = routingProfileIssues(id, body.profile, config, { excludeProfileId: id });
268
+ if (issues.length > 0) {
269
+ return jsonResponse({
270
+ error: {
271
+ code: "invalid_profile",
272
+ message: issues[0]!.message,
273
+ issues,
274
+ },
275
+ }, 400, req, config);
276
+ }
277
+
278
+ const previousProfile = mode === "update" ? getRoutingProfile(config, id) : undefined;
279
+ if (mode === "update" && previousProfile) {
280
+ const oldPublicModel = policyPublicModelId(id, previousProfile);
281
+ const newProfile = normalizeRoutingProfile(id, body.profile as OcxRoutingProfileConfig);
282
+ const newPublicModel = policyPublicModelId(id, newProfile);
283
+ const collision = modelMapMigrationCollision(config, oldPublicModel, newPublicModel);
284
+ if (collision) {
285
+ return jsonResponse({
286
+ error: { code: "alias_reference_conflict", message: collision },
287
+ }, 409, req, config);
288
+ }
289
+ }
290
+ const nextProfiles = { ...(config.routingProfiles ?? {}) };
291
+ nextProfiles[id] = storedProfile(id, body.profile as OcxRoutingProfileConfig);
292
+ config.routingProfiles = nextProfiles;
293
+ // Creating the first profile on a process started profile-less must install the
294
+ // compatibility provider now; activation is synchronous and idempotent per configDir.
295
+ if (labActivationRequired(config, getConfigDir())) activateLab(config, getConfigDir());
296
+ // An alias change on update renames the public model id; rewrite config
297
+ // references (disabledModels, subagentModels, injectionModel,
298
+ // shadowCallIntercept, claudeCode) so they follow the new alias.
299
+ let shouldSyncClaudeAgentDefs = false;
300
+ if (previousProfile) {
301
+ const oldPublicModel = policyPublicModelId(id, previousProfile);
302
+ const newPublicModel = policyPublicModelId(id, getRoutingProfile(config, id)!);
303
+ shouldSyncClaudeAgentDefs = migrateProfileModelReferences(config, oldPublicModel, newPublicModel);
304
+ }
305
+ const save = deps.saveConfigPreservingClaudeCode ?? saveConfigPreservingClaudeCode;
306
+ save(config);
307
+ reconcileLiveStateStores();
308
+ const catalogRefresh = await convergeCodexCatalog();
309
+ if (shouldSyncClaudeAgentDefs) await syncClaudeAgentDefsBestEffort();
310
+ const profile = profileDto(config, id)!;
311
+ return jsonResponse({
312
+ success: true,
313
+ id,
314
+ model: profile.model,
315
+ profile,
316
+ catalogRefresh,
317
+ }, 200, req, config);
318
+ }
319
+
320
+ if (url.pathname === "/api/routing-profiles" && req.method === "DELETE") {
321
+ const id = url.searchParams.get("id")?.trim();
322
+ if (!id) {
323
+ return jsonResponse({ error: "id query param is required" }, 400, req, config);
324
+ }
325
+ if (!Object.hasOwn(config.routingProfiles ?? {}, id)) {
326
+ return jsonResponse({ error: "unknown routing profile" }, 404, req, config);
327
+ }
328
+
329
+ const nextProfiles = { ...(config.routingProfiles ?? {}) };
330
+ delete nextProfiles[id];
331
+ if (Object.keys(nextProfiles).length > 0) config.routingProfiles = nextProfiles;
332
+ else delete config.routingProfiles;
333
+ const saveConfigPreservingClaudeCodeSafe = deps.saveConfigPreservingClaudeCode ?? saveConfigPreservingClaudeCode;
334
+ saveConfigPreservingClaudeCodeSafe(config);
335
+ reconcileLiveStateStores();
336
+ const catalogRefresh = await convergeCodexCatalog();
337
+ return jsonResponse({ success: true, id, catalogRefresh }, 200, req, config);
338
+ }
339
+
340
+ if (url.pathname === "/api/routing-profiles/dry-run" && req.method === "POST") {
341
+ let rawBody: unknown;
342
+ try { rawBody = await readManagementJsonBody(req); } catch (error) {
343
+ rethrowManagementBodyTooLarge(error);
344
+ return jsonResponse({ error: "invalid JSON body" }, 400, req, config);
345
+ }
346
+ if (!isPlainRecord(rawBody)) {
347
+ return jsonResponse({ error: "request body must be an object" }, 400, req, config);
348
+ }
349
+ const body = rawBody as Record<string, unknown>;
350
+ const profile = typeof body.profile === "string" ? body.profile.trim() : "";
351
+ if (!profile) {
352
+ return jsonResponse({ error: { code: "missing_profile", message: "profile is required" } }, 400, req, config);
353
+ }
354
+ const resolvedProfile = getRoutingProfile(config, profile);
355
+ if (!resolvedProfile) {
356
+ return jsonResponse({ error: { code: "unknown_profile", message: `unknown routing profile: ${profile}` } }, 404, req, config);
357
+ }
358
+ const { evidence, ok } = parseEvidence(body.evidence);
359
+ if (!ok) {
360
+ return jsonResponse({ error: { code: "invalid_evidence", message: "evidence must be an object" } }, 400, req, config);
361
+ }
362
+ // One clock read for both assembly and evaluation keeps freshness, health,
363
+ // and trace timestamps mutually consistent with the production router.
364
+ const now = Date.now();
365
+ // R3-1: dry-run assembles candidate evidence independently of the startup gate, so an
366
+ // operator preview on a process started without profiles would silently omit
367
+ // compatibility evidence and disagree with production. Activate first.
368
+ if (labActivationRequired(config, getConfigDir())) activateLab(config, getConfigDir());
369
+ const candidateEvidence = body.candidates === undefined
370
+ ? assembleCandidateEvidence(config, resolvedProfile, now)
371
+ : parseCandidateEvidence(body.candidates);
372
+ if (candidateEvidence === null) {
373
+ return jsonResponse({ error: { code: "invalid_candidates", message: "candidates must be an array of evidence objects" } }, 400, req, config);
374
+ }
375
+ const result = evaluatePolicyProfile(config, profile, evidence, candidateEvidence, now);
376
+ return jsonResponse(result, 200, req, config);
377
+ }
378
+
379
+ return null;
380
+ }