@yansigit/opencodex 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 (826) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/LICENSE +21 -0
  3. package/README.md +303 -0
  4. package/assets/architecture.png +0 -0
  5. package/assets/banner.png +0 -0
  6. package/assets/claude-code-models.gif +0 -0
  7. package/assets/codex-app-picker.png +0 -0
  8. package/bin/ocx.mjs +587 -0
  9. package/bin/package-main.mjs +9 -0
  10. package/gui/dist/assets/index-BNJ7r4Gd.js +102 -0
  11. package/gui/dist/assets/index-CGoDO3uO.css +1 -0
  12. package/gui/dist/favicon.png +0 -0
  13. package/gui/dist/icons.svg +24 -0
  14. package/gui/dist/index.html +25 -0
  15. package/gui/dist/logo.png +0 -0
  16. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  17. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  18. package/gui/dist/provider-icons/claude-color.svg +1 -0
  19. package/gui/dist/provider-icons/cline-color.svg +16 -0
  20. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  21. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  22. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  23. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  24. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  25. package/gui/dist/provider-icons/discord.svg +1 -0
  26. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  27. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  28. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  29. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  30. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  31. package/gui/dist/provider-icons/grok.svg +1 -0
  32. package/gui/dist/provider-icons/groq-color.svg +1 -0
  33. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  34. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  35. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  36. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  37. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  38. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  39. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  40. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  41. package/gui/dist/provider-icons/openai.svg +1 -0
  42. package/gui/dist/provider-icons/opencode.svg +2 -0
  43. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  44. package/gui/dist/provider-icons/pi.svg +21 -0
  45. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  46. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  47. package/gui/dist/provider-icons/telegram.svg +1 -0
  48. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  49. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  50. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  51. package/package.json +108 -0
  52. package/src/AGENTS.md +28 -0
  53. package/src/adapters/anthropic-image-guard.ts +251 -0
  54. package/src/adapters/anthropic-image-normalize.ts +518 -0
  55. package/src/adapters/anthropic-output-schema.ts +137 -0
  56. package/src/adapters/anthropic.ts +1327 -0
  57. package/src/adapters/azure.ts +36 -0
  58. package/src/adapters/base.ts +121 -0
  59. package/src/adapters/client-fingerprint.ts +65 -0
  60. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  61. package/src/adapters/command-code.ts +601 -0
  62. package/src/adapters/cursor/arg-codec.ts +38 -0
  63. package/src/adapters/cursor/arg-normalize.ts +104 -0
  64. package/src/adapters/cursor/checkpoint-store.ts +303 -0
  65. package/src/adapters/cursor/cursor-errors.ts +288 -0
  66. package/src/adapters/cursor/discovery.ts +333 -0
  67. package/src/adapters/cursor/effort-map.ts +151 -0
  68. package/src/adapters/cursor/exec-policy.ts +88 -0
  69. package/src/adapters/cursor/framing.ts +250 -0
  70. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  71. package/src/adapters/cursor/h2-pool.ts +123 -0
  72. package/src/adapters/cursor/http1-bidi.ts +361 -0
  73. package/src/adapters/cursor/images.ts +704 -0
  74. package/src/adapters/cursor/kv-store.ts +52 -0
  75. package/src/adapters/cursor/live-models.ts +269 -0
  76. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  77. package/src/adapters/cursor/live-transport.ts +1653 -0
  78. package/src/adapters/cursor/mcp-config.ts +42 -0
  79. package/src/adapters/cursor/mcp-manager.ts +333 -0
  80. package/src/adapters/cursor/message-mapper.ts +49 -0
  81. package/src/adapters/cursor/native-exec-common.ts +76 -0
  82. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  83. package/src/adapters/cursor/native-exec-fs.ts +332 -0
  84. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  85. package/src/adapters/cursor/native-exec-network.ts +43 -0
  86. package/src/adapters/cursor/native-exec-shell.ts +547 -0
  87. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  88. package/src/adapters/cursor/native-exec.ts +663 -0
  89. package/src/adapters/cursor/protobuf-events.ts +1381 -0
  90. package/src/adapters/cursor/protobuf-request.ts +1032 -0
  91. package/src/adapters/cursor/request-builder.ts +461 -0
  92. package/src/adapters/cursor/thread-continuity.ts +67 -0
  93. package/src/adapters/cursor/tool-definitions.ts +735 -0
  94. package/src/adapters/cursor/tool-result-normalize.ts +92 -0
  95. package/src/adapters/cursor/transport-retry.ts +132 -0
  96. package/src/adapters/cursor/transport.ts +79 -0
  97. package/src/adapters/cursor/types.ts +79 -0
  98. package/src/adapters/cursor.ts +322 -0
  99. package/src/adapters/google-antigravity-hosts.ts +48 -0
  100. package/src/adapters/google-antigravity-replay.ts +827 -0
  101. package/src/adapters/google-antigravity-tools.ts +105 -0
  102. package/src/adapters/google-antigravity-wire.ts +141 -0
  103. package/src/adapters/google-errors.ts +92 -0
  104. package/src/adapters/google-http.ts +460 -0
  105. package/src/adapters/google-tool-schema.ts +238 -0
  106. package/src/adapters/google-truncation.ts +24 -0
  107. package/src/adapters/google-wire-compiler.ts +232 -0
  108. package/src/adapters/google.ts +1377 -0
  109. package/src/adapters/identity.ts +77 -0
  110. package/src/adapters/image.ts +23 -0
  111. package/src/adapters/kiro-constants.ts +16 -0
  112. package/src/adapters/kiro-errors.ts +208 -0
  113. package/src/adapters/kiro-events.ts +197 -0
  114. package/src/adapters/kiro-images.ts +129 -0
  115. package/src/adapters/kiro-retry.ts +312 -0
  116. package/src/adapters/kiro-thinking.ts +112 -0
  117. package/src/adapters/kiro-tool-fallback.ts +36 -0
  118. package/src/adapters/kiro-tools.ts +224 -0
  119. package/src/adapters/kiro-truncation.ts +33 -0
  120. package/src/adapters/kiro-wire.ts +129 -0
  121. package/src/adapters/kiro.ts +1936 -0
  122. package/src/adapters/mimo-free.ts +280 -0
  123. package/src/adapters/openai-chat-url.ts +11 -0
  124. package/src/adapters/openai-chat.ts +1980 -0
  125. package/src/adapters/openai-responses-url.ts +16 -0
  126. package/src/adapters/openai-responses.ts +1911 -0
  127. package/src/adapters/registry.ts +175 -0
  128. package/src/adapters/responses-tool-schema.ts +67 -0
  129. package/src/adapters/run-turn-queue.ts +114 -0
  130. package/src/adapters/tool-call-id.ts +119 -0
  131. package/src/adapters/tool-catalog-nudge.ts +154 -0
  132. package/src/adapters/upstream-http-error.ts +48 -0
  133. package/src/adapters/xai-web-search.ts +185 -0
  134. package/src/bridge.ts +1986 -0
  135. package/src/chat/inbound.ts +319 -0
  136. package/src/chat/outbound.ts +821 -0
  137. package/src/claude/agents-inject.ts +266 -0
  138. package/src/claude/alias.ts +149 -0
  139. package/src/claude/auth-detect.ts +229 -0
  140. package/src/claude/auth-mode-migration.ts +32 -0
  141. package/src/claude/auth-mode.ts +62 -0
  142. package/src/claude/context-windows.ts +205 -0
  143. package/src/claude/desktop-3p-guard.ts +35 -0
  144. package/src/claude/desktop-3p-paths.ts +84 -0
  145. package/src/claude/desktop-3p.ts +615 -0
  146. package/src/claude/desktop-health.ts +26 -0
  147. package/src/claude/desktop-profile.ts +263 -0
  148. package/src/claude/gateway-cache.ts +107 -0
  149. package/src/claude/inbound-debug.ts +163 -0
  150. package/src/claude/inbound.ts +578 -0
  151. package/src/claude/model-info.ts +174 -0
  152. package/src/claude/outbound.ts +926 -0
  153. package/src/cli/access.ts +108 -0
  154. package/src/cli/account-api.ts +302 -0
  155. package/src/cli/account-auth.ts +250 -0
  156. package/src/cli/account-catalog-refresh.ts +14 -0
  157. package/src/cli/account-extended.ts +737 -0
  158. package/src/cli/account-main.ts +317 -0
  159. package/src/cli/account.ts +299 -0
  160. package/src/cli/agent-driven.ts +70 -0
  161. package/src/cli/agent.ts +290 -0
  162. package/src/cli/catalog-prewarm.ts +27 -0
  163. package/src/cli/claude-agent-startup-sync.ts +73 -0
  164. package/src/cli/claude-desktop.ts +213 -0
  165. package/src/cli/claude.ts +355 -0
  166. package/src/cli/codex-log-guard-doctor.ts +103 -0
  167. package/src/cli/codex-shim-autorestore.ts +47 -0
  168. package/src/cli/codex-shim-readiness.ts +76 -0
  169. package/src/cli/combo.ts +127 -0
  170. package/src/cli/config-command.ts +209 -0
  171. package/src/cli/debug.ts +228 -0
  172. package/src/cli/dispatch.ts +585 -0
  173. package/src/cli/doctor.ts +1202 -0
  174. package/src/cli/ensure-desired-integrations.ts +152 -0
  175. package/src/cli/export-command.ts +213 -0
  176. package/src/cli/help.ts +101 -0
  177. package/src/cli/index.ts +973 -0
  178. package/src/cli/init.ts +211 -0
  179. package/src/cli/integrations.ts +260 -0
  180. package/src/cli/interactive-confirm.ts +133 -0
  181. package/src/cli/lab.ts +607 -0
  182. package/src/cli/launcher-context.ts +77 -0
  183. package/src/cli/minimax.ts +497 -0
  184. package/src/cli/models-runtime.ts +245 -0
  185. package/src/cli/models.ts +422 -0
  186. package/src/cli/observe.ts +206 -0
  187. package/src/cli/opencode.ts +588 -0
  188. package/src/cli/provider-replit.ts +232 -0
  189. package/src/cli/provider-runtime.ts +179 -0
  190. package/src/cli/provider.ts +492 -0
  191. package/src/cli/ready.ts +301 -0
  192. package/src/cli/registry.ts +422 -0
  193. package/src/cli/replit-gateway-key-input.ts +138 -0
  194. package/src/cli/root.ts +86 -0
  195. package/src/cli/route-policy.ts +92 -0
  196. package/src/cli/runtime-api.ts +328 -0
  197. package/src/cli/star-prompt.ts +211 -0
  198. package/src/cli/status-oauth.ts +78 -0
  199. package/src/cli/status.ts +328 -0
  200. package/src/cli/system-command.ts +112 -0
  201. package/src/cli/system-restart-client.ts +146 -0
  202. package/src/cli/tray-proxy.ts +199 -0
  203. package/src/cli/v2.ts +268 -0
  204. package/src/cli.ts +10 -0
  205. package/src/clients/config-export.ts +1704 -0
  206. package/src/codex/account-id.ts +34 -0
  207. package/src/codex/account-label.ts +47 -0
  208. package/src/codex/account-lifecycle.ts +172 -0
  209. package/src/codex/account-namespace-match.ts +63 -0
  210. package/src/codex/account-namespaces.ts +195 -0
  211. package/src/codex/account-pause.ts +20 -0
  212. package/src/codex/account-priority.ts +83 -0
  213. package/src/codex/account-runtime-state.ts +31 -0
  214. package/src/codex/account-store.ts +544 -0
  215. package/src/codex/account-usability.ts +43 -0
  216. package/src/codex/admission.ts +256 -0
  217. package/src/codex/affinity-debug.ts +162 -0
  218. package/src/codex/agent-roles-sync.ts +225 -0
  219. package/src/codex/agent-roles.ts +238 -0
  220. package/src/codex/app-server-processes.ts +1143 -0
  221. package/src/codex/app-server-restart-service.ts +232 -0
  222. package/src/codex/auth-api.ts +2147 -0
  223. package/src/codex/auth-collision.ts +109 -0
  224. package/src/codex/auth-context.ts +665 -0
  225. package/src/codex/autostart-health.ts +156 -0
  226. package/src/codex/catalog/account-models.ts +67 -0
  227. package/src/codex/catalog/aggregation.ts +436 -0
  228. package/src/codex/catalog/bundled.ts +549 -0
  229. package/src/codex/catalog/effort.ts +446 -0
  230. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  231. package/src/codex/catalog/kinds.ts +2 -0
  232. package/src/codex/catalog/metadata.ts +664 -0
  233. package/src/codex/catalog/native-models.ts +72 -0
  234. package/src/codex/catalog/parsing.ts +650 -0
  235. package/src/codex/catalog/provider-fetch.ts +2064 -0
  236. package/src/codex/catalog/sync.ts +1883 -0
  237. package/src/codex/catalog-admission.ts +199 -0
  238. package/src/codex/catalog-refresh-status.ts +105 -0
  239. package/src/codex/catalog-write-serialization.ts +242 -0
  240. package/src/codex/catalog.ts +14 -0
  241. package/src/codex/codex-write-lock.ts +384 -0
  242. package/src/codex/convergence-types.ts +614 -0
  243. package/src/codex/convergence.ts +651 -0
  244. package/src/codex/coordinator-doctor.ts +332 -0
  245. package/src/codex/custom-model-catalog-migration.ts +176 -0
  246. package/src/codex/data/upstream-models.json +830 -0
  247. package/src/codex/desired-state.ts +230 -0
  248. package/src/codex/exec-invocation.ts +22 -0
  249. package/src/codex/features.ts +1566 -0
  250. package/src/codex/generation.ts +202 -0
  251. package/src/codex/history-job.ts +407 -0
  252. package/src/codex/history-lock.ts +242 -0
  253. package/src/codex/history-migration-guardian.ts +108 -0
  254. package/src/codex/history-provider.ts +979 -0
  255. package/src/codex/history-transition.ts +105 -0
  256. package/src/codex/history-worker.ts +220 -0
  257. package/src/codex/home.ts +206 -0
  258. package/src/codex/inject-coordination.ts +290 -0
  259. package/src/codex/inject.ts +1733 -0
  260. package/src/codex/injected-marker.ts +106 -0
  261. package/src/codex/integration-record.ts +266 -0
  262. package/src/codex/internal/catalog-writer.ts +203 -0
  263. package/src/codex/internal/history-writer.ts +80 -0
  264. package/src/codex/journal.ts +225 -0
  265. package/src/codex/log-guard/inspect.ts +506 -0
  266. package/src/codex/log-guard/lock.ts +150 -0
  267. package/src/codex/log-guard/maintenance.ts +403 -0
  268. package/src/codex/log-guard/path-safety.ts +88 -0
  269. package/src/codex/log-guard/policy.ts +44 -0
  270. package/src/codex/log-guard/processes.ts +205 -0
  271. package/src/codex/log-guard/protection.ts +489 -0
  272. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  273. package/src/codex/main-account-cache.ts +56 -0
  274. package/src/codex/main-account.ts +68 -0
  275. package/src/codex/management-convergence.ts +167 -0
  276. package/src/codex/model-cache.ts +273 -0
  277. package/src/codex/model-entitlements.ts +353 -0
  278. package/src/codex/native-main-admission.ts +47 -0
  279. package/src/codex/native-main-auth-temp.ts +187 -0
  280. package/src/codex/native-main-claim.ts +178 -0
  281. package/src/codex/native-main-lock-file.ts +162 -0
  282. package/src/codex/native-main-owner.ts +329 -0
  283. package/src/codex/native-profile-api.ts +247 -0
  284. package/src/codex/native-profile-manager.ts +1531 -0
  285. package/src/codex/native-profile-processes.ts +121 -0
  286. package/src/codex/native-profile-recovery.ts +99 -0
  287. package/src/codex/native-profile-stage-store.ts +387 -0
  288. package/src/codex/native-profile-startup.ts +492 -0
  289. package/src/codex/native-profile-store.ts +855 -0
  290. package/src/codex/native-profile-types.ts +120 -0
  291. package/src/codex/native-residue.ts +682 -0
  292. package/src/codex/paths.ts +144 -0
  293. package/src/codex/plan-from-token.ts +140 -0
  294. package/src/codex/plan.ts +40 -0
  295. package/src/codex/plugins-doctor.ts +242 -0
  296. package/src/codex/pool-rotation.ts +295 -0
  297. package/src/codex/project-config-warnings.ts +425 -0
  298. package/src/codex/prompt-journal.ts +352 -0
  299. package/src/codex/prompt-layers.ts +967 -0
  300. package/src/codex/prompt-lock.ts +143 -0
  301. package/src/codex/quota-rejection.ts +298 -0
  302. package/src/codex/quota.ts +573 -0
  303. package/src/codex/refresh.ts +62 -0
  304. package/src/codex/reset-credit-recovery.ts +1044 -0
  305. package/src/codex/routing.ts +1888 -0
  306. package/src/codex/runtime.ts +659 -0
  307. package/src/codex/shim.ts +2170 -0
  308. package/src/codex/subagent-defaults.ts +550 -0
  309. package/src/codex/subagent-model-fallback.ts +784 -0
  310. package/src/codex/sync.ts +319 -0
  311. package/src/codex/transition-state.ts +612 -0
  312. package/src/codex/upstream-host-health.ts +368 -0
  313. package/src/codex/user-identity.ts +557 -0
  314. package/src/codex/warmup.ts +298 -0
  315. package/src/codex/websocket-registry.ts +100 -0
  316. package/src/codex/write-coordination.ts +114 -0
  317. package/src/combos/failover.ts +160 -0
  318. package/src/combos/index.ts +45 -0
  319. package/src/combos/request.ts +94 -0
  320. package/src/combos/resolve.ts +232 -0
  321. package/src/combos/types.ts +398 -0
  322. package/src/config/provider-name.ts +24 -0
  323. package/src/config.ts +4041 -0
  324. package/src/fork/register.ts +3 -0
  325. package/src/generated/compatibility-version.json +3116 -0
  326. package/src/generated/model-metadata.ts +106 -0
  327. package/src/github/star-state.ts +203 -0
  328. package/src/grok/inject.ts +530 -0
  329. package/src/grok/inspect.ts +45 -0
  330. package/src/grok/status.ts +121 -0
  331. package/src/grok/sync.ts +66 -0
  332. package/src/images/artifacts.ts +516 -0
  333. package/src/images/fulfill-video.ts +163 -0
  334. package/src/images/fulfill.ts +149 -0
  335. package/src/images/index.ts +4 -0
  336. package/src/images/loop.ts +955 -0
  337. package/src/images/plan.ts +143 -0
  338. package/src/images/synthetic-tool.ts +133 -0
  339. package/src/images/types.ts +41 -0
  340. package/src/images/xai-client.ts +141 -0
  341. package/src/images/xai-video-client.ts +163 -0
  342. package/src/index.ts +22 -0
  343. package/src/integrations/config-io.ts +269 -0
  344. package/src/integrations/journal.ts +315 -0
  345. package/src/integrations/merge.ts +135 -0
  346. package/src/integrations/mutation-flight.ts +71 -0
  347. package/src/integrations/native/ownership-preflight.ts +202 -0
  348. package/src/integrations/omp-yaml-source.ts +358 -0
  349. package/src/integrations/owned-refresh.ts +74 -0
  350. package/src/integrations/ownership.ts +111 -0
  351. package/src/integrations/registry.ts +159 -0
  352. package/src/integrations/serialize.ts +314 -0
  353. package/src/integrations/state.ts +361 -0
  354. package/src/integrations/store.ts +103 -0
  355. package/src/integrations/writer-lock.ts +98 -0
  356. package/src/integrations/writer.ts +691 -0
  357. package/src/lab/artifacts/sanitize.ts +586 -0
  358. package/src/lab/artifacts/secure-fs.ts +475 -0
  359. package/src/lab/artifacts/store.ts +310 -0
  360. package/src/lab/automation/budgets.ts +78 -0
  361. package/src/lab/automation/config-persistence.ts +256 -0
  362. package/src/lab/automation/constants.ts +39 -0
  363. package/src/lab/automation/cooldown.ts +103 -0
  364. package/src/lab/automation/dispatch.ts +211 -0
  365. package/src/lab/automation/index.ts +13 -0
  366. package/src/lab/automation/orchestrator.ts +499 -0
  367. package/src/lab/automation/persistence.ts +512 -0
  368. package/src/lab/automation/planner.ts +371 -0
  369. package/src/lab/automation/policy.ts +136 -0
  370. package/src/lab/automation/queue.ts +191 -0
  371. package/src/lab/automation/recovery.ts +24 -0
  372. package/src/lab/automation/route-context.ts +21 -0
  373. package/src/lab/automation/run-key.ts +44 -0
  374. package/src/lab/automation/runs-query.ts +34 -0
  375. package/src/lab/automation/types.ts +160 -0
  376. package/src/lab/conformance/assertion.ts +325 -0
  377. package/src/lab/conformance/digest.ts +22 -0
  378. package/src/lab/conformance/executor.ts +741 -0
  379. package/src/lab/conformance/fixture-provider.ts +27 -0
  380. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  381. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  382. package/src/lab/conformance/harness-budget.ts +47 -0
  383. package/src/lab/conformance/index.ts +5 -0
  384. package/src/lab/conformance/jcs.ts +64 -0
  385. package/src/lab/conformance/json-pointer.ts +39 -0
  386. package/src/lab/conformance/manifest.ts +180 -0
  387. package/src/lab/conformance/mcp-stub.ts +179 -0
  388. package/src/lab/conformance/negative-controls.ts +164 -0
  389. package/src/lab/conformance/observation.ts +355 -0
  390. package/src/lab/conformance/runner.ts +68 -0
  391. package/src/lab/conformance/sse-normalize.ts +59 -0
  392. package/src/lab/conformance/suite-manifest.ts +78 -0
  393. package/src/lab/conformance/types.ts +214 -0
  394. package/src/lab/constants.ts +126 -0
  395. package/src/lab/digest.ts +64 -0
  396. package/src/lab/events/errors.ts +9 -0
  397. package/src/lab/events/limits.ts +117 -0
  398. package/src/lab/events/types.ts +229 -0
  399. package/src/lab/events/validate.ts +781 -0
  400. package/src/lab/fabric/constants.ts +40 -0
  401. package/src/lab/fabric/executor.ts +492 -0
  402. package/src/lab/fabric/index.ts +80 -0
  403. package/src/lab/fabric/manifest.ts +222 -0
  404. package/src/lab/fabric/observe.ts +489 -0
  405. package/src/lab/fabric/patch.ts +79 -0
  406. package/src/lab/fabric/producer-child.ts +139 -0
  407. package/src/lab/fabric/producer-isolate.ts +276 -0
  408. package/src/lab/fabric/producer-protocol.ts +61 -0
  409. package/src/lab/fabric/scratch.ts +439 -0
  410. package/src/lab/fabric/subject.ts +106 -0
  411. package/src/lab/fabric/types.ts +134 -0
  412. package/src/lab/fabric/verifier.ts +98 -0
  413. package/src/lab/index.ts +54 -0
  414. package/src/lab/ledger/artifact-refs.ts +127 -0
  415. package/src/lab/ledger/invalidation.ts +136 -0
  416. package/src/lab/ledger/purge.ts +310 -0
  417. package/src/lab/ledger/store.ts +532 -0
  418. package/src/lab/live/credential-lease.ts +53 -0
  419. package/src/lab/live/destination.ts +155 -0
  420. package/src/lab/live/executor.ts +336 -0
  421. package/src/lab/live/inert-tools.ts +56 -0
  422. package/src/lab/live/manifest.ts +85 -0
  423. package/src/lab/live/mcp-loopback.ts +57 -0
  424. package/src/lab/live/runner.ts +19 -0
  425. package/src/lab/live/sandbox.ts +61 -0
  426. package/src/lab/live/suite-manifest.ts +41 -0
  427. package/src/lab/live/transport.ts +118 -0
  428. package/src/lab/live/types.ts +197 -0
  429. package/src/lab/observe/from-conformance.ts +301 -0
  430. package/src/lab/observe/from-live.ts +117 -0
  431. package/src/lab/paths.ts +153 -0
  432. package/src/lab/projection/rebuild.ts +495 -0
  433. package/src/lab/projection/schema.ts +135 -0
  434. package/src/lab/projection/verdicts.ts +474 -0
  435. package/src/lab/projection/verification.ts +412 -0
  436. package/src/lab/public/bundle.ts +217 -0
  437. package/src/lab/public/community-authority.ts +175 -0
  438. package/src/lab/public/community-files.ts +29 -0
  439. package/src/lab/public/community.ts +479 -0
  440. package/src/lab/public/file-safety.ts +155 -0
  441. package/src/lab/public/ids.ts +26 -0
  442. package/src/lab/public/index.ts +16 -0
  443. package/src/lab/public/mutation-lock.ts +424 -0
  444. package/src/lab/public/operator.ts +353 -0
  445. package/src/lab/public/origin-purge.ts +79 -0
  446. package/src/lab/public/origin.ts +203 -0
  447. package/src/lab/public/privacy.ts +143 -0
  448. package/src/lab/public/private-file.ts +261 -0
  449. package/src/lab/public/project.ts +124 -0
  450. package/src/lab/public/purge-test-fault.ts +21 -0
  451. package/src/lab/public/purge.ts +223 -0
  452. package/src/lab/public/registry.ts +44 -0
  453. package/src/lab/public/revocation.ts +252 -0
  454. package/src/lab/public/signature.ts +243 -0
  455. package/src/lab/public/storage.ts +105 -0
  456. package/src/lab/public/strict-json.ts +206 -0
  457. package/src/lab/public/time.ts +26 -0
  458. package/src/lab/public/types.ts +172 -0
  459. package/src/lab/public/validate.ts +391 -0
  460. package/src/lab/query/catalog.ts +101 -0
  461. package/src/lab/query/connection.ts +107 -0
  462. package/src/lab/query/constants.ts +4 -0
  463. package/src/lab/query/cursor.ts +132 -0
  464. package/src/lab/query/dto-map.ts +277 -0
  465. package/src/lab/query/errors.ts +22 -0
  466. package/src/lab/query/freshness.ts +53 -0
  467. package/src/lab/query/index.ts +45 -0
  468. package/src/lab/query/latest-observation.ts +59 -0
  469. package/src/lab/query/passive-production.ts +159 -0
  470. package/src/lab/query/queries.ts +444 -0
  471. package/src/lab/query/types.ts +266 -0
  472. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  473. package/src/lab/subject/installation-salt.ts +112 -0
  474. package/src/lab/subject/protocol-subject.ts +80 -0
  475. package/src/lab/subject/route-subject.ts +74 -0
  476. package/src/lib/abort.ts +146 -0
  477. package/src/lib/admin-secrets.ts +25 -0
  478. package/src/lib/admission.ts +83 -0
  479. package/src/lib/app-owned-memory-stores.ts +195 -0
  480. package/src/lib/app-owned-memory.ts +265 -0
  481. package/src/lib/bounded-body.ts +346 -0
  482. package/src/lib/bun-binary-validator.d.mts +3 -0
  483. package/src/lib/bun-binary-validator.mjs +18 -0
  484. package/src/lib/bun-runtime.ts +184 -0
  485. package/src/lib/bun-stream-caps.ts +130 -0
  486. package/src/lib/codex-restart-contract.ts +120 -0
  487. package/src/lib/config-ownership.ts +364 -0
  488. package/src/lib/crash-guard.ts +344 -0
  489. package/src/lib/debug-log-buffer.ts +83 -0
  490. package/src/lib/debug-settings.ts +108 -0
  491. package/src/lib/debug.ts +31 -0
  492. package/src/lib/destination-policy.ts +380 -0
  493. package/src/lib/errors.ts +406 -0
  494. package/src/lib/eventstream-decoder.ts +253 -0
  495. package/src/lib/fabric-task-execution-authority.ts +7 -0
  496. package/src/lib/fabric-task-host.ts +29 -0
  497. package/src/lib/gcp-adc.ts +341 -0
  498. package/src/lib/injection-debug-log.ts +58 -0
  499. package/src/lib/lab-activation.ts +223 -0
  500. package/src/lib/lab-live-execution-authority.ts +13 -0
  501. package/src/lib/lab-live-host.ts +30 -0
  502. package/src/lib/lab-live-pinned-sender.ts +56 -0
  503. package/src/lib/lab-live-route-production.ts +130 -0
  504. package/src/lib/lab-passive-linker-registration.ts +26 -0
  505. package/src/lib/local-management-attestation.ts +51 -0
  506. package/src/lib/local-management-capability.ts +100 -0
  507. package/src/lib/local-provider-reload-contract.ts +100 -0
  508. package/src/lib/open-url.ts +25 -0
  509. package/src/lib/optional-shutdown-hooks.ts +57 -0
  510. package/src/lib/pinned-http.ts +270 -0
  511. package/src/lib/privacy.ts +20 -0
  512. package/src/lib/process-control.ts +168 -0
  513. package/src/lib/provider-outbound.ts +210 -0
  514. package/src/lib/provider-url.ts +14 -0
  515. package/src/lib/proxy-env.ts +18 -0
  516. package/src/lib/redact.ts +521 -0
  517. package/src/lib/retry-after.ts +55 -0
  518. package/src/lib/self-launch-argv.ts +15 -0
  519. package/src/lib/server-resource-ownership.ts +71 -0
  520. package/src/lib/service-secrets.ts +25 -0
  521. package/src/lib/shadow-call.ts +61 -0
  522. package/src/lib/sidecar-tracker.ts +52 -0
  523. package/src/lib/sse-decoder.ts +364 -0
  524. package/src/lib/state-store-registrations.ts +119 -0
  525. package/src/lib/state-store-sweeper.ts +184 -0
  526. package/src/lib/system-restart-contract.ts +73 -0
  527. package/src/lib/test-home-guard.ts +90 -0
  528. package/src/lib/token-estimate.ts +86 -0
  529. package/src/lib/tool-argument-integers.ts +202 -0
  530. package/src/lib/translator-budget.ts +400 -0
  531. package/src/lib/upstream-http-version.ts +57 -0
  532. package/src/lib/upstream-reachability.ts +95 -0
  533. package/src/lib/upstream-retry.ts +392 -0
  534. package/src/lib/win-exec.ts +115 -0
  535. package/src/lib/win-paths.ts +68 -0
  536. package/src/lib/windows-atomic-replace.ts +156 -0
  537. package/src/lib/windows-elevation.ts +773 -0
  538. package/src/lib/windows-secret-acl.ts +854 -0
  539. package/src/lib/windows-service-wrappers.ts +72 -0
  540. package/src/lib/windows-text.ts +106 -0
  541. package/src/lib/windows-user-principal.ts +341 -0
  542. package/src/lib/winsw.ts +403 -0
  543. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  544. package/src/oauth/account-import/index.ts +15 -0
  545. package/src/oauth/account-import/parser.ts +83 -0
  546. package/src/oauth/account-import/registry.ts +18 -0
  547. package/src/oauth/account-import/service.ts +75 -0
  548. package/src/oauth/account-import/types.ts +91 -0
  549. package/src/oauth/anthropic-routing.ts +594 -0
  550. package/src/oauth/anthropic.ts +188 -0
  551. package/src/oauth/antigravity-routing.ts +151 -0
  552. package/src/oauth/callback-server.ts +300 -0
  553. package/src/oauth/chatgpt.ts +161 -0
  554. package/src/oauth/command-code.ts +239 -0
  555. package/src/oauth/cursor.ts +252 -0
  556. package/src/oauth/github-copilot.ts +428 -0
  557. package/src/oauth/google-antigravity.ts +262 -0
  558. package/src/oauth/health.ts +407 -0
  559. package/src/oauth/index.ts +1504 -0
  560. package/src/oauth/key-providers.ts +124 -0
  561. package/src/oauth/kimi.ts +227 -0
  562. package/src/oauth/kiro-credentials.ts +726 -0
  563. package/src/oauth/kiro.ts +621 -0
  564. package/src/oauth/local-token-detect.ts +130 -0
  565. package/src/oauth/log.ts +50 -0
  566. package/src/oauth/login-cli.ts +223 -0
  567. package/src/oauth/nous.ts +798 -0
  568. package/src/oauth/pkce.ts +15 -0
  569. package/src/oauth/store.ts +728 -0
  570. package/src/oauth/token-guardian.ts +309 -0
  571. package/src/oauth/types.ts +62 -0
  572. package/src/oauth/xai.ts +241 -0
  573. package/src/providers/alibaba-region-backup.ts +75 -0
  574. package/src/providers/alibaba-region-migration.ts +156 -0
  575. package/src/providers/alibaba-region-startup.ts +36 -0
  576. package/src/providers/antigravity-models.ts +695 -0
  577. package/src/providers/antigravity-quota.ts +216 -0
  578. package/src/providers/api-keys.ts +140 -0
  579. package/src/providers/base-url-choices.ts +74 -0
  580. package/src/providers/codex-capacity.ts +292 -0
  581. package/src/providers/command-code-efforts.ts +144 -0
  582. package/src/providers/context-cap.ts +82 -0
  583. package/src/providers/cursor-pool.ts +72 -0
  584. package/src/providers/derive.ts +586 -0
  585. package/src/providers/fastwire.ts +501 -0
  586. package/src/providers/free-directory.ts +187 -0
  587. package/src/providers/github-copilot-transport.ts +56 -0
  588. package/src/providers/google-vertex-location.ts +14 -0
  589. package/src/providers/key-failover.ts +271 -0
  590. package/src/providers/kiro-models.ts +67 -0
  591. package/src/providers/label.ts +19 -0
  592. package/src/providers/model-discovery-limits.ts +16 -0
  593. package/src/providers/model-discovery.ts +449 -0
  594. package/src/providers/model-rename-migration.ts +255 -0
  595. package/src/providers/model-rename-startup.ts +28 -0
  596. package/src/providers/openai-sidecar.ts +243 -0
  597. package/src/providers/openai-tier-startup.ts +56 -0
  598. package/src/providers/openai-tiers.ts +423 -0
  599. package/src/providers/openai-virtual-models.ts +83 -0
  600. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  601. package/src/providers/openrouter-routing.ts +102 -0
  602. package/src/providers/provider-id-rewrite.ts +185 -0
  603. package/src/providers/quota.ts +2345 -0
  604. package/src/providers/registry.ts +2918 -0
  605. package/src/providers/replit/constants.ts +27 -0
  606. package/src/providers/replit/derive.ts +85 -0
  607. package/src/providers/replit/headers.ts +28 -0
  608. package/src/providers/replit/origin.ts +55 -0
  609. package/src/providers/replit/pair-install-response.ts +72 -0
  610. package/src/providers/replit/probe.ts +199 -0
  611. package/src/providers/replit/setup.ts +350 -0
  612. package/src/providers/request-pacing.ts +310 -0
  613. package/src/providers/service-tier.ts +277 -0
  614. package/src/providers/slug-codec.ts +103 -0
  615. package/src/providers/static-model-discovery.ts +86 -0
  616. package/src/providers/xai-responses-opt-in.ts +15 -0
  617. package/src/providers/xai-transport.ts +148 -0
  618. package/src/reasoning-effort.ts +183 -0
  619. package/src/responses/compaction.ts +142 -0
  620. package/src/responses/custom-tool-compat.ts +266 -0
  621. package/src/responses/hosted-tool-policy.ts +9 -0
  622. package/src/responses/namespace-tool-compat.ts +355 -0
  623. package/src/responses/parser.ts +838 -0
  624. package/src/responses/provider-continuation.ts +98 -0
  625. package/src/responses/provider-opaque-metadata.ts +73 -0
  626. package/src/responses/reasoning-envelope.ts +60 -0
  627. package/src/responses/reasoning-replay-cache.ts +426 -0
  628. package/src/responses/schema.ts +165 -0
  629. package/src/responses/spill-store.ts +459 -0
  630. package/src/responses/state.ts +1433 -0
  631. package/src/responses/thought-signature-replay.ts +347 -0
  632. package/src/responses/tool-groups.ts +19 -0
  633. package/src/responses/tool-search-compat.ts +301 -0
  634. package/src/responses/truncated-stop-reason.ts +60 -0
  635. package/src/router.ts +761 -0
  636. package/src/routing/analytics.ts +378 -0
  637. package/src/routing/capability.ts +244 -0
  638. package/src/routing/compatibility/assemble.ts +73 -0
  639. package/src/routing/compatibility/behavior.ts +278 -0
  640. package/src/routing/compatibility/catalog.ts +99 -0
  641. package/src/routing/compatibility/endpoint.ts +52 -0
  642. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  643. package/src/routing/compatibility/policy.ts +181 -0
  644. package/src/routing/compatibility/provider-slot.ts +56 -0
  645. package/src/routing/compatibility/reader.ts +110 -0
  646. package/src/routing/compatibility/subject.ts +191 -0
  647. package/src/routing/compatibility/types.ts +64 -0
  648. package/src/routing/compatibility/version.ts +104 -0
  649. package/src/routing/cost.ts +77 -0
  650. package/src/routing/evaluator.ts +495 -0
  651. package/src/routing/health.ts +412 -0
  652. package/src/routing/history/cursor.ts +43 -0
  653. package/src/routing/history/indexer.ts +605 -0
  654. package/src/routing/history/schema.ts +72 -0
  655. package/src/routing/profile-namespace.ts +15 -0
  656. package/src/routing/profile.ts +547 -0
  657. package/src/routing/quota.ts +145 -0
  658. package/src/routing/request-evidence.ts +45 -0
  659. package/src/routing/trace.ts +776 -0
  660. package/src/server/adapter-resolve.ts +53 -0
  661. package/src/server/auth-cors.ts +751 -0
  662. package/src/server/background-lifecycle.ts +182 -0
  663. package/src/server/chat-completions.ts +442 -0
  664. package/src/server/chat-native-sse.ts +331 -0
  665. package/src/server/chat-native.ts +426 -0
  666. package/src/server/claude-messages.ts +1030 -0
  667. package/src/server/direct-local-http.ts +347 -0
  668. package/src/server/effort-policy.ts +190 -0
  669. package/src/server/github-copilot-responses-repair.ts +338 -0
  670. package/src/server/gui-static.ts +152 -0
  671. package/src/server/image-retry.ts +42 -0
  672. package/src/server/images.ts +568 -0
  673. package/src/server/index.ts +1813 -0
  674. package/src/server/lifecycle.ts +498 -0
  675. package/src/server/live.ts +717 -0
  676. package/src/server/local-management-read-client.ts +90 -0
  677. package/src/server/local-provider-reload-client.ts +137 -0
  678. package/src/server/management/agent-settings-routes.ts +1433 -0
  679. package/src/server/management/api-access.ts +141 -0
  680. package/src/server/management/api-key-usage.ts +193 -0
  681. package/src/server/management/body.ts +41 -0
  682. package/src/server/management/combo-routes.ts +263 -0
  683. package/src/server/management/config-routes.ts +835 -0
  684. package/src/server/management/context.ts +113 -0
  685. package/src/server/management/integration-routes.ts +498 -0
  686. package/src/server/management/lab-automation-routes.ts +206 -0
  687. package/src/server/management/lab-routes.ts +563 -0
  688. package/src/server/management/logs-usage-routes.ts +586 -0
  689. package/src/server/management/model-routes.ts +560 -0
  690. package/src/server/management/model-rows.ts +163 -0
  691. package/src/server/management/native-integration-routes.ts +769 -0
  692. package/src/server/management/oauth-account-routes.ts +637 -0
  693. package/src/server/management/provider-capability-config.ts +48 -0
  694. package/src/server/management/provider-routes.ts +1033 -0
  695. package/src/server/management/replit-provider-routes.ts +86 -0
  696. package/src/server/management/request-history-routes.ts +191 -0
  697. package/src/server/management/routing-analytics-routes.ts +74 -0
  698. package/src/server/management/routing-profile-routes.ts +385 -0
  699. package/src/server/management/shared.ts +286 -0
  700. package/src/server/management/sidebar-routes.ts +106 -0
  701. package/src/server/management/storage-log-guard-routes.ts +186 -0
  702. package/src/server/management/sync-response.ts +69 -0
  703. package/src/server/management/system-restart.ts +435 -0
  704. package/src/server/management/system-routes.ts +194 -0
  705. package/src/server/management/usage-summary-cache.ts +94 -0
  706. package/src/server/management/vision-sidecar-options.ts +167 -0
  707. package/src/server/management/web-search-sidecar-options.ts +120 -0
  708. package/src/server/management-api.ts +314 -0
  709. package/src/server/management-auth.ts +482 -0
  710. package/src/server/memory-watchdog.ts +156 -0
  711. package/src/server/passive-route-linker.ts +66 -0
  712. package/src/server/port-reclaim.ts +307 -0
  713. package/src/server/ports.ts +156 -0
  714. package/src/server/proxy-liveness.ts +328 -0
  715. package/src/server/readiness.ts +99 -0
  716. package/src/server/relay-eager.ts +353 -0
  717. package/src/server/relay.ts +1209 -0
  718. package/src/server/request-decompress.ts +239 -0
  719. package/src/server/request-log-conversation.ts +168 -0
  720. package/src/server/request-log.ts +1259 -0
  721. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  722. package/src/server/responses/agent-task-recovery.ts +465 -0
  723. package/src/server/responses/collaboration.ts +551 -0
  724. package/src/server/responses/compact.ts +771 -0
  725. package/src/server/responses/core.ts +5389 -0
  726. package/src/server/responses/empty-completion-guard.ts +276 -0
  727. package/src/server/responses/encrypted-payload.ts +331 -0
  728. package/src/server/responses/fetch-helpers.ts +232 -0
  729. package/src/server/responses/input-admission.ts +185 -0
  730. package/src/server/responses/pacing-overload.ts +13 -0
  731. package/src/server/responses/passthrough-error.ts +78 -0
  732. package/src/server/responses/policy-fallback.ts +178 -0
  733. package/src/server/responses/responses-field-backfill.ts +251 -0
  734. package/src/server/responses/terminal-guard.ts +251 -0
  735. package/src/server/responses/upstream-error.ts +53 -0
  736. package/src/server/responses/ws-upstream.ts +308 -0
  737. package/src/server/responses-custom-tool-repair.ts +282 -0
  738. package/src/server/responses-image-gen-repair.ts +132 -0
  739. package/src/server/responses-item-id-repair.ts +272 -0
  740. package/src/server/responses-json-events.ts +90 -0
  741. package/src/server/responses-model-rewrite.ts +29 -0
  742. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  743. package/src/server/responses-snapshot-repair.ts +621 -0
  744. package/src/server/responses-terminal-repair.ts +342 -0
  745. package/src/server/responses-tool-search-repair.ts +267 -0
  746. package/src/server/responses-undeclared-tool-guard.ts +153 -0
  747. package/src/server/responses.ts +25 -0
  748. package/src/server/search.ts +201 -0
  749. package/src/server/sse-frame-buffer.ts +292 -0
  750. package/src/server/sse-payload-rewrite.ts +263 -0
  751. package/src/server/startup-action-control.ts +315 -0
  752. package/src/server/startup-health-cache.ts +131 -0
  753. package/src/server/system-env.ts +484 -0
  754. package/src/server/windows-tcp-drop.ts +184 -0
  755. package/src/server/windows-tray-control.ts +41 -0
  756. package/src/server/ws-bridge.ts +472 -0
  757. package/src/service-manager-probe.ts +892 -0
  758. package/src/service.ts +3575 -0
  759. package/src/sidecar/auth.ts +92 -0
  760. package/src/sidecar/candidates.ts +83 -0
  761. package/src/stall-timeout.ts +20 -0
  762. package/src/storage/cleanup-job.ts +57 -0
  763. package/src/storage/cleanup.ts +3085 -0
  764. package/src/storage/policy-job.ts +457 -0
  765. package/src/storage/policy-scheduler.ts +40 -0
  766. package/src/storage/policy-worker.ts +59 -0
  767. package/src/storage/policy.ts +527 -0
  768. package/src/storage/restore-job.ts +299 -0
  769. package/src/storage/restore-worker.ts +58 -0
  770. package/src/storage/scanner.ts +238 -0
  771. package/src/storage/storage-mutation-coordinator.ts +139 -0
  772. package/src/storage/worker-lifecycle.ts +215 -0
  773. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  774. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  775. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  776. package/src/tray/assets/opencodex-tray.png +0 -0
  777. package/src/tray/windows-tray.ps1 +364 -0
  778. package/src/tray/windows.ts +757 -0
  779. package/src/types/accounts.ts +37 -0
  780. package/src/types/config.ts +876 -0
  781. package/src/types/provider.ts +545 -0
  782. package/src/types/request.ts +384 -0
  783. package/src/types/tools.ts +131 -0
  784. package/src/types/wire.ts +80 -0
  785. package/src/types.ts +106 -0
  786. package/src/update/badge.ts +72 -0
  787. package/src/update/index.ts +415 -0
  788. package/src/update/job.ts +1887 -0
  789. package/src/update/notify.ts +263 -0
  790. package/src/update/npm-cache-preflight.d.mts +47 -0
  791. package/src/update/npm-cache-preflight.mjs +201 -0
  792. package/src/update/npm-invocation.d.mts +23 -0
  793. package/src/update/npm-invocation.mjs +94 -0
  794. package/src/update/transactional-install.d.mts +22 -0
  795. package/src/update/transactional-install.mjs +259 -0
  796. package/src/update/tray-update-plan.d.mts +18 -0
  797. package/src/update/tray-update-plan.mjs +38 -0
  798. package/src/usage/cost.ts +625 -0
  799. package/src/usage/debug.ts +97 -0
  800. package/src/usage/expected-prices.ts +416 -0
  801. package/src/usage/log.ts +1223 -0
  802. package/src/usage/summary.ts +753 -0
  803. package/src/usage/totals.ts +14 -0
  804. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  805. package/src/usage/user-cost-overlays.ts +314 -0
  806. package/src/vision/anthropic-describe.ts +189 -0
  807. package/src/vision/backends.ts +97 -0
  808. package/src/vision/describe.ts +131 -0
  809. package/src/vision/eligibility.ts +250 -0
  810. package/src/vision/index.ts +681 -0
  811. package/src/vision/reasoning.ts +55 -0
  812. package/src/vision/routed-describe.ts +175 -0
  813. package/src/vision/timeout-bounds.ts +9 -0
  814. package/src/web-search/anthropic-executor.ts +195 -0
  815. package/src/web-search/backends.ts +108 -0
  816. package/src/web-search/exa-executor.ts +88 -0
  817. package/src/web-search/executor.ts +113 -0
  818. package/src/web-search/format-result.ts +89 -0
  819. package/src/web-search/gemini-executor.ts +141 -0
  820. package/src/web-search/index.ts +331 -0
  821. package/src/web-search/loop.ts +896 -0
  822. package/src/web-search/parse.ts +315 -0
  823. package/src/web-search/progress-stream.ts +342 -0
  824. package/src/web-search/sources.ts +60 -0
  825. package/src/web-search/synthetic-tool.ts +47 -0
  826. package/src/web-search/xai-executor.ts +219 -0
@@ -0,0 +1,2918 @@
1
+ import type { CodexAccountMode, FastWire, OcxProviderConfig } from "../types";
2
+ import { fastWireDeclarationError } from "./fastwire";
3
+ import { KIRO_MODELS, KIRO_MODEL_CONTEXT_WINDOWS, KIRO_MODEL_REASONING_EFFORTS } from "./kiro-models";
4
+ import { ANTIGRAVITY_MODELS, ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, ANTIGRAVITY_MODEL_EFFORTS, ANTIGRAVITY_MODEL_INPUT_MODALITIES } from "./antigravity-models";
5
+ import type { ProviderBaseUrlChoice } from "./base-url-choices";
6
+ import {
7
+ QWEN_CLOUD_BASE_URL_CHOICES, QWEN_CLOUD_TOKEN_PLAN_BASE_URL,
8
+ ALIBABA_INTL_BASE_URL_CHOICES, ALIBABA_INTL_TOKEN_PLAN_BASE_URL,
9
+ ALIBABA_CODING_BASE_URL_CHOICES, ALIBABA_CODING_INTL_BASE_URL,
10
+ MOONSHOT_BASE_URL_CHOICES, MOONSHOT_INTL_BASE_URL,
11
+ } from "./base-url-choices";
12
+ import {
13
+ CURSOR_NO_VISION_MODELS,
14
+ CURSOR_STATIC_MODELS,
15
+ cursorModelContextWindows,
16
+ cursorModelIds,
17
+ cursorModelInputModalities,
18
+ cursorModelReasoningEfforts,
19
+ } from "../adapters/cursor/discovery";
20
+ import { COMMAND_CODE_MODEL_REASONING_EFFORTS } from "./command-code-efforts";
21
+ import { isCanonicalOpenRouterTarget } from "./openrouter-routing";
22
+
23
+ export type ProviderAuthKind = "forward" | "oauth" | "key" | "local";
24
+ export type MetadataModelIdNormalize = "case-insensitive";
25
+
26
+ /**
27
+ * Wire protocol a client spoke when it reached the proxy. Chat and Anthropic surfaces
28
+ * translate into a Responses-shaped body and replay through `handleResponses`, so the
29
+ * original inbound has to travel with the request or the replay looks native.
30
+ */
31
+ export type InboundWire = "responses" | "chat" | "anthropic";
32
+
33
+ /**
34
+ * A per-model wire default: a bare string applies to every inbound, while the object
35
+ * form may scope the default to listed inbound protocols and authentication modes.
36
+ */
37
+ export type ModelWireDefault = string | {
38
+ wire: string;
39
+ inbound: readonly InboundWire[];
40
+ authModes?: readonly ProviderAuthKind[];
41
+ /** Whether this registry-selected route may relay a caller-owned service_tier. */
42
+ forwardCallerServiceTier?: boolean;
43
+ };
44
+
45
+ export interface ResponsesTerminalRepairPolicy {
46
+ /** Quiet time after a structurally complete output graph before synthesizing completion. */
47
+ graceMs: number;
48
+ }
49
+
50
+ export type ProviderModelDiscoveryScalar = string | number | boolean;
51
+
52
+ export type ProviderModelDiscoveryPredicate =
53
+ | {
54
+ path: readonly string[];
55
+ equalsAny: readonly ProviderModelDiscoveryScalar[];
56
+ caseInsensitive?: boolean;
57
+ }
58
+ | {
59
+ path: readonly string[];
60
+ /**
61
+ * A string-valued upstream target uses substring matching; an array-valued target uses
62
+ * exact element matching. Use `equalsAny` when the string must match in full.
63
+ */
64
+ containsAny: readonly ProviderModelDiscoveryScalar[];
65
+ caseInsensitive?: boolean;
66
+ }
67
+ | {
68
+ path: readonly string[];
69
+ /** Uses the same string-substring and array-element semantics as `containsAny`. */
70
+ containsAll: readonly ProviderModelDiscoveryScalar[];
71
+ caseInsensitive?: boolean;
72
+ };
73
+
74
+ export interface ProviderModelDiscoveryFilter {
75
+ /** Every predicate must match. */
76
+ allOf?: readonly ProviderModelDiscoveryPredicate[];
77
+ /** At least one predicate must match. */
78
+ anyOf?: readonly ProviderModelDiscoveryPredicate[];
79
+ /** No predicate may match. */
80
+ noneOf?: readonly ProviderModelDiscoveryPredicate[];
81
+ }
82
+
83
+ interface ProviderModelDiscoverySharedSpec {
84
+ /** Query parameters applied to the resolved discovery URL. */
85
+ query?: Readonly<Record<string, string>>;
86
+ /** Declarative eligibility rules evaluated against each untrusted model row. */
87
+ filter?: ProviderModelDiscoveryFilter;
88
+ /** Optional lower byte ceiling; the process-wide hard ceiling still wins. */
89
+ maxResponseBytes?: number;
90
+ /** Optional lower raw-row ceiling; the process-wide hard ceiling still wins. */
91
+ maxModels?: number;
92
+ /**
93
+ * If a valid extracted id starts with this prefix, strip it and re-validate the remainder.
94
+ * Empty/invalid remainders skip that row only.
95
+ */
96
+ stripIdPrefix?: string;
97
+ }
98
+
99
+ type ProviderModelDiscoveryLocation =
100
+ | {
101
+ /** Registry-owned absolute endpoint. Mutually exclusive with `path`. */
102
+ url: string;
103
+ path?: never;
104
+ }
105
+ | {
106
+ /** Resource path relative to baseUrl; query strings and fragments are disallowed. */
107
+ path: string;
108
+ url?: never;
109
+ }
110
+ | {
111
+ /** Keep the adapter-derived default discovery endpoint. */
112
+ url?: never;
113
+ path?: never;
114
+ };
115
+
116
+ /**
117
+ * Trusted live-model discovery policy. This metadata is registry-only: it must never be copied
118
+ * into config.json, where a same-named custom provider could otherwise redirect a stored key.
119
+ */
120
+ export type ProviderModelDiscoverySpec = ProviderModelDiscoverySharedSpec & ProviderModelDiscoveryLocation;
121
+
122
+ export interface ProviderRegistryEntry {
123
+ id: string;
124
+ label: string;
125
+ adapter: string;
126
+ baseUrl: string;
127
+ apiKeyTransport?: OcxProviderConfig["apiKeyTransport"];
128
+ authKind: ProviderAuthKind;
129
+ codexAccountMode?: CodexAccountMode;
130
+ /** OAuth preset may explicitly honor a persisted API-key billing mode. */
131
+ allowKeyAuthOverride?: boolean;
132
+ allowPrivateNetworkByDefault?: boolean;
133
+ keyOptional?: boolean;
134
+ /**
135
+ * Registry-only key-login policy for public model catalogs that cannot authenticate a key.
136
+ * The dashboard flow then reports the key as unverifiable instead of a false positive.
137
+ */
138
+ apiKeyValidation?: "unknown";
139
+ /**
140
+ * Free-tier pricing (no paid subscription required). Distinct from `keyOptional`:
141
+ * free tiers may still require an API key (e.g. NVIDIA NIM free credits).
142
+ */
143
+ freeTier?: boolean;
144
+ allowBaseUrlOverride?: boolean;
145
+ /**
146
+ * Do not claim an existing same-named key provider whose fixed destination differs from this
147
+ * preset. Enable for newly promoted ids so an older custom key cannot be silently retargeted.
148
+ */
149
+ preserveCustomDestination?: boolean;
150
+ /**
151
+ * Optional endpoint picker for providers with multiple official hosts
152
+ * (e.g. Qwen Cloud token plan vs pay-as-you-go). Requires `allowBaseUrlOverride`
153
+ * so the selected URL is honored at route time. A choice without `baseUrl` is "Custom".
154
+ */
155
+ baseUrlChoices?: readonly ProviderBaseUrlChoice[];
156
+ /** Static headers merged into every upstream request for this provider. */
157
+ staticHeaders?: Record<string, string>;
158
+ modelSuffixBracketStrip?: boolean;
159
+ featured?: boolean;
160
+ dashboardPreset?: boolean;
161
+ note?: string;
162
+ dashboardUrl?: string;
163
+ defaultModel?: string;
164
+ models?: string[];
165
+ liveModels?: boolean;
166
+ /**
167
+ * Registry-only per-model wire defaults for mixed OpenAI-compatible gateways.
168
+ * These are intentionally not seeded into saved config: an explicit `modelAdapters`
169
+ * entry must remain distinguishable and must always win over a default.
170
+ *
171
+ * A bare string applies to every inbound protocol. The object form scopes the
172
+ * default to the inbound surfaces named in `inbound`, which is how a model that is
173
+ * native on two wires can serve each client on the wire it already speaks instead
174
+ * of paying a translation hop.
175
+ */
176
+ modelWireDefaults?: Record<string, ModelWireDefault>;
177
+ /** Explicit Fast wire declaration; absence derives from the final model adapter. */
178
+ fastWire?: FastWire | null;
179
+ /**
180
+ * Registry-only per-model override for the upstream request shape used behind a
181
+ * Codex Responses WebSocket turn. `false` keeps the client-facing WebSocket but
182
+ * asks the upstream Responses endpoint for bounded JSON, which the bridge then
183
+ * reframes as Responses events. Use only for upstreams whose streaming response
184
+ * can omit or indefinitely delay the terminal event.
185
+ */
186
+ modelResponsesUpstreamStreaming?: Record<string, boolean>;
187
+ /** Registry-only repair for a model whose native Responses stream may omit its terminal. */
188
+ modelResponsesTerminalRepair?: Record<string, ResponsesTerminalRepairPolicy>;
189
+ /**
190
+ * Registry-only client-facing item-id repair policy (#938), filled onto the
191
+ * runtime provider only when the user has no explicit policy (derive.ts);
192
+ * never seeded into saved config.
193
+ */
194
+ responsesItemIdRepair?: {
195
+ message?: string[];
196
+ reasoning?: string[];
197
+ repairMissingTerminalIds?: boolean;
198
+ repairInvalidIds?: boolean;
199
+ };
200
+ /**
201
+ * Responses-API resource path for providers whose route is not `/v1/responses`.
202
+ * Unlike `modelWireDefaults` above, this IS seeded into saved config: it describes
203
+ * the provider's fixed endpoint rather than a default a user might want to override
204
+ * per model. DeepSeek documents `POST /responses` with no `/v1` segment.
205
+ */
206
+ responsesPath?: string;
207
+ /**
208
+ * Responses upstream that stores nothing server-side. Stateful request parameters
209
+ * are dropped and `store` is pinned false, and orphaned tool results left by a
210
+ * replay miss are repaired rather than forwarded.
211
+ */
212
+ statelessResponses?: boolean;
213
+ /**
214
+ * Responses parser requires an unambiguous call batch and its matched result batch
215
+ * to stay contiguous. This is seeded/backfilled like other fixed wire capabilities.
216
+ */
217
+ requiresAdjacentResponsesToolResults?: boolean;
218
+ /**
219
+ * Registry default for the provider's `service_tier` support; see
220
+ * `OcxProviderConfig.supportsServiceTier`. Registry-only: backfilled (never
221
+ * overriding) at enrich/route time and deliberately NOT seeded into saved
222
+ * config, so an explicit user value stays distinguishable from the default
223
+ * (and the canonical openai seed comparison keeps its exact key set).
224
+ */
225
+ supportsServiceTier?: boolean;
226
+ /** Registry default for OpenAI extended hosted web_search field support. */
227
+ supportsOpenAiWebSearchToolFields?: boolean;
228
+ /** Registry default for native Responses custom-tool support. */
229
+ supportsResponsesCustomTools?: boolean;
230
+ /** Registry default for exact model service-tier capability; explicit config keys win. */
231
+ modelSupportsServiceTier?: Record<string, boolean>;
232
+ /**
233
+ * Registry-only service-tier defaults for an OAuth preset's explicit API-key transport.
234
+ * Applied only when `allowKeyAuthOverride` is true and the captured effective auth transport
235
+ * is key-based. Explicit provider config still wins field-by-field, including `false`.
236
+ */
237
+ keyAuthServiceTier?: {
238
+ supportsServiceTier?: boolean;
239
+ modelSupportsServiceTier?: Record<string, boolean>;
240
+ chatServiceTier?: boolean;
241
+ };
242
+ /** Provider-specific copy for the Codex catalog's Fast tier. */
243
+ fastTierDescription?: string;
244
+ /**
245
+ * Registry-only destination guard for `modelSupportsServiceTier`. This scopes vendor evidence
246
+ * without changing provider ownership, routing, authentication, or config validation.
247
+ */
248
+ modelServiceTierCapabilityBaseUrlGuard?: (baseUrl: string) => boolean;
249
+ /** Registry default for plaintext reasoning replay; see `OcxProviderConfig.preserveResponsesReasoningContent`. Registry-only like `supportsServiceTier`. */
250
+ preserveResponsesReasoningContent?: boolean;
251
+ /** Registry defaults for per-model Codex reasoning propagation; explicit user keys win during enrichment. */
252
+ modelSupportsReasoningSummaries?: Record<string, boolean>;
253
+ modelDiscovery?: ProviderModelDiscoverySpec;
254
+ contextWindow?: number;
255
+ modelContextWindows?: Record<string, number>;
256
+ modelInputModalities?: Record<string, string[]>;
257
+ defaultMaxOutputTokens?: number;
258
+ modelMaxOutputTokens?: Record<string, number>;
259
+ reasoningEfforts?: string[];
260
+ modelReasoningEfforts?: Record<string, string[]>;
261
+ modelDefaultReasoningEfforts?: Record<string, string>;
262
+ reasoningEffortMap?: Record<string, string>;
263
+ modelReasoningEffortMap?: Record<string, Record<string, string>>;
264
+ /**
265
+ * Registry-authoritative models that send OpenAI's direct `reasoning_effort` field.
266
+ * Runtime enrichment uses this to repair stale preset metadata that still classifies a model
267
+ * as a thinking-budget/toggle model. This is registry-only and is never persisted as user config.
268
+ */
269
+ directReasoningEffortModels?: string[];
270
+ reasoningWireFormat?: OcxProviderConfig["reasoningWireFormat"];
271
+ noVisionModels?: string[];
272
+ noReasoningModels?: string[];
273
+ noTemperatureModels?: string[];
274
+ noTopPModels?: string[];
275
+ noPenaltyModels?: string[];
276
+ /** Opt this provider into parallel tool calls (see OcxProviderConfig.parallelToolCalls). */
277
+ parallelToolCalls?: boolean;
278
+ /** Opt this provider into forwarding prompt_cache_key (OpenAI-specific; strict backends reject it). */
279
+ promptCacheKey?: boolean;
280
+ /**
281
+ * Opt-in: forward `service_tier` on the `/chat/completions` wire. Same hazard as
282
+ * `promptCacheKey` — an OpenAI-specific extension that strict gateways reject. Distinct from
283
+ * `supportsServiceTier`, which governs the Responses wire.
284
+ */
285
+ chatServiceTier?: boolean;
286
+ /** OpenAI Chat EOF policy for gateways that omit terminal frames after complete tool calls. */
287
+ openaiChatEofTolerance?: boolean;
288
+ autoToolChoiceOnlyModels?: string[];
289
+ preserveReasoningContentModels?: string[];
290
+ requiresReasoningPlaceholderModels?: string[];
291
+ reasoningSplitModels?: string[];
292
+ thinkingToggleModels?: string[];
293
+ thinkingBudgetModels?: string[];
294
+ escapeBuiltinToolNames?: boolean;
295
+ oauthId?: string;
296
+ virtualModels?: Record<string, { wireModelId: string; reasoningMode: "pro" }>;
297
+ modelMaxInputTokens?: Record<string, number>;
298
+ jawcodeBundle?: string;
299
+ extraMetadataAliases?: string[];
300
+ metadataModelIdNormalize?: MetadataModelIdNormalize;
301
+ googleMode?: "ai-studio" | "vertex" | "cloud-code-assist";
302
+ project?: string;
303
+ location?: string;
304
+ }
305
+
306
+ export type ProviderConfigSeed = Pick<
307
+ OcxProviderConfig,
308
+ "adapter" | "baseUrl" | "apiKeyTransport" | "responsesPath" | "authMode" | "keyOptional" | "freeTier" | "modelSuffixBracketStrip" | "defaultModel" | "models"
309
+ | "liveModels" | "contextWindow" | "modelContextWindows" | "modelInputModalities"
310
+ | "modelMaxInputTokens" | "defaultMaxOutputTokens" | "modelMaxOutputTokens"
311
+ | "reasoningEfforts" | "modelReasoningEfforts" | "modelDefaultReasoningEfforts" | "reasoningEffortMap" | "modelReasoningEffortMap" | "reasoningWireFormat"
312
+ | "noVisionModels" | "noReasoningModels" | "noTemperatureModels" | "noTopPModels" | "noPenaltyModels"
313
+ | "autoToolChoiceOnlyModels" | "preserveReasoningContentModels" | "requiresReasoningPlaceholderModels" | "reasoningSplitModels" | "thinkingToggleModels" | "thinkingBudgetModels" | "escapeBuiltinToolNames" | "openaiChatEofTolerance"
314
+ | "googleMode" | "project" | "location" | "headers"
315
+ >;
316
+
317
+ // Shared between the OAuth (Claude account) and API-key Anthropic entries so both expose the
318
+ // same static model seed.
319
+ // 260710 context refresh: Tier-2 evidence in
320
+ // devlog/_plan/260710_provider_hardening/001_research_frontier.md.
321
+ const ANTHROPIC_MODELS = ["claude-fable-5", "claude-sonnet-5", "claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"];
322
+ const ANTHROPIC_MODEL_CONTEXT_WINDOWS: Record<string, number> = { "claude-sonnet-5": 1_000_000, "claude-fable-5": 1_000_000, "claude-opus-5": 1_000_000, "claude-opus-4-8": 1_000_000, "claude-opus-4-7": 1_000_000, "claude-opus-4-6": 1_000_000, "claude-sonnet-4-6": 1_000_000, "claude-haiku-4-5": 200_000 };
323
+
324
+ // 260814 GLM-5.3 is registered pre-emptively alongside 5.2 everywhere 5.2 appears. Z.AI's
325
+ // devpack "How to Switch Models" page (docs.z.ai/devpack/latest-model) lists glm-5.3 and
326
+ // glm-5.3[1m] as Coding Plan ids on the unchanged endpoints; the capability and pricing
327
+ // tables were not published yet, so every 5.3 row mirrors its 5.2 sibling until they settle.
328
+ // The non-Z.AI providers below are speculative on purpose: they carry 5.2 today and are
329
+ // expected to pick 5.3 up on their usual lag. Providers whose live /v1/models discovery is
330
+ // enabled self-correct on the next successful fetch; static ones need a follow-up refresh.
331
+ const ZAI_GLM_53_MODELS = ["glm-5.3", "glm-5.3[1m]"];
332
+ const ZAI_GLM_52_MODELS = ["glm-5.2", "glm-5.2[1m]"];
333
+ const ZAI_GLM_5X_MODELS = [...ZAI_GLM_53_MODELS, ...ZAI_GLM_52_MODELS];
334
+ const ZAI_GLM_52_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
335
+ /**
336
+ * GLM-5.3 does NOT share 5.2's five-tier ladder. docs.z.ai/devpack/latest-model folds every
337
+ * incoming effort into three effective tiers — low/minimal/light -> low, medium/high -> high,
338
+ * xhigh/max/ultra -> max — with max as both the default and the unknown-value fallback.
339
+ * Advertising five levels would publish two picker rows that are indistinguishable on the wire,
340
+ * so only the effective tiers are exposed (same treatment Cursor and Baseten already give GLM).
341
+ */
342
+ const ZAI_GLM_53_REASONING_EFFORTS = ["low", "high", "max"];
343
+ /** Per-model ladders for the Coding Plan rows: 5.3 gets its three effective tiers, 5.2 keeps five. */
344
+ const ZAI_GLM_5X_REASONING_EFFORTS: Record<string, string[]> = {
345
+ ...Object.fromEntries(ZAI_GLM_53_MODELS.map(id => [id, ZAI_GLM_53_REASONING_EFFORTS])),
346
+ ...Object.fromEntries(ZAI_GLM_52_MODELS.map(id => [id, ZAI_GLM_52_REASONING_EFFORTS])),
347
+ };
348
+ // 260710 MiniMax models and context windows: Tier-2 evidence in
349
+ // devlog/_plan/260710_provider_hardening/002_research_cn.md.
350
+ const MINIMAX_MODELS = [
351
+ "MiniMax-M3",
352
+ "MiniMax-M2.7", "MiniMax-M2.7-highspeed",
353
+ "MiniMax-M2.5", "MiniMax-M2.5-highspeed",
354
+ "MiniMax-M2.1", "MiniMax-M2.1-highspeed",
355
+ "MiniMax-M2",
356
+ ];
357
+ const MINIMAX_MODEL_CONTEXT_WINDOWS: Record<string, number> = Object.fromEntries(
358
+ MINIMAX_MODELS.map(id => [id, id === "MiniMax-M3" ? 1_000_000 : 204_800]),
359
+ );
360
+ const MINIMAX_M3_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
361
+ const MINIMAX_M3_REASONING_EFFORT_MAP: Record<string, string> = {
362
+ none: "disabled",
363
+ minimal: "disabled",
364
+ low: "disabled",
365
+ medium: "adaptive",
366
+ high: "adaptive",
367
+ xhigh: "adaptive",
368
+ max: "adaptive",
369
+ };
370
+ const OPENAI_GPT56_MODELS = ["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"];
371
+ const OPENAI_GPT56_PRO_MODELS = ["gpt-5.6-sol-pro", "gpt-5.6-terra-pro", "gpt-5.6-luna-pro"];
372
+ const OPENAI_API_GPT56_CONTEXT_WINDOW = 1_050_000;
373
+ const OPENAI_API_GPT56_CONTEXT_WINDOWS: Record<string, number> = {
374
+ ...Object.fromEntries([...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS].map(id => [id, OPENAI_API_GPT56_CONTEXT_WINDOW])),
375
+ "gpt-5.5": OPENAI_API_GPT56_CONTEXT_WINDOW,
376
+ };
377
+ const OPENAI_API_GPT56_MAX_INPUT_TOKENS: Record<string, number> = {
378
+ ...Object.fromEntries([...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS].map(id => [id, 922_000])),
379
+ "gpt-5.5": 922_000,
380
+ };
381
+ const OPENAI_API_GPT56_VIRTUAL_MODELS: Record<string, { wireModelId: string; reasoningMode: "pro" }> = {
382
+ "gpt-5.6-sol-pro": { wireModelId: "gpt-5.6-sol", reasoningMode: "pro" },
383
+ "gpt-5.6-terra-pro": { wireModelId: "gpt-5.6-terra", reasoningMode: "pro" },
384
+ "gpt-5.6-luna-pro": { wireModelId: "gpt-5.6-luna", reasoningMode: "pro" },
385
+ };
386
+ const OPENAI_API_GPT56_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
387
+ /**
388
+ * Daybreak program aliases. These `-latest` ids are the stable contract: OpenAI repoints
389
+ * them at newer snapshots over time (red -> gpt-5.6-cyber, blue -> gpt-5.6-sol as of
390
+ * 2026-08-11), so registering the ALIAS inherits future model swaps while a pinned
391
+ * snapshot id would silently go stale. Snapshot ids are deliberately absent here.
392
+ * Responses-only per both published endpoint tables (`v1/chat/completions` is marked
393
+ * Not supported) — never add these to a chat-completions provider. Access needs separate
394
+ * Daybreak approval and provisioning, so neither is ever a default.
395
+ * Verified 2026-08-11: developers.openai.com/api/docs/models/daybreak-red-latest.md
396
+ * and .../daybreak-blue-latest.md
397
+ */
398
+ const OPENAI_DAYBREAK_MODELS = ["daybreak-red-latest", "daybreak-blue-latest"];
399
+ const OPENAI_DAYBREAK_CONTEXT_WINDOWS: Record<string, number> = {
400
+ "daybreak-red-latest": 400_000,
401
+ "daybreak-blue-latest": 1_050_000,
402
+ };
403
+ const OPENAI_DAYBREAK_MAX_INPUT_TOKENS: Record<string, number> = {
404
+ "daybreak-red-latest": 272_000,
405
+ "daybreak-blue-latest": 922_000,
406
+ };
407
+ /**
408
+ * Neither Daybreak page publishes a reasoning-effort ladder. An explicit empty array means
409
+ * "expose no effort control"; OMITTING the key would instead fall back to the full routed
410
+ * ladder (`configuredReasoningEfforts` returns undefined -> `applyReasoningLevels` uses
411
+ * ROUTED_REASONING_LEVELS), which would advertise efforts the models never documented.
412
+ * `noReasoningModels` is wrong here: both pages document reasoning-token support, so these
413
+ * are reasoning models with no *selectable* ladder.
414
+ */
415
+ const OPENAI_DAYBREAK_REASONING_EFFORTS: Record<string, string[]> = Object.fromEntries(
416
+ OPENAI_DAYBREAK_MODELS.map(id => [id, [] as string[]]),
417
+ );
418
+ const OPENROUTER_GPT56_MODELS = OPENAI_GPT56_MODELS.map(id => `openai/${id}`);
419
+ // OpenRouter's live /endpoints routes report 1,050,000; keep this separate from the
420
+ // unverified OpenAI API-key seed. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
421
+ const OPENROUTER_GPT56_CONTEXT_WINDOW = 1_050_000;
422
+ const OPENROUTER_GPT56_CONTEXT_WINDOWS = {
423
+ "openai/gpt-5.6-sol": OPENROUTER_GPT56_CONTEXT_WINDOW,
424
+ "openai/gpt-5.6-terra": OPENROUTER_GPT56_CONTEXT_WINDOW,
425
+ "openai/gpt-5.6-luna": OPENROUTER_GPT56_CONTEXT_WINDOW,
426
+ };
427
+
428
+ /**
429
+ * Vendor thinking-toggle models (MiMo v2.x, GLM 5/5.1 on Zen Go): the wire knob is
430
+ * `thinking: {type: enabled|disabled}` — a binary. Advertise the full Codex picker ladder
431
+ * and map efforts onto the toggle. Zen Go
432
+ * pass-through probed live 2026-07-07 (glm-5.2 toggle verified; mimo/minimax accept shape).
433
+ */
434
+ const THINKING_TOGGLE_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
435
+ const THINKING_TOGGLE_MAP: Record<string, string> = {
436
+ none: "disabled",
437
+ minimal: "disabled",
438
+ low: "disabled",
439
+ medium: "enabled",
440
+ high: "enabled",
441
+ xhigh: "enabled",
442
+ max: "enabled",
443
+ };
444
+ const OPENCODE_GO_THINKING_TOGGLE_MODELS = [
445
+ "mimo-v2.5", "mimo-v2.5-pro", "mimo-v2-omni", "mimo-v2-pro", "glm-5", "glm-5.1",
446
+ ];
447
+ /**
448
+ * Zhipu's domestic BigModel platform. Text families first, then the vision member: modalities are
449
+ * declared per model because `noVisionModels` means the opposite of "text only" here — it routes
450
+ * images through the proxy's vision sidecar (src/codex/catalog/provider-fetch.ts), a claim nobody
451
+ * has verified for BigModel-hosted GLM.
452
+ */
453
+ const ZHIPU_BIGMODEL_TEXT_MODELS = ["glm-4.6", "glm-4.7", "glm-4.7-flash", "glm-5", "glm-5.1", "glm-5.2", "glm-5.3"];
454
+ const ZHIPU_BIGMODEL_MODELS = [...ZHIPU_BIGMODEL_TEXT_MODELS, "glm-4.6v"];
455
+ const ZHIPU_BIGMODEL_INPUT_MODALITIES: Record<string, string[]> = {
456
+ ...Object.fromEntries(ZHIPU_BIGMODEL_TEXT_MODELS.map(id => [id, ["text"]])),
457
+ "glm-4.6v": ["text", "image"],
458
+ };
459
+ const ZHIPU_BIGMODEL_THINKING_TOGGLE_MODELS = ["glm-4.6", "glm-4.7", "glm-5", "glm-5.1", "glm-5.2", "glm-5.3"];
460
+ const THINKING_BUDGET_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
461
+ // Qwen3.8-Max is the first Qwen3.x model with official direct `reasoning_effort` support.
462
+ // Evidence: https://qwen.ai/blog?id=qwen3.8
463
+ const QWEN38_REASONING_EFFORTS = ["low", "medium", "xhigh"];
464
+ const THINKING_BUDGET_MODELS = [
465
+ "qwen3.5-397b", "qwen3.6-35b",
466
+ "qwen3.5-plus", "qwen3.6-plus", "qwen3.7-max", "qwen3.7-plus",
467
+ ];
468
+ const OPENCODE_GO_THINKING_BUDGET_MODELS = ["qwen3.5-plus", "qwen3.6-plus", "qwen3.7-max", "qwen3.7-plus"];
469
+ const DEEPSEEK_THINKING_MODELS = ["deepseek-v4-pro", "deepseek-v4-flash"];
470
+ /*
471
+ * DeepSeek's experimental vision preview (released 2026-08-21, api-docs.deepseek.com):
472
+ * text+image input on the V4 Flash base. DeepSeek positions it as a preview id;
473
+ * the expectation is that vision merges into `deepseek-v4-flash` proper later,
474
+ * at which point this id retires the same way deepseek-chat/reasoner did.
475
+ */
476
+ const DEEPSEEK_VISION_PREVIEW_MODEL = "deepseek-v4-flash-vision-exp";
477
+ const OPENCODE_FREE_DEEPSEEK_MODELS = ["deepseek-v4-flash-free"];
478
+ /*
479
+ * OpenCode Zen's free slug for the OpenRouter stealth model "Ox Alpha"
480
+ * (openrouter.ai/stealth/ox-alpha): 1,048,576-token context, multimodal
481
+ * (text+image+video upstream; Zen serves text+image), mandatory reasoning,
482
+ * free during the stealth window. Zen displays it as "Ox Alpha Free" under
483
+ * this exact id (opencode.ai/docs/zen, verified 2026-08-21).
484
+ */
485
+ const OPENCODE_OX_ALPHA_FREE_MODEL = "x-preview-f-free";
486
+ const OX_ALPHA_CONTEXT_WINDOW = 1_048_576;
487
+ /*
488
+ * Zen free models that reject `image_url` upstream (#1043, and the reproducible
489
+ * half of #1024).
490
+ *
491
+ * Zen publishes NO modality metadata — its `/v1/models` returns only id, object,
492
+ * created, owned_by — so this list is measured, not derived. Each id was probed
493
+ * once against https://opencode.ai/zen/v1 on 2026-08-05 with a text control first
494
+ * and then a 1x1 PNG; the six below failed the image request, four of them with
495
+ * `[404] No endpoints found that support image input` and `big-pickle` with the
496
+ * exact deserialize error quoted in #1043.
497
+ *
498
+ * `mimo-v2.5-free` and `longcat-2.0-free` ACCEPT images and are deliberately
499
+ * absent. Adding them would silently replace a working image with a caption,
500
+ * which is worse than the loud 400 this list exists to prevent — see the negative
501
+ * assertion in tests/provider-registry-parity.test.ts.
502
+ *
503
+ * Zen's roster is discovered live while this list is static, so it is a dated
504
+ * exception list, not a capability model. Re-probe before extending it.
505
+ * Evidence: devlog/_fin/260805_bug_fix_stack/002_zen_modality_probe.md
506
+ */
507
+ const OPENCODE_ZEN_TEXT_ONLY_MODELS = [
508
+ "big-pickle",
509
+ "nemotron-3-ultra-free",
510
+ "ling-3.0-flash-free",
511
+ "north-mini-code-free",
512
+ "laguna-s-2.1-free",
513
+ "deepseek-v4-flash-free",
514
+ ];
515
+ /*
516
+ * DeepSeek's Codex ladder is low/high/max. With the V4 Pro GA release
517
+ * (DeepSeek-V4-Pro-0813) the official thinking-mode table is IDENTICAL for both
518
+ * V4 models (api-docs.deepseek.com/guides/thinking_mode, verified 2026-08-13):
519
+ *
520
+ * requested | v4-flash | v4-pro
521
+ * low | low | low
522
+ * medium | high | high
523
+ * high | high | high
524
+ * xhigh | high | high
525
+ * max | max | max
526
+ *
527
+ * Before GA, Pro silently upgraded low->high and mapped xhigh->max (#1057-era
528
+ * table); the page's footnote about an early-August Pro mapping update landed
529
+ * with this GA, so Pro now advertises the same three real tiers as Flash.
530
+ *
531
+ * Two standing notes (#1057):
532
+ *
533
+ * - `xhigh` is a COMPATIBILITY ALIAS, not a native tier. It stays in the wire maps
534
+ * so existing requests and saved configs keep working, but it is not advertised.
535
+ * - `medium` has no row in the vendor table — mapping it to `high` is OUR
536
+ * compatibility choice for clients that only speak the OpenAI ladder.
537
+ */
538
+ const DEEPSEEK_FLASH_THINKING_EFFORTS = ["low", "high", "max"];
539
+ const DEEPSEEK_PRO_THINKING_EFFORTS = ["low", "high", "max"];
540
+ const DEEPSEEK_PRO_REASONING_MAP: Record<string, string> = {
541
+ low: "low",
542
+ medium: "high",
543
+ high: "high",
544
+ xhigh: "high",
545
+ max: "max",
546
+ };
547
+ const DEEPSEEK_FLASH_REASONING_MAP: Record<string, string> = {
548
+ low: "low",
549
+ medium: "high",
550
+ high: "high",
551
+ xhigh: "high",
552
+ max: "max",
553
+ };
554
+ /**
555
+ * Flash-versus-Pro classification for DeepSeek V4 model ids, including prefixed
556
+ * (`deepseek/deepseek-v4-pro`) and suffixed (`deepseek-v4-flash-free`) forms.
557
+ * `tests/provider-registry-parity.test.ts` enumerates every id the registry
558
+ * actually passes here, so a future id this substring test would misread cannot
559
+ * land silently.
560
+ */
561
+ const isDeepseekFlashModel = (modelId: string): boolean =>
562
+ modelId.toLowerCase().includes("flash");
563
+ const deepseekThinkingEffortsFor = (modelId: string): string[] =>
564
+ isDeepseekFlashModel(modelId) ? DEEPSEEK_FLASH_THINKING_EFFORTS : DEEPSEEK_PRO_THINKING_EFFORTS;
565
+ const deepseekReasoningMapFor = (modelId: string): Record<string, string> =>
566
+ isDeepseekFlashModel(modelId) ? DEEPSEEK_FLASH_REASONING_MAP : DEEPSEEK_PRO_REASONING_MAP;
567
+ // 260719 Alibaba Token Plan Personal Edition (China/Beijing). Keep it distinct from
568
+ // Coding Plan: the products use different exact allowlists and different base URLs.
569
+ // Evidence: https://help.aliyun.com/en/model-studio/token-plan-personal-overview
570
+ // https://help.aliyun.com/en/model-studio/token-plan-quickstart
571
+ const ALIBABA_TOKEN_PLAN_MODELS = [
572
+ "qwen3.8-max", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-flash",
573
+ "glm-5.3", "glm-5.2", "deepseek-v4-pro",
574
+ ];
575
+ const ALIBABA_TOKEN_PLAN_QWEN_MODELS = [
576
+ "qwen3.8-max", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-flash",
577
+ ];
578
+ const ALIBABA_TOKEN_PLAN_INPUT_MODALITIES: Record<string, string[]> = {
579
+ "qwen3.8-max": ["text", "image"],
580
+ "qwen3.7-max": ["text", "image"],
581
+ "qwen3.7-plus": ["text", "image"],
582
+ "qwen3.6-flash": ["text", "image"],
583
+ "glm-5.3": ["text"],
584
+ "glm-5.2": ["text"],
585
+ "deepseek-v4-pro": ["text"],
586
+ };
587
+
588
+ // 260721 Alibaba Token Plan International (ap-southeast-1 / Singapore, hardened 260721).
589
+ // Multi-vendor lineup distinct from Beijing — includes DeepSeek V4 flash, Kimi K2.7, MiniMax.
590
+ // Evidence: https://www.alibabacloud.com/help/en/model-studio/token-plan-overview
591
+ // https://qwencloud.com/pricing/token-plan (qwen3.8 metadata)
592
+ const ALIBABA_INTL_TOKEN_PLAN_MODELS = [
593
+ "qwen3.8-max", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", "qwen3.6-flash",
594
+ "deepseek-v4-pro", "deepseek-v4-flash", "deepseek-v3.2",
595
+ "kimi-k2.7-code", "kimi-k2.6", "kimi-k2.5",
596
+ "glm-5.3", "glm-5.2", "glm-5.1", "glm-5",
597
+ "MiniMax-M2.5",
598
+ ];
599
+ const ALIBABA_INTL_TOKEN_PLAN_QWEN_MODELS = [
600
+ "qwen3.8-max", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", "qwen3.6-flash",
601
+ ];
602
+
603
+ // 260722 Tencent Cloud Coding Plan. The plan's model set is explicitly dynamic; these are the
604
+ // current documented ids and live discovery remains enabled so successful /models responses win.
605
+ // Tencent marks every Coding Plan model as text-only input and restricts plan keys to interactive
606
+ // coding tools (not custom application backends or non-interactive batch automation).
607
+ // Evidence: https://cloud.tencent.cn/document/product/1823/130092
608
+ const TENCENT_CODING_PLAN_MODELS = ["tc-code-latest", "glm-5", "kimi-k2.5", "minimax-m2.5"];
609
+ // Volcengine's authenticated /api/v3/models catalog mixes chat models with embedding,
610
+ // image, video, and 3D generation resources. Keep the Codex-facing presets scoped to
611
+ // models documented for text/agent or Coding Plan use.
612
+ //
613
+ // Maintenance owner: @lidge-jun. Verified 2026-08-01 against the vendor's own docs —
614
+ // endpoints https://docs.volcengine.com/docs/82379/1528783 (Coding Plan) and
615
+ // https://docs.volcengine.com/docs/82379/2165245 (Agent Plan); Codex CLI integration
616
+ // https://www.volcengine.com/docs/82379/2556056; supported clients
617
+ // https://www.volcengine.com/docs/82379/2188957; terms https://www.volcengine.com/docs/6256/64903
618
+ // (北京火山引擎科技有限公司). Plan quota is restricted to supported AI coding tools and misuse
619
+ // is documented as grounds for suspension — see the `note` on both Plan entries.
620
+ // Report a break by opening an issue tagging the owner; the three things that rot first are the
621
+ // static catalogs (liveModels:false cannot self-heal), the base URLs, and those Plan terms.
622
+ // Full evidence ledger: devlog/_fin/260801_pr611_volcengine_evidence/000_evidence_ledger.md
623
+ const VOLCENGINE_ARK_MODELS = [
624
+ "doubao-seed-2-1-pro-260628",
625
+ "doubao-seed-2-1-turbo-260628",
626
+ "doubao-seed-evolving",
627
+ "deepseek-v4-pro-260425",
628
+ "deepseek-v4-flash-260425",
629
+ "deepseek-v3-2-251201",
630
+ // No glm-5-3 row: Ark pins date-stamped snapshot ids (glm-5-2-260617) that cannot be
631
+ // guessed ahead of the vendor publishing them. Add it once /api/v3/models lists one.
632
+ "glm-5-2-260617",
633
+ "glm-4-7-251222",
634
+ ];
635
+ const VOLCENGINE_DOUBAO_THINKING_MODELS = [
636
+ "doubao-seed-2-1-pro-260628",
637
+ "doubao-seed-2-1-turbo-260628",
638
+ "doubao-seed-evolving",
639
+ ];
640
+ const VOLCENGINE_CODING_PLAN_MODELS = [
641
+ "ark-code-latest",
642
+ "doubao-seed-2.0-code",
643
+ "deepseek-v4-pro",
644
+ "deepseek-v4-flash",
645
+ "glm-5.3",
646
+ "glm-5.2",
647
+ "kimi-k2.6",
648
+ "minimax-m3",
649
+ ];
650
+ const VOLCENGINE_AGENT_PLAN_MODELS = [
651
+ "deepseek-v4-pro",
652
+ "deepseek-v4-flash",
653
+ "glm-5.3",
654
+ "glm-5.2",
655
+ "kimi-k2.6",
656
+ "minimax-m3",
657
+ "doubao-seed-2.0-pro",
658
+ ];
659
+ const VOLCENGINE_PLAN_INPUT_MODALITIES: Record<string, string[]> = {
660
+ "kimi-k2.6": ["text", "image"],
661
+ "minimax-m3": ["text", "image"],
662
+ };
663
+ // Every other Plan model is text-only. Declaring this explicitly keeps the vision
664
+ // sidecar from advertising image input for models that cannot accept it — the same
665
+ // treatment tencent-coding-plan gives its (entirely text-only) plan catalog.
666
+ const VOLCENGINE_PLAN_TEXT_ONLY_MODELS = [
667
+ "ark-code-latest",
668
+ "doubao-seed-2.0-code",
669
+ "deepseek-v4-pro",
670
+ "deepseek-v4-flash",
671
+ "glm-5.3",
672
+ "glm-5.2",
673
+ "doubao-seed-2.0-pro",
674
+ ];
675
+ const ALIBABA_INTL_TOKEN_PLAN_INPUT_MODALITIES: Record<string, string[]> = {
676
+ "qwen3.8-max": ["text", "image"],
677
+ "qwen3.7-max": ["text", "image"],
678
+ "qwen3.7-plus": ["text", "image"],
679
+ "qwen3.6-plus": ["text", "image"],
680
+ "qwen3.6-flash": ["text", "image"],
681
+ "deepseek-v4-pro": ["text"],
682
+ "deepseek-v4-flash": ["text"],
683
+ "deepseek-v3.2": ["text"],
684
+ "kimi-k2.7-code": ["text", "image"],
685
+ "kimi-k2.6": ["text", "image"],
686
+ "kimi-k2.5": ["text", "image"],
687
+ "glm-5.3": ["text"],
688
+ "glm-5.2": ["text"],
689
+ "glm-5.1": ["text"],
690
+ "glm-5": ["text"],
691
+ "MiniMax-M2.5": ["text"],
692
+ };
693
+
694
+ // 260717 Kimi K3: the subscription endpoint uses one upstream id (`k3`) for both
695
+ // entitlement tiers. Bare `k3` advertises the Moderato 256K ceiling; the local `[1m]`
696
+ // alias advertises Allegretto's 1M ceiling and is stripped before the upstream request.
697
+ // The separately billed Moonshot API uses `kimi-k3`.
698
+ // Evidence: https://www.kimi.com/code/docs/en/kimi-code/models.html
699
+ // https://www.kimi.com/code/docs/en/kimi-code/error-reference.html
700
+ const KIMI_K3_STANDARD_CONTEXT_WINDOW = 262_144;
701
+ const KIMI_K3_1M_CONTEXT_WINDOW = 1_048_576;
702
+ const KIMI_CODING_K3_MODELS = ["k3", "k3[1m]"];
703
+ const KIMI_LEGACY_API_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"];
704
+ const KIMI_API_MODELS = ["kimi-k3", ...KIMI_LEGACY_API_MODELS];
705
+ const KIMI_CODING_MODELS = [...KIMI_CODING_K3_MODELS, ...KIMI_LEGACY_API_MODELS, "kimi-for-coding"];
706
+ const KIMI_THINKING_MODELS = KIMI_CODING_MODELS;
707
+ const KIMI_CODING_NO_REASONING_MODELS = KIMI_CODING_MODELS.filter(id => !KIMI_CODING_K3_MODELS.includes(id));
708
+ const KIMI_API_NO_REASONING_MODELS = KIMI_API_MODELS.filter(id => id !== "kimi-k3");
709
+ const KIMI_CODING_K3_REASONING_EFFORTS = ["low", "high", "max"];
710
+ const KIMI_CODING_K3_REASONING_EFFORT_MAP: Record<string, string> = {
711
+ none: "none",
712
+ low: "low",
713
+ medium: "high",
714
+ high: "high",
715
+ xhigh: "max",
716
+ max: "max",
717
+ };
718
+ const KIMI_CODING_REASONING_EFFORTS = Object.fromEntries(
719
+ KIMI_CODING_MODELS.map(id => [id, KIMI_CODING_K3_MODELS.includes(id) ? KIMI_CODING_K3_REASONING_EFFORTS : []]),
720
+ );
721
+ const KIMI_CODING_DEFAULT_REASONING_EFFORTS = Object.fromEntries(
722
+ KIMI_CODING_K3_MODELS.map(id => [id, "max"]),
723
+ );
724
+ const KIMI_CODING_REASONING_EFFORT_MAPS = Object.fromEntries(
725
+ KIMI_CODING_K3_MODELS.map(id => [id, KIMI_CODING_K3_REASONING_EFFORT_MAP]),
726
+ );
727
+ const KIMI_API_REASONING_EFFORTS = Object.fromEntries(
728
+ KIMI_API_MODELS.map(id => [id, id === "kimi-k3" ? ["max"] : []]),
729
+ );
730
+ const KIMI_LOCKED_PARAMETER_MODELS = KIMI_CODING_MODELS;
731
+ const KIMI_AUTO_TOOL_CHOICE_ONLY_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-for-coding"];
732
+ const KIMI_API_MODEL_CONTEXT_WINDOWS: Record<string, number> = Object.fromEntries(
733
+ KIMI_API_MODELS.map(id => [id, id === "kimi-k3" ? KIMI_K3_1M_CONTEXT_WINDOW : 262_144]),
734
+ );
735
+ const KIMI_API_MODEL_INPUT_MODALITIES = { "kimi-k3": ["text", "image"] };
736
+
737
+ // 260715 NVIDIA NIM kimi family (issue #126): documented served ids on integrate
738
+ // chat/completions per docs.api.nvidia.com/nim/reference/llm-apis; live /v1/models
739
+ // currently lists only kimi-k2.6 but the list is dynamic, so carry the documented family.
740
+ const NVIDIA_NIM_KIMI_THINKING_MODELS = [
741
+ "moonshotai/kimi-k2.6", "moonshotai/kimi-k2.5", "moonshotai/kimi-k2-thinking",
742
+ ];
743
+ const NVIDIA_NIM_KIMI_MODELS = [
744
+ ...NVIDIA_NIM_KIMI_THINKING_MODELS,
745
+ "moonshotai/kimi-k2-instruct", "moonshotai/kimi-k2-instruct-0905",
746
+ ];
747
+ /**
748
+ * 260804 issue #956: NIM publishes no input-modality metadata on `/v1/models`, so the
749
+ * registry is the only source of truth for which models can see images.
750
+ *
751
+ * Two lists, both verified per-model against NVIDIA documentation on 2026-08-04
752
+ * (build.nvidia.com model pages and docs.api.nvidia.com/nim/reference/*). Evidence and
753
+ * the per-id audit: devlog/_fin/260804_stack7_service_vision/011_nim_id_audit.md.
754
+ *
755
+ * Read `noVisionModels` carefully — it lists models that CANNOT see images, which is
756
+ * what routes them through the proxy's vision sidecar (src/vision/index.ts) and makes the
757
+ * catalog advertise image input for them. Membership is wrong in BOTH directions:
758
+ * - a text-only model missing from it keeps issue #956 (images blocked or rejected);
759
+ * - a vision model wrongly IN it gets its image silently replaced by another model's
760
+ * text description — no error, worse answers, extra cost.
761
+ *
762
+ * A new NIM id must be classified DELIBERATELY against its NVIDIA page, never assumed
763
+ * from its name: `google/gemma-4-31b-it` carries no vision marker yet accepts images,
764
+ * `-vl` also appears on embedding/reranking models, and `google/codegemma-7b` is
765
+ * text-only while `google/codegemma-1.1-7b` has no current page at all. An unclassified
766
+ * id is intentionally left alone rather than defaulted, because NIM serves non-chat
767
+ * endpoints (embeddings, rerankers, guards, OCR) that reach the same code path.
768
+ */
769
+ const NVIDIA_NIM_VISION_MODELS = [
770
+ "meta/llama-3.2-11b-vision-instruct", "meta/llama-3.2-90b-vision-instruct",
771
+ "nvidia/llama-3.1-nemotron-nano-vl-8b-v1", "nvidia/nemotron-nano-12b-v2-vl",
772
+ "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning", "nvidia/cosmos3-nano-reasoner",
773
+ "nvidia/ising-calibration-1.5-31b", "nvidia/ising-calibration-1-35b-a3b",
774
+ "google/gemma-4-31b-it", "google/diffusiongemma-26b-a4b-it",
775
+ "minimaxai/minimax-m3", "moonshotai/kimi-k2.6", "moonshotai/kimi-k2.5",
776
+ "stepfun-ai/step-3.7-flash", "thinkingmachines/inkling",
777
+ "mistralai/mistral-medium-3.5-128b",
778
+ ];
779
+ /**
780
+ * The catalog advertises image input only for `noVisionModels` members, so a natively
781
+ * vision-capable model would otherwise be published as text-only and the Codex app would
782
+ * block attachments before the native path ever runs.
783
+ */
784
+ const NVIDIA_NIM_VISION_INPUT_MODALITIES: Record<string, string[]> = Object.fromEntries(
785
+ NVIDIA_NIM_VISION_MODELS.map(id => [id, ["text", "image"]]),
786
+ );
787
+ /**
788
+ * Text-only NIM chat models — 26 ids, each carrying an explicit `Input Modalities: Text`
789
+ * (or equivalent) on its NVIDIA page. PR #964 proposed ~64; six of those are natively
790
+ * image-capable and live in NVIDIA_NIM_VISION_MODELS above, and 32 more had no current
791
+ * NVIDIA page and were dropped rather than assumed.
792
+ *
793
+ * kimi-k2-thinking and kimi-k2-instruct are text-only while k2.5/k2.6 are not — vision
794
+ * and reasoning are independent axes, so all four stay in NVIDIA_NIM_KIMI_MODELS for
795
+ * reasoning suppression regardless of which list they appear in here.
796
+ */
797
+ const NVIDIA_NIM_NO_VISION_MODELS = [
798
+ "deepseek-ai/deepseek-v4-flash", "deepseek-ai/deepseek-v4-pro",
799
+ "google/codegemma-7b",
800
+ "meta/llama-3.1-70b-instruct", "meta/llama-3.1-8b-instruct",
801
+ "meta/llama-3.2-1b-instruct", "meta/llama-3.2-3b-instruct",
802
+ "meta/llama-3.3-70b-instruct", "meta/llama2-70b",
803
+ "mistralai/mistral-7b-instruct-v0.3", "mistralai/mistral-nemotron",
804
+ "moonshotai/kimi-k2-thinking", "moonshotai/kimi-k2-instruct",
805
+ "nvidia/llama-3.1-nemotron-nano-8b-v1", "nvidia/llama-3.1-nemotron-ultra-253b-v1",
806
+ "nvidia/llama-3.3-nemotron-super-49b-v1", "nvidia/llama-3.3-nemotron-super-49b-v1.5",
807
+ "nvidia/nemotron-3-nano-30b-a3b", "nvidia/nemotron-3-super-120b-a12b",
808
+ "nvidia/nemotron-3-ultra-550b-a55b", "nvidia/nemotron-mini-4b-instruct",
809
+ "nvidia/nvidia-nemotron-nano-9b-v2",
810
+ "openai/gpt-oss-120b", "openai/gpt-oss-20b",
811
+ "poolside/laguna-xs-2.1", "z-ai/glm-5.3", "z-ai/glm-5.2",
812
+ ];
813
+ const KIMI_CODING_MODEL_CONTEXT_WINDOWS: Record<string, number> = Object.fromEntries(
814
+ KIMI_CODING_MODELS.map(id => [id, id === "k3[1m]" ? KIMI_K3_1M_CONTEXT_WINDOW : KIMI_K3_STANDARD_CONTEXT_WINDOW]),
815
+ );
816
+ const KIMI_CODING_MODEL_INPUT_MODALITIES = Object.fromEntries(
817
+ KIMI_CODING_K3_MODELS.map(id => [id, ["text", "image"]]),
818
+ );
819
+ const NEURALWATT_REASONING_HISTORY_MODELS = [
820
+ "glm-5.3", "glm-5.3-short",
821
+ "glm-5.2", "glm-5.2-short",
822
+ "kimi-k2.6", "kimi-k2.7-code",
823
+ "qwen3.5-397b", "qwen3.6-35b",
824
+ ];
825
+
826
+ // 260728 Baseten Model APIs: `/v1/models` owns the live lineup, while these hints
827
+ // describe only capabilities that Baseten documents per slug. Unlisted live models
828
+ // intentionally inherit the empty provider ladder instead of being advertised with
829
+ // opencodex's generic reasoning defaults. Audio is omitted because the current proxy
830
+ // request model does not carry OpenAI `audio_url` parts.
831
+ // Evidence: https://docs.baseten.co/inference/model-apis/reasoning
832
+ // https://docs.baseten.co/inference/model-apis/vision
833
+ const BASETEN_FULL_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
834
+ const BASETEN_MODEL_REASONING_EFFORTS: Record<string, string[]> = {
835
+ "deepseek-ai/DeepSeek-V4-Pro": BASETEN_FULL_REASONING_EFFORTS,
836
+ "thinkingmachines/inkling": BASETEN_FULL_REASONING_EFFORTS,
837
+ "openai/gpt-oss-120b": BASETEN_FULL_REASONING_EFFORTS,
838
+ "moonshotai/Kimi-K3": ["low", "high", "max"],
839
+ // 260814: GLM-5.3 honours low/high/max upstream, unlike 5.2's high/max on Baseten.
840
+ "zai-org/GLM-5.3": ["low", "high", "max"],
841
+ "zai-org/GLM-5.3-Fast": ["low", "high", "max"],
842
+ "zai-org/GLM-5.2": ["high", "max"],
843
+ "zai-org/GLM-5.2-Fast": ["high", "max"],
844
+ };
845
+ const BASETEN_MODEL_REASONING_EFFORT_MAP: Record<string, Record<string, string>> = {
846
+ "deepseek-ai/DeepSeek-V4-Pro": { none: "none", minimal: "minimal" },
847
+ "thinkingmachines/inkling": { none: "none", minimal: "minimal" },
848
+ "openai/gpt-oss-120b": { none: "none", minimal: "minimal" },
849
+ "moonshotai/Kimi-K3": { none: "none" },
850
+ "zai-org/GLM-5.3": { none: "none" },
851
+ "zai-org/GLM-5.3-Fast": { none: "none" },
852
+ "zai-org/GLM-5.2": { none: "none" },
853
+ "zai-org/GLM-5.2-Fast": { none: "none" },
854
+ };
855
+ const BASETEN_MODEL_DEFAULT_REASONING_EFFORTS: Record<string, string> = {
856
+ "deepseek-ai/DeepSeek-V4-Pro": "medium",
857
+ "thinkingmachines/inkling": "high",
858
+ "openai/gpt-oss-120b": "medium",
859
+ "moonshotai/Kimi-K3": "max",
860
+ };
861
+ const BASETEN_MODEL_INPUT_MODALITIES: Record<string, string[]> = {
862
+ "thinkingmachines/inkling": ["text", "image"],
863
+ "moonshotai/Kimi-K2.6": ["text", "image"],
864
+ "moonshotai/Kimi-K2.7-Code": ["text", "image"],
865
+ "moonshotai/Kimi-K3": ["text", "image"],
866
+ };
867
+
868
+ // 260801 DigitalOcean and Scaleway expose OpenAI-shaped `/v1/models` rows with only
869
+ // id/object/created/owned_by, while their shared serverless catalogs also contain
870
+ // non-chat and endpoint-specific models. Fail closed by intersecting live discovery
871
+ // with ids that the providers' current first-party model tables establish for Chat
872
+ // Completions. A newly listed id therefore needs a docs-backed registry refresh before
873
+ // it can enter the Codex catalog.
874
+ // Evidence: https://docs.digitalocean.com/products/inference/details/models/
875
+ // https://docs.digitalocean.com/reference/api/reference/serverless-inference/
876
+ // https://www.scaleway.com/en/docs/generative-apis/reference-content/supported-models/
877
+ const DIGITALOCEAN_CHAT_COMPLETION_MODELS = [
878
+ "arcee-trinity-large-thinking",
879
+ "openai-gpt-5.6-sol",
880
+ "openai-gpt-5.6-terra",
881
+ "openai-gpt-5.6-luna",
882
+ "qwen3-coder-flash",
883
+ "qwen3.5-397b-a17b",
884
+ "deepseek-v4-pro",
885
+ "deepseek-4-flash",
886
+ "deepseek-3.2",
887
+ "gemma-4-31B-it",
888
+ "minimax-m2.5",
889
+ "kimi-k3",
890
+ "kimi-k2.6",
891
+ "kimi-k2.5",
892
+ "llama3.3-70b-instruct",
893
+ "llama-4-maverick",
894
+ "mistral-3-14B",
895
+ "nemotron-3-ultra-550b",
896
+ "nvidia-nemotron-3-super-120b",
897
+ "nemotron-3-nano-omni",
898
+ "nemotron-nano-12b-v2-vl",
899
+ "mimo-v2.5-pro",
900
+ "glm-5.3",
901
+ "glm-5.2",
902
+ "glm-5.1",
903
+ "glm-5",
904
+ // The API reference uses this native slash id in its Chat Completions example.
905
+ "meta-llama/Meta-Llama-3.1-8B-Instruct",
906
+ ] as const;
907
+ const SCALEWAY_SERVERLESS_CHAT_MODELS = [
908
+ "glm-5.3",
909
+ "glm-5.2",
910
+ // gpt-oss-120b is intentionally omitted: Scaleway requires Responses API for tool calling,
911
+ // while this preset routes Codex agent tools through Chat Completions.
912
+ "qwen3.6-35b-a3b",
913
+ "qwen3.5-397b-a17b",
914
+ "qwen3-235b-a22b-instruct-2507",
915
+ "qwen3-coder-30b-a3b-instruct",
916
+ "gemma-4-26b-a4b-it",
917
+ "llama-3.3-70b-instruct",
918
+ "mistral-medium-3.5-128b",
919
+ "mistral-small-3.2-24b-instruct-2506",
920
+ "pixtral-12b-2409",
921
+ ] as const;
922
+ const SCALEWAY_MODEL_INPUT_MODALITIES: Record<string, string[]> = {
923
+ "pixtral-12b-2409": ["text", "image"],
924
+ };
925
+ const UMANS_MODELS = [
926
+ "umans-coder",
927
+ "umans-kimi-k2.7",
928
+ "umans-flash",
929
+ "umans-glm-5.3",
930
+ "umans-glm-5.2",
931
+ "umans-glm-5.1",
932
+ "umans-qwen3.6-35b-a3b",
933
+ ];
934
+ const UMANS_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
935
+ const UMANS_GLM_REASONING_EFFORTS = ["high", "xhigh", "max"];
936
+ // 260814: Z.AI folds GLM-5.3 efforts into low/high/max, so `low` is a real tier here and
937
+ // `xhigh` is not distinct from `max` (docs.z.ai/devpack/latest-model).
938
+ const UMANS_GLM_53_REASONING_EFFORTS = ["low", "high", "max"];
939
+ const UMANS_TEXT_ONLY_MODELS = ["umans-glm-5.3", "umans-glm-5.2", "umans-glm-5.1"];
940
+ const UMANS_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
941
+ "umans-coder": 262_144,
942
+ "umans-kimi-k2.7": 262_144,
943
+ "umans-flash": 262_144,
944
+ "umans-glm-5.3": 405_504,
945
+ "umans-glm-5.2": 405_504,
946
+ "umans-glm-5.1": 202_752,
947
+ "umans-qwen3.6-35b-a3b": 262_144,
948
+ };
949
+ const UMANS_MODEL_INPUT_MODALITIES: Record<string, string[]> = Object.fromEntries(
950
+ UMANS_MODELS.map(id => [id, UMANS_TEXT_ONLY_MODELS.includes(id) ? ["text"] : ["text", "image"]]),
951
+ );
952
+ const CLINE_PASS_MODELS = [
953
+ "cline-pass/glm-5.3",
954
+ "cline-pass/glm-5.2",
955
+ "cline-pass/kimi-k3",
956
+ "cline-pass/kimi-k2.7-code",
957
+ "cline-pass/kimi-k2.6",
958
+ "cline-pass/deepseek-v4-pro",
959
+ "cline-pass/deepseek-v4-flash",
960
+ "cline-pass/mimo-v2.5",
961
+ "cline-pass/mimo-v2.5-pro",
962
+ "cline-pass/minimax-m3",
963
+ "cline-pass/qwen3.8-max",
964
+ "cline-pass/qwen3.7-max",
965
+ "cline-pass/qwen3.7-plus",
966
+ ];
967
+ const CLINE_PASS_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
968
+ "cline-pass/glm-5.3": 1_048_576,
969
+ "cline-pass/glm-5.2": 1_048_576,
970
+ "cline-pass/kimi-k3": 1_048_576,
971
+ "cline-pass/kimi-k2.7-code": 262_144,
972
+ "cline-pass/kimi-k2.6": 262_144,
973
+ "cline-pass/deepseek-v4-pro": 1_048_576,
974
+ "cline-pass/deepseek-v4-flash": 1_048_576,
975
+ "cline-pass/mimo-v2.5": 1_050_000,
976
+ "cline-pass/mimo-v2.5-pro": 1_050_000,
977
+ "cline-pass/minimax-m3": 1_048_576,
978
+ "cline-pass/qwen3.7-max": 1_000_000,
979
+ "cline-pass/qwen3.7-plus": 1_000_000,
980
+ };
981
+ const CLINE_PASS_IMAGE_MODELS = new Set([
982
+ "cline-pass/kimi-k3",
983
+ "cline-pass/kimi-k2.7-code",
984
+ "cline-pass/kimi-k2.6",
985
+ "cline-pass/mimo-v2.5",
986
+ "cline-pass/minimax-m3",
987
+ "cline-pass/qwen3.7-plus",
988
+ ]);
989
+ const CLINE_PASS_MODALITY_KNOWN_MODELS = CLINE_PASS_MODELS.filter(id => id !== "cline-pass/qwen3.8-max");
990
+ const CLINE_PASS_TEXT_ONLY_MODELS = CLINE_PASS_MODALITY_KNOWN_MODELS.filter(id => !CLINE_PASS_IMAGE_MODELS.has(id));
991
+ const CLINE_PASS_MODEL_INPUT_MODALITIES: Record<string, string[]> = Object.fromEntries(
992
+ CLINE_PASS_MODALITY_KNOWN_MODELS.map(id => [id, CLINE_PASS_IMAGE_MODELS.has(id) ? ["text", "image"] : ["text"]]),
993
+ );
994
+
995
+ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
996
+ {
997
+ id: "openai",
998
+ label: "OpenAI (Codex login)",
999
+ adapter: "openai-responses",
1000
+ baseUrl: "https://chatgpt.com/backend-api/codex",
1001
+ authKind: "forward",
1002
+ codexAccountMode: "pool",
1003
+ supportsServiceTier: true,
1004
+ featured: true,
1005
+ note: "Codex login account pool (default) or Direct main-account mode via codexAccountMode",
1006
+ },
1007
+ {
1008
+ id: "cursor",
1009
+ label: "Cursor (experimental)",
1010
+ adapter: "cursor",
1011
+ baseUrl: "https://api2.cursor.sh",
1012
+ authKind: "oauth",
1013
+ featured: false,
1014
+ dashboardPreset: true,
1015
+ note: "Experimental Cursor bridge. Live transport and live model discovery are enabled after a standalone PKCE browser login via 'ocx login cursor'; native read/write/delete/shell/fetch execution is disabled by default and request text such as Codex sandbox markers never authorizes it. Set \"nativeLocalExec\": \"on\" on providers.cursor in ~/.opencodex/config.json (dashboard: Providers → Cursor → Edit JSON) only for a trusted local experiment where every data-plane caller is trusted. \"off\" denies all, \"codex-sandbox\" is accepted for backwards compatibility but fails closed, and legacy \"unsafeAllowNativeLocalExec\": true still means explicit operator opt-in.",
1016
+ models: cursorModelIds(CURSOR_STATIC_MODELS),
1017
+ liveModels: true,
1018
+ defaultModel: "auto",
1019
+ modelContextWindows: cursorModelContextWindows(CURSOR_STATIC_MODELS),
1020
+ modelInputModalities: cursorModelInputModalities(CURSOR_STATIC_MODELS),
1021
+ modelReasoningEfforts: cursorModelReasoningEfforts(CURSOR_STATIC_MODELS),
1022
+ // Kimi K3 documents `max` as its API default, and its Cursor ladder has no `medium`
1023
+ // rung — so applyReasoningLevels' medium->high->first fallback would settle the catalog
1024
+ // default on `high`, the picker would send `high` explicitly, and the request builder's
1025
+ // no-effort fallback to `kimi-k3-max` would never be reached. Mirrors the other K3
1026
+ // routes (kimi, kimi-code, opencode-go).
1027
+ modelDefaultReasoningEfforts: { "kimi-k3": "max" },
1028
+ // Blind Cursor models (Auto routers, Composer, GLM-5.2, GLM-5.3) go through the vision sidecar;
1029
+ // multimodal hosts (Claude/Gemini/GPT/Kimi/Grok) take native SelectedImage. The catalog
1030
+ // still advertises image for noVision members so Codex can attach (sidecar option B).
1031
+ noVisionModels: [...CURSOR_NO_VISION_MODELS],
1032
+ },
1033
+ {
1034
+ id: "xai",
1035
+ label: "xAI Grok",
1036
+ adapter: "openai-chat",
1037
+ baseUrl: "https://api.x.ai/v1",
1038
+ authKind: "oauth",
1039
+ allowKeyAuthOverride: true,
1040
+ // Priority Processing is documented for xAI's public API-key Chat Completions and
1041
+ // Responses endpoints. OAuth is a separate Grok CLI subscription gateway and remains
1042
+ // unclassified; do not turn this into a provider-wide supportsServiceTier declaration.
1043
+ keyAuthServiceTier: {
1044
+ supportsServiceTier: true,
1045
+ chatServiceTier: true,
1046
+ },
1047
+ fastTierDescription: "Priority processing, 2x token price",
1048
+ featured: true,
1049
+ oauthId: "xai",
1050
+ jawcodeBundle: "xai",
1051
+ supportsOpenAiWebSearchToolFields: false,
1052
+ // Live A/B on 2026-08-20: xAI rejects native custom/custom_tool_call shapes while accepting
1053
+ // the otherwise-identical request after the custom tool is lowered to a function.
1054
+ supportsResponsesCustomTools: false,
1055
+ note: "Log in with your Grok account",
1056
+ // Parallel tool calls: officially supported and default-on per docs.x.ai function-calling
1057
+ // (verified 260709, devlog/_plan/260709_parallel_tool_calls). Streamed calls arrive whole
1058
+ // per chunk, so the buffered parser assembles them losslessly.
1059
+ parallelToolCalls: true,
1060
+ // Live /v1/models discovery is the authoritative lineup (verified 260709: returns grok-4.5);
1061
+ // the static list below is the logged-out fallback seed.
1062
+ liveModels: true,
1063
+ // 260709 refresh: lineup + metadata from official docs.x.ai (grok-4.5 announced 07-08);
1064
+ // grok-composer-2.5-fast kept as account-verified (absent from public docs). Evidence:
1065
+ // devlog/model_update/260709_model_refresh/001_xai_lineup.md.
1066
+ // grok-4.20-multi-agent-0309 is intentionally absent: the OAuth chat-completions
1067
+ // transport returns 400 ("Multi Agent requests are not allowed on chat completions").
1068
+ // 260813: grok-4.6 added per docs.x.ai/developers/grok-4-6. Context/vision still match
1069
+ // grok-4.5; the reasoning ladder does not — 4.6 adds the documented xhigh rung.
1070
+ models: ["grok-4.6", "grok-4.5", "grok-4.3", "grok-4.20-0309-reasoning", "grok-4.20-0309-non-reasoning", "grok-build-0.1", "grok-composer-2.5-fast"],
1071
+ defaultModel: "grok-4.5",
1072
+ // Keep Codex Responses callers on the compatibility Chat wire until xAI can replay
1073
+ // opaque reasoning continuation and compaction state across later turns. The scoped
1074
+ // declaration also keeps caller-owned service tiers off the OAuth subscription route.
1075
+ modelWireDefaults: {
1076
+ "grok-4.6": {
1077
+ wire: "openai-chat",
1078
+ inbound: ["responses"],
1079
+ authModes: ["oauth"],
1080
+ forwardCallerServiceTier: false,
1081
+ },
1082
+ "grok-4.5": {
1083
+ wire: "openai-chat",
1084
+ inbound: ["responses"],
1085
+ authModes: ["oauth"],
1086
+ forwardCallerServiceTier: false,
1087
+ },
1088
+ },
1089
+ // Vision lineup per docs.x.ai model-capabilities/images/understanding: the grok-4.x chat
1090
+ // models accept image input (JPEG/PNG, URL or base64). Without this the catalog leaves
1091
+ // inputModalities undefined, and deriveComboCatalogModel defaults an undefined member to
1092
+ // ["text"] — so any combo containing an xAI target is advertised to Codex as text-only and
1093
+ // the app blocks attachments client-side. grok-build-0.1 / grok-composer-2.5-fast stay out
1094
+ // (they are already listed in noVisionModels below).
1095
+ modelInputModalities: {
1096
+ "grok-4.6": ["text", "image"],
1097
+ "grok-4.5": ["text", "image"],
1098
+ "grok-4.3": ["text", "image"],
1099
+ "grok-4.20-0309-reasoning": ["text", "image"],
1100
+ "grok-4.20-0309-non-reasoning": ["text", "image"],
1101
+ },
1102
+ noReasoningModels: ["grok-4.20-0309-non-reasoning", "grok-build-0.1", "grok-composer-2.5-fast"],
1103
+ // Replay assistant reasoning_content for grok reasoning models: xAI documents dropped
1104
+ // reasoning_content as the top cause of prompt-cache misses on multi-turn conversations
1105
+ // (docs.x.ai prompt-caching/multi-turn, verified 2026-07-13 — devlog/_plan/260713_grok_caching).
1106
+ // Models that never emit reasoning simply have no thinking parts to replay (no-op).
1107
+ preserveReasoningContentModels: ["grok-4.6", "grok-4.5", "grok-4.3", "grok-4.20-0309-reasoning"],
1108
+ // grok-4.5 reasoning is always-on with low/medium/high (no off tier, no xhigh).
1109
+ // grok-4.6 adds xhigh per docs.x.ai/developers/model-capabilities/text/reasoning;
1110
+ // xAI documents high as the upstream default.
1111
+ modelReasoningEfforts: { "grok-4.6": ["low", "medium", "high", "xhigh"], "grok-4.5": ["low", "medium", "high"] },
1112
+ modelDefaultReasoningEfforts: { "grok-4.6": "high" },
1113
+ modelContextWindows: {
1114
+ "grok-4.6": 500_000,
1115
+ "grok-4.5": 500_000,
1116
+ "grok-4.3": 1_000_000,
1117
+ "grok-4.20-0309-reasoning": 1_000_000,
1118
+ "grok-4.20-0309-non-reasoning": 1_000_000,
1119
+ "grok-build-0.1": 256_000,
1120
+ },
1121
+ noVisionModels: ["grok-build-0.1", "grok-composer-2.5-fast"],
1122
+ },
1123
+ {
1124
+ id: "command-code",
1125
+ label: "Command Code - Auth",
1126
+ adapter: "command-code",
1127
+ baseUrl: "https://api.commandcode.ai",
1128
+ authKind: "oauth",
1129
+ oauthId: "command-code",
1130
+ featured: true,
1131
+ note: "Log in with your Command Code account",
1132
+ // OAuth needs one initial selection, but the exposed catalog is always discovered from the
1133
+ // signed-in account. Do not add a static model list here.
1134
+ defaultModel: "deepseek/deepseek-v4-flash",
1135
+ liveModels: true,
1136
+ modelDiscovery: {
1137
+ url: "https://api.commandcode.ai/provider/v1/models",
1138
+ maxResponseBytes: 262_144,
1139
+ maxModels: 256,
1140
+ },
1141
+ // These are capability facts from official Command Code model profiles, not seeded models.
1142
+ // Unknown/new live models deliberately do not advertise a reasoning picker.
1143
+ reasoningEfforts: [],
1144
+ modelReasoningEfforts: COMMAND_CODE_MODEL_REASONING_EFFORTS,
1145
+ // Ox Alpha (stealth preview, changelog v1.31.0): free 1M multimodal reasoning
1146
+ // model on every plan. DeepSeek vision preview id is preemptive metadata —
1147
+ // it is expected to merge into deepseek-v4-flash later.
1148
+ modelContextWindows: {
1149
+ "stealth/ox-alpha": OX_ALPHA_CONTEXT_WINDOW,
1150
+ [`deepseek/${DEEPSEEK_VISION_PREVIEW_MODEL}`]: 1_048_576,
1151
+ },
1152
+ modelInputModalities: {
1153
+ "stealth/ox-alpha": ["text", "image"],
1154
+ [`deepseek/${DEEPSEEK_VISION_PREVIEW_MODEL}`]: ["text", "image"],
1155
+ },
1156
+ defaultMaxOutputTokens: 64_000,
1157
+ // The proprietary generate wire has no verified per-request serialization flag.
1158
+ parallelToolCalls: false,
1159
+ },
1160
+ {
1161
+ id: "anthropic",
1162
+ label: "Anthropic Claude",
1163
+ adapter: "anthropic",
1164
+ baseUrl: "https://api.anthropic.com",
1165
+ authKind: "oauth",
1166
+ allowBaseUrlOverride: true,
1167
+ featured: true,
1168
+ oauthId: "anthropic",
1169
+ jawcodeBundle: "anthropic",
1170
+ note: "Log in with your Claude account",
1171
+ models: [...ANTHROPIC_MODELS],
1172
+ modelContextWindows: { ...ANTHROPIC_MODEL_CONTEXT_WINDOWS },
1173
+ defaultModel: "claude-sonnet-5",
1174
+ },
1175
+ {
1176
+ id: "anthropic-apikey",
1177
+ label: "Anthropic (API key)",
1178
+ adapter: "anthropic",
1179
+ baseUrl: "https://api.anthropic.com",
1180
+ authKind: "key",
1181
+ featured: true,
1182
+ dashboardUrl: "https://console.anthropic.com/settings/keys",
1183
+ jawcodeBundle: "anthropic",
1184
+ extraMetadataAliases: ["anthropic-key"],
1185
+ note: "Direct Anthropic API billing — no Claude subscription",
1186
+ models: [...ANTHROPIC_MODELS],
1187
+ liveModels: true,
1188
+ modelContextWindows: { ...ANTHROPIC_MODEL_CONTEXT_WINDOWS },
1189
+ defaultModel: "claude-sonnet-5",
1190
+ },
1191
+ {
1192
+ id: "kimi",
1193
+ label: "Kimi",
1194
+ adapter: "openai-chat",
1195
+ baseUrl: "https://api.kimi.com/coding/v1",
1196
+ authKind: "oauth",
1197
+ modelSuffixBracketStrip: true,
1198
+ // Kimi Code Plan documents a stable session/task prompt_cache_key as required to improve
1199
+ // cache hit rates.
1200
+ // The chat adapter only forwards a key already on the internal request (Codex's session key,
1201
+ // or the one the Claude /v1/messages inbound derives); the adapter itself never invents one.
1202
+ // Evidence: https://platform.kimi.com/docs/api/chat
1203
+ promptCacheKey: true,
1204
+ featured: true,
1205
+ oauthId: "kimi",
1206
+ jawcodeBundle: "moonshot",
1207
+ note: "Log in with your Kimi account",
1208
+ models: KIMI_CODING_MODELS,
1209
+ defaultModel: "kimi-k2.7-code",
1210
+ modelContextWindows: KIMI_CODING_MODEL_CONTEXT_WINDOWS,
1211
+ modelInputModalities: KIMI_CODING_MODEL_INPUT_MODALITIES,
1212
+ // K3 accepts low/high/max; Codex aliases are normalized by the model-scoped wire map.
1213
+ noReasoningModels: KIMI_CODING_NO_REASONING_MODELS,
1214
+ modelReasoningEfforts: KIMI_CODING_REASONING_EFFORTS,
1215
+ modelDefaultReasoningEfforts: KIMI_CODING_DEFAULT_REASONING_EFFORTS,
1216
+ modelReasoningEffortMap: KIMI_CODING_REASONING_EFFORT_MAPS,
1217
+ noTemperatureModels: KIMI_LOCKED_PARAMETER_MODELS,
1218
+ noTopPModels: KIMI_LOCKED_PARAMETER_MODELS,
1219
+ noPenaltyModels: KIMI_LOCKED_PARAMETER_MODELS,
1220
+ autoToolChoiceOnlyModels: KIMI_AUTO_TOOL_CHOICE_ONLY_MODELS,
1221
+ preserveReasoningContentModels: KIMI_THINKING_MODELS,
1222
+ },
1223
+ {
1224
+ id: "kiro",
1225
+ label: "Kiro (AWS CodeWhisperer)",
1226
+ adapter: "kiro",
1227
+ baseUrl: "https://runtime.us-east-1.kiro.dev",
1228
+ authKind: "oauth",
1229
+ oauthId: "kiro",
1230
+ note: "Import-first: reuses your installed and signed-in Kiro CLI session (requires `kiro-cli login`). Add account logs `kiro-cli` out, switches it through a fresh browser login, stores the account by profile ARN, and restores the previous CLI session on cancellation or failure. Experimental third-party harness — see Kiro ToS.",
1231
+ models: KIRO_MODELS,
1232
+ defaultModel: "kiro-auto",
1233
+ // Kiro speaks CodeWhisperer wire, not OpenAI-style GET /models. Keep the static
1234
+ // catalog authoritative so a spurious 2xx from runtime.../models cannot drop seeded ids
1235
+ // (e.g. newly listed GPT-5.6 tiers) via live-discovery reconciliation.
1236
+ liveModels: false,
1237
+ // Per-model context metadata is maintained next to the Kiro model list.
1238
+ modelContextWindows: KIRO_MODEL_CONTEXT_WINDOWS,
1239
+ modelReasoningEfforts: KIRO_MODEL_REASONING_EFFORTS,
1240
+ },
1241
+ {
1242
+ // Nous Portal — Nous Research subscription gateway (same backend Hermes Agent
1243
+ // uses). OAuth is a device grant (src/oauth/nous.ts): the access token IS the
1244
+ // per-request inference JWT (scope inference:invoke), refresh tokens are
1245
+ // single-use and rotated on every refresh. Catalog is a mix of paid models
1246
+ // (billed against the Portal subscription) and `:free` slugs (e.g.
1247
+ // tencent/hy3:free, stepfun/step-3.7-flash:free, inclusionai/ling-3.0-flash:free);
1248
+ // free-tier gating is decided live by the Portal per account, so discovery
1249
+ // from the signed-in account is authoritative; the static seed below is the
1250
+ // logged-out fallback and only lists free models verified on a real account
1251
+ // (2026-08-10): the Portal free list is authoritative and currently has
1252
+ // exactly 4 :free models: tencent/hy3:free, poolside/laguna-s-2.1:free,
1253
+ // stepfun/step-3.7-flash:free, poolside/laguna-xs-2.1:free.
1254
+ // inclusionai/ling-3.0-flash:free was removed from the Portal free list
1255
+ // (404 on the inference API since 2026-08-07) and must not be seeded.
1256
+ id: "nous",
1257
+ label: "Nous Portal",
1258
+ adapter: "openai-chat",
1259
+ baseUrl: "https://inference-api.nousresearch.com/v1",
1260
+ authKind: "oauth",
1261
+ oauthId: "nous",
1262
+ featured: true,
1263
+ // Mixed free + paid provider: the free tier is per-model (the `:free`
1264
+ // slugs), not a property of the whole provider, so freeTier stays false to
1265
+ // avoid implying every model is free.
1266
+ freeTier: false,
1267
+ dashboardUrl: "https://portal.nousresearch.com",
1268
+ defaultModel: "tencent/hy3:free",
1269
+ liveModels: true,
1270
+ models: ["tencent/hy3:free", "poolside/laguna-s-2.1:free", "stepfun/step-3.7-flash:free", "poolside/laguna-xs-2.1:free"],
1271
+ modelDiscovery: {
1272
+ // Resolves against effectiveBaseUrl (registry baseUrl .../v1) to the same
1273
+ // canonical endpoint https://inference-api.nousresearch.com/v1/models.
1274
+ path: "models",
1275
+ maxResponseBytes: 262_144,
1276
+ maxModels: 512,
1277
+ },
1278
+ note: "Nous Research subscription gateway. OAuth device login with your own Portal account; mixed paid + :free models discovered live (fallback seed 2026-08-10: tencent/hy3:free, poolside/laguna-s-2.1:free, stepfun/step-3.7-flash:free, poolside/laguna-xs-2.1:free).",
1279
+ },
1280
+ {
1281
+ id: "openai-apikey",
1282
+ label: "OpenAI API",
1283
+ adapter: "openai-responses",
1284
+ baseUrl: "https://api.openai.com/v1",
1285
+ authKind: "key",
1286
+ supportsServiceTier: true,
1287
+ featured: true,
1288
+ dashboardUrl: "https://platform.openai.com/api-keys",
1289
+ defaultModel: "gpt-5.5",
1290
+ models: ["gpt-5.5", ...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS, ...OPENAI_DAYBREAK_MODELS],
1291
+ liveModels: true,
1292
+ modelContextWindows: { ...OPENAI_API_GPT56_CONTEXT_WINDOWS, ...OPENAI_DAYBREAK_CONTEXT_WINDOWS },
1293
+ modelMaxInputTokens: { ...OPENAI_API_GPT56_MAX_INPUT_TOKENS, ...OPENAI_DAYBREAK_MAX_INPUT_TOKENS },
1294
+ modelInputModalities: Object.fromEntries(
1295
+ ["gpt-5.5", ...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS, ...OPENAI_DAYBREAK_MODELS]
1296
+ .map(id => [id, ["text", "image"]]),
1297
+ ),
1298
+ modelReasoningEfforts: {
1299
+ ...Object.fromEntries(
1300
+ [...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS].map(id => [id, OPENAI_API_GPT56_REASONING_EFFORTS]),
1301
+ ),
1302
+ ...OPENAI_DAYBREAK_REASONING_EFFORTS,
1303
+ },
1304
+ virtualModels: OPENAI_API_GPT56_VIRTUAL_MODELS,
1305
+ },
1306
+ {
1307
+ id: "umans",
1308
+ label: "Umans AI Coding Plan",
1309
+ adapter: "anthropic",
1310
+ baseUrl: "https://api.code.umans.ai",
1311
+ authKind: "key",
1312
+ featured: true,
1313
+ dashboardUrl: "https://app.umans.ai/billing",
1314
+ defaultModel: "umans-coder",
1315
+ models: UMANS_MODELS,
1316
+ modelContextWindows: UMANS_MODEL_CONTEXT_WINDOWS,
1317
+ modelInputModalities: UMANS_MODEL_INPUT_MODALITIES,
1318
+ note: "Coding plan via Anthropic Messages",
1319
+ modelReasoningEfforts: {
1320
+ "umans-coder": UMANS_REASONING_EFFORTS,
1321
+ "umans-kimi-k2.7": UMANS_REASONING_EFFORTS,
1322
+ "umans-flash": UMANS_REASONING_EFFORTS,
1323
+ "umans-glm-5.3": UMANS_GLM_53_REASONING_EFFORTS,
1324
+ "umans-glm-5.2": UMANS_GLM_REASONING_EFFORTS,
1325
+ "umans-glm-5.1": UMANS_GLM_REASONING_EFFORTS,
1326
+ "umans-qwen3.6-35b-a3b": UMANS_REASONING_EFFORTS,
1327
+ },
1328
+ noVisionModels: UMANS_TEXT_ONLY_MODELS,
1329
+ escapeBuiltinToolNames: true,
1330
+ },
1331
+ {
1332
+ id: "opencode-go", label: "opencode go", adapter: "openai-chat", baseUrl: "https://opencode.ai/zen/go/v1",
1333
+ authKind: "key", featured: true, dashboardUrl: "https://opencode.ai/auth", defaultModel: "kimi-k2.7-code",
1334
+ jawcodeBundle: "opencode-go", note: "GLM, DeepSeek, Kimi, Qwen, MiMo…",
1335
+ // Zen Go can close a Chat stream after a fully assembled function call without sending
1336
+ // finish_reason or [DONE] (#2260). The adapter still rejects incomplete argument JSON.
1337
+ openaiChatEofTolerance: true,
1338
+ /* [Decision Log]
1339
+ - 목적과 의도: Route GPT 5.6 Luna to the Responses endpoint that OpenCode Go documents for that exact model.
1340
+ - 기존 구현 및 제약 조건: The provider is mixed-wire but its provider-wide `openai-chat` adapter sent Luna to `/chat/completions`; explicit user `modelAdapters` entries must remain authoritative.
1341
+ - 검토한 주요 대안: Change the whole provider to Responses; infer the wire from model-family names; add one registry-only exact-model default.
1342
+ - 선택한 방식: Declare only `gpt-5.6-luna` as `openai-responses` through the existing registry default mechanism.
1343
+ - 다른 대안 대신 이 방식을 선택한 이유: OpenCode Go documents sibling models on Chat or Anthropic endpoints, and an exact registry default preserves both those routes and explicit opt-out precedence.
1344
+ - 장점, 단점 및 영향: Luna reaches `/responses` from every inbound surface without changing siblings; a future upstream endpoint change requires an evidence-backed registry update.
1345
+ */
1346
+ modelWireDefaults: { "gpt-5.6-luna": "openai-responses" },
1347
+ modelContextWindows: {
1348
+ "kimi-k3": KIMI_K3_STANDARD_CONTEXT_WINDOW,
1349
+ // Ox Alpha (stealth 1M multimodal) and the DeepSeek vision preview are
1350
+ // metadata-only here: the Go roster is discovered live, so these apply
1351
+ // the moment the gateway starts serving the ids.
1352
+ [OPENCODE_OX_ALPHA_FREE_MODEL]: OX_ALPHA_CONTEXT_WINDOW,
1353
+ [DEEPSEEK_VISION_PREVIEW_MODEL]: 1_048_576,
1354
+ },
1355
+ modelInputModalities: {
1356
+ "kimi-k3": ["text", "image"],
1357
+ [OPENCODE_OX_ALPHA_FREE_MODEL]: ["text", "image"],
1358
+ // Experimental DeepSeek vision preview — expected to merge into deepseek-v4-flash later.
1359
+ [DEEPSEEK_VISION_PREVIEW_MODEL]: ["text", "image"],
1360
+ },
1361
+ modelReasoningEfforts: {
1362
+ "glm-5.3": ZAI_GLM_53_REASONING_EFFORTS,
1363
+ "glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
1364
+ "kimi-k3": KIMI_CODING_K3_REASONING_EFFORTS,
1365
+ "kimi-k2.7-code": [],
1366
+ "kimi-k2.7-code-highspeed": [],
1367
+ ...Object.fromEntries(OPENCODE_GO_THINKING_TOGGLE_MODELS.map(id => [id, THINKING_TOGGLE_EFFORTS])),
1368
+ ...Object.fromEntries(OPENCODE_GO_THINKING_BUDGET_MODELS.map(id => [id, THINKING_BUDGET_EFFORTS])),
1369
+ ...Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id, deepseekThinkingEffortsFor(id)])),
1370
+ },
1371
+ modelDefaultReasoningEfforts: { "kimi-k3": "max" },
1372
+ // glm-5.2 uses identity labels now that `max` is a native Codex level (no alias map);
1373
+ // the thinking-toggle map is a REAL wire alias (effort -> enabled/disabled) and stays.
1374
+ modelReasoningEffortMap: {
1375
+ "kimi-k3": KIMI_CODING_K3_REASONING_EFFORT_MAP,
1376
+ ...Object.fromEntries(OPENCODE_GO_THINKING_TOGGLE_MODELS.map(id => [id, THINKING_TOGGLE_MAP])),
1377
+ ...Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id, deepseekReasoningMapFor(id)])),
1378
+ },
1379
+ modelSupportsReasoningSummaries: {
1380
+ "glm-5.3": true,
1381
+ "glm-5.2": true,
1382
+ "glm-5.1": true,
1383
+ "glm-5": true,
1384
+ ...Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id, true])),
1385
+ },
1386
+ thinkingToggleModels: OPENCODE_GO_THINKING_TOGGLE_MODELS,
1387
+ thinkingBudgetModels: THINKING_BUDGET_MODELS,
1388
+ noReasoningModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
1389
+ // Text-only Zen Go models (jawcode metadata) — the vision sidecar describes images for
1390
+ // every model listed here (and the catalog advertises image input on their behalf).
1391
+ // Kimi K2.7 Code accepts text+image+video: do NOT list it here.
1392
+ noVisionModels: [
1393
+ "glm-5.3", "glm-5.2", "glm-5", "glm-5.1",
1394
+ "deepseek-v4-flash", "deepseek-v4-pro",
1395
+ "mimo-v2-pro", "mimo-v2.5-pro",
1396
+ "minimax-m2.5", "minimax-m2.7",
1397
+ "qwen3.7-max",
1398
+ ],
1399
+ noTemperatureModels: ["kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
1400
+ noTopPModels: ["kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
1401
+ noPenaltyModels: ["kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
1402
+ autoToolChoiceOnlyModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
1403
+ // Issue #78: DeepSeek V4 thinking mode requires reasoning_content replay on tool-call turns.
1404
+ preserveReasoningContentModels: ["glm-5.3", "glm-5.2", "kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed", ...DEEPSEEK_THINKING_MODELS],
1405
+ },
1406
+ {
1407
+ id: "neuralwatt",
1408
+ label: "Neuralwatt Cloud",
1409
+ adapter: "openai-chat",
1410
+ baseUrl: "https://api.neuralwatt.com/v1",
1411
+ authKind: "key",
1412
+ dashboardUrl: "https://portal.neuralwatt.com",
1413
+ defaultModel: "glm-5.3",
1414
+ // 2026-07-10 live /v1/models: K2.5 rows were removed and GLM-5.2 short variants added.
1415
+ // 260814: the glm-5.3 quartet is speculative; live discovery is authoritative and drops
1416
+ // any id Neuralwatt has not published yet.
1417
+ // Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md and https://api.neuralwatt.com/v1/models.
1418
+ models: [
1419
+ "glm-5.3", "glm-5.3-fast", "glm-5.3-short", "glm-5.3-short-fast",
1420
+ "glm-5.2", "glm-5.2-fast", "glm-5.2-short", "glm-5.2-short-fast",
1421
+ "kimi-k2.6", "kimi-k2.6-fast",
1422
+ "kimi-k2.7-code",
1423
+ "qwen3.5-397b", "qwen3.5-397b-fast", "qwen3.6-35b", "qwen3.6-35b-fast",
1424
+ ],
1425
+ // Neuralwatt's /v1/models metadata is authoritative; these static hints are the offline fallback.
1426
+ modelReasoningEfforts: {
1427
+ "glm-5.3": ZAI_GLM_53_REASONING_EFFORTS,
1428
+ "glm-5.3-fast": [],
1429
+ "glm-5.3-short": ZAI_GLM_53_REASONING_EFFORTS,
1430
+ "glm-5.3-short-fast": [],
1431
+ "glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
1432
+ "glm-5.2-fast": [],
1433
+ "glm-5.2-short": ZAI_GLM_52_REASONING_EFFORTS,
1434
+ "glm-5.2-short-fast": [],
1435
+ "kimi-k2.6": [],
1436
+ "kimi-k2.6-fast": [],
1437
+ "kimi-k2.7-code": [],
1438
+ // Qwen3.x uses thinking_budget, NOT graded reasoning_effort; the adapter maps the five
1439
+ // Codex picker levels onto budget fractions.
1440
+ "qwen3.5-397b": THINKING_BUDGET_EFFORTS,
1441
+ "qwen3.5-397b-fast": [],
1442
+ "qwen3.6-35b": THINKING_BUDGET_EFFORTS,
1443
+ "qwen3.6-35b-fast": [],
1444
+ },
1445
+ thinkingBudgetModels: THINKING_BUDGET_MODELS,
1446
+ noReasoningModels: ["glm-5.3-fast", "glm-5.3-short-fast", "glm-5.2-fast", "glm-5.2-short-fast", "kimi-k2.6-fast", "qwen3.5-397b-fast", "qwen3.6-35b-fast"],
1447
+ noVisionModels: ["glm-5.3", "glm-5.3-fast", "glm-5.3-short", "glm-5.3-short-fast", "glm-5.2", "glm-5.2-fast", "glm-5.2-short", "glm-5.2-short-fast", "qwen3.5-397b", "qwen3.5-397b-fast"],
1448
+ noTemperatureModels: ["kimi-k2.7-code"],
1449
+ noTopPModels: ["kimi-k2.7-code"],
1450
+ noPenaltyModels: ["kimi-k2.7-code"],
1451
+ autoToolChoiceOnlyModels: ["kimi-k2.7-code"],
1452
+ preserveReasoningContentModels: NEURALWATT_REASONING_HISTORY_MODELS,
1453
+ },
1454
+ {
1455
+ id: "openrouter",
1456
+ label: "OpenRouter",
1457
+ adapter: "openai-chat",
1458
+ baseUrl: "https://openrouter.ai/api/v1",
1459
+ authKind: "key",
1460
+ featured: true,
1461
+ dashboardUrl: "https://openrouter.ai/keys",
1462
+ jawcodeBundle: "openrouter",
1463
+ // stealth/ox-alpha: free stealth-window frontier model (launched 2026-08-20).
1464
+ // /api/v1/models reports 1,048,576 context, 131,072 max output, text+image+video
1465
+ // input, $0 pricing, mandatory reasoning. Single provider slug: `stealth`.
1466
+ models: ["anthropic/claude-sonnet-5", "stealth/ox-alpha", ...OPENROUTER_GPT56_MODELS],
1467
+ modelContextWindows: {
1468
+ "anthropic/claude-sonnet-5": 1_000_000,
1469
+ "stealth/ox-alpha": OX_ALPHA_CONTEXT_WINDOW,
1470
+ ...OPENROUTER_GPT56_CONTEXT_WINDOWS,
1471
+ },
1472
+ modelInputModalities: { "stealth/ox-alpha": ["text", "image"] },
1473
+ // OpenRouter documents priority support for OpenAI endpoints, but not Anthropic. Keep the
1474
+ // provider unclassified and opt in only the exact OpenAI-backed slugs we ship. These facts
1475
+ // belong only to the canonical destination; a same-named custom gateway is unknown to us.
1476
+ modelServiceTierCapabilityBaseUrlGuard: isCanonicalOpenRouterTarget,
1477
+ modelSupportsServiceTier: {
1478
+ "openai/gpt-5.6-sol": true,
1479
+ "openai/gpt-5.6-terra": true,
1480
+ "openai/gpt-5.6-luna": true,
1481
+ },
1482
+ // Deliberately no OpenRouter route pin: it bills the endpoint actually used and reports the
1483
+ // actual service_tier. B0 confirmation therefore owns downgrade safety. Forcing `only` plus
1484
+ // `allow_fallbacks:false` would turn a graceful priority-capacity fallback into a hard failure.
1485
+ },
1486
+ {
1487
+ // Primary sources checked 2026-08-02:
1488
+ // - docs.cline.bot/getting-started/clinepass publishes this exact catalog and explicitly
1489
+ // authorizes using the full slugs through Cline's external API.
1490
+ // - docs.cline.bot/api/chat-completions and /api/errors define the endpoint, reasoning delta,
1491
+ // and choice-scoped mid-stream error contract.
1492
+ // - Cline's official catalog source resolves per-model capabilities through OpenRouter data;
1493
+ // the static context/modality snapshot below was cross-checked against that catalog.
1494
+ // - cline.bot/tos identifies Cline Bot Inc. as the operator. Maintenance owner: @lidge-jun.
1495
+ id: "cline-pass",
1496
+ label: "ClinePass",
1497
+ adapter: "openai-chat",
1498
+ baseUrl: "https://api.cline.bot/api/v1",
1499
+ authKind: "key",
1500
+ dashboardUrl: "https://app.cline.bot",
1501
+ defaultModel: "cline-pass/kimi-k3",
1502
+ models: CLINE_PASS_MODELS,
1503
+ modelContextWindows: CLINE_PASS_MODEL_CONTEXT_WINDOWS,
1504
+ modelInputModalities: CLINE_PASS_MODEL_INPUT_MODALITIES,
1505
+ noVisionModels: CLINE_PASS_TEXT_ONLY_MODELS,
1506
+ // Live-probed 2026-08-13 across every static ClinePass model: the gateway accepts and
1507
+ // validates low/medium/high/xhigh/max, and rejects an invalid sentinel. Preserve the
1508
+ // caller's requested tier and let ClinePass own any backend-specific normalization.
1509
+ reasoningEfforts: ["low", "medium", "high", "xhigh", "max"],
1510
+ reasoningWireFormat: "gateway-object",
1511
+ preserveCustomDestination: true,
1512
+ note: "ClinePass subscription API. Uses a Cline API key and the full cline-pass/<model> upstream slug; quota is shared across the account's rolling 5-hour, weekly, and monthly limits.",
1513
+ },
1514
+ // Cline API (usage-billing): OpenAI-compatible Chat Completions. Model IDs follow the
1515
+ // OpenRouter-style `provider/model` convention. Live /models discovery is key-gated (401
1516
+ // without auth), so the static seed is the cold-start fallback. Evidence: docs.cline.bot/api/*.
1517
+ {
1518
+ id: "cline",
1519
+ label: "Cline",
1520
+ adapter: "openai-chat",
1521
+ baseUrl: "https://api.cline.bot/api/v1",
1522
+ authKind: "key",
1523
+ dashboardUrl: "https://app.cline.bot",
1524
+ liveModels: true,
1525
+ defaultModel: "anthropic/claude-sonnet-4-6",
1526
+ models: [
1527
+ "anthropic/claude-sonnet-4-6",
1528
+ "openai/gpt-4o",
1529
+ "google/gemini-2.5-pro",
1530
+ "deepseek/deepseek-chat",
1531
+ "minimax/minimax-m2.5",
1532
+ ],
1533
+ preserveCustomDestination: true,
1534
+ note: "Cline usage-billing API: one key, 100+ models, OpenRouter-style ids. Promotional free models are IDE/CLI-only per Cline docs; minimax/minimax-m2.5 is the documented API free experimentation model.",
1535
+ },
1536
+ {
1537
+ // OrcaRouter: OpenAI-compatible adaptive router (api.orcarouter.ai). Model ids are
1538
+ // vendor-namespaced (`<vendor>/<model>`) and pass through to the upstream as-is.
1539
+ // The default pins a tool-capable model; the adaptive `orcarouter/auto` router is also
1540
+ // selectable. Live-verified 2026-07-20: /v1/chat/completions accepts the `tools` field
1541
+ // and routes to a function-calling-capable upstream.
1542
+ id: "orcarouter", label: "OrcaRouter", adapter: "openai-chat", baseUrl: "https://api.orcarouter.ai/v1",
1543
+ authKind: "key", dashboardUrl: "https://www.orcarouter.ai/console",
1544
+ defaultModel: "openai/gpt-5.5",
1545
+ models: [
1546
+ "openai/gpt-5.5",
1547
+ "anthropic/claude-opus-4.8",
1548
+ "google/gemini-3.5-flash",
1549
+ "deepseek/deepseek-v4-pro",
1550
+ "orcarouter/auto",
1551
+ ],
1552
+ // Text-only models → the vision sidecar describes images instead.
1553
+ noVisionModels: ["deepseek/deepseek-v4-pro"],
1554
+ // Reasoning/temperature behavior verified live 2026-07-20 against api.orcarouter.ai:
1555
+ // - openai/gpt-5.5 accepts reasoning_effort none|low|medium|high|xhigh but rejects `max` (400),
1556
+ // so advertise up to xhigh and let mapReasoningEffort clamp a `max`/`ultra` request to xhigh.
1557
+ // - deepseek/deepseek-v4-pro mirrors the direct-DeepSeek wiring (thinking-effort map +
1558
+ // reasoning_content history replay) so the namespaced selection behaves identically.
1559
+ // - temperature is accepted by every seeded model (gpt-5.5, claude-opus-4.8, deepseek-v4-pro all
1560
+ // returned 200), so no noTemperatureModels entry is warranted here.
1561
+ modelReasoningEfforts: {
1562
+ "openai/gpt-5.5": ["low", "medium", "high", "xhigh"],
1563
+ "deepseek/deepseek-v4-pro": deepseekThinkingEffortsFor("deepseek/deepseek-v4-pro"),
1564
+ },
1565
+ modelReasoningEffortMap: { "deepseek/deepseek-v4-pro": deepseekReasoningMapFor("deepseek/deepseek-v4-pro") },
1566
+ preserveReasoningContentModels: ["deepseek/deepseek-v4-pro"],
1567
+ note: "OpenAI-compatible adaptive router. Default is a tool-capable model; orcarouter/auto (adaptive routing) is also selectable. Full catalog: https://www.orcarouter.ai/models",
1568
+ },
1569
+ {
1570
+ // BizRouter: Korean enterprise LLM gateway (api.bizrouter.ai). Model ids are
1571
+ // vendor-namespaced (`<vendor>/<model>`) and pass through to the upstream as-is.
1572
+ // Live-verified 2026-07-24: /v1/chat/completions accepts the `tools` field and
1573
+ // streams, and GET /v1/models returns the per-API-key allowed catalog in the
1574
+ // OpenAI list shape, so live model discovery narrows to what the key can use.
1575
+ id: "bizrouter", label: "BizRouter", adapter: "openai-chat", baseUrl: "https://api.bizrouter.ai/v1",
1576
+ authKind: "key", dashboardUrl: "https://bizrouter.ai/settings/keys",
1577
+ defaultModel: "openai/gpt-5.6-sol",
1578
+ models: ["openai/gpt-5.6-sol", "anthropic/claude-sonnet-5", "google/gemini-3.5-flash"],
1579
+ note: "Korean enterprise LLM gateway. Per-key allowed models are discovered live from /v1/models. Full catalog: https://bizrouter.ai/models",
1580
+ },
1581
+ { id: "groq", label: "Groq", adapter: "openai-chat", baseUrl: "https://api.groq.com/openai/v1", authKind: "key", featured: true, dashboardUrl: "https://console.groq.com/keys" },
1582
+ // 2026-07-10 Gemini API refresh: Tier-2 ai.google.dev evidence recorded in
1583
+ // devlog/_plan/260710_provider_hardening/001_research_frontier.md.
1584
+ {
1585
+ id: "google", label: "Google Gemini", adapter: "google", baseUrl: "https://generativelanguage.googleapis.com", authKind: "key", featured: true,
1586
+ dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3.5-flash", models: ["gemini-3.6-flash", "gemini-3.5-flash", "gemini-3.5-flash-lite", "gemini-3.1-pro-preview", "gemini-3.7-flash"],
1587
+ modelContextWindows: { "gemini-3.6-flash": 1_048_576, "gemini-3.5-flash": 1_000_000, "gemini-3.5-flash-lite": 1_048_576, "gemini-3.7-flash": 1_048_576 },
1588
+ modelInputModalities: { "gemini-3.6-flash": ["text", "image"], "gemini-3.5-flash-lite": ["text", "image"], "gemini-3.7-flash": ["text", "image"] },
1589
+ modelReasoningEfforts: {
1590
+ "gemini-3.6-flash": ["minimal", "low", "medium", "high"],
1591
+ "gemini-3.5-flash": ["minimal", "low", "medium", "high"],
1592
+ "gemini-3.7-flash": ["minimal", "low", "medium", "high"],
1593
+ "gemini-3.1-pro-preview": ["low", "medium", "high"],
1594
+ },
1595
+ jawcodeBundle: "google", extraMetadataAliases: ["gemini"],
1596
+ },
1597
+ // 2026-07-10: defaultModel is frozen pending Vertex-specific Tier-2 evidence; Gemini API
1598
+ // evidence from ai.google.dev does not establish Vertex publisher availability.
1599
+ { id: "google-vertex", label: "Google Vertex AI", adapter: "google", baseUrl: "https://aiplatform.googleapis.com", authKind: "key", dashboardUrl: "https://console.cloud.google.com/vertex-ai", defaultModel: "gemini-3-pro", googleMode: "vertex", jawcodeBundle: "google", extraMetadataAliases: ["gemini-vertex"] },
1600
+ { id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", allowBaseUrlOverride: true, dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, liveModels: true, defaultModel: "gemini-3.7-flash", modelContextWindows: ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, modelInputModalities: ANTIGRAVITY_MODEL_INPUT_MODALITIES, modelReasoningEfforts: ANTIGRAVITY_MODEL_EFFORTS, googleMode: "cloud-code-assist", jawcodeBundle: "google", extraMetadataAliases: ["antigravity", "gemini-antigravity"] },
1601
+ { id: "azure-openai", label: "Azure OpenAI", adapter: "azure-openai", baseUrl: "https://{resource}.openai.azure.com/openai", authKind: "key", featured: true, dashboardUrl: "https://portal.azure.com" },
1602
+ { id: "ollama", label: "Ollama (local)", adapter: "openai-chat", baseUrl: "http://localhost:11434/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
1603
+ { id: "vllm", label: "vLLM (local)", adapter: "openai-chat", baseUrl: "http://localhost:8000/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
1604
+ { id: "lm-studio", label: "LM Studio (local)", adapter: "openai-chat", baseUrl: "http://localhost:1234/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — no key needed" },
1605
+ {
1606
+ id: "deepseek",
1607
+ label: "DeepSeek",
1608
+ baseUrl: "https://api.deepseek.com",
1609
+ adapter: "openai-chat",
1610
+ authKind: "key",
1611
+ dashboardUrl: "https://platform.deepseek.com/api_keys",
1612
+ // Route DeepSeek's own catalog bundle so routed rebuilds restore the official
1613
+ // context window from the vendored model-metadata bundle instead of falling
1614
+ // back to the 128k strict-fields default (scripts/model-metadata.source.json,
1615
+ // verified 2026-08-08).
1616
+ jawcodeBundle: "deepseek",
1617
+ // deepseek-chat/deepseek-reasoner were deprecated upstream on 2026-07-24 15:59 UTC;
1618
+ // official identifiers are now deepseek-v4-flash / deepseek-v4-pro. They stay in
1619
+ // the list only as compatibility aliases so existing saved configs and requests
1620
+ // keep validating and routing (they previously mapped to v4-flash; devlog
1621
+ // _fin/260710_provider_hardening/002_research_cn.md). The current offerings are
1622
+ // the V4 ids — defaultModel and the model-specific wiring above use them.
1623
+ // deepseek-v4-flash-vision-exp: experimental vision preview (2026-08-21) —
1624
+ // expected to merge into deepseek-v4-flash later; see DEEPSEEK_VISION_PREVIEW_MODEL.
1625
+ models: ["deepseek-chat", "deepseek-reasoner", ...DEEPSEEK_THINKING_MODELS, DEEPSEEK_VISION_PREVIEW_MODEL],
1626
+ defaultModel: "deepseek-v4-flash",
1627
+ // Official DeepSeek Codex setup (codex-deepseek-setup.sh) advertises 1,048,576
1628
+ // for both V4 models; the older 1,000,000 figure was a rounded approximation.
1629
+ modelContextWindows: { "deepseek-v4-flash": 1_048_576, "deepseek-v4-pro": 1_048_576, [DEEPSEEK_VISION_PREVIEW_MODEL]: 1_048_576 },
1630
+ modelInputModalities: { [DEEPSEEK_VISION_PREVIEW_MODEL]: ["text", "image"] },
1631
+ // DeepSeek documents both V4 models as native Responses API models adapted for Codex
1632
+ // (model table marks Responses API ✓ for flash and pro; the /responses reference lists
1633
+ // both ids as accepted `model` values — verified 2026-08-13 with the V4 Pro GA,
1634
+ // version label DeepSeek-V4-Pro-0813).
1635
+ modelWireDefaults: {
1636
+ // Codex speaks Responses natively and DeepSeek ships a Codex-compatible
1637
+ // apply_patch tool on that wire, so a Responses inbound goes straight out with
1638
+ // no translation. Claude Code and OpenAI-compatible clients keep the
1639
+ // provider-wide Chat wire: DeepSeek serves Chat Completions natively too, so
1640
+ // translating them into Responses would add a hop onto our newest upstream path
1641
+ // for no gain.
1642
+ "deepseek-v4-flash": { wire: "openai-responses", inbound: ["responses"] },
1643
+ "deepseek-v4-pro": { wire: "openai-responses", inbound: ["responses"] },
1644
+ },
1645
+ // The #875-era bounded-JSON force (`modelResponsesUpstreamStreaming`) is retired
1646
+ // for this entry: the official guide documents a `response.completed` /
1647
+ // `response.incomplete` / `response.failed` terminal with NO `data: [DONE]`
1648
+ // sentinel, and live probes (2026-08-07, including the tool-result replay shape
1649
+ // that originally stalled) close on the terminal. The relay's terminal boundary
1650
+ // (src/server/relay.ts) already cuts the stream at that event and synthesizes
1651
+ // `[DONE]`, so forcing stream:false only delayed every byte until generation
1652
+ // finished (28-46 s of silence on long turns). The registry knob itself remains
1653
+ // for providers that need it — re-adding one line here restores the old policy.
1654
+ // Evidence: https://api-docs.deepseek.com/guides/responses_api/ +
1655
+ // devlog/_fin/260807_deepseek_responses_streaming/000_plan.md.
1656
+ // Current official streams normally carry a real terminal; retain a narrow grace
1657
+ // repair for the historical shape that closes after a complete graph without one.
1658
+ modelResponsesTerminalRepair: { "deepseek-v4-flash": { graceMs: 5_000 }, "deepseek-v4-pro": { graceMs: 5_000 } },
1659
+ // DeepSeek's Responses route emits bare UUID item ids, which leave Codex
1660
+ // clients stuck on an uncommitted turn (#938). Client-facing only — raw
1661
+ // continuation snapshots keep the upstream ids.
1662
+ responsesItemIdRepair: { repairInvalidIds: true, repairMissingTerminalIds: true },
1663
+ // DeepSeek's Responses route is `POST /responses` with no `/v1` segment. Without
1664
+ // this the passthrough adapter falls back to its legacy `/v1/responses`
1665
+ // construction and the wire above can never route.
1666
+ // Evidence: https://api-docs.deepseek.com/api/create-response/
1667
+ responsesPath: "/responses",
1668
+ // DeepSeek's Responses reference does not list `service_tier`; unsupported
1669
+ // parameters are documented as silently ignored, but the fail-closed policy
1670
+ // strips the field rather than forwarding a knob the upstream never asked for.
1671
+ supportsServiceTier: false,
1672
+ // DeepSeek's Responses compatibility guide accepts plaintext reasoning items and
1673
+ // merges them into the adjacent assistant message, so replayed reasoning must
1674
+ // not be blanked the way the ChatGPT backend requires. (Whether the Responses
1675
+ // route REQUIRES replay on tool-call continuations is an inference from the
1676
+ // Chat Thinking-Mode docs, not a confirmed Responses contract.)
1677
+ preserveResponsesReasoningContent: true,
1678
+ // "The API is stateless: responses and conversations are not stored on the
1679
+ // server." https://api-docs.deepseek.com/api/create-response/
1680
+ statelessResponses: true,
1681
+ // DeepSeek rejects a valid Codex continuation when hook-provided developer
1682
+ // context splits a call from its result (#1292); parallel calls remain one
1683
+ // reasoning-bearing assistant batch rather than being split per pair (#1477).
1684
+ requiresAdjacentResponsesToolResults: true,
1685
+ /* [Decision Log]
1686
+ - 목적: DeepSeek V4 thinking mode multi-turn/tool-call requests must replay prior assistant reasoning_content.
1687
+ - 대안 분석: Globally preserve reasoning_content for all OpenAI-compatible models; preserve it for legacy deepseek-reasoner too; mark only V4 thinking models in registry metadata.
1688
+ - 선택 근거: DeepSeek V4 thinking mode requires history replay, while older DeepSeek reasoner has different compatibility rules. A model-scoped registry flag fixes built-in and stale saved configs without broad provider regressions.
1689
+ */
1690
+ modelReasoningEfforts: Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id, deepseekThinkingEffortsFor(id)])),
1691
+ modelReasoningEffortMap: Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id, deepseekReasoningMapFor(id)])),
1692
+ modelSupportsReasoningSummaries: Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id, true])),
1693
+ preserveReasoningContentModels: DEEPSEEK_THINKING_MODELS,
1694
+ // Issue #88: every DeepSeek API model is text-only input (no image support upstream) — the
1695
+ // vision sidecar describes attached images for them, and the catalog advertises image input
1696
+ // on their behalf (same treatment as opencode-go's DeepSeek V4 entries above).
1697
+ noVisionModels: ["deepseek-chat", "deepseek-reasoner", ...DEEPSEEK_THINKING_MODELS],
1698
+ },
1699
+ // llama-3.3-70b was deprecated by Cerebras on 2026-02-16. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
1700
+ { id: "cerebras", label: "Cerebras", baseUrl: "https://api.cerebras.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://cloud.cerebras.ai/platform/apikeys", defaultModel: "gpt-oss-120b" },
1701
+ {
1702
+ // Primary sources checked 2026-08-08:
1703
+ // - https://chutes.ai/pricing documents the shared llm.chutes.ai/v1 OpenAI-compatible
1704
+ // gateway, Bearer API keys, and chat completions. Its public
1705
+ // https://llm.chutes.ai/v1/models response supplies supported_features for filtering.
1706
+ // - https://chutes.ai/terms identifies Chutes Global Corp as the platform operator, applies
1707
+ // to API consumers, and directs production/high-volume automated inference to PAYGO.
1708
+ // Maintainer: @olddonkey; no affiliation with Chutes.
1709
+ id: "chutes",
1710
+ label: "Chutes",
1711
+ baseUrl: "https://llm.chutes.ai/v1",
1712
+ adapter: "openai-chat",
1713
+ authKind: "key",
1714
+ dashboardUrl: "https://chutes.ai/auth/start",
1715
+ liveModels: true,
1716
+ preserveCustomDestination: true,
1717
+ // The public model catalog cannot prove that a supplied Bearer key is valid.
1718
+ apiKeyValidation: "unknown",
1719
+ // Chutes documents tool calling, but not a provider-wide parallel tool-call contract.
1720
+ parallelToolCalls: false,
1721
+ // The live catalog reports reasoning support, but not a stable effort ladder.
1722
+ reasoningEfforts: [],
1723
+ modelDiscovery: {
1724
+ path: "models",
1725
+ maxResponseBytes: 256 * 1024,
1726
+ maxModels: 128,
1727
+ filter: {
1728
+ // The shared LLM catalog also contains rows without native tool support. Codex needs a
1729
+ // complete agent loop, so admit only rows whose live metadata advertises tools.
1730
+ allOf: [{ path: ["supported_features"], containsAny: ["tools"] }],
1731
+ },
1732
+ },
1733
+ note: "Shared OpenAI-compatible LLM gateway only; live discovery exposes tool-capable rows. User-deployed custom Chute endpoints and non-LLM APIs require a custom provider.",
1734
+ },
1735
+ {
1736
+ id: "deepinfra",
1737
+ label: "DeepInfra",
1738
+ baseUrl: "https://api.deepinfra.com/v1/openai",
1739
+ adapter: "openai-chat",
1740
+ authKind: "key",
1741
+ dashboardUrl: "https://deepinfra.com/dash/api_keys",
1742
+ liveModels: true,
1743
+ preserveCustomDestination: true,
1744
+ modelDiscovery: {
1745
+ // DeepInfra documents the OpenAI model catalog outside the chat-compatible `/v1/openai`
1746
+ // namespace, so keep this destination registry-owned instead of deriving it from baseUrl.
1747
+ url: "https://api.deepinfra.com/v1/models",
1748
+ maxResponseBytes: 512 * 1024,
1749
+ maxModels: 512,
1750
+ filter: {
1751
+ allOf: [{ path: ["metadata", "tags"], containsAny: ["chat"] }],
1752
+ },
1753
+ },
1754
+ note: "OpenAI-compatible chat models only; live discovery excludes non-chat rows from DeepInfra's mixed model catalog.",
1755
+ },
1756
+ {
1757
+ id: "hyperbolic",
1758
+ label: "Hyperbolic",
1759
+ baseUrl: "https://api.hyperbolic.xyz/v1",
1760
+ adapter: "openai-chat",
1761
+ authKind: "key",
1762
+ dashboardUrl: "https://app.hyperbolic.ai",
1763
+ liveModels: true,
1764
+ preserveCustomDestination: true,
1765
+ modelDiscovery: {
1766
+ path: "models",
1767
+ maxResponseBytes: 256 * 1024,
1768
+ maxModels: 256,
1769
+ },
1770
+ note: "Serverless text and vision-language chat models only; Hyperbolic's separate image, audio, and GPU endpoints are out of scope.",
1771
+ },
1772
+ {
1773
+ // Primary sources checked 2026-08-03:
1774
+ // - docs.nscale.com documents the production OpenAI-compatible endpoint, bearer service
1775
+ // tokens, /v1/models, and a tool-calling request using this exact Llama model id.
1776
+ // - nscale.com/policies/terms-conditions identifies Nscale AS as the service operator and
1777
+ // covers customers using its public-cloud inference offering. Maintainer: @olddonkey;
1778
+ // no affiliation with Nscale.
1779
+ id: "nscale",
1780
+ label: "Nscale Serverless Inference",
1781
+ baseUrl: "https://inference.api.nscale.com/v1",
1782
+ adapter: "openai-chat",
1783
+ authKind: "key",
1784
+ dashboardUrl: "https://console.nscale.com",
1785
+ defaultModel: "meta-llama/Llama-3.1-8B-Instruct",
1786
+ models: ["meta-llama/Llama-3.1-8B-Instruct"],
1787
+ liveModels: true,
1788
+ preserveCustomDestination: true,
1789
+ // Nscale documents tools but not parallel tool calls. Keep requests serialized.
1790
+ parallelToolCalls: false,
1791
+ // The API schema accepts reasoning_effort, but does not publish per-model tiers.
1792
+ reasoningEfforts: [],
1793
+ modelDiscovery: {
1794
+ path: "models",
1795
+ maxResponseBytes: 256 * 1024,
1796
+ maxModels: 256,
1797
+ filter: {
1798
+ // Nscale's catalog mixes chat, image, and embedding rows without a modality field.
1799
+ // Admit only the exact model used in its official tool-calling API example.
1800
+ allOf: [{ path: ["id"], equalsAny: ["meta-llama/Llama-3.1-8B-Instruct"] }],
1801
+ },
1802
+ },
1803
+ note: "Serverless OpenAI-compatible inference. Live discovery admits only the tool-capable model established by Nscale's official API example; other mixed-catalog rows remain hidden pending equivalent evidence.",
1804
+ },
1805
+ {
1806
+ // Primary sources checked 2026-08-03:
1807
+ // - docs.vultr.com documents the fixed OpenAI-compatible base URL, per-subscription bearer
1808
+ // key, /v1/models, and states that tool calling is currently limited to kimi-k2-instruct.
1809
+ // - Vultr's official properties identify VULTR as a The Constant Company, LLC trademark and
1810
+ // document customer API integrations. Maintainer: @olddonkey; no affiliation with Vultr.
1811
+ id: "vultr",
1812
+ label: "Vultr Serverless Inference",
1813
+ baseUrl: "https://api.vultrinference.com/v1",
1814
+ adapter: "openai-chat",
1815
+ authKind: "key",
1816
+ dashboardUrl: "https://my.vultr.com",
1817
+ defaultModel: "kimi-k2-instruct",
1818
+ models: ["kimi-k2-instruct"],
1819
+ liveModels: true,
1820
+ preserveCustomDestination: true,
1821
+ parallelToolCalls: false,
1822
+ reasoningEfforts: [],
1823
+ modelDiscovery: {
1824
+ path: "models",
1825
+ maxResponseBytes: 256 * 1024,
1826
+ maxModels: 256,
1827
+ filter: {
1828
+ // Vultr explicitly limits tool calling to this model. A coding agent must not select
1829
+ // another chat model that cannot complete its tool loop.
1830
+ allOf: [{ path: ["id"], equalsAny: ["kimi-k2-instruct"] }],
1831
+ },
1832
+ },
1833
+ note: "Serverless Inference subscription API. Live discovery exposes only kimi-k2-instruct because Vultr documents it as the sole tool-calling model.",
1834
+ },
1835
+ {
1836
+ id: "baseten",
1837
+ label: "Baseten Model APIs",
1838
+ baseUrl: "https://inference.baseten.co/v1",
1839
+ adapter: "openai-chat",
1840
+ authKind: "key",
1841
+ dashboardUrl: "https://app.baseten.co/settings/api_keys",
1842
+ liveModels: true,
1843
+ preserveCustomDestination: true,
1844
+ // Baseten's Chat Completions contract documents parallel_tool_calls as default-on.
1845
+ parallelToolCalls: true,
1846
+ // Baseten says models outside its reasoning table do not support reasoning. Keep
1847
+ // unknown/new live slugs conservative until an official-docs registry refresh proves it.
1848
+ reasoningEfforts: [],
1849
+ modelReasoningEfforts: BASETEN_MODEL_REASONING_EFFORTS,
1850
+ modelReasoningEffortMap: BASETEN_MODEL_REASONING_EFFORT_MAP,
1851
+ modelDefaultReasoningEfforts: BASETEN_MODEL_DEFAULT_REASONING_EFFORTS,
1852
+ modelInputModalities: BASETEN_MODEL_INPUT_MODALITIES,
1853
+ modelDiscovery: {
1854
+ path: "models",
1855
+ maxResponseBytes: 1_048_576,
1856
+ maxModels: 256,
1857
+ },
1858
+ note: "Shared Model APIs only (personal API key, or team key with Call Model APIs access); dedicated Truss predict endpoints are outside this preset.",
1859
+ },
1860
+ {
1861
+ id: "commandcode",
1862
+ label: "Command Code - API",
1863
+ adapter: "openai-chat",
1864
+ baseUrl: "https://api.commandcode.ai/provider/v1",
1865
+ authKind: "key",
1866
+ dashboardUrl: "https://commandcode.ai/studio/",
1867
+ liveModels: true,
1868
+ preserveCustomDestination: true,
1869
+ defaultModel: "deepseek/deepseek-v4-flash",
1870
+ // The default is also the cold-start seed: live discovery failure must not empty the catalog
1871
+ // for a freshly configured provider with no stale cache (issue #308 pattern).
1872
+ models: ["deepseek/deepseek-v4-flash"],
1873
+ // The public model catalog is unauthenticated, so a Bearer probe cannot prove key validity.
1874
+ apiKeyValidation: "unknown",
1875
+ // The public catalog reports ids/context windows only; no trustworthy reasoning contract.
1876
+ reasoningEfforts: [],
1877
+ // Official Command Code model-profile reasoning facts (shared with the OAuth
1878
+ // `command-code` entry). Without them the API-key preset never advertises a
1879
+ // reasoning picker, and the router's known-ids decode source misses the native
1880
+ // slash ids — so a Codex-facing slug like `commandcode/deepseek-deepseek-v4-pro`
1881
+ // is sent upstream verbatim and rejected with `unsupported_model`.
1882
+ modelReasoningEfforts: COMMAND_CODE_MODEL_REASONING_EFFORTS,
1883
+ // Ox Alpha (stealth preview, Command Code changelog v1.31.0) ships with a
1884
+ // 1.05M-token multimodal context; the DeepSeek vision preview id is
1885
+ // preemptive for when the catalog serves it (merges into v4-flash later).
1886
+ modelContextWindows: {
1887
+ "stealth/ox-alpha": OX_ALPHA_CONTEXT_WINDOW,
1888
+ [`deepseek/${DEEPSEEK_VISION_PREVIEW_MODEL}`]: 1_048_576,
1889
+ },
1890
+ modelInputModalities: {
1891
+ "stealth/ox-alpha": ["text", "image"],
1892
+ [`deepseek/${DEEPSEEK_VISION_PREVIEW_MODEL}`]: ["text", "image"],
1893
+ },
1894
+ modelDiscovery: {
1895
+ path: "models",
1896
+ maxResponseBytes: 256 * 1024,
1897
+ maxModels: 256,
1898
+ },
1899
+ // Verified 2026-08-03: public /provider/v1/models returns 51 rows; /chat/completions returns
1900
+ // 401 UNAUTHORIZED without a Bearer key. Primary source: https://commandcode.ai/docs/provider.
1901
+ note: "Command Code Provider API (OpenAI-compatible); API access requires the Provider plan. Use `ocx login command-code` for OAuth account login (imports an existing local Command Code CLI credential when present). Docs: https://commandcode.ai/docs/provider.",
1902
+ },
1903
+ {
1904
+ id: "sambanova",
1905
+ label: "SambaNova Cloud",
1906
+ baseUrl: "https://api.sambanova.ai/v1",
1907
+ adapter: "openai-chat",
1908
+ authKind: "key",
1909
+ dashboardUrl: "https://cloud.sambanova.ai/apis",
1910
+ liveModels: true,
1911
+ preserveCustomDestination: true,
1912
+ apiKeyValidation: "unknown",
1913
+ // SambaNova documents this request field but does not yet support parallel function calls.
1914
+ parallelToolCalls: false,
1915
+ // The public catalog does not report a trustworthy per-model reasoning contract.
1916
+ reasoningEfforts: [],
1917
+ modelDiscovery: {
1918
+ path: "models",
1919
+ maxResponseBytes: 128 * 1024,
1920
+ maxModels: 128,
1921
+ },
1922
+ note: "SambaNova Cloud text-generation models only; private SambaStudio deployment endpoints are outside this preset.",
1923
+ },
1924
+ {
1925
+ id: "nebius",
1926
+ label: "Nebius Token Factory",
1927
+ baseUrl: "https://api.tokenfactory.nebius.com/v1",
1928
+ adapter: "openai-chat",
1929
+ authKind: "key",
1930
+ dashboardUrl: "https://tokenfactory.nebius.com",
1931
+ liveModels: true,
1932
+ preserveCustomDestination: true,
1933
+ // The public tools guide documents single function selection, not parallel tool calls.
1934
+ parallelToolCalls: false,
1935
+ // Missing reasoning metadata must not promote a model to Codex's full fallback ladder.
1936
+ reasoningEfforts: [],
1937
+ modelDiscovery: {
1938
+ path: "models",
1939
+ query: { verbose: "true" },
1940
+ maxResponseBytes: 512 * 1024,
1941
+ maxModels: 512,
1942
+ filter: {
1943
+ // Keep rows whose reported architecture output includes text (for example,
1944
+ // text->text or text+image->text); embedding and image-generation rows are excluded.
1945
+ allOf: [{ path: ["architecture", "modality"], containsAny: ["->text"] }],
1946
+ },
1947
+ },
1948
+ note: "Shared Token Factory text-output inference only; live discovery excludes embedding and image-generation rows.",
1949
+ },
1950
+ {
1951
+ id: "digitalocean",
1952
+ label: "DigitalOcean Serverless Inference",
1953
+ baseUrl: "https://inference.do-ai.run/v1",
1954
+ adapter: "openai-chat",
1955
+ authKind: "key",
1956
+ dashboardUrl: "https://cloud.digitalocean.com/model-studio/manage-keys",
1957
+ liveModels: true,
1958
+ preserveCustomDestination: true,
1959
+ // The Chat Completions contract documents function calls but not universal parallel support.
1960
+ parallelToolCalls: false,
1961
+ // Unknown catalog rows must not inherit Codex's full fallback reasoning ladder.
1962
+ reasoningEfforts: [],
1963
+ modelDiscovery: {
1964
+ path: "models",
1965
+ maxResponseBytes: 256 * 1024,
1966
+ maxModels: 256,
1967
+ filter: {
1968
+ allOf: [{ path: ["id"], equalsAny: DIGITALOCEAN_CHAT_COMPLETION_MODELS }],
1969
+ },
1970
+ },
1971
+ note: "Shared Serverless Inference Chat Completions only; agent-specific, dedicated, Responses-only, embedding, and media-generation models are outside this preset.",
1972
+ },
1973
+ {
1974
+ id: "scaleway",
1975
+ label: "Scaleway Generative APIs",
1976
+ baseUrl: "https://api.scaleway.ai/v1",
1977
+ adapter: "openai-chat",
1978
+ authKind: "key",
1979
+ dashboardUrl: "https://console.scaleway.com/generative-api",
1980
+ liveModels: true,
1981
+ freeTier: true,
1982
+ preserveCustomDestination: true,
1983
+ // Parallel support varies by model; avoid advertising it as a provider-wide capability.
1984
+ parallelToolCalls: false,
1985
+ // The generic `/models` rows carry no trustworthy reasoning metadata.
1986
+ reasoningEfforts: [],
1987
+ modelInputModalities: SCALEWAY_MODEL_INPUT_MODALITIES,
1988
+ modelDiscovery: {
1989
+ path: "models",
1990
+ maxResponseBytes: 128 * 1024,
1991
+ maxModels: 128,
1992
+ filter: {
1993
+ allOf: [{ path: ["id"], equalsAny: SCALEWAY_SERVERLESS_CHAT_MODELS }],
1994
+ },
1995
+ },
1996
+ note: "Shared Generative APIs Serverless Chat Completions only; project-qualified and dedicated deployment hosts require a custom provider.",
1997
+ },
1998
+ {
1999
+ // Primary sources checked 2026-08-08:
2000
+ // - https://featherless.ai/docs/api-overview-and-common-options documents the fixed
2001
+ // OpenAI-compatible base URL, Bearer keys, and Chat Completions.
2002
+ // - https://featherless.ai/docs/api-reference-models documents authenticated plan filtering,
2003
+ // chat capability filtering, popularity sorting, pagination, and per-row tool metadata.
2004
+ // - https://featherless.ai/legal/terms-of-service identifies Featherless as a Delaware LLC,
2005
+ // covers developers building on its APIs, and reserves arbitrary applications for Scale
2006
+ // plans. Maintainer: @olddonkey; no affiliation with Featherless.
2007
+ id: "featherless",
2008
+ label: "Featherless AI",
2009
+ baseUrl: "https://api.featherless.ai/v1",
2010
+ adapter: "openai-chat",
2011
+ authKind: "key",
2012
+ dashboardUrl: "https://featherless.ai/account/api-keys",
2013
+ liveModels: true,
2014
+ preserveCustomDestination: true,
2015
+ // /v1/models is documented as callable authenticated or unauthenticated, so a 2xx catalog
2016
+ // response cannot prove that the supplied Bearer key is valid.
2017
+ apiKeyValidation: "unknown",
2018
+ // Featherless documents tool calling, but not a provider-wide parallel tool-call contract.
2019
+ parallelToolCalls: false,
2020
+ // Reasoning controls use model-specific chat_template_kwargs, not OpenAI reasoning_effort.
2021
+ reasoningEfforts: [],
2022
+ modelDiscovery: {
2023
+ path: "models",
2024
+ query: {
2025
+ available_on_current_plan: "true",
2026
+ capabilities: "chat",
2027
+ page: "1",
2028
+ per_page: "100",
2029
+ sort: "-popularity",
2030
+ },
2031
+ maxResponseBytes: 128 * 1024,
2032
+ maxModels: 100,
2033
+ filter: {
2034
+ // Treat server-side filters as a size optimization, not an authority boundary. A row must
2035
+ // independently prove plan availability, no separate Hugging Face gate, and tool support.
2036
+ allOf: [
2037
+ { path: ["available_on_current_plan"], equalsAny: [true] },
2038
+ { path: ["is_gated"], equalsAny: [false] },
2039
+ { path: ["features", "tool_use"], equalsAny: [true] },
2040
+ ],
2041
+ },
2042
+ },
2043
+ note: "Authenticated first page of popular chat models only; live discovery admits at most 100 plan-available, ungated rows whose metadata explicitly reports tool use.",
2044
+ },
2045
+ {
2046
+ // Primary sources checked 2026-08-08:
2047
+ // - https://novita.ai/docs/api-reference/model-apis-llm-create-chat-completion and
2048
+ // https://novita.ai/docs/api-reference/model-apis-llm-list-models document the fixed
2049
+ // OpenAI-compatible Chat Completions and model-list endpoints.
2050
+ // - https://novita.ai/docs/api-reference/basic-authentication documents Bearer API keys.
2051
+ // - https://novita.ai/legal/terms-of-service (updated 2026-08-05) expressly covers AI
2052
+ // inference APIs, third-party Model Providers, and customer Input/Output processing.
2053
+ // - https://huggingface.co/docs/inference-providers/main/providers/novita lists Novita as an
2054
+ // Inference Providers partner for chat/VLM traffic, independently supporting routing use.
2055
+ // - https://tsdr.uspto.gov/statusview/sn99255805 is the official use-in-commerce record
2056
+ // connecting the NOVITA AI mark to Hivemind Labs, Inc., a Delaware corporation. The mark
2057
+ // application is now abandoned; it is cited only as the public operator-identity record.
2058
+ // Maintainer: @olddonkey; no affiliation with Novita AI or Hivemind Labs, Inc.
2059
+ id: "novita",
2060
+ label: "Novita AI",
2061
+ baseUrl: "https://api.novita.ai/openai/v1",
2062
+ adapter: "openai-chat",
2063
+ authKind: "key",
2064
+ dashboardUrl: "https://novita.ai/settings/key-management",
2065
+ liveModels: true,
2066
+ preserveCustomDestination: true,
2067
+ // The live catalog is public even though the reference shows an Authorization header, so a
2068
+ // successful model fetch cannot prove that a supplied key is valid.
2069
+ apiKeyValidation: "unknown",
2070
+ // The request reference documents tools but not a provider-wide parallel-tool contract.
2071
+ parallelToolCalls: false,
2072
+ // Novita exposes model-specific thinking flags, not an OpenAI reasoning_effort contract.
2073
+ reasoningEfforts: [],
2074
+ modelDiscovery: {
2075
+ path: "models",
2076
+ maxResponseBytes: 512 * 1024,
2077
+ maxModels: 256,
2078
+ filter: {
2079
+ // Require both Novita's chat classification and the exact configured wire endpoint.
2080
+ allOf: [
2081
+ { path: ["model_type"], equalsAny: ["chat"] },
2082
+ { path: ["endpoints"], containsAny: ["chat/completions"] },
2083
+ ],
2084
+ },
2085
+ },
2086
+ note: "Public live catalog filtered to rows that explicitly report chat type and Chat Completions support; key validity remains unknown until an authenticated inference request.",
2087
+ },
2088
+ // FREEZE 2026-07-10: exact serverless ids remain auth-gated/unverified. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
2089
+ { id: "together", label: "Together", baseUrl: "https://api.together.xyz/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://api.together.xyz/settings/api-keys" },
2090
+ { id: "fireworks", label: "Fireworks", baseUrl: "https://api.fireworks.ai/inference/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://fireworks.ai/account/api-keys" },
2091
+ {
2092
+ id: "firepass", label: "Fire Pass (Fireworks Kimi)", baseUrl: "https://api.fireworks.ai/inference/v1", adapter: "openai-chat", authKind: "key",
2093
+ dashboardUrl: "https://fireworks.ai/account/api-keys",
2094
+ note: "Model data frozen pending Tier-2 entitlement proof",
2095
+ },
2096
+ {
2097
+ id: "moonshot", label: "Moonshot (Kimi API)", baseUrl: MOONSHOT_INTL_BASE_URL, adapter: "openai-chat", authKind: "key",
2098
+ allowBaseUrlOverride: true,
2099
+ baseUrlChoices: MOONSHOT_BASE_URL_CHOICES,
2100
+ dashboardUrl: "https://platform.moonshot.ai/console/api-keys", defaultModel: "kimi-k2.7-code", jawcodeBundle: "moonshot",
2101
+ models: KIMI_API_MODELS,
2102
+ modelContextWindows: KIMI_API_MODEL_CONTEXT_WINDOWS,
2103
+ modelInputModalities: KIMI_API_MODEL_INPUT_MODALITIES,
2104
+ noReasoningModels: KIMI_API_NO_REASONING_MODELS,
2105
+ modelReasoningEfforts: KIMI_API_REASONING_EFFORTS,
2106
+ noTemperatureModels: KIMI_API_MODELS,
2107
+ noTopPModels: KIMI_API_MODELS,
2108
+ noPenaltyModels: KIMI_API_MODELS,
2109
+ autoToolChoiceOnlyModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
2110
+ preserveReasoningContentModels: KIMI_API_MODELS,
2111
+ note: "International default (api.moonshot.ai). China accounts: choose China (.cn) or Custom for api.moonshot.cn.",
2112
+ },
2113
+ { id: "huggingface", label: "Hugging Face", baseUrl: "https://router.huggingface.co/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://huggingface.co/settings/tokens" },
2114
+ // 260715 NIM hardening (issue #126, devlog/_plan/260715_issue126_nim_kimi):
2115
+ // - NIM kimi rejects `parallel_tool_calls: true` with 400 "This model only supports single
2116
+ // tool-calls at once!" (openclaw#37048). NVIDIA's own function-calling docs default the
2117
+ // Boolean to false, so provider-wide `false` is the documented-safe wire value.
2118
+ // - `reasoning_effort` is not portable on NIM (models use chat_template_kwargs); the kimi
2119
+ // family is live-discovered with no capability metadata, so Codex would otherwise send
2120
+ // reasoning_effort=medium. Exact-id lists per modelInList semantics; gpt-oss on NIM keeps
2121
+ // its working reasoning_effort. Future kimi ids must be appended individually.
2122
+ {
2123
+ id: "nvidia", label: "NVIDIA NIM", baseUrl: "https://integrate.api.nvidia.com/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://build.nvidia.com",
2124
+ // Free pricing, but an API key is still required (free key from build.nvidia.com).
2125
+ freeTier: true,
2126
+ parallelToolCalls: false,
2127
+ // 260804 issue #956: NIM exposes no input modalities, so vision capability is
2128
+ // classified here. Both lists are verified per-model; unlisted ids stay unclassified
2129
+ // by design (see the comment on NVIDIA_NIM_VISION_MODELS).
2130
+ noVisionModels: NVIDIA_NIM_NO_VISION_MODELS,
2131
+ modelInputModalities: NVIDIA_NIM_VISION_INPUT_MODALITIES,
2132
+ noReasoningModels: NVIDIA_NIM_KIMI_MODELS,
2133
+ modelReasoningEfforts: Object.fromEntries(NVIDIA_NIM_KIMI_MODELS.map(id => [id, []])),
2134
+ preserveReasoningContentModels: NVIDIA_NIM_KIMI_THINKING_MODELS,
2135
+ note: "Free tier on NVIDIA NIM — API key still required (get a free key at build.nvidia.com).",
2136
+ },
2137
+ { id: "venice", label: "Venice", baseUrl: "https://api.venice.ai/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://venice.ai/settings/api" },
2138
+ // 260710 GLM-5.2 context and path-specific ids: Tier-2 evidence in
2139
+ // devlog/_plan/260710_provider_hardening/002_research_cn.md.
2140
+ // 260814: glm-5.3 / glm-5.3[1m] added per docs.z.ai/devpack/latest-model, which lists them as
2141
+ // Coding Plan ids on this same endpoint.
2142
+ // 260815: docs.z.ai/guides/llm/glm-5.3 now publishes the capability table (thinking, streaming,
2143
+ // function calling, caching, structured output) and a 128K output budget, recorded here as the
2144
+ // exact 131_072 every other source in this repo uses for that model. Coding Plan pricing stays
2145
+ // unpublished, so no cost entry is asserted.
2146
+ {
2147
+ id: "zai", label: "Z.AI — GLM Coding Plan", baseUrl: "https://api.z.ai/api/coding/paas/v4", adapter: "openai-chat", authKind: "key",
2148
+ dashboardUrl: "https://z.ai/manage-apikey/apikey-list", defaultModel: "glm-5.3",
2149
+ note: "GLM-5.3 coding subscription",
2150
+ models: ["glm-5.3", "glm-5.3[1m]", "glm-5.2", "glm-5.2[1m]", "glm-5.1", "glm-5", "glm-4.6"],
2151
+ modelContextWindows: { "glm-5.3": 1_000_000, "glm-5.3[1m]": 1_000_000, "glm-5.2": 1_000_000, "glm-5.2[1m]": 1_000_000 },
2152
+ // Z.AI's OpenAI path returns 400 code 1211 for bracketed model ids.
2153
+ modelSuffixBracketStrip: true,
2154
+ noVisionModels: ZAI_GLM_5X_MODELS,
2155
+ modelReasoningEfforts: ZAI_GLM_5X_REASONING_EFFORTS,
2156
+ modelDefaultReasoningEfforts: Object.fromEntries(ZAI_GLM_53_MODELS.map(id => [id, "max"])),
2157
+ modelMaxOutputTokens: Object.fromEntries(ZAI_GLM_53_MODELS.map(id => [id, 131_072])),
2158
+ modelSupportsReasoningSummaries: Object.fromEntries(ZAI_GLM_5X_MODELS.map(id => [id, true])),
2159
+ preserveReasoningContentModels: ZAI_GLM_5X_MODELS,
2160
+ },
2161
+ // Zhipu's domestic BigModel platform: OpenAI-compatible pay-as-you-go on open.bigmodel.cn — a
2162
+ // different host and billing product from the `zai` coding-plan subscription above.
2163
+ // The id is deliberately NOT `glm` or `glm-cn`: both are already bound in FREE_PROVIDER_DIRECTORY
2164
+ // (to api.z.ai and to the BigModel *coding* path), and routedProviderConfig() canonicalizes a
2165
+ // saved provider onto the registry baseUrl — reusing either id would silently retarget an
2166
+ // existing config's endpoint and send its API key to another host.
2167
+ // Evidence: docs.bigmodel.cn/api-reference (OpenAI-compatible chat completions),
2168
+ // docs.bigmodel.cn/cn/guide/models/text/glm-4.6 (thinking: {type: enabled|disabled}).
2169
+ // Originally proposed in #536 by @Lucinegogo.
2170
+ {
2171
+ id: "zhipu-bigmodel",
2172
+ label: "Zhipu AI — BigModel",
2173
+ baseUrl: "https://open.bigmodel.cn/api/paas/v4",
2174
+ adapter: "openai-chat",
2175
+ authKind: "key",
2176
+ dashboardUrl: "https://bigmodel.cn/console/usercenter/apikeys",
2177
+ defaultModel: "glm-4.6",
2178
+ models: ZHIPU_BIGMODEL_MODELS,
2179
+ // The GLM families here are the same ones the `zai` metadata bundle already describes, so the
2180
+ // bundle owns context windows and modalities for the whole list instead of a hand-copied table.
2181
+ jawcodeBundle: "zai",
2182
+ // Declared explicitly for the default model so its window survives a bundle-lookup miss:
2183
+ // without it, catalog normalization falls back to a generic 128k and compacts ~76,800 early.
2184
+ modelContextWindows: { "glm-4.6": 204_800 },
2185
+ modelInputModalities: ZHIPU_BIGMODEL_INPUT_MODALITIES,
2186
+ // GLM exposes a binary thinking knob, not an effort ladder: the adapter emits
2187
+ // `thinking: {type}` for these ids and would otherwise send a rejected reasoning_effort.
2188
+ thinkingToggleModels: ZHIPU_BIGMODEL_THINKING_TOGGLE_MODELS,
2189
+ modelReasoningEfforts: Object.fromEntries(
2190
+ ZHIPU_BIGMODEL_THINKING_TOGGLE_MODELS.map(id => [id, THINKING_TOGGLE_EFFORTS]),
2191
+ ),
2192
+ modelReasoningEffortMap: Object.fromEntries(
2193
+ ZHIPU_BIGMODEL_THINKING_TOGGLE_MODELS.map(id => [id, THINKING_TOGGLE_MAP]),
2194
+ ),
2195
+ modelSupportsReasoningSummaries: Object.fromEntries(
2196
+ ZHIPU_BIGMODEL_THINKING_TOGGLE_MODELS.map(id => [id, true]),
2197
+ ),
2198
+ preserveReasoningContentModels: ZHIPU_BIGMODEL_THINKING_TOGGLE_MODELS,
2199
+ // GLM thinking is a binary toggle (low maps to disabled), so a legitimate
2200
+ // tool round can carry no reasoning at all; never fabricate a placeholder
2201
+ // for it, only replay real recorded text (P2 on #1205).
2202
+ requiresReasoningPlaceholderModels: [],
2203
+ // No liveModels: GET /api/paas/v4/models has not been observed to answer on this host, and a
2204
+ // false live claim yields an empty picker at runtime. Flip it on once someone verifies it.
2205
+ note: "Domestic BigModel pay-as-you-go endpoint (open.bigmodel.cn)",
2206
+ },
2207
+ // BigModel's Coding Plan is a SEPARATE endpoint from the pay-as-you-go row above, and that is
2208
+ // the whole reason this one exists. #1100 was reported against
2209
+ // `https://open.bigmodel.cn/api/coding/paas/v4`; the row above covers only `/api/paas/v4`, so
2210
+ // destination enrichment matched nothing, `modelSupportsReasoningSummaries` stayed unset, and
2211
+ // Codex kept dropping the inbound reasoning object — effort displayed as `-`.
2212
+ //
2213
+ // A prefix or fuzzy endpoint match would have been the shortcut. It is also how a config
2214
+ // pointed at one vendor route silently inherits another route's metadata, so endpoints stay
2215
+ // exact and each one gets its own row.
2216
+ //
2217
+ // The id is NOT `glm-cn`, which the free-provider directory already binds to this same coding
2218
+ // path: registering it here would let routedProviderConfig() canonicalize a saved `glm-cn`
2219
+ // config onto this baseUrl. Same reasoning as `zhipu-bigmodel` above.
2220
+ //
2221
+ // Models follow Z.AI's coding-plan list rather than the pay-as-you-go one. This endpoint is
2222
+ // the subscription product, and the reporter's `glm-5.2` is only on that side.
2223
+ {
2224
+ id: "zhipu-bigmodel-coding",
2225
+ label: "Zhipu AI — BigModel Coding Plan",
2226
+ baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
2227
+ adapter: "openai-chat",
2228
+ authKind: "key",
2229
+ dashboardUrl: "https://bigmodel.cn/console/usercenter/apikeys",
2230
+ defaultModel: "glm-5.3",
2231
+ models: ["glm-5.3", "glm-5.3[1m]", "glm-5.2", "glm-5.2[1m]", "glm-5.1", "glm-5", "glm-4.6"],
2232
+ jawcodeBundle: "zai",
2233
+ modelContextWindows: { "glm-5.3": 1_000_000, "glm-5.3[1m]": 1_000_000, "glm-5.2": 1_000_000, "glm-5.2[1m]": 1_000_000 },
2234
+ modelSuffixBracketStrip: true,
2235
+ noVisionModels: ZAI_GLM_5X_MODELS,
2236
+ modelReasoningEfforts: ZAI_GLM_5X_REASONING_EFFORTS,
2237
+ modelSupportsReasoningSummaries: Object.fromEntries(ZAI_GLM_5X_MODELS.map(id => [id, true])),
2238
+ preserveReasoningContentModels: ZAI_GLM_5X_MODELS,
2239
+ // No liveModels: the same reasoning as the pay-as-you-go row — an unverified live claim
2240
+ // yields an empty picker at runtime.
2241
+ note: "Domestic BigModel Coding Plan endpoint (open.bigmodel.cn)",
2242
+ },
2243
+ { id: "nanogpt", label: "NanoGPT", baseUrl: "https://nano-gpt.com/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://nano-gpt.com/api" },
2244
+ { id: "synthetic", label: "Synthetic", baseUrl: "https://api.synthetic.new/openai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://synthetic.new" },
2245
+ // SiliconFlow publishes an OpenAI-compatible chat endpoint and a dynamic model catalog. Do not
2246
+ // freeze reasoning controls here: enable_thinking/thinking_budget support and limits vary by
2247
+ // model, so live metadata or an explicit user override must own those capabilities.
2248
+ // Evidence: https://docs.siliconflow.cn/en/api-reference/chat-completions/chat-completions
2249
+ {
2250
+ id: "siliconflow",
2251
+ label: "SiliconFlow",
2252
+ baseUrl: "https://api.siliconflow.cn/v1",
2253
+ adapter: "openai-chat",
2254
+ authKind: "key",
2255
+ dashboardUrl: "https://cloud.siliconflow.cn/account/ak",
2256
+ liveModels: true,
2257
+ note: "OpenAI-compatible live model catalog; reasoning controls vary by model.",
2258
+ },
2259
+ // Qwen Cloud: token plan is the preset default; GUI offers pay-as-you-go + custom via baseUrlChoices.
2260
+ // Formerly `qwen-portal` / portal.qwen.ai — that host is outdated.
2261
+ {
2262
+ id: "qwen-cloud",
2263
+ label: "Qwen Cloud",
2264
+ baseUrl: QWEN_CLOUD_TOKEN_PLAN_BASE_URL,
2265
+ adapter: "openai-chat",
2266
+ authKind: "key",
2267
+ allowBaseUrlOverride: true,
2268
+ baseUrlChoices: QWEN_CLOUD_BASE_URL_CHOICES,
2269
+ dashboardUrl: "https://docs.qwencloud.com",
2270
+ note: "Pick token plan, pay as you go, or a custom compatible-mode base URL",
2271
+ },
2272
+ {
2273
+ id: "tencent-coding-plan",
2274
+ label: "Tencent Cloud Coding Plan",
2275
+ baseUrl: "https://api.lkeap.cloud.tencent.com/coding/v3",
2276
+ adapter: "openai-chat",
2277
+ authKind: "key",
2278
+ dashboardUrl: "https://console.cloud.tencent.com/tokenhub/codingplan",
2279
+ defaultModel: "tc-code-latest",
2280
+ models: TENCENT_CODING_PLAN_MODELS,
2281
+ liveModels: true,
2282
+ modelInputModalities: Object.fromEntries(TENCENT_CODING_PLAN_MODELS.map(id => [id, ["text"]])),
2283
+ noVisionModels: TENCENT_CODING_PLAN_MODELS,
2284
+ note: "Coding tools only. Tencent forbids general API automation, custom backends, and non-interactive batch use.",
2285
+ },
2286
+ {
2287
+ id: "volcengine",
2288
+ label: "Volcengine Ark",
2289
+ baseUrl: "https://ark.cn-beijing.volces.com/api/v3",
2290
+ adapter: "openai-chat",
2291
+ authKind: "key",
2292
+ preserveCustomDestination: true,
2293
+ dashboardUrl: "https://console.volcengine.com/ark/region:ark+cn-beijing/apikey",
2294
+ defaultModel: "doubao-seed-2-1-pro-260628",
2295
+ models: VOLCENGINE_ARK_MODELS,
2296
+ liveModels: false,
2297
+ modelReasoningEfforts: Object.fromEntries(
2298
+ VOLCENGINE_DOUBAO_THINKING_MODELS.map(id => [id, THINKING_TOGGLE_EFFORTS]),
2299
+ ),
2300
+ modelReasoningEffortMap: Object.fromEntries(
2301
+ VOLCENGINE_DOUBAO_THINKING_MODELS.map(id => [id, THINKING_TOGGLE_MAP]),
2302
+ ),
2303
+ thinkingToggleModels: VOLCENGINE_DOUBAO_THINKING_MODELS,
2304
+ preserveReasoningContentModels: [
2305
+ "deepseek-v4-pro-260425",
2306
+ "deepseek-v4-flash-260425",
2307
+ "glm-5-2-260617",
2308
+ "glm-4-7-251222",
2309
+ ],
2310
+ noVisionModels: [
2311
+ "deepseek-v4-pro-260425",
2312
+ "deepseek-v4-flash-260425",
2313
+ "deepseek-v3-2-251201",
2314
+ "glm-5-2-260617",
2315
+ "glm-4-7-251222",
2316
+ ],
2317
+ note: "Pay-as-you-go Ark API with a curated text/agent catalog. Calls on this endpoint do not consume Coding Plan or Agent Plan quota.",
2318
+ },
2319
+ {
2320
+ id: "volcengine-coding-plan",
2321
+ label: "Volcengine Ark Coding Plan",
2322
+ baseUrl: "https://ark.cn-beijing.volces.com/api/coding/v3",
2323
+ adapter: "openai-chat",
2324
+ authKind: "key",
2325
+ preserveCustomDestination: true,
2326
+ dashboardUrl: "https://console.volcengine.com/ark/region:ark+cn-beijing/overview",
2327
+ defaultModel: "ark-code-latest",
2328
+ models: VOLCENGINE_CODING_PLAN_MODELS,
2329
+ liveModels: false,
2330
+ modelInputModalities: VOLCENGINE_PLAN_INPUT_MODALITIES,
2331
+ noVisionModels: VOLCENGINE_PLAN_TEXT_ONLY_MODELS,
2332
+ modelReasoningEfforts: Object.fromEntries(
2333
+ DEEPSEEK_THINKING_MODELS.map(id => [id, deepseekThinkingEffortsFor(id)]),
2334
+ ),
2335
+ modelReasoningEffortMap: Object.fromEntries(
2336
+ DEEPSEEK_THINKING_MODELS.map(id => [id, deepseekReasoningMapFor(id)]),
2337
+ ),
2338
+ preserveReasoningContentModels: DEEPSEEK_THINKING_MODELS,
2339
+ note: "Coding tools only. Volcengine restricts Coding Plan quota to supported AI coding tools and warns that using this key for general API calls may suspend the subscription or ban the account. Use the plan key issued by the Ark console.",
2340
+ },
2341
+ {
2342
+ id: "volcengine-agent-plan",
2343
+ label: "Volcengine Ark Agent Plan",
2344
+ baseUrl: "https://ark.cn-beijing.volces.com/api/plan/v3",
2345
+ responsesPath: "/responses",
2346
+ adapter: "openai-responses",
2347
+ authKind: "key",
2348
+ // Ark's plan route does not document `service_tier`; fail closed like DeepSeek.
2349
+ supportsServiceTier: false,
2350
+ preserveCustomDestination: true,
2351
+ dashboardUrl: "https://console.volcengine.com/ark/region:ark+cn-beijing/overview",
2352
+ defaultModel: "deepseek-v4-pro",
2353
+ models: VOLCENGINE_AGENT_PLAN_MODELS,
2354
+ liveModels: false,
2355
+ modelInputModalities: VOLCENGINE_PLAN_INPUT_MODALITIES,
2356
+ noVisionModels: VOLCENGINE_PLAN_TEXT_ONLY_MODELS,
2357
+ note: "Coding tools only. Agent Plan is a subscription endpoint over the native Responses API with a static fallback catalog; Ark plan quota is intended for supported AI coding and agent tools, so avoid using this key as a general-purpose API key.",
2358
+ },
2359
+ // 2026-07-10: docs unverified; model data frozen. Evidence: devlog/_plan/260710_provider_hardening/002_research_cn.md.
2360
+ { id: "qianfan", label: "Qianfan (Baidu)", baseUrl: "https://qianfan.baidubce.com/v2", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://console.bce.baidu.com/iam/#/iam/apikey/list" },
2361
+ // 2026-07-10: docs unverified; model data frozen. Evidence: devlog/_plan/260710_provider_hardening/002_research_cn.md.
2362
+ { id: "alibaba", label: "Alibaba Coding Plan", baseUrl: ALIBABA_CODING_INTL_BASE_URL, adapter: "openai-chat", authKind: "key", allowBaseUrlOverride: true, baseUrlChoices: ALIBABA_CODING_BASE_URL_CHOICES, dashboardUrl: "https://dashscope.console.aliyun.com/apiKey" },
2363
+ {
2364
+ id: "alibaba-token-plan",
2365
+ label: "Alibaba Token Plan (Beijing)",
2366
+ baseUrl: "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1",
2367
+ adapter: "openai-chat",
2368
+ authKind: "key",
2369
+ dashboardUrl: "https://bailian.console.aliyun.com/cn-beijing?tab=plan",
2370
+ defaultModel: "qwen3.8-max",
2371
+ models: ALIBABA_TOKEN_PLAN_MODELS,
2372
+ liveModels: false,
2373
+ note: "Token Plan Personal Edition · China (Beijing)",
2374
+ modelInputModalities: ALIBABA_TOKEN_PLAN_INPUT_MODALITIES,
2375
+ modelContextWindows: {
2376
+ "qwen3.8-max": 983_616, "qwen3.7-max": 1_000_000, "qwen3.7-plus": 1_000_000,
2377
+ "qwen3.6-flash": 1_000_000, "glm-5.3": 1_000_000, "glm-5.2": 1_000_000, "deepseek-v4-pro": 1_000_000,
2378
+ },
2379
+ modelReasoningEfforts: {
2380
+ ...Object.fromEntries(ALIBABA_TOKEN_PLAN_QWEN_MODELS.map(id => [id, THINKING_BUDGET_EFFORTS])),
2381
+ "qwen3.8-max": QWEN38_REASONING_EFFORTS,
2382
+ "glm-5.3": ZAI_GLM_53_REASONING_EFFORTS,
2383
+ "glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
2384
+ "deepseek-v4-pro": deepseekThinkingEffortsFor("deepseek-v4-pro"),
2385
+ },
2386
+ modelDefaultReasoningEfforts: { "qwen3.8-max": "xhigh" },
2387
+ modelReasoningEffortMap: { "deepseek-v4-pro": deepseekReasoningMapFor("deepseek-v4-pro") },
2388
+ directReasoningEffortModels: ["qwen3.8-max"],
2389
+ thinkingBudgetModels: ALIBABA_TOKEN_PLAN_QWEN_MODELS.filter(id => id !== "qwen3.8-max"),
2390
+ preserveReasoningContentModels: ["glm-5.3", "glm-5.2", "deepseek-v4-pro", "qwen3.8-max", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-flash"],
2391
+ noVisionModels: ["glm-5.3", "glm-5.2", "deepseek-v4-pro"],
2392
+ },
2393
+ {
2394
+ id: "alibaba-token-plan-intl",
2395
+ label: "Alibaba Token Plan (International)",
2396
+ baseUrl: ALIBABA_INTL_TOKEN_PLAN_BASE_URL,
2397
+ adapter: "openai-chat",
2398
+ authKind: "key",
2399
+ allowBaseUrlOverride: true,
2400
+ baseUrlChoices: ALIBABA_INTL_BASE_URL_CHOICES,
2401
+ dashboardUrl: "https://modelstudio.console.alibabacloud.com/?tab=api#/api",
2402
+ defaultModel: "qwen3.7-max",
2403
+ models: ALIBABA_INTL_TOKEN_PLAN_MODELS,
2404
+ liveModels: false,
2405
+ note: "Token Plan Team Edition · Singapore (ap-southeast-1)",
2406
+ metadataModelIdNormalize: "case-insensitive",
2407
+ modelInputModalities: ALIBABA_INTL_TOKEN_PLAN_INPUT_MODALITIES,
2408
+ modelContextWindows: {
2409
+ "qwen3.8-max": 983_616,
2410
+ "qwen3.7-max": 1_000_000, "qwen3.7-plus": 1_000_000, "qwen3.6-plus": 1_000_000, "qwen3.6-flash": 1_000_000,
2411
+ "deepseek-v4-pro": 1_000_000, "deepseek-v4-flash": 1_000_000, "deepseek-v3.2": 131_072,
2412
+ "kimi-k2.7-code": 262_144, "kimi-k2.6": 262_144, "kimi-k2.5": 262_144,
2413
+ "glm-5.3": 1_000_000, "glm-5.2": 1_000_000, "glm-5.1": 1_000_000, "glm-5": 1_000_000,
2414
+ "MiniMax-M2.5": 204_800,
2415
+ },
2416
+ modelReasoningEfforts: {
2417
+ ...Object.fromEntries(ALIBABA_INTL_TOKEN_PLAN_QWEN_MODELS.map(id => [id, THINKING_BUDGET_EFFORTS])),
2418
+ "qwen3.8-max": QWEN38_REASONING_EFFORTS,
2419
+ "glm-5.3": ZAI_GLM_53_REASONING_EFFORTS,
2420
+ "glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
2421
+ "deepseek-v4-pro": deepseekThinkingEffortsFor("deepseek-v4-pro"),
2422
+ "deepseek-v4-flash": deepseekThinkingEffortsFor("deepseek-v4-flash"),
2423
+ },
2424
+ modelReasoningEffortMap: {
2425
+ "deepseek-v4-pro": deepseekReasoningMapFor("deepseek-v4-pro"),
2426
+ "deepseek-v4-flash": deepseekReasoningMapFor("deepseek-v4-flash"),
2427
+ },
2428
+ directReasoningEffortModels: ["qwen3.8-max"],
2429
+ thinkingBudgetModels: ALIBABA_INTL_TOKEN_PLAN_QWEN_MODELS.filter(id => id !== "qwen3.8-max"),
2430
+ preserveReasoningContentModels: ["glm-5.3", "glm-5.2", "deepseek-v4-pro", "deepseek-v4-flash", "qwen3.8-max", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", "qwen3.6-flash"],
2431
+ noVisionModels: ["deepseek-v4-pro", "deepseek-v4-flash", "deepseek-v3.2", "glm-5.3", "glm-5.2", "glm-5.1", "glm-5", "MiniMax-M2.5"],
2432
+ noReasoningModels: ["kimi-k2.7-code", "kimi-k2.6", "kimi-k2.5", "deepseek-v3.2", "glm-5.1", "glm-5", "MiniMax-M2.5"],
2433
+ modelDefaultReasoningEfforts: { "qwen3.8-max": "xhigh" },
2434
+ },
2435
+ // NEEDS_HUMAN 2026-07-10: kept for config compatibility, but this is a dashboard URL,
2436
+ // no /models endpoint is documented, and tools are silently ignored upstream per docs.parallel.ai.
2437
+ // Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
2438
+ { id: "parallel", label: "Parallel", baseUrl: "https://platform.parallel.ai", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.parallel.ai" },
2439
+ // ZenMux native ids are vendor-namespaced (`<vendor>/<model>`), verified live against
2440
+ // https://zenmux.ai/api/v1/models on 2026-07-18. The static seed doubles as the
2441
+ // cold-cache decode source for the Codex slug codec (src/providers/slug-codec.ts);
2442
+ // live discovery still owns the full catalog.
2443
+ {
2444
+ id: "zenmux", label: "ZenMux", baseUrl: "https://zenmux.ai/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://zenmux.ai",
2445
+ models: ["moonshotai/kimi-k3-free", "moonshotai/kimi-k3"],
2446
+ },
2447
+ {
2448
+ id: "litellm", label: "LiteLLM (self-hosted)", baseUrl: "http://localhost:4000/v1", adapter: "openai-chat", authKind: "key",
2449
+ dashboardUrl: "https://docs.litellm.ai/docs/proxy/quick_start",
2450
+ allowPrivateNetworkByDefault: true,
2451
+ allowBaseUrlOverride: true,
2452
+ // A self-hosted proxy may legitimately run without a master key.
2453
+ keyOptional: true,
2454
+ },
2455
+ {
2456
+ id: "ollama-cloud",
2457
+ label: "Ollama Cloud",
2458
+ baseUrl: "https://ollama.com/v1",
2459
+ adapter: "openai-chat",
2460
+ authKind: "key",
2461
+ dashboardUrl: "https://ollama.com/settings/keys",
2462
+ // Live IDs verified 2026-07-10; qwen3-coder:480b retires 2026-07-15.
2463
+ models: ["glm-5.3", "glm-5.2", "deepseek-v4-pro", "qwen3-coder:480b", "gpt-oss:120b", "kimi-k2.6", "minimax-m3", "qwen3.5:397b", "gemma4:31b"],
2464
+ defaultModel: "glm-5.3",
2465
+ noVisionModels: [
2466
+ "glm-5.3", "glm-5.2", "glm-5.1", "glm-5", "glm-4.7",
2467
+ "minimax-m2.7", "minimax-m2.5", "minimax-m2.1",
2468
+ "nemotron-3-ultra", "nemotron-3-super",
2469
+ "deepseek-v4-pro", "deepseek-v4-flash",
2470
+ "gpt-oss", "qwen3-coder:480b",
2471
+ ],
2472
+ },
2473
+ // FREEZE 2026-07-10: codestral-latest is unconfirmed behind auth. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
2474
+ { id: "mistral", label: "Mistral", baseUrl: "https://api.mistral.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://console.mistral.ai/api-keys", defaultModel: "codestral-latest" },
2475
+ {
2476
+ id: "minimax", label: "MiniMax — Coding Plan", baseUrl: "https://api.minimax.io/v1", adapter: "openai-chat", authKind: "key",
2477
+ dashboardUrl: "https://platform.minimax.io", defaultModel: "MiniMax-M3", models: MINIMAX_MODELS,
2478
+ modelContextWindows: MINIMAX_MODEL_CONTEXT_WINDOWS,
2479
+ modelReasoningEfforts: { "MiniMax-M3": MINIMAX_M3_REASONING_EFFORTS },
2480
+ modelDefaultReasoningEfforts: { "MiniMax-M3": "medium" },
2481
+ modelReasoningEffortMap: { "MiniMax-M3": MINIMAX_M3_REASONING_EFFORT_MAP },
2482
+ preserveReasoningContentModels: MINIMAX_MODELS,
2483
+ // MiniMax-M3 low effort maps to thinking disabled, so a legitimate tool
2484
+ // round can carry no reasoning at all; only replay real recorded text,
2485
+ // never a fabricated placeholder (chatgpt-codex-connector P2 on #1205).
2486
+ requiresReasoningPlaceholderModels: [],
2487
+ reasoningSplitModels: MINIMAX_MODELS,
2488
+ thinkingToggleModels: ["MiniMax-M3"],
2489
+ jawcodeBundle: "minimax", metadataModelIdNormalize: "case-insensitive", note: "Subscription Key or API Key",
2490
+ },
2491
+ {
2492
+ id: "minimax-cn", label: "MiniMax — Coding Plan (CN)", baseUrl: "https://api.minimaxi.com/v1", adapter: "openai-chat", authKind: "key",
2493
+ dashboardUrl: "https://platform.minimaxi.com", defaultModel: "MiniMax-M3", models: MINIMAX_MODELS,
2494
+ modelContextWindows: MINIMAX_MODEL_CONTEXT_WINDOWS,
2495
+ modelReasoningEfforts: { "MiniMax-M3": MINIMAX_M3_REASONING_EFFORTS },
2496
+ modelDefaultReasoningEfforts: { "MiniMax-M3": "medium" },
2497
+ modelReasoningEffortMap: { "MiniMax-M3": MINIMAX_M3_REASONING_EFFORT_MAP },
2498
+ preserveReasoningContentModels: MINIMAX_MODELS,
2499
+ requiresReasoningPlaceholderModels: [],
2500
+ reasoningSplitModels: MINIMAX_MODELS,
2501
+ thinkingToggleModels: ["MiniMax-M3"],
2502
+ jawcodeBundle: "minimax", metadataModelIdNormalize: "case-insensitive", note: "中国区 Subscription Key",
2503
+ },
2504
+ {
2505
+ id: "kimi-code", label: "Kimi (coding)", baseUrl: "https://api.kimi.com/coding/v1", adapter: "openai-chat", authKind: "key",
2506
+ dashboardUrl: "https://platform.moonshot.cn/console/api-keys", defaultModel: "kimi-k2.7-code",
2507
+ modelSuffixBracketStrip: true,
2508
+ // API-key form of the same Kimi Code Plan transport; keep cache affinity identical to OAuth.
2509
+ promptCacheKey: true,
2510
+ models: KIMI_CODING_MODELS,
2511
+ modelContextWindows: KIMI_CODING_MODEL_CONTEXT_WINDOWS,
2512
+ modelInputModalities: KIMI_CODING_MODEL_INPUT_MODALITIES,
2513
+ noReasoningModels: KIMI_CODING_NO_REASONING_MODELS,
2514
+ modelReasoningEfforts: KIMI_CODING_REASONING_EFFORTS,
2515
+ modelDefaultReasoningEfforts: KIMI_CODING_DEFAULT_REASONING_EFFORTS,
2516
+ modelReasoningEffortMap: KIMI_CODING_REASONING_EFFORT_MAPS,
2517
+ noTemperatureModels: KIMI_LOCKED_PARAMETER_MODELS,
2518
+ noTopPModels: KIMI_LOCKED_PARAMETER_MODELS,
2519
+ noPenaltyModels: KIMI_LOCKED_PARAMETER_MODELS,
2520
+ autoToolChoiceOnlyModels: KIMI_AUTO_TOOL_CHOICE_ONLY_MODELS,
2521
+ preserveReasoningContentModels: KIMI_THINKING_MODELS,
2522
+ },
2523
+ {
2524
+ id: "opencode-zen", label: "opencode zen", baseUrl: "https://opencode.ai/zen/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://opencode.ai/auth",
2525
+ // Same opencode.ai/zen/v1 gateway as `opencode-free` (keyed tier): DeepSeek thinking mode
2526
+ // requires the assistant's original reasoning_content to be replayed on tool-call
2527
+ // continuations, or the gateway answers HTTP 400 (issues #950/#994). Mirror the DeepSeek
2528
+ // reasoning + thinking metadata so `opencode-zen/deepseek-v4-flash-free` — and the other
2529
+ // Zen DeepSeek thinking models — never serialize a bare tool-call turn.
2530
+ note: "Keyed OpenCode Zen gateway. Free models on this tier are often short-window rate-limited at roughly 15-20 requests/minute (community-measured; OpenCode does not publish RPM). Zen may return generic 429s without Retry-After / X-RateLimit headers; when Retry-After is omitted, opencodex adds a synthetic backoff hint (upstream Retry-After still wins). Distinct from the keyless opencode-free desktop quota (~200 Big Pickle/free-model requests per 5 hours). Docs: https://opencode.ai/docs/zen/. Free-model prompts may be retained for training — do not send confidential material.",
2531
+ modelReasoningEfforts: Object.fromEntries(
2532
+ [...DEEPSEEK_THINKING_MODELS, ...OPENCODE_FREE_DEEPSEEK_MODELS].map(id => [id, deepseekThinkingEffortsFor(id)]),
2533
+ ),
2534
+ modelReasoningEffortMap: Object.fromEntries(
2535
+ [...DEEPSEEK_THINKING_MODELS, ...OPENCODE_FREE_DEEPSEEK_MODELS].map(id => [id, deepseekReasoningMapFor(id)]),
2536
+ ),
2537
+ preserveReasoningContentModels: [...DEEPSEEK_THINKING_MODELS, ...OPENCODE_FREE_DEEPSEEK_MODELS],
2538
+ // Same Zen gateway as opencode-free: Ox Alpha Free (1M multimodal stealth model)
2539
+ // and the DeepSeek vision preview (merges into deepseek-v4-flash later).
2540
+ modelContextWindows: {
2541
+ [OPENCODE_OX_ALPHA_FREE_MODEL]: OX_ALPHA_CONTEXT_WINDOW,
2542
+ [DEEPSEEK_VISION_PREVIEW_MODEL]: 1_048_576,
2543
+ },
2544
+ modelInputModalities: {
2545
+ [OPENCODE_OX_ALPHA_FREE_MODEL]: ["text", "image"],
2546
+ [DEEPSEEK_VISION_PREVIEW_MODEL]: ["text", "image"],
2547
+ },
2548
+ noVisionModels: [...OPENCODE_ZEN_TEXT_ONLY_MODELS, ...DEEPSEEK_THINKING_MODELS],
2549
+ },
2550
+ { id: "vercel-ai-gateway", label: "Vercel AI Gateway", baseUrl: "https://ai-gateway.vercel.sh/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://vercel.com/dashboard" },
2551
+ {
2552
+ id: "opencode-free",
2553
+ label: "OpenCode Free",
2554
+ adapter: "openai-chat",
2555
+ baseUrl: "https://opencode.ai/zen/v1",
2556
+ authKind: "key",
2557
+ keyOptional: true,
2558
+ featured: true,
2559
+ liveModels: true,
2560
+ note: "No key needed — public desktop tier. OpenCode currently advertises about 200 Big Pickle/free-model requests per 5 hours. The same Zen gateway can also short-window rate-limit free models at roughly 15-20 requests/minute, and may return generic 429s without Retry-After (opencodex synthesizes backoff only when that header is omitted). Free models are discovered live from Zen. Data use: per OpenCode's Zen docs (https://opencode.ai/docs/zen/), prompts sent to free models may be retained and used for training/improvement — do not send confidential material through this provider.",
2561
+ dashboardUrl: "https://opencode.ai",
2562
+ staticHeaders: {
2563
+ // Zen answers a bare runtime User-Agent (Bun/x.y.z) more aggressively than a client
2564
+ // that identifies itself, which is what the 429 in #2067 traced to. The value is
2565
+ // deliberately unversioned: a pinned "opencode-cli/<version>" is a claim about an
2566
+ // install we do not have and goes stale on the vendor's schedule, not ours.
2567
+ // Corroboration, not authority: OmniRoute — an independent open-source broker against
2568
+ // the same Zen upstream — defaults to exactly this pair (userAgent "opencode", client
2569
+ // "desktop") in open-sse/executors/opencode.ts, and got there by RETREATING from its
2570
+ // own earlier "opencode-cli/1.0.0" pin. An operator can still override either value
2571
+ // through the provider headers API; user headers win case-insensitively at route time.
2572
+ "User-Agent": "opencode",
2573
+ "x-opencode-client": "desktop",
2574
+ },
2575
+ modelReasoningEfforts: Object.fromEntries(OPENCODE_FREE_DEEPSEEK_MODELS.map(id => [id, deepseekThinkingEffortsFor(id)])),
2576
+ modelReasoningEffortMap: Object.fromEntries(OPENCODE_FREE_DEEPSEEK_MODELS.map(id => [id, deepseekReasoningMapFor(id)])),
2577
+ preserveReasoningContentModels: OPENCODE_FREE_DEEPSEEK_MODELS,
2578
+ // Ox Alpha Free (`x-preview-f-free`): the OpenRouter stealth model on Zen's
2579
+ // free tier — 1,048,576 context, text+image input. Deliberately NOT in the
2580
+ // text-only list below. The DeepSeek vision preview id is preemptive
2581
+ // metadata for when Zen starts serving it (merges into v4-flash later).
2582
+ modelContextWindows: {
2583
+ [OPENCODE_OX_ALPHA_FREE_MODEL]: OX_ALPHA_CONTEXT_WINDOW,
2584
+ [DEEPSEEK_VISION_PREVIEW_MODEL]: 1_048_576,
2585
+ },
2586
+ modelInputModalities: {
2587
+ [OPENCODE_OX_ALPHA_FREE_MODEL]: ["text", "image"],
2588
+ [DEEPSEEK_VISION_PREVIEW_MODEL]: ["text", "image"],
2589
+ },
2590
+ // Same Zen roster behind the same base URL, so it carries the same measured
2591
+ // text-only list rather than only its DeepSeek member (#1043).
2592
+ noVisionModels: OPENCODE_ZEN_TEXT_ONLY_MODELS,
2593
+ },
2594
+ { id: "xiaomi", label: "Xiaomi MiMo", baseUrl: "https://api.xiaomimimo.com/anthropic", adapter: "anthropic", authKind: "key", dashboardUrl: "https://xiaomimimo.com", defaultModel: "mimo-v2.5-pro" },
2595
+ // Xiaomi's public OpenAI-compatible endpoint is a distinct transport from both the Anthropic
2596
+ // preset above and the paid token-plan host below. Keep a separate fixed-destination contract
2597
+ // so existing custom providers are never retargeted while the official route receives the
2598
+ // strict reasoning ladder its validator enforces (#1483).
2599
+ {
2600
+ id: "xiaomi-mimo",
2601
+ label: "Xiaomi MiMo (OpenAI Chat)",
2602
+ baseUrl: "https://api.xiaomimimo.com/v1",
2603
+ adapter: "openai-chat",
2604
+ authKind: "key",
2605
+ dashboardUrl: "https://platform.xiaomimimo.com/console/balance",
2606
+ defaultModel: "mimo-v2.5",
2607
+ models: ["mimo-v2.5"],
2608
+ reasoningEfforts: ["low", "medium", "high"],
2609
+ reasoningEffortMap: { xhigh: "high", max: "high", ultra: "high" },
2610
+ preserveCustomDestination: true,
2611
+ note: "Official Xiaomi MiMo OpenAI-compatible Chat endpoint. The upstream validator accepts reasoning_effort none/low/medium/high; higher Codex tiers are clamped to high.",
2612
+ },
2613
+ { id: "kilo", label: "Kilo", baseUrl: "https://api.kilo.ai/api/gateway", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://kilo.ai" },
2614
+ {
2615
+ id: "mimo-free",
2616
+ label: "MiMo Free",
2617
+ adapter: "mimo-free",
2618
+ baseUrl: "https://api.xiaomimimo.com/api/free-ai/openai/chat",
2619
+ authKind: "key",
2620
+ keyOptional: true,
2621
+ featured: true,
2622
+ liveModels: true,
2623
+ dashboardUrl: "https://xiaomimimo.com",
2624
+ defaultModel: "mimo-auto",
2625
+ models: ["mimo-auto"],
2626
+ reasoningEfforts: ["low", "medium", "high"],
2627
+ reasoningEffortMap: { xhigh: "high", max: "high", ultra: "high" },
2628
+ note: "No key needed — uses Xiaomi MiMo's free public tier (limited-time offer). A JWT is bootstrapped automatically with an anonymous random client id stored locally. The endpoint contract mirrors the official MiMoCode client and is not publicly documented — Xiaomi may change or restrict it at any time. Prompts may be processed/retained by Xiaomi; do not send confidential material.",
2629
+ },
2630
+ // Xiaomi MiMo paid token plan. Separate host and wire from both `xiaomi` (Anthropic) and
2631
+ // `mimo-free` (free tier, bespoke adapter), so it needs its own entry rather than a variant.
2632
+ //
2633
+ // Pinned to openai-chat deliberately (#1158). The endpoint answers the Responses wire for
2634
+ // plain turns, which is why users configuring it by hand pick `openai-responses` — MiMo
2635
+ // documents Responses support. But its gateway rejects `type: "custom"` tools with
2636
+ // `400 responses_feature_not_supported`, and `apply_patch` is a custom tool, so every agentic
2637
+ // turn fails while chat turns succeed. The Chat path lowers custom tools to `{input: string}`
2638
+ // functions and restores them as `custom_tool_call`, so the capability survives intact.
2639
+ // Stripping the tools instead would stop the 400 and disable the agent loop.
2640
+ {
2641
+ id: "mimo",
2642
+ label: "Xiaomi MiMo (token plan)",
2643
+ baseUrl: "https://token-plan-cn.xiaomimimo.com/v1",
2644
+ adapter: "openai-chat",
2645
+ authKind: "key",
2646
+ dashboardUrl: "https://xiaomimimo.com",
2647
+ defaultModel: "mimo-v2.5-pro",
2648
+ models: ["mimo-v2.5-pro", "mimo-v2.5"],
2649
+ // The gateway validates the ladder strictly and rejects anything above `high`.
2650
+ reasoningEfforts: ["low", "medium", "high"],
2651
+ reasoningEffortMap: { xhigh: "high", max: "high", ultra: "high" },
2652
+ // Live token-plan verification (#1927): the Pro route rejects image input while
2653
+ // mimo-v2.5 accepts it natively. Keep this provider-scoped so a hand-rolled
2654
+ // provider with the same id but another destination does not inherit the claim.
2655
+ noVisionModels: ["mimo-v2.5-pro"],
2656
+ // A user may already have hand-rolled a provider under this id against a different host;
2657
+ // without this, routedProviderConfig() would canonicalize their base URL onto ours and send
2658
+ // their key somewhere they did not choose.
2659
+ preserveCustomDestination: true,
2660
+ note: "Xiaomi MiMo paid token plan. Pinned to the Chat wire: the Responses endpoint rejects freeform (custom) tools such as apply_patch with 400 responses_feature_not_supported, so agentic turns fail there while plain turns succeed. Reasoning tiers above high are clamped.",
2661
+ },
2662
+ { id: "cloudflare-ai-gateway", label: "Cloudflare AI Gateway", baseUrl: "https://gateway.ai.cloudflare.com/v1/{account-id}/{gateway}/anthropic", adapter: "anthropic", authKind: "key", dashboardUrl: "https://dash.cloudflare.com/?to=/:account/ai/ai-gateway" },
2663
+ {
2664
+ // Cloudflare Workers AI: OpenAI-compatible endpoint. The base URL contains {account_id}
2665
+ // which must be resolved by the user at setup time. Model IDs use the @cf/ prefix.
2666
+ // Live-verified 2026-07-21 against https://developers.cloudflare.com/workers-ai/models/
2667
+ // Official search is sibling to /ai/v1 (GET .../ai/models/search?format=openrouter).
2668
+ id: "cloudflare-workers-ai", label: "Cloudflare Workers AI",
2669
+ baseUrl: "https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/v1",
2670
+ adapter: "openai-chat", authKind: "key", freeTier: true,
2671
+ dashboardUrl: "https://dash.cloudflare.com/?to=/:account/ai/workers-ai",
2672
+ defaultModel: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
2673
+ models: [
2674
+ "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
2675
+ "@cf/qwen/qwq-32b",
2676
+ "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b",
2677
+ "@cf/moonshotai/kimi-k2.7-code",
2678
+ "@cf/zai-org/glm-5.3",
2679
+ "@cf/zai-org/glm-5.2",
2680
+ "@cf/mistralai/mistral-small-3.1-24b-instruct",
2681
+ ],
2682
+ liveModels: true,
2683
+ modelDiscovery: {
2684
+ path: "../models/search",
2685
+ query: { format: "openrouter", per_page: "1000" },
2686
+ stripIdPrefix: "workers-ai/",
2687
+ maxModels: 256,
2688
+ },
2689
+ note: "Workers AI · Free tier included · Account ID required in base URL",
2690
+ },
2691
+ // FREEZE 2026-07-10: /models was auth-gated under key login. OAuth device-flow + copilot_internal
2692
+ // exchange (issue #151) unlocks live discovery; static seed is a cold-start fallback only.
2693
+ {
2694
+ id: "github-copilot",
2695
+ label: "GitHub Copilot",
2696
+ baseUrl: "https://api.githubcopilot.com",
2697
+ adapter: "openai-chat",
2698
+ authKind: "oauth",
2699
+ allowKeyAuthOverride: true,
2700
+ featured: false,
2701
+ dashboardUrl: "https://github.com/settings/copilot",
2702
+ liveModels: true,
2703
+ models: ["gpt-4o", "gpt-4.1", "gpt-4.1-mini", "claude-sonnet-4", "gemini-2.5-pro", "gpt-5-mini", "gpt-5.3-codex", "gpt-5.4", "gpt-5.4-mini", "gpt-5.5", "gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra"],
2704
+ defaultModel: "gpt-4o",
2705
+ // Copilot fronts a mixed-wire catalog: these models reject /chat/completions for
2706
+ // real Codex-agent traffic (function tools + reasoning), so every inbound wire
2707
+ // rides Responses. Evidence: issue #748 field runs, pi.dev/models/github-copilot/*
2708
+ // wire declarations, BerriAI/litellm#23332 (gpt-5.4), JetBrains LLM-29711
2709
+ // (gpt-5.6-sol). gpt-5.4-nano is deliberately absent — it has no field report; a
2710
+ // user can opt it in with an explicit modelAdapters entry, which always wins.
2711
+ modelWireDefaults: {
2712
+ "gpt-5.3-codex": "openai-responses",
2713
+ "gpt-5.4": "openai-responses",
2714
+ "gpt-5.4-mini": "openai-responses",
2715
+ "gpt-5.5": "openai-responses",
2716
+ "gpt-5.6-luna": "openai-responses",
2717
+ "gpt-5.6-sol": "openai-responses",
2718
+ "gpt-5.6-terra": "openai-responses",
2719
+ },
2720
+ note: "Experimental unofficial Copilot bridge. Logs in via GitHub device flow using the public VS Code OAuth client id, then exchanges for a short-lived Copilot API token (copilot_internal). Requires an active Copilot subscription. GitHub may tighten or revoke this path; do not send confidential material you would not paste into Copilot Chat.",
2721
+ },
2722
+ // FREEZE 2026-07-10: no public OpenAI-compatible endpoint is documented. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
2723
+ { id: "gitlab-duo", label: "GitLab Duo", baseUrl: "https://cloud.gitlab.com/ai/v1/proxy/openai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://gitlab.com/-/user_settings/personal_access_tokens" },
2724
+ ];
2725
+
2726
+ export function providerRegistryFastWireError(
2727
+ entry: Pick<ProviderRegistryEntry, "fastWire" | "supportsServiceTier" | "modelSupportsServiceTier">,
2728
+ ): string | null {
2729
+ return fastWireDeclarationError(entry);
2730
+ }
2731
+
2732
+ for (const entry of PROVIDER_REGISTRY) {
2733
+ const error = providerRegistryFastWireError(entry);
2734
+ if (error) throw new TypeError(`Invalid provider registry entry ${entry.id}: ${error}`);
2735
+ }
2736
+
2737
+ export function getProviderRegistryEntry(id: string): ProviderRegistryEntry | undefined {
2738
+ return PROVIDER_REGISTRY.find(entry => entry.id === id);
2739
+ }
2740
+
2741
+ /**
2742
+ * Merge a registry row's `staticHeaders` beneath a provider's own headers.
2743
+ *
2744
+ * The field is documented as "merged into every upstream request for this provider", but that
2745
+ * was only ever true for a freshly seeded config: `providerConfigSeed` copies the block once
2746
+ * (`derive.ts`), `enrichProviderFromCatalog` fills it only when the whole block is absent, and
2747
+ * nothing merged it at request time. So an install that predates a header — or that saved any
2748
+ * header of its own — never received the new one, which is exactly what #2067 would have
2749
+ * shipped for every existing opencode-free user.
2750
+ *
2751
+ * The comparison is case-insensitive on purpose. HTTP header names are case-insensitive, but a
2752
+ * plain object spread is not: merging a registry `User-Agent` over a user's `user-agent`
2753
+ * produces two entries that `Headers` serializes as one comma-joined value
2754
+ * ("opencode, custom-agent"), which is a corrupted request rather than an override. The user's
2755
+ * spelling and value both win; the registry only fills names the user has not spoken for.
2756
+ */
2757
+ export function mergeRegistryStaticHeaders(
2758
+ staticHeaders: Record<string, string> | undefined,
2759
+ userHeaders: Record<string, string> | undefined,
2760
+ ): Record<string, string> | undefined {
2761
+ if (!staticHeaders) return userHeaders;
2762
+ if (!userHeaders) return { ...staticHeaders };
2763
+ const claimed = new Set(Object.keys(userHeaders).map(name => name.toLowerCase()));
2764
+ const merged: Record<string, string> = { ...userHeaders };
2765
+ for (const [name, value] of Object.entries(staticHeaders)) {
2766
+ if (!claimed.has(name.toLowerCase())) merged[name] = value;
2767
+ }
2768
+ return merged;
2769
+ }
2770
+
2771
+ /** Whether this registry row's per-model service-tier evidence applies to one configured target. */
2772
+ export function registryModelServiceTierCapabilityApplies(
2773
+ entry: Pick<ProviderRegistryEntry, "modelServiceTierCapabilityBaseUrlGuard">,
2774
+ provider: Pick<OcxProviderConfig, "baseUrl">,
2775
+ ): boolean {
2776
+ const guard = entry.modelServiceTierCapabilityBaseUrlGuard;
2777
+ return guard === undefined || guard(provider.baseUrl);
2778
+ }
2779
+
2780
+ function normalizedProviderEndpoint(value: string): string {
2781
+ const trimmed = value.trim();
2782
+ try {
2783
+ const parsed = new URL(trimmed);
2784
+ parsed.pathname = parsed.pathname.replace(/\/+$/, "") || "/";
2785
+ return parsed.toString().replace(/\/$/, "");
2786
+ } catch {
2787
+ return trimmed.replace(/\/+$/, "");
2788
+ }
2789
+ }
2790
+
2791
+ /**
2792
+ * Whether registry transport defaults own this configured row.
2793
+ *
2794
+ * OAuth/forward providers stay pinned because their credentials must never be sent to an
2795
+ * arbitrary same-named host. Existing key presets keep their historical pinning behavior; a new
2796
+ * preset can opt into collision preservation, in which case its fixed endpoint owns only rows
2797
+ * that still match that destination.
2798
+ */
2799
+ export function providerMatchesRegistryTransport(
2800
+ id: string,
2801
+ provider: Pick<OcxProviderConfig, "baseUrl" | "adapter"> & Partial<Pick<OcxProviderConfig, "authMode">>,
2802
+ ): boolean {
2803
+ const entry = getProviderRegistryEntry(id);
2804
+ if (!entry) return false;
2805
+ if (entry.authKind !== "key" || entry.preserveCustomDestination !== true) return true;
2806
+ // The opt-in is intentionally limited to fixed key destinations. Fail closed if a future
2807
+ // registry edit combines it with an override/template despite the registry parity tests.
2808
+ if (entry.allowBaseUrlOverride || /\{[^}]*\}/.test(entry.baseUrl)) return false;
2809
+ if (typeof provider.baseUrl !== "string") return false;
2810
+ if (provider.adapter !== entry.adapter) return false;
2811
+ if (provider.authMode !== undefined && provider.authMode !== "key") return false;
2812
+ return normalizedProviderEndpoint(provider.baseUrl) === normalizedProviderEndpoint(entry.baseUrl);
2813
+ }
2814
+
2815
+ /**
2816
+ * Resolve the registry entry a configured provider actually points at, by TRANSPORT
2817
+ * rather than by name.
2818
+ *
2819
+ * `providerMatchesRegistryTransport` answers "does the row named X still point at X's
2820
+ * documented destination", which is the right question for routing but the wrong one
2821
+ * for user-facing metadata: the GUI lets a preset be saved under any name, and a
2822
+ * renamed row would silently lose a usage restriction it still needs to display.
2823
+ *
2824
+ * Only fixed key destinations are matched. Entries with an overridable or templated
2825
+ * base URL are skipped, because their configured URL cannot identify one vendor route.
2826
+ */
2827
+ export function registryEntryForProviderDestination(
2828
+ provider: Pick<OcxProviderConfig, "baseUrl" | "adapter"> & Partial<Pick<OcxProviderConfig, "authMode">>,
2829
+ ): ProviderRegistryEntry | undefined {
2830
+ if (typeof provider.baseUrl !== "string" || !provider.baseUrl) return undefined;
2831
+ if (provider.authMode !== undefined && provider.authMode !== "key") return undefined;
2832
+ const endpoint = normalizedProviderEndpoint(provider.baseUrl);
2833
+ return PROVIDER_REGISTRY.find(entry =>
2834
+ entry.authKind === "key"
2835
+ && !entry.allowBaseUrlOverride
2836
+ && !/\{[^}]*\}/.test(entry.baseUrl)
2837
+ && entry.adapter === provider.adapter
2838
+ && normalizedProviderEndpoint(entry.baseUrl) === endpoint);
2839
+ }
2840
+
2841
+ /**
2842
+ * Resolve a registry-only default for a mixed-wire provider. Defaults only move a provider
2843
+ * between the two OpenAI-shaped adapters and never override a provider configured on another
2844
+ * wire. The resolver receives the allow-list so this helper cannot accidentally widen the
2845
+ * adapter-selection boundary when a new registry entry is added.
2846
+ */
2847
+ export function providerModelWireDefault(
2848
+ id: string,
2849
+ provider: Pick<OcxProviderConfig, "baseUrl" | "adapter"> & Partial<Pick<OcxProviderConfig, "authMode">>,
2850
+ modelId: string,
2851
+ allowedWires: ReadonlySet<string>,
2852
+ inbound: InboundWire,
2853
+ ): string | undefined {
2854
+ if (!allowedWires.has(provider.adapter)) return undefined;
2855
+ const entry = getProviderRegistryEntry(id);
2856
+ if (!entry?.modelWireDefaults || !providerMatchesRegistryTransport(id, provider)) return undefined;
2857
+ const declared = entry.modelWireDefaults[modelId.trim().toLowerCase()];
2858
+ if (declared === undefined) return undefined;
2859
+ // A bare string applies to every inbound/auth mode; the object form may narrow either.
2860
+ if (typeof declared !== "string") {
2861
+ if (!declared.inbound.includes(inbound)) return undefined;
2862
+ const authMode = provider.authMode ?? entry.authKind;
2863
+ if (declared.authModes && !declared.authModes.includes(authMode)) return undefined;
2864
+ }
2865
+ const wire = typeof declared === "string" ? declared : declared.wire;
2866
+ return wire !== undefined && allowedWires.has(wire) ? wire : undefined;
2867
+ }
2868
+
2869
+ /** Resolve a registry-only upstream-streaming compatibility hint for Responses turns. */
2870
+ export function providerModelResponsesUpstreamStreaming(
2871
+ id: string,
2872
+ provider: Pick<OcxProviderConfig, "baseUrl" | "adapter"> & Partial<Pick<OcxProviderConfig, "authMode">>,
2873
+ modelId: string,
2874
+ ): boolean | undefined {
2875
+ const entry = getProviderRegistryEntry(id);
2876
+ if (!entry?.modelResponsesUpstreamStreaming || !providerMatchesRegistryTransport(id, provider)) return undefined;
2877
+ return entry.modelResponsesUpstreamStreaming[modelId.trim().toLowerCase()];
2878
+ }
2879
+
2880
+ /** Resolve a registry-only terminal-repair policy for native Responses streams. */
2881
+ export function providerModelResponsesTerminalRepair(
2882
+ id: string,
2883
+ provider: Pick<OcxProviderConfig, "baseUrl" | "adapter"> & Partial<Pick<OcxProviderConfig, "authMode">>,
2884
+ modelId: string,
2885
+ ): ResponsesTerminalRepairPolicy | undefined {
2886
+ const entry = getProviderRegistryEntry(id);
2887
+ if (!entry?.modelResponsesTerminalRepair || !providerMatchesRegistryTransport(id, provider)) return undefined;
2888
+ const policy = entry.modelResponsesTerminalRepair[modelId.trim().toLowerCase()];
2889
+ const graceMs = Math.floor(policy?.graceMs ?? 0);
2890
+ if (!Number.isFinite(graceMs) || graceMs <= 0) return undefined;
2891
+ return { graceMs };
2892
+ }
2893
+
2894
+ /**
2895
+ * Effective Codex account mode for a provider. For canonical `openai`, a valid persisted
2896
+ * `codexAccountMode` on the provider config wins and a missing/invalid value defaults to
2897
+ * `"pool"`. Other providers keep registry-only metadata (there is no mode for `openai-apikey`).
2898
+ */
2899
+ export function providerCodexAccountMode(id: string, provider?: OcxProviderConfig): CodexAccountMode | undefined {
2900
+ const registryMode = getProviderRegistryEntry(id)?.codexAccountMode;
2901
+ if (id !== "openai") return registryMode;
2902
+ const persisted = provider?.codexAccountMode;
2903
+ if (persisted === "pool" || persisted === "direct") return persisted;
2904
+ return registryMode ?? "pool";
2905
+ }
2906
+
2907
+ /**
2908
+ * Effective Google wire mode for a provider: config value, else registry backfill (a saved
2909
+ * key-login config may omit `googleMode` — mirrors the router's backfill), else "ai-studio"
2910
+ * (the Generative Language API default). Null for non-google adapters.
2911
+ */
2912
+ export function effectiveGoogleMode(
2913
+ providerId: string,
2914
+ prov: { adapter?: string; googleMode?: "ai-studio" | "vertex" | "cloud-code-assist" },
2915
+ ): "ai-studio" | "vertex" | "cloud-code-assist" | null {
2916
+ if (prov.adapter !== "google") return null;
2917
+ return prov.googleMode ?? getProviderRegistryEntry(providerId)?.googleMode ?? "ai-studio";
2918
+ }