@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
@@ -9,7 +9,7 @@
9
9
  * Modelled after src/codex/routing.ts cooldown logic but scoped to plain API-key pools.
10
10
  */
11
11
  import { saveConfigPreservingClaudeCode } from "../config";
12
- import type { OcxConfig, OcxProviderConfig } from "../types";
12
+ import type { OcxConfig, OcxProviderConfig, RateLimitRetryPolicy } from "../types";
13
13
  import { resolveProviderTransport, type OcxProviderTransport } from "./xai-transport";
14
14
  import { sweepExpiredOnWrite } from "../lib/state-store-sweeper";
15
15
 
@@ -22,6 +22,18 @@ interface KeyCooldown {
22
22
  const DEFAULT_COOLDOWN_MS = 60_000;
23
23
  const MAX_COOLDOWN_MS = 10 * 60_000; // cap at 10 min for api-key rotation
24
24
 
25
+ /**
26
+ * Default same-target 429 retry policy used when a provider opts in via a bare
27
+ * `retryOn429: {}` (presence = opt-in with these defaults).
28
+ */
29
+ const DEFAULT_RATE_LIMIT_RETRY = {
30
+ enabled: true,
31
+ attempts: 3,
32
+ intervalMs: 5_000,
33
+ maxIntervalMs: 60_000,
34
+ respectRetryAfter: true,
35
+ } as const satisfies Required<RateLimitRetryPolicy>;
36
+
25
37
  /** Map<`${providerName}\0${keyId}`, KeyCooldown> */
26
38
  const keyCooldowns = new Map<string, KeyCooldown>();
27
39
 
@@ -29,21 +41,32 @@ function cooldownKey(providerName: string, keyId: string): string {
29
41
  return `${providerName}\0${keyId}`;
30
42
  }
31
43
 
44
+ /**
45
+ * Parse an upstream `Retry-After` header: numeric seconds (including `0`) or an HTTP-date.
46
+ * Returns a bounded delay in ms (1..MAX_COOLDOWN_MS), or undefined when the value is
47
+ * malformed. An HTTP-date already in the past yields an immediate (1 ms) retry.
48
+ */
32
49
  function parseRetryAfterMs(value: string | null | undefined, now = Date.now()): number | undefined {
33
50
  const text = value?.trim();
34
51
  if (!text) return undefined;
35
52
  if (/^\d+(?:\.\d+)?$/.test(text)) {
36
53
  const seconds = Number(text);
37
- if (Number.isFinite(seconds) && seconds > 0) {
54
+ if (Number.isFinite(seconds) && seconds >= 0) {
38
55
  return Math.min(Math.max(Math.ceil(seconds * 1000), 1), MAX_COOLDOWN_MS);
39
56
  }
40
57
  }
41
58
  const timestamp = Date.parse(text);
42
59
  if (!Number.isFinite(timestamp)) return undefined;
43
60
  const delay = timestamp - now;
44
- return delay > 0 ? Math.min(delay, MAX_COOLDOWN_MS) : undefined;
61
+ // A valid HTTP-date whose retry time has already passed is an immediate retry, exactly like
62
+ // numeric `Retry-After: 0` — never a malformed-header fallback to the fixed interval.
63
+ return Math.min(Math.max(delay, 1), MAX_COOLDOWN_MS);
45
64
  }
46
65
 
66
+ /**
67
+ * True while the given key is inside its 429 cooldown window (lazily evicting the entry once the
68
+ * window expires). Used to skip keys that the upstream just rate-limited during failover.
69
+ */
47
70
  function isKeyInCooldown(providerName: string, keyId: string, now = Date.now()): boolean {
48
71
  const entry = keyCooldowns.get(cooldownKey(providerName, keyId));
49
72
  if (!entry) return false;
@@ -65,6 +88,51 @@ export function hasKeyPoolFailover(provider: OcxProviderConfig): boolean {
65
88
  return (provider.apiKeyPool?.length ?? 0) >= 2;
66
89
  }
67
90
 
91
+ /**
92
+ * Normalize a provider's `retryOn429` policy, or return null when the knob is absent,
93
+ * explicitly disabled, or the provider is not key-auth (OAuth/forward credentials must not be
94
+ * replayed on the same token, forward passthrough never reaches the recovery loop anyway, and
95
+ * local runtimes have no remote key to preserve). The returned policy is fully defaulted so
96
+ * callers never re-check fields.
97
+ */
98
+ export function rateLimitRetryPolicyFor(
99
+ provider: Pick<OcxProviderConfig, "retryOn429" | "authMode">,
100
+ ): Required<RateLimitRetryPolicy> | null {
101
+ const policy = provider.retryOn429;
102
+ if (!policy || policy.enabled === false) return null;
103
+ // Fail closed: only explicit key auth or the documented omitted-default (undefined == key for
104
+ // custom API-key providers) may use same-key replays. OAuth/forward are never replayed on the
105
+ // same token, local runtimes have no remote key to preserve, and unknown/custom values are
106
+ // rejected rather than guessed at.
107
+ if (provider.authMode !== undefined && provider.authMode !== "key") return null;
108
+ return {
109
+ enabled: policy.enabled ?? DEFAULT_RATE_LIMIT_RETRY.enabled,
110
+ attempts: policy.attempts ?? DEFAULT_RATE_LIMIT_RETRY.attempts,
111
+ intervalMs: policy.intervalMs ?? DEFAULT_RATE_LIMIT_RETRY.intervalMs,
112
+ maxIntervalMs: policy.maxIntervalMs ?? DEFAULT_RATE_LIMIT_RETRY.maxIntervalMs,
113
+ respectRetryAfter: policy.respectRetryAfter ?? DEFAULT_RATE_LIMIT_RETRY.respectRetryAfter,
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Wait before the next same-target replay: upstream Retry-After (seconds or HTTP-date) when
119
+ * `respectRetryAfter` is on and the header parses, capped at `maxIntervalMs`; otherwise the
120
+ * fixed `intervalMs`, also capped at `maxIntervalMs` (a single wait never exceeds the cap).
121
+ * Malformed headers fall back to the fixed interval.
122
+ */
123
+ export function rateLimitRetryDelayMs(
124
+ policy: Required<RateLimitRetryPolicy>,
125
+ retryAfterHeader: string | null | undefined,
126
+ now = Date.now(),
127
+ ): number {
128
+ const raw = retryAfterHeader?.trim();
129
+ if (policy.respectRetryAfter && raw) {
130
+ const parsed = parseRetryAfterMs(raw, now);
131
+ if (parsed !== undefined) return Math.min(parsed, policy.maxIntervalMs);
132
+ }
133
+ return Math.min(policy.intervalMs, policy.maxIntervalMs);
134
+ }
135
+
68
136
  /**
69
137
  * Record a 429 for the current key and attempt to switch to the next available one.
70
138
  *
@@ -1,6 +1,6 @@
1
1
  import { CODEX_ACCOUNT_LOG_LABEL_RE } from "../codex/account-label";
2
2
 
3
- function canonicalUsageProviderLabel(provider: string): string {
3
+ export function canonicalUsageProviderLabel(provider: string): string {
4
4
  return provider === "chatgpt" || provider === "openai-multi" ? "openai" : provider;
5
5
  }
6
6
 
@@ -0,0 +1,16 @@
1
+ /** Hard process-wide limits shared by all live model-discovery parsers. */
2
+ export const MODEL_DISCOVERY_MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
3
+ export const MODEL_DISCOVERY_MAX_MODELS = 2_000;
4
+ export const MODEL_DISCOVERY_MAX_MODEL_ID_LENGTH = 1_024;
5
+
6
+ const MODEL_DISCOVERY_MODEL_ID_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;
7
+
8
+ /** Reject model IDs that cannot safely be published as callable catalog selectors. */
9
+ export function isValidModelDiscoveryModelId(value: unknown): value is string {
10
+ if (typeof value !== "string") return false;
11
+ const normalized = value.trim();
12
+ return Boolean(normalized)
13
+ && normalized === value
14
+ && normalized.length <= MODEL_DISCOVERY_MAX_MODEL_ID_LENGTH
15
+ && !MODEL_DISCOVERY_MODEL_ID_CONTROL_CHARS.test(normalized);
16
+ }
@@ -1,20 +1,28 @@
1
1
  import type { OcxProviderConfig } from "../types";
2
+ import {
3
+ isValidModelDiscoveryModelId,
4
+ MODEL_DISCOVERY_MAX_MODEL_ID_LENGTH,
5
+ MODEL_DISCOVERY_MAX_MODELS,
6
+ MODEL_DISCOVERY_MAX_RESPONSE_BYTES,
7
+ } from "./model-discovery-limits";
8
+ export {
9
+ isValidModelDiscoveryModelId,
10
+ MODEL_DISCOVERY_MAX_MODEL_ID_LENGTH,
11
+ MODEL_DISCOVERY_MAX_MODELS,
12
+ MODEL_DISCOVERY_MAX_RESPONSE_BYTES,
13
+ } from "./model-discovery-limits";
2
14
  import {
3
15
  getProviderRegistryEntry,
4
16
  providerMatchesRegistryTransport,
17
+ registryEntryForProviderDestination,
5
18
  type ProviderModelDiscoveryFilter,
6
19
  type ProviderModelDiscoveryPredicate,
7
20
  type ProviderModelDiscoveryScalar,
8
21
  type ProviderModelDiscoverySpec,
9
22
  } from "./registry";
10
23
 
11
- /** Hard process-wide limits. Registry entries may lower, but never raise, these ceilings. */
12
- export const MODEL_DISCOVERY_MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
13
- export const MODEL_DISCOVERY_MAX_MODELS = 2_000;
14
- export const MODEL_DISCOVERY_MAX_MODEL_ID_LENGTH = 1_024;
15
24
  const MODEL_DISCOVERY_MAX_FILTER_VALUES = 256;
16
25
  const MODEL_DISCOVERY_MAX_FILTER_STRING_LENGTH = 1_024;
17
- const MODEL_DISCOVERY_MODEL_ID_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;
18
26
 
19
27
  export interface ResolvedProviderModelDiscovery {
20
28
  spec?: ProviderModelDiscoverySpec;
@@ -91,7 +99,12 @@ export function providerModelDiscoverySpecError(spec: ProviderModelDiscoverySpec
91
99
  return "discovery path must be a query-free relative/origin path";
92
100
  }
93
101
  if (path.includes("\\")) return "discovery path must use forward slashes";
94
- if (path.split("/").some(segment => segment.replace(/%2e/gi, ".") === "..")) {
102
+ const segments = path.split("/");
103
+ if (segments.some((segment, index) => {
104
+ const decoded = segment.replace(/%2e/gi, ".");
105
+ if (decoded !== "..") return false;
106
+ return index !== 0 || segments.filter(s => s.replace(/%2e/gi, ".") === "..").length !== 1;
107
+ })) {
95
108
  return "discovery path must not contain parent-directory segments";
96
109
  }
97
110
  }
@@ -124,9 +137,14 @@ export function resolveProviderModelDiscovery(
124
137
  providerName: string,
125
138
  provider: Pick<OcxProviderConfig, "baseUrl" | "adapter"> & Partial<Pick<OcxProviderConfig, "authMode">>,
126
139
  ): ResolvedProviderModelDiscovery {
127
- const entry = providerMatchesRegistryTransport(providerName, provider)
128
- ? getProviderRegistryEntry(providerName)
129
- : undefined;
140
+ // The dashboard permits a canonical preset to be saved under a different name. Recover its
141
+ // registry-owned discovery policy by transport in that case. The destination helper is limited
142
+ // to exact fixed-key baseUrl + adapter matches, so custom endpoints, OAuth rows, templates, and
143
+ // overridable destinations cannot acquire another provider's discovery URL or filter.
144
+ const namedEntry = getProviderRegistryEntry(providerName);
145
+ const entry = namedEntry
146
+ ? (providerMatchesRegistryTransport(providerName, provider) ? namedEntry : undefined)
147
+ : registryEntryForProviderDestination(provider);
130
148
  const spec = entry?.modelDiscovery;
131
149
  return {
132
150
  ...(spec ? { spec } : {}),
@@ -313,12 +331,83 @@ export function extractModelEnvelopeRows(
313
331
  }
314
332
 
315
333
  /** Validate, bound, deduplicate, and declaratively filter OpenAI `{data:[...]}` or top-level arrays (Together `#617`). */
334
+ /**
335
+ * Metadata a sibling `models[]` array may contribute to an ALREADY-ADMITTED
336
+ * `data[]` row (#1797).
337
+ *
338
+ * llama.cpp serves a dual-envelope body: an Ollama-style `models[]` array
339
+ * carrying `capabilities` alongside the OpenAI-style `data[]` array carrying
340
+ * `meta`, so the two halves of one model's metadata never meet and a server
341
+ * that truthfully advertises "multimodal" produced an image-blind row.
342
+ *
343
+ * Two boundaries make this safe, and both were added after review found the
344
+ * first attempt unsound:
345
+ *
346
+ * 1. It runs AFTER admission filtering. Enriching first let a sibling supply
347
+ * the exact field a provider filter requires — reproduced against the real
348
+ * Chutes policy, where a row lacking `supported_features: ["tools"]` was
349
+ * admitted once a same-id sibling provided it. Enrichment may change what is
350
+ * KNOWN about a model, never WHICH models are published.
351
+ * 2. Only the capability keys #1797 needs are copied. A blanket "fill every
352
+ * absent key" made the untrusted `models[]` array a way into any field the
353
+ * pipeline consumes.
354
+ *
355
+ * The list deliberately EXCLUDES `supported_features` and `features`, even
356
+ * though both are capability-shaped: they are the two keys real provider
357
+ * filters test (`registry.ts:1591` requires `supported_features` to contain
358
+ * "tools"; `registry.ts:1883` tests `features.tool_use`). Ordering already
359
+ * prevents a sibling from flipping an admission verdict, but a key that is
360
+ * both enrichable and filter-relevant is one refactor away from becoming a
361
+ * bypass again. #1797 does not need them.
362
+ */
363
+ const SIBLING_ENRICHABLE_KEYS = new Set(["capabilities", "modalities", "input_modalities"]);
364
+
365
+ type SiblingIndex = Map<string, Record<string, unknown> | null>;
366
+
367
+ function buildSiblingIndex(value: unknown, limit: number): SiblingIndex | null {
368
+ const record = plainObject(value);
369
+ const sibling = record?.models;
370
+ if (!Array.isArray(sibling) || sibling.length === 0 || sibling.length > limit) return null;
371
+
372
+ const byId: SiblingIndex = new Map();
373
+ for (const raw of sibling) {
374
+ const entry = plainObject(raw);
375
+ if (!entry) continue;
376
+ for (const key of ["id", "model", "name"]) {
377
+ const id = entry[key];
378
+ if (typeof id !== "string" || id.length === 0) continue;
379
+ // `null` marks an ambiguous id: two sibling entries claim it, so neither
380
+ // can be attributed with confidence and both are ignored.
381
+ byId.set(id, byId.has(id) && byId.get(id) !== entry ? null : entry);
382
+ }
383
+ }
384
+ return byId.size > 0 ? byId : null;
385
+ }
386
+
387
+ function enrichAdmittedModel(item: ProviderModelsApiItem, siblings: SiblingIndex): ProviderModelsApiItem {
388
+ const extra = siblings.get(item.id);
389
+ if (!extra) return item;
390
+ let merged: Record<string, unknown> | null = null;
391
+ for (const key of SIBLING_ENRICHABLE_KEYS) {
392
+ if (!(key in extra) || key in item) continue;
393
+ merged ??= { ...(item as Record<string, unknown>) };
394
+ merged[key] = extra[key];
395
+ }
396
+ return (merged ?? item) as ProviderModelsApiItem;
397
+ }
398
+
399
+ function plainObject(value: unknown): Record<string, unknown> | null {
400
+ return typeof value === "object" && value !== null && !Array.isArray(value)
401
+ ? value as Record<string, unknown>
402
+ : null;
403
+ }
316
404
  export function extractProviderModelItems(
317
405
  value: unknown,
318
406
  discovery: ResolvedProviderModelDiscovery,
319
407
  ): ProviderModelItemsResult {
320
408
  const limit = positiveIntegerAtMost(discovery.maxModels, MODEL_DISCOVERY_MAX_MODELS);
321
409
  let data: unknown[];
410
+ let siblings: SiblingIndex | null = null;
322
411
  if (Array.isArray(value)) {
323
412
  // Together-style top-level /models arrays. Catalog discovery must not treat a stray
324
413
  // `models` key on openai-chat responses as valid — only `data` envelopes or top-level arrays.
@@ -328,6 +417,7 @@ export function extractProviderModelItems(
328
417
  const envelope = extractModelEnvelopeRows(value, discovery.maxModels, ["data"]);
329
418
  if (!envelope.ok) return envelope;
330
419
  data = envelope.rows;
420
+ siblings = buildSiblingIndex(value, limit);
331
421
  }
332
422
 
333
423
  const items: ProviderModelsApiItem[] = [];
@@ -337,20 +427,23 @@ export function extractProviderModelItems(
337
427
  return { ok: false, reason: "invalid_shape" };
338
428
  }
339
429
  const id = (raw as { id?: unknown }).id;
340
- if (typeof id !== "string") return { ok: false, reason: "invalid_shape" };
341
- const normalizedId = id.trim();
342
- if (
343
- !normalizedId
344
- || normalizedId !== id
345
- || normalizedId.length > MODEL_DISCOVERY_MAX_MODEL_ID_LENGTH
346
- || MODEL_DISCOVERY_MODEL_ID_CONTROL_CHARS.test(normalizedId)
347
- ) {
348
- return { ok: false, reason: "invalid_shape" };
430
+ if (!isValidModelDiscoveryModelId(id)) return { ok: false, reason: "invalid_shape" };
431
+ const prefix = discovery.spec?.stripIdPrefix;
432
+ let finalId = id;
433
+ if (prefix && finalId.startsWith(prefix)) {
434
+ finalId = finalId.slice(prefix.length);
435
+ if (!isValidModelDiscoveryModelId(finalId)) continue;
349
436
  }
350
- const item = raw as ProviderModelsApiItem;
351
- if (!providerModelMatchesDiscoveryFilter(item, discovery.spec?.filter) || seen.has(normalizedId)) continue;
352
- seen.add(normalizedId);
353
- items.push(item);
437
+ const item = finalId === id ? raw as ProviderModelsApiItem : { ...(raw as ProviderModelsApiItem), id: finalId };
438
+ // Admission is decided on the ORIGINAL `data[]` row, before any sibling
439
+ // enrichment. Merging first let a `models[]` entry supply the very field a
440
+ // provider filter requires — reproduced against the real Chutes policy,
441
+ // where a row lacking `supported_features: ["tools"]` was admitted once a
442
+ // same-id sibling provided it. Enrichment must never change WHICH models
443
+ // are published, only what is known about an already-admitted one.
444
+ if (!providerModelMatchesDiscoveryFilter(item, discovery.spec?.filter) || seen.has(finalId)) continue;
445
+ seen.add(finalId);
446
+ items.push(siblings ? enrichAdmittedModel(item, siblings) : item);
354
447
  }
355
448
  return { ok: true, items, rawCount: data.length };
356
449
  }
@@ -0,0 +1,255 @@
1
+ // Registry model renames do not reach a saved provider config on their own.
2
+ //
3
+ // `reconcileOAuthProviders` refuses to touch a row whose `authMode` is not
4
+ // `oauth` (src/oauth/index.ts), and `enrichProviderFromRegistry` is fill-only by
5
+ // design: it backfills a MISSING field and never rewrites a present one, so a
6
+ // user's hand-edited model list survives an upgrade. Both postures are correct.
7
+ // Their gap is the case where the registry did not ADD a model but RENAMED one:
8
+ // the saved row keeps a retired id forever, the supported id never appears, and
9
+ // the capability metadata stays keyed to an id the vendor is taking offline
10
+ // (issue #1610 — `qwen3.8-max-preview` persisted through the `qwen3.8-max`
11
+ // rename in six separate fields, including a reasoning ladder that had since
12
+ // diverged from the registry's).
13
+ //
14
+ // This migration is deliberately NOT general reconciliation. It rewrites exactly
15
+ // one thing: an id this file declares retired, on a provider that still carries
16
+ // the registry's transport, and only when the registry currently seeds the
17
+ // replacement. Everything else in the row is left alone.
18
+
19
+ import { PROVIDER_REGISTRY } from "./registry";
20
+ import type { OcxConfig, OcxProviderConfig } from "../types";
21
+
22
+ export interface ModelRename {
23
+ /** Registry provider id whose saved rows may carry the retired model id. */
24
+ provider: string;
25
+ from: string;
26
+ to: string;
27
+ /** Why the vendor retired it, for the startup warning and future readers. */
28
+ reason: string;
29
+ /**
30
+ * Drop the retired key from `modelReasoningEffortMap` instead of renaming it.
31
+ *
32
+ * Renaming preserves the VALUE, which is right for records whose values describe the
33
+ * model — a context window or an effort ladder survives a rename — and wrong for the
34
+ * one record whose values are themselves wire ids. An effort map saved as
35
+ * `high -> gemini-3.6-flash-high` would keep naming a dead wire id under the new key,
36
+ * and the adapter maps effort BEFORE resolving CCA routing, so that value arrives as
37
+ * an unrecognised effort and silently degrades to the default tier.
38
+ *
39
+ * Only that one record is dropped. Emptying the others would be worse than the bug:
40
+ * catalog enrichment treats an existing `{}` as "already populated" and will not
41
+ * restore the registry's records, so the migrated user would keep routing correctly
42
+ * but lose the reasoning picker entirely.
43
+ */
44
+ dropReasoningEffortMap?: boolean;
45
+ }
46
+
47
+ /**
48
+ * Renames already applied to `PROVIDER_REGISTRY`. An entry stays here after the
49
+ * registry moves on: it is what repairs configs saved before that move. Removing
50
+ * one strands every config that has not started since the rename shipped.
51
+ */
52
+ export const MODEL_RENAMES: readonly ModelRename[] = [
53
+ {
54
+ provider: "alibaba-token-plan",
55
+ from: "qwen3.8-max-preview",
56
+ to: "qwen3.8-max",
57
+ reason: "Alibaba shipped Qwen3.8-Max as stable and documents the preview endpoint as liable to be taken offline once preview concludes",
58
+ },
59
+ {
60
+ provider: "alibaba-token-plan-intl",
61
+ from: "qwen3.8-max-preview",
62
+ to: "qwen3.8-max",
63
+ reason: "Alibaba shipped Qwen3.8-Max as stable and documents the preview endpoint as liable to be taken offline once preview concludes",
64
+ },
65
+ // Antigravity Flash generations. Google takes the previous Flash model off Cloud Code
66
+ // Assist almost immediately when the next ships, so a saved 3.6 (or older 3.5) id is a
67
+ // dead selection rather than a merely outdated one. Routing already redirects these ids
68
+ // at request time; this migration repairs the saved config so the picker, the allowlist
69
+ // and the capability maps stop naming a model the backend no longer serves.
70
+ ...(["gemini-3.6-flash", "gemini-3.6-flash-low", "gemini-3.6-flash-medium", "gemini-3.6-flash-high",
71
+ "gemini-3.5-flash-extra-low", "gemini-3.5-flash-low", "gemini-3.5-flash-mid", "gemini-3.5-flash-high",
72
+ "gemini-3-flash-agent"] as const).map(from => ({
73
+ provider: "google-antigravity",
74
+ from,
75
+ to: "gemini-3.7-flash",
76
+ reason: "Google retires the previous Antigravity Flash generation from Cloud Code Assist when its successor ships, so the saved id no longer resolves to a live model",
77
+ // The retired Flash tiers were wire ids, so any saved per-model record keyed by one
78
+ // may also hold one as a value. 3.7 expresses tiers as thinkingLevel names instead.
79
+ dropReasoningEffortMap: true,
80
+ })),
81
+ ];
82
+
83
+ /** Provider fields that key metadata by model id. */
84
+ const MODEL_KEYED_RECORDS = [
85
+ "modelContextWindows",
86
+ "modelMaxOutputTokens",
87
+ "modelInputModalities",
88
+ "modelReasoningEfforts",
89
+ "modelDefaultReasoningEfforts",
90
+ "modelReasoningEffortMap",
91
+ ] as const;
92
+
93
+ /** Provider fields that are flat lists of model ids. */
94
+ const MODEL_ID_LISTS = [
95
+ "models",
96
+ // A retired id left here is worse than a stale label: `filterCatalogModels` treats
97
+ // `selectedModels` as an exact-match allowlist, so a user who allowlisted only the
98
+ // retired model gets NO replacement row at all — the model silently vanishes from
99
+ // their catalog instead of being renamed. OAuth reconciliation does not cover this
100
+ // field, so the rename has to.
101
+ "selectedModels",
102
+ "noVisionModels",
103
+ "noReasoningModels",
104
+ "noTemperatureModels",
105
+ "noTopPModels",
106
+ "noPenaltyModels",
107
+ "autoToolChoiceOnlyModels",
108
+ "preserveReasoningContentModels",
109
+ "thinkingBudgetModels",
110
+ "directReasoningEffortModels",
111
+ ] as const;
112
+
113
+ function renameInList(value: unknown, from: string, to: string): string[] | null {
114
+ if (!Array.isArray(value) || !value.includes(from)) return null;
115
+ const seen = new Set<string>();
116
+ const next: string[] = [];
117
+ // Rename in place to preserve ordering, and collapse a duplicate if the target
118
+ // id was already present alongside the retired one.
119
+ for (const entry of value) {
120
+ if (typeof entry !== "string") continue;
121
+ const mapped = entry === from ? to : entry;
122
+ if (seen.has(mapped)) continue;
123
+ seen.add(mapped);
124
+ next.push(mapped);
125
+ }
126
+ return next;
127
+ }
128
+
129
+ function renameInRecord(value: unknown, from: string, to: string): Record<string, unknown> | null {
130
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
131
+ const record = value as Record<string, unknown>;
132
+ if (!(from in record)) return null;
133
+ const next: Record<string, unknown> = {};
134
+ for (const [key, entry] of Object.entries(record)) {
135
+ const mapped = key === from ? to : key;
136
+ if (mapped in next) continue;
137
+ // An explicit entry already saved under the new id is the newer intent.
138
+ next[mapped] = key === from && to in record ? record[to] : entry;
139
+ }
140
+ return next;
141
+ }
142
+
143
+ /** Drop the retired key entirely, leaving any entry already saved under the new id. */
144
+ function dropFromRecord(value: unknown, from: string): Record<string, unknown> | null {
145
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
146
+ const record = value as Record<string, unknown>;
147
+ if (!(from in record)) return null;
148
+ const next: Record<string, unknown> = {};
149
+ for (const [key, entry] of Object.entries(record)) {
150
+ if (key === from) continue;
151
+ next[key] = entry;
152
+ }
153
+ return next;
154
+ }
155
+
156
+ /**
157
+ * `provider/model` rows in the top-level `disabledModels` list.
158
+ *
159
+ * The retired row is DROPPED rather than renamed: carrying its disabled state to
160
+ * the new id would hide the supported model behind a toggle the user set for a
161
+ * different model. An existing row for the new id is left untouched.
162
+ */
163
+ function renameDisabledModels(config: OcxConfig, rename: ModelRename): boolean {
164
+ const list = config.disabledModels;
165
+ if (!Array.isArray(list)) return false;
166
+ const retired = `${rename.provider}/${rename.from}`;
167
+ if (!list.includes(retired)) return false;
168
+ config.disabledModels = list.filter(entry => entry !== retired);
169
+ return true;
170
+ }
171
+
172
+ /**
173
+ * Only migrate a row that still points at the registry's own endpoint. A user who
174
+ * repointed `baseUrl` at a different vendor owns their model ids.
175
+ */
176
+ function providerStillMatchesRegistry(name: string, prov: OcxProviderConfig): boolean {
177
+ const entry = PROVIDER_REGISTRY.find(row => row.id === name);
178
+ if (!entry) return false;
179
+ if (!prov.baseUrl || !entry.baseUrl) return true;
180
+ const choices = entry.baseUrlChoices?.map(choice => choice.baseUrl) ?? [];
181
+ const known = [entry.baseUrl, ...choices]
182
+ .filter((url): url is string => typeof url === "string")
183
+ .map(url => url.replace(/\/+$/, ""));
184
+ return known.includes(prov.baseUrl.replace(/\/+$/, ""));
185
+ }
186
+
187
+ /** Guard against a stale rename: only apply when the registry actually seeds `to`. */
188
+ function registrySeedsTarget(rename: ModelRename): boolean {
189
+ const entry = PROVIDER_REGISTRY.find(row => row.id === rename.provider);
190
+ return !!entry?.models?.includes(rename.to);
191
+ }
192
+
193
+ export interface ModelRenameProjection {
194
+ config: OcxConfig;
195
+ changed: boolean;
196
+ warnings: string[];
197
+ }
198
+
199
+ /**
200
+ * Pure projection: apply every applicable rename and report what changed. The
201
+ * caller decides whether to persist.
202
+ */
203
+ export function projectModelRenames(
204
+ config: OcxConfig,
205
+ renames: readonly ModelRename[] = MODEL_RENAMES,
206
+ ): ModelRenameProjection {
207
+ const warnings: string[] = [];
208
+ let changed = false;
209
+
210
+ for (const rename of renames) {
211
+ const prov = config.providers?.[rename.provider];
212
+ if (!prov) continue;
213
+ if (!registrySeedsTarget(rename)) {
214
+ warnings.push(
215
+ `registry no longer seeds "${rename.to}" for "${rename.provider}"; skipping the `
216
+ + `"${rename.from}" rename rather than writing an id the registry does not know.`,
217
+ );
218
+ continue;
219
+ }
220
+ if (!providerStillMatchesRegistry(rename.provider, prov)) continue;
221
+
222
+ // Provider config is a closed interface, so index through one unknown-cast
223
+ // view rather than casting at each assignment.
224
+ const row = prov as unknown as Record<string, unknown>;
225
+ let touched = false;
226
+ for (const field of MODEL_ID_LISTS) {
227
+ const next = renameInList(row[field], rename.from, rename.to);
228
+ if (!next) continue;
229
+ row[field] = next;
230
+ touched = true;
231
+ }
232
+ for (const field of MODEL_KEYED_RECORDS) {
233
+ const next = rename.dropReasoningEffortMap && field === "modelReasoningEffortMap"
234
+ ? dropFromRecord(row[field], rename.from)
235
+ : renameInRecord(row[field], rename.from, rename.to);
236
+ if (!next) continue;
237
+ row[field] = next;
238
+ touched = true;
239
+ }
240
+ if (prov.defaultModel === rename.from) {
241
+ prov.defaultModel = rename.to;
242
+ touched = true;
243
+ }
244
+ if (renameDisabledModels(config, rename)) touched = true;
245
+
246
+ if (touched) {
247
+ changed = true;
248
+ warnings.push(
249
+ `renamed "${rename.provider}/${rename.from}" to "${rename.to}" in the saved config: ${rename.reason}.`,
250
+ );
251
+ }
252
+ }
253
+
254
+ return { config, changed, warnings };
255
+ }
@@ -0,0 +1,28 @@
1
+ import { saveConfig } from "../config";
2
+ import { projectModelRenames } from "./model-rename-migration";
3
+ import type { OcxConfig } from "../types";
4
+
5
+ export interface ModelRenameStartupDeps {
6
+ project: typeof projectModelRenames;
7
+ save: (config: OcxConfig) => void;
8
+ }
9
+
10
+ /**
11
+ * Apply registry model renames to the saved config at startup (issue #1610).
12
+ *
13
+ * No backup is taken, unlike the OpenAI tier and Alibaba region migrations: those
14
+ * rewrite credentials and provider identity, where a bad projection is not
15
+ * recoverable from the config alone. This one only rewrites model ids that the
16
+ * registry itself no longer seeds, and the pre-migration value is a string this
17
+ * file still names, so the change is reversible by hand.
18
+ */
19
+ export function runModelRenameStartupMigration(
20
+ config: OcxConfig,
21
+ deps: ModelRenameStartupDeps = { project: projectModelRenames, save: saveConfig },
22
+ ): OcxConfig {
23
+ const projection = deps.project(config);
24
+ for (const warning of projection.warnings) console.warn(`[model-rename-migration] ${warning}`);
25
+ if (!projection.changed) return projection.config;
26
+ deps.save(projection.config);
27
+ return projection.config;
28
+ }