@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,412 @@
1
+ /**
2
+ * Evidence-based route health (RI-06).
3
+ *
4
+ * Health evidence combines:
5
+ * - live in-memory routing state: Codex account cooldown / soft-avoid
6
+ * (authoritative hard state);
7
+ * - historical evidence from the request-history index: success rate,
8
+ * consecutive failures, incomplete-stream rate, recent latency, sample
9
+ * count, recency-decayed weights.
10
+ *
11
+ * Failure classification is strict: client cancellations, invalid requests
12
+ * (4xx except quota 429) and synthetic policy refusals never damage target
13
+ * health. Transport-neutral failures are excluded by the classification the
14
+ * routing layer already records (host/account split per #914 work).
15
+ *
16
+ * All formulas are deterministic with documented constants; no ML.
17
+ */
18
+
19
+ import type { OcxConfig } from "../types";
20
+ import { openRequestHistoryIndexSync, requestHistoryDb } from "./history/indexer";
21
+ import { OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
22
+ import {
23
+ getCodexAccountCooldownUntil,
24
+ getCodexAccountSoftAvoidUntil,
25
+ getEffectiveActiveCodexAccountId,
26
+ isCodexAccountInCooldown,
27
+ listLiveCodexAccountIds,
28
+ } from "../codex/routing";
29
+ import type { RouteHealthEvidence } from "./trace";
30
+
31
+ export const HEALTH_SCORE_CONSTANTS = {
32
+ /** Recent-success weight in the composite. */
33
+ SUCCESS_WEIGHT: 0.50,
34
+ /** Incomplete-stream (negative) weight. */
35
+ INCOMPLETE_WEIGHT: 0.15,
36
+ /** Recent-latency weight. */
37
+ LATENCY_WEIGHT: 0.20,
38
+ /** Consecutive-failure recovery weight. */
39
+ RECOVERY_WEIGHT: 0.15,
40
+ /** p50 latency at or above this (ms) scores zero on the latency axis. */
41
+ LATENCY_TARGET_MS: 60_000,
42
+ /** Samples needed for full confidence; fewer samples scale the score down. */
43
+ MIN_CONFIDENCE_SAMPLES: 20,
44
+ /** Soft-avoid multiplies the composite. */
45
+ SOFT_AVOID_MULTIPLIER: 0.5,
46
+ /** Recency decay: a sample loses half its weight every RECENCY_HALF_LIFE_DAYS. */
47
+ RECENCY_HALF_LIFE_DAYS: 7,
48
+ } as const;
49
+
50
+ export const HEALTH_WINDOW_MS = 14 * 86_400_000;
51
+ export const HEALTH_MAX_SAMPLES = 100;
52
+
53
+ /**
54
+ * Historical health evidence is cached briefly across candidates within one
55
+ * routing decision (and rapid successive decisions). Live cooldown/soft-avoid
56
+ * state is always read fresh - it is never cached. The TTL bounds how stale
57
+ * the history index read may be; 1.5s keeps routing deterministic within a
58
+ * decision while bounding per-candidate SQLite work.
59
+ */
60
+ const HEALTH_HISTORY_CACHE_TTL_MS = 1_500;
61
+ const HEALTH_HISTORY_CACHE_MAX_ENTRIES = 64;
62
+
63
+ type HistoricalHealthEvidence = Pick<
64
+ RouteHealthEvidence,
65
+ "sampleCount" | "successRate" | "failures" | "incompleteStreamRate" | "recentLatencyMs" | "recencyWeight"
66
+ >;
67
+
68
+ const healthHistoryCache = new Map<string, { at: number; value: HistoricalHealthEvidence }>();
69
+
70
+ /** Test seam: routing tests append fresh rows and must not see cached history. */
71
+ export function clearHealthHistoryCacheForTests(): void {
72
+ healthHistoryCache.clear();
73
+ }
74
+
75
+ function healthHistoryCacheKey(input: Pick<HealthEvidenceInput, "provider" | "model" | "accountRef">): string {
76
+ return `${input.provider}\u0000${input.model}\u0000${input.accountRef ?? ""}`;
77
+ }
78
+
79
+ export interface HealthEvidenceInput {
80
+ provider: string;
81
+ model: string;
82
+ accountRef?: string;
83
+ /** Live codex account id for cooldown/soft-avoid state (provider "openai"). */
84
+ codexAccountId?: string;
85
+ now?: number;
86
+ }
87
+
88
+ interface HealthSample {
89
+ status: number;
90
+ closeReason: string | null;
91
+ terminalStatus: string | null;
92
+ durationMs: number;
93
+ timestamp: number;
94
+ }
95
+
96
+ interface HealthRow extends HealthSample {
97
+ attemptCount?: number;
98
+ rowJson?: string | null;
99
+ }
100
+
101
+ /**
102
+ * Per-attempt samples for a candidate from a row's persisted entry.
103
+ * Combo/failover requests store each upstream try in `entry.attempts` while
104
+ * the top-level row records the final outcome; a provider/model that failed
105
+ * as a non-final attempt must still contribute its own health samples.
106
+ */
107
+ function attemptSamplesFor(
108
+ row: Pick<HealthRow, "timestamp" | "attemptCount" | "rowJson">,
109
+ provider: string,
110
+ model: string,
111
+ ): HealthSample[] {
112
+ if (!row.rowJson || (row.attemptCount ?? 1) <= 1) return [];
113
+ try {
114
+ const parsed = JSON.parse(row.rowJson) as { attempts?: unknown };
115
+ if (!Array.isArray(parsed.attempts)) return [];
116
+ const samples: HealthSample[] = [];
117
+ for (const attempt of parsed.attempts) {
118
+ if (!attempt || typeof attempt !== "object" || Array.isArray(attempt)) continue;
119
+ const record = attempt as Record<string, unknown>;
120
+ if (record.provider !== provider || record.model !== model) continue;
121
+ if (typeof record.status !== "number" || typeof record.durationMs !== "number") continue;
122
+ samples.push({
123
+ status: record.status,
124
+ closeReason: null,
125
+ terminalStatus: null,
126
+ durationMs: record.durationMs,
127
+ timestamp: row.timestamp,
128
+ });
129
+ }
130
+ return samples;
131
+ } catch {
132
+ return [];
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Live Codex pool account state for an `openai` policy candidate.
138
+ *
139
+ * When a deterministic active account exists (manual selection or
140
+ * `config.activeCodexAccountId`) its cooldown/soft-avoid state is
141
+ * authoritative. Otherwise the target is conservatively cooled only when
142
+ * every live pool account is cooling or soft-avoided; account selection
143
+ * inside `src/codex/routing.ts` stays authoritative when any account is
144
+ * usable, so policy scoring never invents account choices.
145
+ */
146
+ export function codexPoolHealthEvidence(
147
+ config: Parameters<typeof getEffectiveActiveCodexAccountId>[0],
148
+ now = Date.now(),
149
+ ): Pick<RouteHealthEvidence, "cooldownUntilMs" | "softAvoidUntilMs"> | undefined {
150
+ const activeId = getEffectiveActiveCodexAccountId(config);
151
+ if (activeId) {
152
+ const until = getCodexAccountCooldownUntil(activeId, now);
153
+ if (until !== null) return { cooldownUntilMs: until };
154
+ const softUntil = getCodexAccountSoftAvoidUntil(activeId, now);
155
+ if (softUntil !== null && softUntil > now) return { softAvoidUntilMs: softUntil };
156
+ return undefined;
157
+ }
158
+ const live = [...listLiveCodexAccountIds(config)];
159
+ if (live.length === 0) return undefined;
160
+ const cooldowns: number[] = [];
161
+ const softAvoids: number[] = [];
162
+ for (const accountId of live) {
163
+ const until = getCodexAccountCooldownUntil(accountId, now);
164
+ if (until !== null) cooldowns.push(until);
165
+ const softUntil = getCodexAccountSoftAvoidUntil(accountId, now);
166
+ if (softUntil !== null && softUntil > now) softAvoids.push(softUntil);
167
+ }
168
+ if (cooldowns.length === live.length) return { cooldownUntilMs: Math.max(...cooldowns) };
169
+ // Every account is unavailable, but not uniformly hard-cooled (e.g. some in
170
+ // cooldown, some soft-avoided): degrade to soft-avoid with the latest expiry
171
+ // so scoring still penalizes the pool.
172
+ if (cooldowns.length + softAvoids.length >= live.length && softAvoids.length > 0) {
173
+ return { softAvoidUntilMs: Math.max(...softAvoids, ...cooldowns) };
174
+ }
175
+ return undefined;
176
+ }
177
+
178
+ /**
179
+ * Candidate health evidence assembled exactly like the router's policy path:
180
+ * historical evidence plus authoritative live Codex pool state for `openai`
181
+ * targets. Shared by the router and the dry-run management route so the two
182
+ * surfaces cannot drift apart.
183
+ */
184
+ export function policyCandidateHealthEvidence(
185
+ config: Parameters<typeof getEffectiveActiveCodexAccountId>[0],
186
+ candidate: { provider: string; model: string },
187
+ now = Date.now(),
188
+ ): RouteHealthEvidence {
189
+ return {
190
+ ...healthEvidenceForCandidate({
191
+ provider: candidate.provider,
192
+ model: candidate.model,
193
+ codexAccountId: candidate.provider === OPENAI_CODEX_PROVIDER_ID
194
+ ? getEffectiveActiveCodexAccountId(config)
195
+ : undefined,
196
+ now,
197
+ }),
198
+ // Live pool state stays authoritative for `openai` targets even when no
199
+ // account reference exists in the candidate evidence.
200
+ ...(candidate.provider === OPENAI_CODEX_PROVIDER_ID
201
+ ? (codexPoolHealthEvidence(config, now) ?? {})
202
+ : {}),
203
+ };
204
+ }
205
+
206
+ function classifySample(sample: HealthSample): "success" | "failure" | "neutral" {
207
+ if (sample.closeReason === "client_cancel" || sample.status === 499) return "neutral";
208
+ // Invalid requests and policy refusals must not poison target health.
209
+ if (sample.status >= 400 && sample.status < 500 && sample.status !== 429) return "neutral";
210
+ if (sample.terminalStatus === "incomplete") return "failure";
211
+ if (sample.terminalStatus && sample.terminalStatus !== "completed") return "failure";
212
+ if (sample.status >= 400) return "failure";
213
+ return "success";
214
+ }
215
+
216
+ function decayWeight(timestamp: number, now: number): number {
217
+ const ageDays = Math.max(0, now - timestamp) / 86_400_000;
218
+ return Math.pow(0.5, ageDays / HEALTH_SCORE_CONSTANTS.RECENCY_HALF_LIFE_DAYS);
219
+ }
220
+
221
+ function median(sorted: number[]): number | undefined {
222
+ if (sorted.length === 0) return undefined;
223
+ const mid = Math.floor(sorted.length / 2);
224
+ return sorted.length % 2 === 0 ? (sorted[mid - 1]! + sorted[mid]!) / 2 : sorted[mid]!;
225
+ }
226
+
227
+ /**
228
+ * Historical health evidence from the derived index (synchronous: called at
229
+ * routing time). Never throws; an unopened/unreadable index yields unknown.
230
+ */
231
+ function computeHistoricalHealthEvidence(
232
+ input: Pick<HealthEvidenceInput, "provider" | "model" | "accountRef">,
233
+ now: number,
234
+ ): HistoricalHealthEvidence {
235
+ try {
236
+ openRequestHistoryIndexSync();
237
+ const handle = requestHistoryDb();
238
+ const where: string[] = ["provider = ?", "model = ?", "timestamp >= ?"];
239
+ const values: Array<string | number> = [input.provider, input.model, now - HEALTH_WINDOW_MS];
240
+ if (input.accountRef) {
241
+ where.push("api_key_id = ?");
242
+ values.push(input.accountRef);
243
+ }
244
+ const rows = handle.query(
245
+ `SELECT status, close_reason AS closeReason, terminal_status AS terminalStatus,
246
+ duration_ms AS durationMs, timestamp,
247
+ attempt_count AS attemptCount, row_json AS rowJson
248
+ FROM requests WHERE ${where.join(" AND ")}
249
+ ORDER BY timestamp DESC LIMIT ?`,
250
+ ).all(...values, HEALTH_MAX_SAMPLES) as HealthRow[];
251
+ // Rows whose top-level target differs from this candidate may still carry
252
+ // candidate attempts (combo/failover): expand those too. The serialized
253
+ // provider/model LIKE prefilter keeps the LIMIT from being consumed by
254
+ // rows that cannot contribute samples for this candidate.
255
+ const escapeLike = (value: string): string => value.replace(/[\\%_]/g, match => `\\${match}`);
256
+ const attemptRows = handle.query(
257
+ `SELECT timestamp, attempt_count AS attemptCount, row_json AS rowJson
258
+ FROM requests WHERE timestamp >= ? AND attempt_count > 1
259
+ AND row_json LIKE ? ESCAPE '\\'
260
+ AND row_json LIKE ? ESCAPE '\\'
261
+ AND NOT (provider = ? AND model = ?)
262
+ ORDER BY timestamp DESC LIMIT ?`,
263
+ ).all(
264
+ now - HEALTH_WINDOW_MS,
265
+ `%\"provider\":\"${escapeLike(input.provider)}\"%`,
266
+ `%\"model\":\"${escapeLike(input.model)}\"%`,
267
+ input.provider,
268
+ input.model,
269
+ HEALTH_MAX_SAMPLES,
270
+ ) as Array<
271
+ Pick<HealthRow, "timestamp" | "attemptCount" | "rowJson">
272
+ >;
273
+
274
+ const samples: HealthSample[] = [];
275
+ for (const row of rows) {
276
+ const attemptSamples = attemptSamplesFor(row, input.provider, input.model);
277
+ samples.push(...(attemptSamples.length > 0 ? attemptSamples : [row]));
278
+ }
279
+ for (const row of attemptRows) {
280
+ samples.push(...attemptSamplesFor(row, input.provider, input.model));
281
+ }
282
+ // Newest first for the consecutive-failure walk; attempt samples inherit
283
+ // their row's timestamp.
284
+ samples.sort((a, b) => b.timestamp - a.timestamp);
285
+
286
+ let successes = 0;
287
+ let failures = 0;
288
+ let incompleteStreams = 0;
289
+ let weightedSuccess = 0;
290
+ let weightedTotal = 0;
291
+ const latencies: number[] = [];
292
+ for (const sample of samples) {
293
+ const kind = classifySample(sample);
294
+ const weight = decayWeight(sample.timestamp, now);
295
+ if (kind === "neutral") continue;
296
+ if (kind === "success") {
297
+ successes += 1;
298
+ weightedSuccess += weight;
299
+ weightedTotal += weight;
300
+ } else {
301
+ failures += 1;
302
+ weightedTotal += weight;
303
+ }
304
+ if (sample.terminalStatus === "incomplete") incompleteStreams += 1;
305
+ latencies.push(sample.durationMs);
306
+ }
307
+ // Consecutive failures: walk newest -> oldest until a success.
308
+ let consecutiveFailures = 0;
309
+ for (const sample of samples) {
310
+ const kind = classifySample(sample);
311
+ if (kind === "neutral") continue;
312
+ if (kind === "failure") consecutiveFailures += 1;
313
+ else break;
314
+ }
315
+
316
+ const sampleCount = successes + failures;
317
+ const out: HistoricalHealthEvidence = {};
318
+ if (sampleCount > 0) {
319
+ out.sampleCount = sampleCount;
320
+ out.successRate = weightedTotal > 0 ? weightedSuccess / weightedTotal : 0;
321
+ if (consecutiveFailures > 0) out.failures = consecutiveFailures;
322
+ if (incompleteStreams > 0) out.incompleteStreamRate = incompleteStreams / sampleCount;
323
+ latencies.sort((a, b) => a - b);
324
+ const p50 = median(latencies);
325
+ if (p50 !== undefined) out.recentLatencyMs = p50;
326
+ out.recencyWeight = decayWeight(samples[0]!.timestamp, now);
327
+ }
328
+ return out;
329
+ } catch {
330
+ /* index unreadable: evidence stays unknown */
331
+ return {};
332
+ }
333
+ }
334
+
335
+ export function healthEvidenceForCandidate(input: HealthEvidenceInput): RouteHealthEvidence {
336
+ const now = input.now ?? Date.now();
337
+ const evidence: RouteHealthEvidence = {};
338
+
339
+ // Live authoritative state: hard cooldown and soft-avoid for Codex pool
340
+ // accounts. Cooldown stays authoritative over any historical score. Always
341
+ // read fresh - never cached.
342
+ if (input.codexAccountId && input.provider === "openai") {
343
+ if (isCodexAccountInCooldown(input.codexAccountId, now)) {
344
+ const until = getCodexAccountCooldownUntil(input.codexAccountId, now);
345
+ if (until !== null) evidence.cooldownUntilMs = until;
346
+ }
347
+ const softAvoidUntil = getCodexAccountSoftAvoidUntil(input.codexAccountId, now);
348
+ if (softAvoidUntil !== null && softAvoidUntil > now) evidence.softAvoidUntilMs = softAvoidUntil;
349
+ }
350
+
351
+ const cacheKey = healthHistoryCacheKey(input);
352
+ const cached = healthHistoryCache.get(cacheKey);
353
+ if (cached && now - cached.at < HEALTH_HISTORY_CACHE_TTL_MS) {
354
+ Object.assign(evidence, cached.value);
355
+ } else {
356
+ const history = computeHistoricalHealthEvidence(input, now);
357
+ Object.assign(evidence, history);
358
+ if (healthHistoryCache.size >= HEALTH_HISTORY_CACHE_MAX_ENTRIES) {
359
+ let oldestKey: string | null = null;
360
+ let oldestAt = Number.POSITIVE_INFINITY;
361
+ for (const [key, entry] of healthHistoryCache) {
362
+ if (entry.at < oldestAt) {
363
+ oldestAt = entry.at;
364
+ oldestKey = key;
365
+ }
366
+ }
367
+ if (oldestKey !== null) healthHistoryCache.delete(oldestKey);
368
+ }
369
+ healthHistoryCache.set(cacheKey, { at: now, value: history });
370
+ }
371
+
372
+ return evidence;
373
+ }
374
+
375
+ /**
376
+ * Deterministic latency score in [0,1] from the recorded p50, shared by the health
377
+ * composite and the standalone `optimize.latency` term so the two cannot drift apart.
378
+ *
379
+ * An unmeasured candidate scores the NEUTRAL midpoint, not 0. Punishing it into last
380
+ * place would make selection depend on which candidate happened to be exercised first,
381
+ * which is the order-dependence this scoring exists to remove.
382
+ */
383
+ export function latencyScoreFromEvidence(evidence: RouteHealthEvidence | undefined): number {
384
+ const p50 = evidence?.recentLatencyMs;
385
+ if (p50 === undefined) return 0.5;
386
+ return Math.max(0, Math.min(1, 1 - p50 / HEALTH_SCORE_CONSTANTS.LATENCY_TARGET_MS));
387
+ }
388
+
389
+ /**
390
+ * Deterministic health score in [0,1]. Returns null when evidence is unknown
391
+ * (no samples) so callers can apply the profile's unknownEvidence policy.
392
+ * A live hard cooldown scores 0 (authoritative).
393
+ */
394
+ export function healthScore(evidence: RouteHealthEvidence | undefined, now = Date.now()): number | null {
395
+ if (!evidence) return null;
396
+ if (evidence.cooldownUntilMs !== undefined && evidence.cooldownUntilMs > now) return 0;
397
+ if (!evidence.sampleCount || evidence.sampleCount < 1) return null;
398
+ const successRate = evidence.successRate ?? 0;
399
+ const incompleteRate = evidence.incompleteStreamRate ?? 0;
400
+ const latency = latencyScoreFromEvidence(evidence);
401
+ const consecutive = evidence.failures ?? 0;
402
+ const recoveryScore = 1 - Math.min(1, consecutive / 5);
403
+ const composite = HEALTH_SCORE_CONSTANTS.SUCCESS_WEIGHT * successRate
404
+ + HEALTH_SCORE_CONSTANTS.INCOMPLETE_WEIGHT * (1 - incompleteRate)
405
+ + HEALTH_SCORE_CONSTANTS.LATENCY_WEIGHT * latency
406
+ + HEALTH_SCORE_CONSTANTS.RECOVERY_WEIGHT * recoveryScore;
407
+ const confidence = Math.min(1, evidence.sampleCount / HEALTH_SCORE_CONSTANTS.MIN_CONFIDENCE_SAMPLES);
408
+ const softAvoid = evidence.softAvoidUntilMs !== undefined && evidence.softAvoidUntilMs > now
409
+ ? HEALTH_SCORE_CONSTANTS.SOFT_AVOID_MULTIPLIER
410
+ : 1;
411
+ return composite * confidence * softAvoid;
412
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Opaque keyset cursor for request-history pagination (RI-02, ADR-9).
3
+ *
4
+ * Ordering is `timestamp DESC, request_id DESC`; the cursor encodes the last
5
+ * returned row's `(timestamp, requestId)` pair as base64url JSON. Cursors are
6
+ * opaque to clients: any decode failure or shape mismatch yields `null` and
7
+ * the API answers `400 invalid_cursor` instead of guessing.
8
+ */
9
+
10
+ export interface HistoryCursor {
11
+ t: number;
12
+ i: string;
13
+ }
14
+
15
+ export class InvalidCursorError extends Error {
16
+ readonly code = "invalid_cursor" as const;
17
+
18
+ constructor() {
19
+ super("invalid_cursor");
20
+ this.name = "InvalidCursorError";
21
+ }
22
+ }
23
+
24
+ export function encodeHistoryCursor(cursor: HistoryCursor): string {
25
+ return Buffer.from(JSON.stringify(cursor)).toString("base64url");
26
+ }
27
+
28
+ export function decodeHistoryCursor(raw: string | null | undefined): HistoryCursor | null {
29
+ if (typeof raw !== "string" || raw.length === 0 || raw.length > 4096) return null;
30
+ let parsed: unknown;
31
+ try {
32
+ parsed = JSON.parse(Buffer.from(raw, "base64url").toString("utf-8"));
33
+ } catch {
34
+ return null;
35
+ }
36
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
37
+ const record = parsed as Record<string, unknown>;
38
+ const t = record.t;
39
+ const i = record.i;
40
+ if (typeof t !== "number" || !Number.isFinite(t)) return null;
41
+ if (typeof i !== "string" || i.length === 0 || i.length > 256) return null;
42
+ return { t, i };
43
+ }