@linxiraos/pi-catalog 1.1.7 → 1.1.9

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 (308) hide show
  1. package/CHANGELOG.md +7 -8
  2. package/README.md +9 -8
  3. package/package.json +96 -100
  4. package/src/build.ts +165 -48
  5. package/src/cline-pass-model-id.ts +11 -0
  6. package/src/compat/anthropic.ts +15 -136
  7. package/src/compat/auth-ids.ts +157 -0
  8. package/src/compat/auth.ts +60 -0
  9. package/src/compat/axes.ts +301 -0
  10. package/src/compat/behavior.ts +207 -0
  11. package/src/compat/cascade.ts +230 -0
  12. package/src/{variant-collapse.ts → compat/collapse.ts} +381 -773
  13. package/src/compat/delegation.ts +37 -0
  14. package/src/compat/openai.ts +17 -848
  15. package/src/compat/resolve.ts +1231 -0
  16. package/src/compat/revision.ts +154 -0
  17. package/src/compat/rules/README.md +315 -0
  18. package/src/compat/rules/auth/_order.kdl +13 -0
  19. package/src/compat/rules/auth/abliteration.kdl +9 -0
  20. package/src/compat/rules/auth/aiand.kdl +9 -0
  21. package/src/compat/rules/auth/aimlapi.kdl +3 -0
  22. package/src/compat/rules/auth/alibaba-coding-plan.kdl +5 -0
  23. package/src/compat/rules/auth/alibaba-token-plan.kdl +11 -0
  24. package/src/compat/rules/auth/amazon-bedrock.kdl +5 -0
  25. package/src/compat/rules/auth/anthropic.kdl +53 -0
  26. package/src/compat/rules/auth/azure.kdl +3 -0
  27. package/src/compat/rules/auth/baseten.kdl +9 -0
  28. package/src/compat/rules/auth/bedrock-mantle.kdl +5 -0
  29. package/src/compat/rules/auth/cerebras.kdl +9 -0
  30. package/src/compat/rules/auth/cline-pass.kdl +12 -0
  31. package/src/compat/rules/auth/cloudflare-ai-gateway.kdl +4 -0
  32. package/src/compat/rules/auth/coreweave.kdl +9 -0
  33. package/src/compat/rules/auth/cursor.kdl +5 -0
  34. package/src/compat/rules/auth/deepinfra.kdl +11 -0
  35. package/src/compat/rules/auth/deepseek.kdl +10 -0
  36. package/src/compat/rules/auth/devin.kdl +28 -0
  37. package/src/compat/rules/auth/exa.kdl +9 -0
  38. package/src/compat/rules/auth/firepass.kdl +14 -0
  39. package/src/compat/rules/auth/fireworks.kdl +16 -0
  40. package/src/compat/rules/auth/github-copilot.kdl +7 -0
  41. package/src/compat/rules/auth/gitlab-duo-agent.kdl +23 -0
  42. package/src/compat/rules/auth/gitlab-duo.kdl +24 -0
  43. package/src/compat/rules/auth/gmi-cloud.kdl +9 -0
  44. package/src/compat/rules/auth/google-antigravity.kdl +28 -0
  45. package/src/compat/rules/auth/google-gemini-cli.kdl +26 -0
  46. package/src/compat/rules/auth/google-vertex.kdl +5 -0
  47. package/src/compat/rules/auth/google.kdl +3 -0
  48. package/src/compat/rules/auth/groq.kdl +3 -0
  49. package/src/compat/rules/auth/huggingface.kdl +9 -0
  50. package/src/compat/rules/auth/kagi.kdl +9 -0
  51. package/src/compat/rules/auth/kilo.kdl +4 -0
  52. package/src/compat/rules/auth/kimi-code.kdl +18 -0
  53. package/src/compat/rules/auth/litellm.kdl +8 -0
  54. package/src/compat/rules/auth/llama.cpp.kdl +10 -0
  55. package/src/compat/rules/auth/lm-studio.kdl +7 -0
  56. package/src/compat/rules/auth/meta.kdl +9 -0
  57. package/src/compat/rules/auth/minimax-code-cn.kdl +9 -0
  58. package/src/compat/rules/auth/minimax-code.kdl +9 -0
  59. package/src/compat/rules/auth/minimax.kdl +3 -0
  60. package/src/compat/rules/auth/mistral.kdl +3 -0
  61. package/src/compat/rules/auth/moonshot.kdl +9 -0
  62. package/src/compat/rules/auth/nanogpt.kdl +9 -0
  63. package/src/compat/rules/auth/novita.kdl +12 -0
  64. package/src/compat/rules/auth/nvidia.kdl +11 -0
  65. package/src/compat/rules/auth/ollama-cloud.kdl +8 -0
  66. package/src/compat/rules/auth/ollama.kdl +9 -0
  67. package/src/compat/rules/auth/openai-codex-device.kdl +20 -0
  68. package/src/compat/rules/auth/openai-codex.kdl +33 -0
  69. package/src/compat/rules/auth/openai.kdl +3 -0
  70. package/src/compat/rules/auth/opencode-go.kdl +10 -0
  71. package/src/compat/rules/auth/opencode-zen.kdl +9 -0
  72. package/src/compat/rules/auth/openrouter.kdl +32 -0
  73. package/src/compat/rules/auth/parallel.kdl +9 -0
  74. package/src/compat/rules/auth/perplexity.kdl +7 -0
  75. package/src/compat/rules/auth/qianfan.kdl +9 -0
  76. package/src/compat/rules/auth/qwen-portal.kdl +9 -0
  77. package/src/compat/rules/auth/sakana.kdl +9 -0
  78. package/src/compat/rules/auth/siliconflow-cn.kdl +9 -0
  79. package/src/compat/rules/auth/siliconflow.kdl +9 -0
  80. package/src/compat/rules/auth/synthetic.kdl +9 -0
  81. package/src/compat/rules/auth/tavily.kdl +9 -0
  82. package/src/compat/rules/auth/together.kdl +14 -0
  83. package/src/compat/rules/auth/umans.kdl +9 -0
  84. package/src/compat/rules/auth/venice.kdl +9 -0
  85. package/src/compat/rules/auth/vercel-ai-gateway.kdl +8 -0
  86. package/src/compat/rules/auth/vllm.kdl +9 -0
  87. package/src/compat/rules/auth/wafer-serverless.kdl +11 -0
  88. package/src/compat/rules/auth/xai-oauth.kdl +27 -0
  89. package/src/compat/rules/auth/xai.kdl +9 -0
  90. package/src/compat/rules/auth/xiaomi-token-plan-ams.kdl +9 -0
  91. package/src/compat/rules/auth/xiaomi-token-plan-cn.kdl +9 -0
  92. package/src/compat/rules/auth/xiaomi-token-plan-sgp.kdl +9 -0
  93. package/src/compat/rules/auth/xiaomi.kdl +6 -0
  94. package/src/compat/rules/auth/yolo-auto.kdl +11 -0
  95. package/src/compat/rules/auth/zai-coding-plan.kdl +33 -0
  96. package/src/compat/rules/auth/zai.kdl +9 -0
  97. package/src/compat/rules/auth/zenmux.kdl +9 -0
  98. package/src/compat/rules/auth/zhipu-coding-plan.kdl +9 -0
  99. package/src/compat/rules/classes/amazon.kdl +21 -0
  100. package/src/compat/rules/classes/anthropic.kdl +285 -0
  101. package/src/compat/rules/classes/baidu.kdl +9 -0
  102. package/src/compat/rules/classes/bytedance.kdl +9 -0
  103. package/src/compat/rules/classes/cohere.kdl +7 -0
  104. package/src/compat/rules/classes/deepseek.kdl +80 -0
  105. package/src/compat/rules/classes/gemini.kdl +107 -0
  106. package/src/compat/rules/classes/gemma.kdl +14 -0
  107. package/src/compat/rules/classes/glm.kdl +115 -0
  108. package/src/compat/rules/classes/gpt-oss.kdl +16 -0
  109. package/src/compat/rules/classes/kimi.kdl +113 -0
  110. package/src/compat/rules/classes/meta.kdl +15 -0
  111. package/src/compat/rules/classes/mimo.kdl +32 -0
  112. package/src/compat/rules/classes/minimax.kdl +79 -0
  113. package/src/compat/rules/classes/mistral.kdl +19 -0
  114. package/src/compat/rules/classes/openai.kdl +58 -0
  115. package/src/compat/rules/classes/qwen.kdl +72 -0
  116. package/src/compat/rules/classes/stepfun.kdl +10 -0
  117. package/src/compat/rules/classes/xai.kdl +45 -0
  118. package/src/compat/rules/providers/abliteration.kdl +8 -0
  119. package/src/compat/rules/providers/aiand.kdl +19 -0
  120. package/src/compat/rules/providers/aimlapi.kdl +24 -0
  121. package/src/compat/rules/providers/alibaba-coding-plan.kdl +20 -0
  122. package/src/compat/rules/providers/alibaba-token-plan.kdl +35 -0
  123. package/src/compat/rules/providers/amazon-bedrock.kdl +36 -0
  124. package/src/compat/rules/providers/anthropic.kdl +24 -0
  125. package/src/compat/rules/providers/azure.kdl +19 -0
  126. package/src/compat/rules/providers/baseten.kdl +19 -0
  127. package/src/compat/rules/providers/bedrock-mantle.kdl +15 -0
  128. package/src/compat/rules/providers/cerebras.kdl +19 -0
  129. package/src/compat/rules/providers/cline-pass.kdl +8 -0
  130. package/src/compat/rules/providers/cloudflare-ai-gateway.kdl +26 -0
  131. package/src/compat/rules/providers/coreweave.kdl +16 -0
  132. package/src/compat/rules/providers/cursor.kdl +52 -0
  133. package/src/compat/rules/providers/deepseek.kdl +21 -0
  134. package/src/compat/rules/providers/firepass.kdl +15 -0
  135. package/src/compat/rules/providers/fireworks.kdl +43 -0
  136. package/src/compat/rules/providers/github-copilot.kdl +62 -0
  137. package/src/compat/rules/providers/gitlab-duo.kdl +24 -0
  138. package/src/compat/rules/providers/gmi-cloud.kdl +8 -0
  139. package/src/compat/rules/providers/google-antigravity.kdl +59 -0
  140. package/src/compat/rules/providers/google-gemini-cli.kdl +12 -0
  141. package/src/compat/rules/providers/google-vertex.kdl +25 -0
  142. package/src/compat/rules/providers/google.kdl +17 -0
  143. package/src/compat/rules/providers/groq.kdl +8 -0
  144. package/src/compat/rules/providers/huggingface.kdl +24 -0
  145. package/src/compat/rules/providers/kilo.kdl +51 -0
  146. package/src/compat/rules/providers/kimi-code.kdl +37 -0
  147. package/src/compat/rules/providers/llama.cpp.kdl +6 -0
  148. package/src/compat/rules/providers/lm-studio.kdl +6 -0
  149. package/src/compat/rules/providers/meta.kdl +10 -0
  150. package/src/compat/rules/providers/minimax-code-cn.kdl +13 -0
  151. package/src/compat/rules/providers/minimax-code.kdl +13 -0
  152. package/src/compat/rules/providers/minimax.kdl +6 -0
  153. package/src/compat/rules/providers/mistral.kdl +8 -0
  154. package/src/compat/rules/providers/moonshot.kdl +36 -0
  155. package/src/compat/rules/providers/nanogpt.kdl +100 -0
  156. package/src/compat/rules/providers/novita.kdl +34 -0
  157. package/src/compat/rules/providers/nvidia.kdl +27 -0
  158. package/src/compat/rules/providers/ollama-cloud.kdl +20 -0
  159. package/src/compat/rules/providers/ollama.kdl +12 -0
  160. package/src/compat/rules/providers/openai-codex.kdl +121 -0
  161. package/src/compat/rules/providers/openai.kdl +82 -0
  162. package/src/compat/rules/providers/opencode-go.kdl +97 -0
  163. package/src/compat/rules/providers/opencode-zen.kdl +103 -0
  164. package/src/compat/rules/providers/openrouter.kdl +66 -0
  165. package/src/compat/rules/providers/qianfan.kdl +8 -0
  166. package/src/compat/rules/providers/sakana.kdl +10 -0
  167. package/src/compat/rules/providers/synthetic.kdl +16 -0
  168. package/src/compat/rules/providers/together.kdl +22 -0
  169. package/src/compat/rules/providers/umans.kdl +28 -0
  170. package/src/compat/rules/providers/venice.kdl +46 -0
  171. package/src/compat/rules/providers/vercel-ai-gateway.kdl +73 -0
  172. package/src/compat/rules/providers/vllm.kdl +8 -0
  173. package/src/compat/rules/providers/wafer-serverless.kdl +35 -0
  174. package/src/compat/rules/providers/xai-oauth.kdl +48 -0
  175. package/src/compat/rules/providers/xai.kdl +50 -0
  176. package/src/compat/rules/providers/xiaomi-token-plan-ams.kdl +9 -0
  177. package/src/compat/rules/providers/xiaomi-token-plan-cn.kdl +9 -0
  178. package/src/compat/rules/providers/xiaomi-token-plan-sgp.kdl +9 -0
  179. package/src/compat/rules/providers/xiaomi.kdl +22 -0
  180. package/src/compat/rules/providers/yolo-auto.kdl +16 -0
  181. package/src/compat/rules/providers/zai.kdl +36 -0
  182. package/src/compat/rules/providers/zenmux.kdl +108 -0
  183. package/src/compat/rules/providers/zhipu-coding-plan.kdl +49 -0
  184. package/src/compat/rules/runtime/behavior.kdl +254 -0
  185. package/src/compat/rules/taxonomy/_collapse.kdl +747 -0
  186. package/src/compat/rules/taxonomy/_discovery.kdl +60 -0
  187. package/src/compat/rules/taxonomy/ai21.kdl +6 -0
  188. package/src/compat/rules/taxonomy/amazon.kdl +8 -0
  189. package/src/compat/rules/taxonomy/anthropic.kdl +17 -0
  190. package/src/compat/rules/taxonomy/baidu.kdl +5 -0
  191. package/src/compat/rules/taxonomy/bytedance.kdl +5 -0
  192. package/src/compat/rules/taxonomy/cohere.kdl +6 -0
  193. package/src/compat/rules/taxonomy/deepseek.kdl +18 -0
  194. package/src/compat/rules/taxonomy/gemini.kdl +9 -0
  195. package/src/compat/rules/taxonomy/gemma.kdl +3 -0
  196. package/src/compat/rules/taxonomy/glm.kdl +17 -0
  197. package/src/compat/rules/taxonomy/gpt-oss.kdl +11 -0
  198. package/src/compat/rules/taxonomy/kimi.kdl +20 -0
  199. package/src/compat/rules/taxonomy/meta.kdl +10 -0
  200. package/src/compat/rules/taxonomy/mimo.kdl +7 -0
  201. package/src/compat/rules/taxonomy/minimax.kdl +9 -0
  202. package/src/compat/rules/taxonomy/mistral.kdl +7 -0
  203. package/src/compat/rules/taxonomy/openai.kdl +43 -0
  204. package/src/compat/rules/taxonomy/qwen.kdl +33 -0
  205. package/src/compat/rules/taxonomy/stepfun.kdl +5 -0
  206. package/src/compat/rules/taxonomy/unknown.kdl +3 -0
  207. package/src/compat/rules/taxonomy/xai.kdl +12 -0
  208. package/src/compat/rules.d.json.ts +5 -0
  209. package/src/compat/rules.json +16984 -0
  210. package/src/compat/taxonomy.ts +452 -0
  211. package/src/compat/types.ts +588 -0
  212. package/src/discovery/antigravity.ts +5 -6
  213. package/src/discovery/codex.ts +22 -7
  214. package/src/discovery/cursor-proto.ts +7 -7
  215. package/src/discovery/cursor.ts +47 -58
  216. package/src/discovery/devin-proto.ts +613 -0
  217. package/src/discovery/devin.ts +358 -52
  218. package/src/discovery/gemini-cli.ts +14 -11
  219. package/src/discovery/gemini.ts +2 -0
  220. package/src/identity/dialect.ts +2 -2
  221. package/src/identity/id.ts +12 -0
  222. package/src/identity/index.ts +3 -3
  223. package/src/identity/metrics.ts +165 -0
  224. package/src/identity/reference.ts +37 -5
  225. package/src/index.ts +1 -1
  226. package/src/model-manager.ts +19 -6
  227. package/src/model-thinking.ts +6 -810
  228. package/src/model-tokenizer.ts +37 -44
  229. package/src/models.json +89584 -22358
  230. package/src/models.ts +6 -1
  231. package/src/provider-models/bundled-references.ts +3 -1
  232. package/src/provider-models/cline-pass.ts +226 -0
  233. package/src/provider-models/descriptors.ts +18 -0
  234. package/src/provider-models/google.ts +14 -5
  235. package/src/provider-models/models-dev-policies.ts +4 -29
  236. package/src/provider-models/ollama.ts +15 -2
  237. package/src/provider-models/openai-compat.ts +798 -520
  238. package/src/provider-models/special.ts +211 -12
  239. package/src/types.ts +214 -11
  240. package/src/wire/cline-pass.ts +35 -0
  241. package/src/wire/codex.ts +23 -2
  242. package/src/wire/devin-proto.ts +30 -0
  243. package/src/wire/devin.ts +72 -0
  244. package/src/wire/github-copilot.ts +49 -7
  245. package/THIRD-PARTY-NOTICES.txt +0 -22909
  246. package/dist/types/build.d.ts +0 -19
  247. package/dist/types/compat/anthropic.d.ts +0 -18
  248. package/dist/types/compat/apply.d.ts +0 -7
  249. package/dist/types/compat/bedrock.d.ts +0 -3
  250. package/dist/types/compat/devin.d.ts +0 -9
  251. package/dist/types/compat/openai.d.ts +0 -28
  252. package/dist/types/discovery/antigravity.d.ts +0 -70
  253. package/dist/types/discovery/codex.d.ts +0 -36
  254. package/dist/types/discovery/cursor-proto.d.ts +0 -5368
  255. package/dist/types/discovery/cursor.d.ts +0 -29
  256. package/dist/types/discovery/devin-proto.d.ts +0 -1549
  257. package/dist/types/discovery/devin.d.ts +0 -24
  258. package/dist/types/discovery/gemini-cli.d.ts +0 -33
  259. package/dist/types/discovery/gemini.d.ts +0 -25
  260. package/dist/types/discovery/gitlab-duo-workflow.d.ts +0 -30
  261. package/dist/types/discovery/index.d.ts +0 -7
  262. package/dist/types/discovery/openai-compatible.d.ts +0 -89
  263. package/dist/types/discovery/protobuf.d.ts +0 -102
  264. package/dist/types/effort.d.ts +0 -10
  265. package/dist/types/fireworks-model-id.d.ts +0 -23
  266. package/dist/types/hosts.d.ts +0 -169
  267. package/dist/types/identity/bundled.d.ts +0 -3
  268. package/dist/types/identity/classify.d.ts +0 -69
  269. package/dist/types/identity/dialect.d.ts +0 -3
  270. package/dist/types/identity/family.d.ts +0 -243
  271. package/dist/types/identity/id.d.ts +0 -12
  272. package/dist/types/identity/index.d.ts +0 -8
  273. package/dist/types/identity/markers.d.ts +0 -4
  274. package/dist/types/identity/priority.d.ts +0 -1
  275. package/dist/types/identity/reference.d.ts +0 -29
  276. package/dist/types/index.d.ts +0 -19
  277. package/dist/types/model-cache.d.ts +0 -23
  278. package/dist/types/model-manager.d.ts +0 -93
  279. package/dist/types/model-thinking.d.ts +0 -81
  280. package/dist/types/model-tokenizer.d.ts +0 -8
  281. package/dist/types/models.d.ts +0 -17
  282. package/dist/types/openai-pricing.d.ts +0 -17
  283. package/dist/types/provider-models/bundled-references.d.ts +0 -13
  284. package/dist/types/provider-models/cache-provider-id.d.ts +0 -11
  285. package/dist/types/provider-models/descriptor-types.d.ts +0 -76
  286. package/dist/types/provider-models/descriptors.d.ts +0 -524
  287. package/dist/types/provider-models/google.d.ts +0 -29
  288. package/dist/types/provider-models/index.d.ts +0 -7
  289. package/dist/types/provider-models/models-dev-policies.d.ts +0 -8
  290. package/dist/types/provider-models/ollama.d.ts +0 -22
  291. package/dist/types/provider-models/openai-compat.d.ts +0 -703
  292. package/dist/types/provider-models/special.d.ts +0 -53
  293. package/dist/types/types.d.ts +0 -879
  294. package/dist/types/utils.d.ts +0 -20
  295. package/dist/types/variant-collapse.d.ts +0 -146
  296. package/dist/types/wire/alibaba-token-plan.d.ts +0 -22
  297. package/dist/types/wire/cloudflare-ai-gateway.d.ts +0 -18
  298. package/dist/types/wire/codex.d.ts +0 -66
  299. package/dist/types/wire/coreweave.d.ts +0 -12
  300. package/dist/types/wire/gemini-headers.d.ts +0 -62
  301. package/dist/types/wire/github-copilot.d.ts +0 -47
  302. package/dist/types/wire/image-fetchers.d.ts +0 -76
  303. package/src/compat/bedrock.ts +0 -153
  304. package/src/compat/devin.ts +0 -12
  305. package/src/identity/classify.ts +0 -242
  306. package/src/identity/family.ts +0 -496
  307. package/src/identity/markers.ts +0 -49
  308. package/src/openai-pricing.ts +0 -29
@@ -1,185 +1,13 @@
1
1
  /**
2
- * OpenAI-API compat builders — chat-completions and Responses flavors.
3
- *
4
- * `buildOpenAICompat`/`buildOpenAIResponsesCompat` run exactly once per model
5
- * (from `buildModel`): detection writes a fresh record, sparse spec overrides
6
- * are assigned onto it in place, and conditional policies are materialized as
7
- * complete alternate views. Request handlers read `model.compat` fields and
8
- * never detect, resolve, or allocate.
2
+ * OpenAI-side compat residue: the first-party xAI Responses effort remap used
3
+ * by the xai-oauth discovery mapper when curating sparse rows. Everything else
4
+ * that lived here (the chat-completions/Responses compat builders) moved into
5
+ * the compat engine (`./resolve`) and the KDL rules.
9
6
  */
10
- import { isFireworksFastModelId } from "../fireworks-model-id";
11
- import { hostMatchesUrl, modelMatchesHost } from "../hosts";
12
- import { bareModelId, parseOpenAIModel, semverGte } from "../identity/classify";
13
- import {
14
- isAnthropicNamespacedModelId,
15
- isClaudeModelId,
16
- isDeepseekModelIdOrName,
17
- isGlm52ReasoningEffortModelId,
18
- isGrokReasoningEffortCapable,
19
- isGrokXHighEffortCapable,
20
- isKimiK3ModelId,
21
- isKimiK26ModelId,
22
- isKimiModelId,
23
- isMimoModelIdOrName,
24
- isOpenAISamplingRestrictedModelId,
25
- isQwen38PlusTemplateEffortModelId,
26
- isQwenModelId,
27
- } from "../identity/family";
28
- import type {
29
- ModelSpec,
30
- OpenAICompat,
31
- OpenAIStreamMarkupHealingPattern,
32
- ResolvedOpenAICompat,
33
- ResolvedOpenAIResponsesCompat,
34
- ResolvedOpenAISharedCompat,
35
- ResolvedOpenRouterCompat,
36
- } from "../types";
37
- import { applyCompatOverrides } from "./apply";
38
7
 
39
- /** GLM coding-plan SKUs idle for minutes mid-reasoning; see `streamIdleTimeoutMs`. */
40
- const GLM_CODING_PLAN_MODEL_PATTERN = /(^|\/)glm-5(?:[.-]|$)/i;
41
- const GLM_CODING_PLAN_STREAM_IDLE_TIMEOUT_MS = 600_000;
42
- /** Direct DeepSeek reasoning models stall between thinking and answer phases. */
43
- const DEEPSEEK_REASONING_STREAM_IDLE_TIMEOUT_MS = 300_000;
44
- /** Kimi K2.6 and native K2.7 Code can spend several minutes reasoning before the first visible token. */
45
- const KIMI_REASONING_STREAM_IDLE_TIMEOUT_MS = 300_000;
46
- /**
47
- * Native Kimi K2.7 Code requires `thinking.type: "enabled"` and rejects
48
- * disabled thinking. Match the public id, its Fast variant, and the
49
- * `kimi-code/kimi-for-coding` alias (which keeps the family name).
50
- * Caller-disabled requests on non-native dialects (Fireworks `openai`,
51
- * OpenRouter `openrouter`, …) MUST keep their per-dialect disable shape —
52
- * gating on `isMoonshotKimi` is the caller's responsibility.
53
- */
54
- const KIMI_K27_CODE_MODEL_PATTERN = /(?:^|\/)kimi[-._]?k2(?:[._-]?|p)7[-._]?code(?:[-._]?highspeed)?$/i;
55
-
56
- function matchesKimiK27CodeFamily(spec: ModelSpec<"openai-completions">): boolean {
57
- if (KIMI_K27_CODE_MODEL_PATTERN.test(spec.id)) return true;
58
- return spec.id === "kimi-for-coding" && /k2\.?7 code/i.test(spec.name ?? "");
59
- }
60
- /** Xiaomi MiMo Pro on api.xiaomimimo.com can stall ~2min before the first event (issue #1770). */
61
- const XIAOMI_MIMO_STREAM_IDLE_TIMEOUT_MS = 300_000;
62
- /** Alibaba Coding Plan (coding-intl.dashscope) qwen models idle before the first event (issue #1770). */
63
- const ALIBABA_CODING_PLAN_STREAM_IDLE_TIMEOUT_MS = 600_000;
64
- /** Local OpenAI-compatible backends can spend minutes cold-loading a model before the first SSE event. */
65
- const LOCAL_OPENAI_COMPAT_STREAM_IDLE_TIMEOUT_MS = 300_000;
66
- const MINIMAX_PROVIDER_OR_ID_PATTERN = /minimax/i;
67
- const DSML_HEALING_PROVIDERS = new Set([
68
- "ollama",
69
- "ollama-cloud",
70
- "nvidia",
71
- "deepseek",
72
- "fireworks",
73
- "nanogpt",
74
- "opencode-go",
75
- "openrouter",
76
- ]);
77
-
78
- // Ollama's OpenAI-compatible `reasoning.effort` accepts `high|medium|low|max|none`;
79
- // `ollama`-provider reasoning models carry the wire-exact `low..max` effort
80
- // ladder (see getModelDefinedEfforts), so no compat-level remapping is needed.
81
- // Custom OpenAI-compatible providers pointed at a local Ollama port under a
82
- // different provider id must set `compat.reasoningEffortMap` themselves.
83
-
84
- function resolveReasoningDisableMode(
85
- thinkingFormat: ResolvedOpenAISharedCompat["thinkingFormat"],
86
- ): ResolvedOpenAISharedCompat["reasoningDisableMode"] {
87
- switch (thinkingFormat) {
88
- case "openrouter":
89
- return "openrouter-enabled-false";
90
- case "zai":
91
- case "kimi":
92
- return "zai-thinking-disabled";
93
- case "qwen":
94
- return "qwen-enable-thinking-false";
95
- case "qwen-chat-template":
96
- return "qwen-template-false";
97
- case "chat-template":
98
- return "chat-template-thinking-false";
99
- default:
100
- return "lowest-effort";
101
- }
102
- }
103
-
104
- /**
105
- * Pick the leaked-markup healer for an OpenAI-compatible visible-text stream.
106
- * Kimi chat-template tokens and DeepSeek DSML envelopes need their dedicated
107
- * tool-call grammars. Every other OpenAI-compatible model defaults to
108
- * `"thinking"` so leaked reasoning idioms (e.g. a Gemini ` ```thinking ` fence
109
- * on OpenRouter) are recovered from `delta.content` — **except** the official
110
- * OpenAI endpoint (`provider: "openai"` + `api.openai.com`), which returns
111
- * structured reasoning and never leaks, so it heals nothing (returns
112
- * `undefined`) to avoid misfiring on legitimate fenced content.
113
- */
114
- function detectStreamMarkupHealingPattern(
115
- provider: string,
116
- modelId: string,
117
- baseUrl: string,
118
- ): OpenAIStreamMarkupHealingPattern | undefined {
119
- if (provider === "kimi-code" || provider === "moonshot" || /kimi[-/_.]?k2/i.test(modelId)) {
120
- return "kimi";
121
- }
122
- if (isDeepseekModelIdOrName(modelId) && DSML_HEALING_PROVIDERS.has(provider)) {
123
- return "dsml";
124
- }
125
- if (isOfficialOpenAIEndpoint(provider, baseUrl)) return undefined;
126
- return "thinking";
127
- }
128
-
129
- /** Strict official-OpenAI check: provider id `openai` and an `api.openai.com` host (missing baseUrl defaults there). */
130
- function isOfficialOpenAIEndpoint(provider: string, baseUrl: string): boolean {
131
- if (provider !== "openai") return false;
132
- if (!baseUrl) return true;
133
- try {
134
- return new URL(baseUrl).hostname === "api.openai.com";
135
- } catch {
136
- return false;
137
- }
138
- }
139
-
140
- /**
141
- * Explicit prompt-cache breakpoints are a GPT-5.6+ first-party contract. Keep
142
- * this intentionally narrow: compatible gateways and older OpenAI models
143
- * reject the new request fields unless their catalog compat opts in.
144
- */
145
- function supportsOfficialOpenAIPromptCacheBreakpoints(provider: string, modelId: string, baseUrl: string): boolean {
146
- if (!isOfficialOpenAIEndpoint(provider, baseUrl)) return false;
147
- const model = parseOpenAIModel(bareModelId(modelId));
148
- return model !== null && semverGte(model.version, "5.6");
149
- }
150
-
151
- /**
152
- * OpenCode's gateways (https://opencode.ai/zen|go) gate `reasoning_content`
153
- * on the request's thinking state for every model they front (Kimi K2.x,
154
- * DeepSeek V4, GLM-5.x, Qwen3.x, MiMo, MiniMax, …): they 400 with `Extra
155
- * inputs are not permitted` when thinking is off but the field is supplied
156
- * (#1071), and 400 with `thinking is enabled but reasoning_content is missing
157
- * in assistant tool call message at index N` (#1484) when thinking is on and
158
- * the field is absent. The base compat therefore leaves the replay off, and
159
- * this `whenThinking` policy reactivates it for thinking-engaged requests.
160
- * `allowsSyntheticReasoningContentForToolCalls` is forced to `false` on the
161
- * same path: the gateway specifically requires `reasoning_content`, and the
162
- * synthetic-friendly default would echo whichever field the upstream streamed
163
- * (e.g. `reasoning` for many opencode turns), landing the replay in the wrong
164
- * key and re-triggering the 400.
165
- */
166
- const OPENCODE_WHEN_THINKING: NonNullable<OpenAICompat["whenThinking"]> = {
167
- requiresReasoningContentForToolCalls: true,
168
- allowsSyntheticReasoningContentForToolCalls: false,
169
- reasoningContentField: "reasoning_content",
170
- };
171
-
172
- const KIMI_K3_REASONING_EFFORT_MAP: NonNullable<OpenAICompat["reasoningEffortMap"]> = {
173
- minimal: "low",
174
- medium: "high",
175
- xhigh: "max",
176
- max: "max",
177
- };
178
-
179
- const MIMO_REASONING_EFFORT_MAP: NonNullable<OpenAICompat["reasoningEffortMap"]> = {
180
- minimal: "low",
181
- xhigh: "high",
182
- };
8
+ import type { OpenAICompat } from "../types";
9
+ import { compareRevision, parseRevision } from "./revision";
10
+ import { classifyModel } from "./taxonomy";
183
11
 
184
12
  /** Shared `minimal → low` clamp. xhigh-capable Grok keeps `xhigh` unmapped. */
185
13
  const XAI_RESPONSES_MINIMAL_EFFORT_MAP: NonNullable<OpenAICompat["reasoningEffortMap"]> = {
@@ -194,673 +22,14 @@ const XAI_RESPONSES_CLAMPED_EFFORT_MAP: NonNullable<OpenAICompat["reasoningEffor
194
22
 
195
23
  /** Wire effort remap for first-party xAI Responses. */
196
24
  export function xaiResponsesReasoningEffortMap(modelId: string): NonNullable<OpenAICompat["reasoningEffortMap"]> {
197
- return isGrokXHighEffortCapable(modelId) ? XAI_RESPONSES_MINIMAL_EFFORT_MAP : XAI_RESPONSES_CLAMPED_EFFORT_MAP;
198
- }
199
-
200
- function mergeModelReasoningEffortMap(
201
- compat: ResolvedOpenAISharedCompat,
202
- modelId: string,
203
- isMimoReasoningEffortModel: boolean,
204
- ): void {
205
- let detected: NonNullable<OpenAICompat["reasoningEffortMap"]>;
206
- if (isKimiK3ModelId(modelId)) {
207
- detected = KIMI_K3_REASONING_EFFORT_MAP;
208
- } else if (isMimoReasoningEffortModel) {
209
- detected = MIMO_REASONING_EFFORT_MAP;
210
- } else {
211
- return;
212
- }
213
- compat.reasoningEffortMap = { ...detected, ...compat.reasoningEffortMap };
214
- }
215
-
216
- function detectStrictModeSupport(provider: string, baseUrl: string): boolean {
217
- if (
218
- provider === "openai" ||
219
- provider === "openrouter" ||
220
- provider === "cerebras" ||
221
- provider === "together" ||
222
- provider === "github-copilot" ||
223
- provider === "zenmux"
224
- ) {
225
- return true;
226
- }
227
- return (
228
- hostMatchesUrl(baseUrl, "openai") ||
229
- hostMatchesUrl(baseUrl, "azureOpenAI") ||
230
- hostMatchesUrl(baseUrl, "cerebras") ||
231
- hostMatchesUrl(baseUrl, "together") ||
232
- hostMatchesUrl(baseUrl, "openrouter") ||
233
- hostMatchesUrl(baseUrl, "deepseekFamily")
234
- );
235
- }
236
-
237
- /**
238
- * Local OpenAI-compatible inference servers whose chat templates re-tokenize
239
- * the entire prompt every request — llama.cpp prefix-KV-cache reuse only
240
- * survives when the rendered tokens stay byte-identical across turns. The
241
- * runtime auto-enables {@link OpenAICompat.replayReasoningContent} for these
242
- * providers (and for any provider pointed at a loopback / RFC1918 baseUrl) so
243
- * Qwen3 / DeepSeek-R1 / GLM templates can reconstruct the prior assistant
244
- * turn's `<think>` block from `reasoning_content` (#3528).
245
- */
246
- const LOCAL_OPENAI_COMPAT_PROVIDERS = new Set(["llama.cpp", "lm-studio", "vllm", "ollama"]);
247
-
248
- /** Hosts that accept only none/auto/required rather than a named tool-choice object. */
249
- const STRING_ONLY_NAMED_TOOL_CHOICE_PROVIDERS: Record<string, true> = {
250
- "llama.cpp": true,
251
- "lm-studio": true,
252
- };
253
-
254
- /**
255
- * Local proxy providers that share the loopback-default baseUrl but forward
256
- * to an unrelated upstream (OpenAI, Anthropic, …) rather than running a
257
- * chat-template renderer themselves — `replayReasoningContent` would push
258
- * `reasoning_content` to the upstream, which gains no KV-cache benefit and
259
- * may 400 on the extra field. Excluded from BOTH the provider check above
260
- * and the loopback heuristic below; users who want the replay on a custom
261
- * proxy setup can opt in via the sparse `compat.replayReasoningContent`
262
- * override.
263
- */
264
- const PROXY_OPENAI_COMPAT_PROVIDERS = new Set(["litellm"]);
265
-
266
- function hasLocalLoopbackBaseUrl(baseUrl: string | undefined): boolean {
267
- if (!baseUrl) return false;
268
- let hostname: string;
269
- try {
270
- hostname = new URL(baseUrl).hostname.toLowerCase();
271
- } catch {
272
- return false;
273
- }
274
- if (
275
- hostname === "localhost" ||
276
- hostname === "127.0.0.1" ||
277
- hostname === "0.0.0.0" ||
278
- hostname === "::1" ||
279
- hostname === "[::1]"
280
- ) {
281
- return true;
282
- }
283
- if (/^10\./.test(hostname)) return true;
284
- if (/^192\.168\./.test(hostname)) return true;
285
- if (/^172\.(1[6-9]|2[0-9]|3[01])\./.test(hostname)) return true;
286
- if (hostname.endsWith(".local")) return true;
287
- return false;
288
- }
289
-
290
- /**
291
- * Build the resolved chat-completions compat record for a model spec.
292
- * Provider takes precedence over URL-based detection since it's explicitly configured.
293
- */
294
- export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): ResolvedOpenAICompat {
295
- const provider = spec.provider;
296
- const baseUrl = spec.baseUrl;
297
- const hostModel = { provider, baseUrl };
298
-
299
- const isCerebras = modelMatchesHost(hostModel, "cerebras");
300
- const isZai = modelMatchesHost(hostModel, "zai");
301
- const isZhipu = modelMatchesHost(hostModel, "zhipu");
302
- const supportsZaiReasoningEffort = (isZai || isZhipu) && isGlm52ReasoningEffortModelId(spec.id);
303
- const isKilo = modelMatchesHost(hostModel, "kilo");
304
- const isKimiModel = isKimiModelId(spec.id);
305
- const isMoonshotNative = modelMatchesHost(hostModel, "moonshotNative");
306
- const isMoonshotKimi = isKimiModel && isMoonshotNative;
307
- // Native Kimi K3 uses OpenAI-style `reasoning_effort` with mandatory
308
- // low/high/max thinking, not the K2.x binary `thinking: { type }` block.
309
- const isKimiK3 = isKimiK3ModelId(spec.id);
310
- const isMoonshotKimiK3 = isMoonshotKimi && isKimiK3;
311
- const requiresEnabledThinking = isMoonshotKimi && matchesKimiK27CodeFamily(spec);
312
- const usesMoonshotKimiPreservedThinking = isMoonshotKimi && isKimiK26ModelId(spec.id);
313
- const isAnthropicModel =
314
- modelMatchesHost(hostModel, "anthropic") || isClaudeModelId(spec.id) || isAnthropicNamespacedModelId(spec.id);
315
- const isAlibaba = modelMatchesHost(hostModel, "alibabaDashscope");
316
- const isNvidiaNim = modelMatchesHost(hostModel, "nvidia");
317
- const isVenice = modelMatchesHost(hostModel, "venice");
318
- const isQwen = isQwenModelId(spec.id);
319
- // DeepSeek V4 (and other reasoning-capable DeepSeek models) reject follow-up requests in
320
- // thinking mode unless prior assistant tool-call turns include `reasoning_content`. The
321
- // upstream model is reachable through many OpenAI-compat hosts (api.deepseek.com, Deepinfra,
322
- // Kilo, NVIDIA NIM, Zenmux, OpenRouter, …), so we match by model id/name as well as by
323
- // provider/baseUrl. The flag is gated by `spec.reasoning` because the invariant only
324
- // applies when thinking mode is actually engaged.
325
- const lowerId = spec.id.toLowerCase();
326
- const lowerName = (spec.name ?? "").toLowerCase();
327
- const isXiaomiHost = modelMatchesHost(hostModel, "xiaomi");
328
- const isXiaomiMimo = isXiaomiHost && (isMimoModelIdOrName(spec.id) || isMimoModelIdOrName(spec.name ?? ""));
329
- const isMimoReasoningEffortModel =
330
- !isXiaomiHost && (isMimoModelIdOrName(spec.id) || isMimoModelIdOrName(spec.name ?? ""));
331
- // OpenCode Zen's `big-pickle` is a DeepSeek reasoning alias; the upstream
332
- // 400s come from DeepSeek and require exact reasoning_content replay.
333
- const isOpenCodeDeepseekAlias =
334
- provider === "opencode-zen" && (lowerId === "big-pickle" || lowerName === "big pickle");
335
- const isDeepseekFamily =
336
- modelMatchesHost(hostModel, "deepseekFamily") ||
337
- isDeepseekModelIdOrName(spec.id) ||
338
- isDeepseekModelIdOrName(spec.name ?? "") ||
339
- isOpenCodeDeepseekAlias;
340
- const isDirectDeepseekApi = modelMatchesHost(hostModel, "deepseekDirect");
341
- const isDeepseekReasoning = isDeepseekFamily && Boolean(spec.reasoning);
342
- const isDirectDeepseekReasoning = isDirectDeepseekApi && isDeepseekReasoning;
343
- const isGrok = modelMatchesHost(hostModel, "xai");
344
- const isMistral = modelMatchesHost(hostModel, "mistral");
345
- const isOpenCodeHost = modelMatchesHost(hostModel, "opencode");
346
- // Google AI Studio's OpenAI-compat shim (`generativelanguage.googleapis.com/v1beta/openai`)
347
- // implements a subset of chat-completions and 400s on `store` ("Unknown name \"store\"").
348
- const isGoogleAistudioOpenAI = hostMatchesUrl(baseUrl, "googleAistudio");
349
- const isNonStandard =
350
- isCerebras ||
351
- isGrok ||
352
- isMistral ||
353
- isGoogleAistudioOpenAI ||
354
- hostMatchesUrl(baseUrl, "chutes") ||
355
- hostMatchesUrl(baseUrl, "deepseekFamily") ||
356
- hostMatchesUrl(baseUrl, "fireworks") ||
357
- isAlibaba ||
358
- isZai ||
359
- isZhipu ||
360
- isKilo ||
361
- isQwen ||
362
- isXiaomiHost ||
363
- isMoonshotNative ||
364
- isOpenCodeHost;
365
- const isOpenCodeProvider = provider === "opencode-go" || provider === "opencode-zen";
366
- const isLocalOpenAICompatBackend =
367
- !PROXY_OPENAI_COMPAT_PROVIDERS.has(provider) &&
368
- (LOCAL_OPENAI_COMPAT_PROVIDERS.has(provider) || hasLocalLoopbackBaseUrl(baseUrl));
369
- // Stream-timeout floor applies to ANY loopback/RFC1918 backend, INCLUDING
370
- // local proxies (litellm) excluded from `isLocalOpenAICompatBackend` above:
371
- // widening the first-event/idle abort ceiling only helps a slow local
372
- // upstream and never pushes an extra wire field, so the proxy carve-out (a
373
- // `replayReasoningContent` safety measure) must not also strip the timeout
374
- // floor. Without this, a loopback litellm fronting a cold/reprocessing
375
- // llama-server aborts prefill at the 100s default and retry-loops (#4786).
376
- const isLocalServingBackend = isLocalOpenAICompatBackend || hasLocalLoopbackBaseUrl(baseUrl);
377
-
378
- const useMaxTokens =
379
- isMistral ||
380
- isMoonshotNative ||
381
- isZai ||
382
- isZhipu ||
383
- hostMatchesUrl(baseUrl, "chutes") ||
384
- hostMatchesUrl(baseUrl, "fireworks") ||
385
- isDirectDeepseekApi;
386
-
387
- const supportsPromptCacheBreakpoints = supportsOfficialOpenAIPromptCacheBreakpoints(provider, spec.id, baseUrl);
388
- // Hosts whose chat-completions endpoints are known to accept multiple
389
- // leading `system`/`developer` messages (preferred for KV-cache reuse).
390
- // Anything outside this allowlist defaults to coalescing because
391
- // strict chat templates (Qwen 3.5+ via vLLM, MiniMax, etc.) reject
392
- // follow-up system messages with a 400.
393
- const isOpenAIHost = modelMatchesHost(hostModel, "openai");
394
- const isAzureHost = modelMatchesHost(hostModel, "azureOpenAI");
395
- const isOpenRouter = modelMatchesHost(hostModel, "openrouter");
396
- const isVercelGateway = modelMatchesHost(hostModel, "vercelAIGateway");
397
- const isTogether = modelMatchesHost(hostModel, "together");
398
- const isFireworks = hostMatchesUrl(baseUrl, "fireworks");
399
- const isGroqHost = modelMatchesHost(hostModel, "groq");
400
- const isCopilotHost = provider === "github-copilot";
401
- const isZenmuxHost = provider === "zenmux";
402
- // Endpoints/models that MUST receive a single system block. MiniMax's OpenAI
403
- // endpoint returns error 2013 on multiple system messages; the Qwen 3.5+ chat
404
- // template raises "System message must be at the beginning" / 500s with an
405
- // internal_server_error when any system block appears past index 0. That
406
- // template ships with the weights, so every Qwen-serving vLLM/SGLang host
407
- // hits it — confirmed on Alibaba Dashscope, Qwen Portal, and Fireworks
408
- // (`fireworks/qwen3.7-plus` 500'd on two leading system blocks). Gate on the
409
- // Qwen family itself, not per-host: coalescing only trades away KV-cache reuse.
410
- const isMiniMaxHost = modelMatchesHost(hostModel, "minimax");
411
- const isQwenPortal = modelMatchesHost(hostModel, "qwenPortal");
412
- const supportsMultipleSystemMessagesDefault =
413
- !isMiniMaxHost &&
414
- !isAlibaba &&
415
- !isQwenPortal &&
416
- !isQwen &&
417
- (isOpenAIHost ||
418
- isAzureHost ||
419
- isOpenRouter ||
420
- isCerebras ||
421
- isTogether ||
422
- isFireworks ||
423
- isGroqHost ||
424
- isDeepseekFamily ||
425
- isMistral ||
426
- isGrok ||
427
- isZai ||
428
- isZhipu ||
429
- isCopilotHost ||
430
- isZenmuxHost);
431
-
432
- // Stream-watchdog floor: GLM coding-plan SKUs, Kimi K2.6, direct
433
- // DeepSeek reasoning models, and local OpenAI-compatible backends can idle
434
- // for minutes while reasoning or cold-loading weights; widen the idle
435
- // timeout so warm-ups stop aborting and retrying.
436
- const streamIdleTimeoutMs =
437
- GLM_CODING_PLAN_MODEL_PATTERN.test(spec.id) && (isZai || isZhipu || isOpenCodeHost)
438
- ? GLM_CODING_PLAN_STREAM_IDLE_TIMEOUT_MS
439
- : provider === "alibaba-coding-plan"
440
- ? ALIBABA_CODING_PLAN_STREAM_IDLE_TIMEOUT_MS
441
- : isXiaomiMimo
442
- ? XIAOMI_MIMO_STREAM_IDLE_TIMEOUT_MS
443
- : spec.reasoning &&
444
- (isKimiK26ModelId(spec.id) ||
445
- isMoonshotKimiK3 ||
446
- (isMoonshotKimi && matchesKimiK27CodeFamily(spec)))
447
- ? KIMI_REASONING_STREAM_IDLE_TIMEOUT_MS
448
- : spec.reasoning && isDirectDeepseekApi
449
- ? DEEPSEEK_REASONING_STREAM_IDLE_TIMEOUT_MS
450
- : isLocalServingBackend
451
- ? LOCAL_OPENAI_COMPAT_STREAM_IDLE_TIMEOUT_MS
452
- : undefined;
453
-
454
- // Fireworks "Fast" variants (`<id>-fast`) are served from the router
455
- // namespace (`accounts/fireworks/routers/<id>-fast`), like Fire Pass, rather
456
- // than the `models/` namespace the rest of the `fireworks` provider uses.
457
- const isFireworksFastRouter = provider === "fireworks" && isFireworksFastModelId(spec.id);
458
- const wireModelIdMode: ResolvedOpenAISharedCompat["wireModelIdMode"] =
459
- provider === "firepass" || isFireworksFastRouter
460
- ? "firepass"
461
- : provider === "fireworks"
462
- ? "fireworks"
463
- : isOpenRouter
464
- ? "openrouter"
465
- : "raw";
466
- const thinkingFormat: ResolvedOpenAISharedCompat["thinkingFormat"] =
467
- (isMoonshotKimi && !isMoonshotKimiK3) || isZai || isZhipu || isXiaomiMimo
468
- ? "zai"
469
- : isOpenRouter
470
- ? "openrouter"
471
- : isQwen && (isNvidiaNim || provider === "vllm")
472
- ? "qwen-chat-template"
473
- : isQwen && (isFireworks || isVenice)
474
- ? "openai"
475
- : isAlibaba || isQwen
476
- ? "qwen"
477
- : "openai";
478
-
479
- const compat: ResolvedOpenAICompat = {
480
- supportsStore: !isNonStandard,
481
- // `developer` is an OpenAI-Responses-era extension to the chat-completions schema. Almost
482
- // every OpenAI-compatible host other than OpenAI itself (and Azure OpenAI, which mirrors
483
- // the schema exactly) treats it as an unknown role: Moonshot returns a 400 "tokenization
484
- // failed", Groq/Cerebras/etc. error or silently misroute. Default to `system` and require
485
- // callers to opt in via `compat.supportsDeveloperRole: true` for hosts known to mirror
486
- // OpenAI's reasoning-API surface.
487
- supportsDeveloperRole: isOpenAIHost || isAzureHost,
488
- supportsMultipleSystemMessages: supportsMultipleSystemMessagesDefault,
489
- supportsReasoningEffort: !isGrok && !isXiaomiMimo && (!(isZai || isZhipu) || supportsZaiReasoningEffort),
490
- // GitHub Copilot's chat-completions endpoint rejects reasoning params wholesale.
491
- supportsReasoningParams: provider !== "github-copilot",
492
- // OpenAI proprietary reasoning models (o-series, gpt-5+) reject explicit
493
- // temperature/top_p/… with a 400 on every serving host (#5606).
494
- supportsSamplingParams: !isOpenAISamplingRestrictedModelId(spec.id),
495
- // xAI reasoning models 400 on presence/frequency penalties and stop.
496
- supportsPenaltyAndStopParams: !(isGrok && Boolean(spec.reasoning)),
497
- reasoningEffortMap: {},
498
- supportsUsageInStreaming: !isCerebras,
499
- // Kimi (including via OpenRouter and Fireworks router-form IDs such as
500
- // `accounts/fireworks/routers/kimi-*`) calculates TPM rate limits based on
501
- // max_tokens, not actual output. The official Kimi K2 model guidance
502
- // (https://docs.fireworks.ai/models/kimi-k2) also requires `max_tokens` for
503
- // every call since the family can otherwise emit very long reasoning traces
504
- // before the final answer.
505
- alwaysSendMaxTokens: isKimiModel,
506
- // Native Kimi K3 always reasons through `reasoning_effort` (never the
507
- // K2.x binary `thinking` block that #827's forced-tool-choice conflict is
508
- // about), so suppressing its effort would leave K3 in an unsupported mode.
509
- disableReasoningOnForcedToolChoice: (isKimiModel && !isMoonshotKimiK3) || isAnthropicModel,
510
- disableReasoningOnToolChoice: isDeepseekFamily && Boolean(spec.reasoning) && !isOpenRouter,
511
- supportsToolChoice: !isDirectDeepseekReasoning,
512
- // DeepSeek reasoning models on OpenCode Zen/Go 400 with
513
- // "Thinking mode does not support this tool_choice" when a specific
514
- // function is forced while the gateway's default thinking mode is active.
515
- // Downgrade only on those gateways: other hosts can turn thinking off via
516
- // disableReasoningOnToolChoice and must retain hard tool selection.
517
- supportsForcedToolChoice: !requiresEnabledThinking && !(isOpenCodeHost && isDeepseekReasoning),
518
- supportsNamedToolChoice: STRING_ONLY_NAMED_TOOL_CHOICE_PROVIDERS[provider] !== true,
519
- maxTokensField: useMaxTokens ? "max_tokens" : "max_completion_tokens",
520
- requiresToolResultName: isMistral,
521
- requiresAssistantAfterToolResult: isMistral,
522
- requiresThinkingAsText: isMistral,
523
- requiresMistralToolIds: isMistral,
524
- // Only Kimi's native K2.x hosts (Moonshot / Kimi-code, matched by
525
- // `isMoonshotKimi`) speak the z.ai binary `thinking: { type }` field.
526
- // K3 and Kimi reached through OpenAI-compatible proxies drive reasoning
527
- // via OpenAI-style `reasoning_effort`.
528
- // NVIDIA NIM hosts Qwen with the vLLM convention
529
- // (`chat_template_kwargs.enable_thinking`); top-level `enable_thinking`
530
- // is rejected by NIM's `additionalProperties: false` request schema
531
- // (issue #2299).
532
- thinkingFormat,
533
- kimiApiFormat: undefined,
534
- reasoningDisableMode: isVenice ? "venice-disable-thinking" : resolveReasoningDisableMode(thinkingFormat),
535
- omitReasoningEffort: false,
536
- includeEncryptedReasoning: true,
537
- filterReasoningHistory: isOpenRouter && isAnthropicModel,
538
- thinkingKeep: usesMoonshotKimiPreservedThinking ? "all" : undefined,
539
- reasoningContentField: "reasoning_content",
540
- // Backends that 400 follow-up requests when prior assistant tool-call turns lack `reasoning_content`:
541
- // - Kimi: documented invariant on its native API.
542
- // - DeepSeek-family reasoning models, including aliased OpenCode Zen models
543
- // like `big-pickle`, validate exact thinking-mode replay.
544
- // - Xiaomi MiMo models require exact `reasoning_content` replay on
545
- // thinking-mode tool-call continuations across standard and Token Plan hosts.
546
- // - Any reasoning-capable model reached through OpenRouter can enforce this
547
- // server-side whenever the request is in thinking mode. We can't translate
548
- // Anthropic's redacted/encrypted reasoning into provider-native plaintext,
549
- // so cross-provider continuations rely on a placeholder.
550
- // OpenCode Kimi aliases handle reasoning content internally and reject
551
- // client-sent `reasoning_content`, so exclude only that Kimi-on-OpenCode path
552
- // (the `whenThinking` policy below re-enables the replay for thinking turns).
553
- requiresReasoningContentForToolCalls:
554
- (isKimiModel && !isOpenCodeProvider) ||
555
- (isDeepseekFamily && Boolean(spec.reasoning)) ||
556
- isXiaomiMimo ||
557
- (isOpenRouter && Boolean(spec.reasoning)),
558
- requiresReasoningContentForAllAssistantTurns:
559
- ((isDeepseekFamily && Boolean(spec.reasoning)) || isXiaomiMimo) && !isOpenRouter,
560
- // DeepSeek V4 and Xiaomi MiMo reject synthetic reasoning_content placeholders (".") on tool-call turns.
561
- // Kimi and OpenRouter accept them when actual reasoning is unavailable.
562
- allowsSyntheticReasoningContentForToolCalls: (!isDeepseekFamily || !spec.reasoning) && !isXiaomiMimo,
563
- // Local llama.cpp-style servers re-tokenize the entire chat-template
564
- // prompt each request; Qwen3 / DeepSeek-R1 / GLM templates reconstruct
565
- // the prior assistant turn's `<think>` block from `reasoning_content`,
566
- // so dropping the field re-renders the assistant turn without thinking
567
- // content and forces full prompt re-processing (#3528). The
568
- // `requires*ReasoningContent*` flags above stay off for these hosts —
569
- // they accept but don't validate the field — so the encoder needs a
570
- // distinct opt-in to replay on every reasoning turn. NOT gated on
571
- // `spec.reasoning`: the runtime discovery paths for `llama.cpp` /
572
- // `lm-studio` / `openai-models-list` hardcode `reasoning: false`
573
- // because the upstream `/models` endpoints don't advertise the
574
- // capability, but the OpenAI stream parser still records incoming
575
- // `reasoning_content` deltas as thinking blocks. Gating on the spec
576
- // flag would leave every discovered local Qwen / DeepSeek model
577
- // re-triggering #3528. The encoder only writes `reasoning_content`
578
- // when a thinking block actually exists on the turn
579
- // (`nonEmptyThinkingBlocks.length > 0`), so the flag is a no-op on
580
- // pure-text histories.
581
- replayReasoningContent: isLocalOpenAICompatBackend,
582
- // `preserve_thinking: true` makes the Qwen3.6+ chat template render
583
- // `<think>...</think>` for older assistant turns too, instead of
584
- // stripping it the moment a new user message moves them past
585
- // `last_query_index`. Without it, the slot's KV cache (which holds the
586
- // raw `<think>X</think>` tokens emitted during generation) diverges
587
- // from the next-turn render and llama.cpp falls back to full prompt
588
- // re-processing — the exact symptom reported in #3541. Auto-enabled
589
- // for Qwen thinking dialects on local llama.cpp-style backends (paired
590
- // with `replayReasoningContent` above). Non-Qwen templates ignore the
591
- // parameter, so the flag stays a no-op outside the Qwen path.
592
- qwenPreserveThinking:
593
- (thinkingFormat === "qwen" || thinkingFormat === "qwen-chat-template") && isLocalOpenAICompatBackend,
594
- // Qwen 3.8+ templates steer thinking depth via the `reasoning_effort`
595
- // template kwarg (low/medium/xhigh, default xhigh); without routing the
596
- // requested effort there, the enable_thinking toggle alone leaves the
597
- // model at xhigh no matter what the user selects.
598
- // Local-only like `qwenPreserveThinking`: first-party Qwen APIs
599
- // (Dashscope, Qwen Portal) drive effort through their own OpenAI-style
600
- // dialect, and local Ollama keeps its native effort vocabulary.
601
- qwenTemplateReasoningEffort:
602
- (thinkingFormat === "qwen" || thinkingFormat === "qwen-chat-template") &&
603
- isLocalOpenAICompatBackend &&
604
- provider !== "ollama" &&
605
- isQwen38PlusTemplateEffortModelId(spec.id),
606
- requiresAssistantContentForToolCalls: isKimiModel || isDirectDeepseekReasoning,
607
- cacheControlFormat: isOpenRouter && spec.id.startsWith("anthropic/") ? "anthropic" : undefined,
608
- supportsPromptCacheBreakpoints,
609
- promptCacheBreakpointTtl: supportsPromptCacheBreakpoints ? "30m" : undefined,
610
- openRouterRouting: undefined,
611
- vercelGatewayRouting: undefined,
612
- isOpenRouterHost: isOpenRouter,
613
- wireModelIdMode,
614
- isVercelGatewayHost: isVercelGateway,
615
- supportsStrictMode: detectStrictModeSupport(provider, baseUrl),
616
- extraBody: undefined,
617
- toolStrictMode: isCerebras ? "all_strict" : "mixed",
618
- // Kimi-family ids trigger MFJS on any host, not just native base URLs:
619
- // proxies (OpenRouter, custom gateways) forward `tools.function.parameters`
620
- // to Moonshot verbatim, which 400s on non-MFJS constructs.
621
- toolSchemaFlavor:
622
- isMoonshotNative || isKimiModel ? "moonshot-mfjs" : isLocalOpenAICompatBackend ? "grammar" : undefined,
623
- streamFirstEventTimeoutMs: isLocalServingBackend ? 0 : undefined,
624
- streamIdleTimeoutMs,
625
- stripDeepseekSpecialTokens:
626
- isDeepseekModelIdOrName(spec.id) && (provider === "nvidia" || provider === "deepseek"),
627
- streamMarkupHealingPattern: detectStreamMarkupHealingPattern(provider, spec.id, baseUrl),
628
- reasoningDeltasMayBeCumulative:
629
- MINIMAX_PROVIDER_OR_ID_PATTERN.test(provider) || MINIMAX_PROVIDER_OR_ID_PATTERN.test(spec.id),
630
- emptyLengthFinishIsContextError: provider === "ollama",
631
- usesOpenAIToolCallIdLimit: provider === "openai",
632
- promptCacheSessionHeader: isGrok ? "x-grok-conv-id" : undefined,
633
- dropThinkingWhenReasoningEffort: provider === "fireworks",
634
- };
635
-
636
- applyCompatOverrides(compat, spec.compat);
637
- const deepseekThinking = compat.extraBody?.thinking;
638
- if (
639
- isDirectDeepseekReasoning &&
640
- typeof deepseekThinking === "object" &&
641
- deepseekThinking !== null &&
642
- "type" in deepseekThinking &&
643
- deepseekThinking.type === "enabled"
644
- ) {
645
- const extraBody = { ...compat.extraBody };
646
- delete extraBody.thinking;
647
- compat.extraBody = Object.keys(extraBody).length > 0 ? extraBody : undefined;
648
- }
649
- if (spec.compat?.reasoningDisableMode === undefined) {
650
- compat.reasoningDisableMode = requiresEnabledThinking
651
- ? "omit"
652
- : isDirectDeepseekReasoning
653
- ? "zai-thinking-disabled"
654
- : isVenice
655
- ? "venice-disable-thinking"
656
- : resolveReasoningDisableMode(compat.thinkingFormat);
657
- }
658
- if (spec.compat?.omitReasoningEffort === undefined && !compat.supportsReasoningEffort) {
659
- compat.omitReasoningEffort = true;
660
- }
661
- mergeModelReasoningEffortMap(compat, spec.id, isMimoReasoningEffortModel);
662
-
663
- const whenThinkingPolicy =
664
- spec.compat?.whenThinking ??
665
- (isDirectDeepseekReasoning
666
- ? { extraBody: { ...compat.extraBody, thinking: { type: "enabled" } } }
667
- : isOpenCodeProvider && spec.reasoning
668
- ? OPENCODE_WHEN_THINKING
669
- : undefined);
670
- if (whenThinkingPolicy) {
671
- const variant: ResolvedOpenAICompat = { ...compat };
672
- applyCompatOverrides(variant, whenThinkingPolicy);
673
- if (whenThinkingPolicy.reasoningDisableMode === undefined) {
674
- variant.reasoningDisableMode = isVenice
675
- ? "venice-disable-thinking"
676
- : resolveReasoningDisableMode(variant.thinkingFormat);
677
- }
678
- if (whenThinkingPolicy.omitReasoningEffort === undefined && !variant.supportsReasoningEffort) {
679
- variant.omitReasoningEffort = true;
680
- }
681
- mergeModelReasoningEffortMap(variant, spec.id, isMimoReasoningEffortModel);
682
- compat.whenThinking = variant;
683
- }
684
-
685
- return compat;
686
- }
687
-
688
- interface OpenAIResponsesSpecLike {
689
- id?: string;
690
- provider: string;
691
- name: string;
692
- baseUrl: string;
693
- reasoning?: boolean;
694
- compat?: OpenAICompat;
695
- }
696
-
697
- /**
698
- * Build the resolved Responses-API compat record. Most shared OpenAI-compatible
699
- * capability defaults intentionally mirror chat-completions, while Responses-
700
- * only behavior (developer role, prompt cache, pairing strictness, image detail)
701
- * keeps endpoint-specific detection. Azure is detected by provider id as well
702
- * as URL — bundled `azure` models carry no baseUrl (the deployment host is per-
703
- * resource, resolved at runtime) — while OpenAI/Copilot developer-role and
704
- * prompt-cache detection stay URL-keyed, as the historical call sites were.
705
- */
706
- export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): ResolvedOpenAIResponsesCompat {
707
- const baseUrl = spec.baseUrl ?? "";
708
- const isAzure = modelMatchesHost({ provider: spec.provider, baseUrl }, "azureOpenAI");
709
- const isOpenRouter = modelMatchesHost({ provider: spec.provider, baseUrl }, "openrouter");
710
- const isOpenAIUrl = hostMatchesUrl(baseUrl, "openai");
711
- const isVercelGateway = modelMatchesHost({ provider: spec.provider, baseUrl }, "vercelAIGateway");
712
- const id = spec.id ?? "";
713
- const supportsPromptCacheBreakpoints = supportsOfficialOpenAIPromptCacheBreakpoints(spec.provider, id, baseUrl);
714
- const thinkingFormat: ResolvedOpenAISharedCompat["thinkingFormat"] = isOpenRouter ? "openrouter" : "openai";
715
- const isKimiModel = id ? isKimiModelId(id) : false;
716
- const isAnthropicModel = id ? isClaudeModelId(id) || isAnthropicNamespacedModelId(id) : false;
717
- const isDeepseekFamily = id ? isDeepseekModelIdOrName(id) || isDeepseekModelIdOrName(spec.name) : false;
718
- const reasoningCapable = Boolean(spec.reasoning);
719
- // `replayReasoningContent` is Responses-only-false, so the proxy carve-out is
720
- // irrelevant here; the stream-timeout floor still applies to ANY loopback /
721
- // RFC1918 backend, including local proxies (litellm), so a slow local
722
- // upstream is not aborted at the 100s default and retry-looped (#4786).
723
- const isLocalServingBackend =
724
- (!PROXY_OPENAI_COMPAT_PROVIDERS.has(spec.provider) && LOCAL_OPENAI_COMPAT_PROVIDERS.has(spec.provider)) ||
725
- hasLocalLoopbackBaseUrl(baseUrl);
726
- const isXaiHost = modelMatchesHost({ provider: spec.provider, baseUrl }, "xai");
727
-
728
- const compat: ResolvedOpenAIResponsesCompat = {
729
- supportsDeveloperRole: isAzure || isOpenAIUrl || hostMatchesUrl(baseUrl, "githubCopilot"),
730
- supportsStrictMode: isAzure || detectStrictModeSupport(spec.provider, baseUrl),
731
- // Paid `xai` and SuperGrok `xai-oauth` share api.x.ai `/v1/responses`.
732
- // Only the Grok effort-capable allowlist accepts `reasoning.effort`;
733
- // other reasoners (grok-build, grok-code-fast-1, …) 400 if it is sent.
734
- supportsReasoningEffort: !isXaiHost || isGrokReasoningEffortCapable(id),
735
- supportsLongPromptCacheRetention: isOpenAIUrl,
736
- supportsPromptCacheBreakpoints,
737
- promptCacheBreakpointTtl: supportsPromptCacheBreakpoints ? "30m" : undefined,
738
- // Azure OpenAI and GitHub Copilot Responses paths require tool results
739
- // to strictly match prior tool calls when building Responses inputs.
740
- strictResponsesPairing: isAzure || spec.provider === "github-copilot",
741
- // GitHub Copilot and first-party xAI `/v1/responses` reject
742
- // `detail: "original"` (400 / 422). Every other host preserves
743
- // native-resolution frames (snapcompact relies on `original`). Detect
744
- // Copilot by provider id or base-URL host so a model pointed at the
745
- // Copilot host under a different provider id still clamps.
746
- supportsImageDetailOriginal:
747
- !isXaiHost && !modelMatchesHost({ provider: spec.provider, baseUrl }, "githubCopilot"),
748
- // api.x.ai rejects `reasoning.summary` (SuperGrok and paid key alike).
749
- supportsReasoningSummary: !isXaiHost,
750
- reasoningEffortMap: isXaiHost ? { ...xaiResponsesReasoningEffortMap(id) } : {},
751
- supportsReasoningParams: true,
752
- // OpenAI proprietary reasoning models (o-series, gpt-5+) reject explicit
753
- // temperature/top_p/… with a 400 on every serving host (#5606).
754
- supportsSamplingParams: !isOpenAISamplingRestrictedModelId(id),
755
- // xAI `/v1/responses` rejects presence/frequency penalties for every
756
- // model, not only reasoners (https://docs.x.ai/developers/rest-api-reference/inference/chat).
757
- supportsPenaltyAndStopParams: !isXaiHost,
758
- thinkingFormat,
759
- reasoningDisableMode: resolveReasoningDisableMode(thinkingFormat),
760
- omitReasoningEffort: false,
761
- // Ask xAI `/v1/responses` for `reasoning.encrypted_content` and replay
762
- // those items on later turns. OpenRouter Anthropic still filters
763
- // reasoning wrappers independently.
764
- includeEncryptedReasoning: true,
765
- filterReasoningHistory: isOpenRouter && isAnthropicModel,
766
- disableReasoningOnForcedToolChoice: isKimiModel,
767
- disableReasoningOnToolChoice: isDeepseekFamily && reasoningCapable && !isOpenRouter,
768
- supportsToolChoice: true,
769
- supportsForcedToolChoice: spec.provider !== "opencode-go" && spec.provider !== "opencode-zen",
770
- supportsNamedToolChoice: STRING_ONLY_NAMED_TOOL_CHOICE_PROVIDERS[spec.provider] !== true,
771
- reasoningContentField: "reasoning_content",
772
- requiresReasoningContentForToolCalls:
773
- (isKimiModel || (isDeepseekFamily && reasoningCapable) || (isOpenRouter && reasoningCapable)) &&
774
- reasoningCapable,
775
- requiresReasoningContentForAllAssistantTurns: isDeepseekFamily && reasoningCapable && !isOpenRouter,
776
- allowsSyntheticReasoningContentForToolCalls: !isDeepseekFamily || !reasoningCapable,
777
- // The Responses API replays reasoning through encrypted `summary` items,
778
- // not via a top-level `reasoning_content` field — this flag is
779
- // chat-completions-only.
780
- replayReasoningContent: false,
781
- // Responses-only; the Qwen `preserve_thinking` template knob lives on
782
- // the chat-completions wire shape, never on Responses.
783
- qwenPreserveThinking: false,
784
- qwenTemplateReasoningEffort: false,
785
- requiresThinkingAsText: false,
786
- requiresMistralToolIds: false,
787
- requiresToolResultName: false,
788
- requiresAssistantAfterToolResult: false,
789
- requiresAssistantContentForToolCalls: isKimiModel,
790
- openRouterRouting: undefined,
791
- vercelGatewayRouting: undefined,
792
- isOpenRouterHost: isOpenRouter,
793
- isVercelGatewayHost: isVercelGateway,
794
- wireModelIdMode: isOpenRouter ? "openrouter" : "raw",
795
- // Mirrors buildOpenAICompat: Kimi behind a Responses-capable proxy still
796
- // lands on Moonshot's MFJS validator.
797
- toolSchemaFlavor: isKimiModel ? "moonshot-mfjs" : undefined,
798
- alwaysSendMaxTokens: spec.id ? isKimiModelId(spec.id) : false,
799
- supportsObfuscationOptOut: isOpenAIUrl || spec.provider === "openai",
800
- stripDeepseekSpecialTokens:
801
- Boolean(id) && isDeepseekModelIdOrName(id) && (spec.provider === "nvidia" || spec.provider === "deepseek"),
802
- streamMarkupHealingPattern: id ? detectStreamMarkupHealingPattern(spec.provider, id, baseUrl) : undefined,
803
- reasoningDeltasMayBeCumulative:
804
- MINIMAX_PROVIDER_OR_ID_PATTERN.test(spec.provider) || (id ? MINIMAX_PROVIDER_OR_ID_PATTERN.test(id) : false),
805
- emptyLengthFinishIsContextError: spec.provider === "ollama",
806
- usesOpenAIToolCallIdLimit: spec.provider === "openai",
807
- promptCacheSessionHeader: isXaiHost ? "x-grok-conv-id" : undefined,
808
- streamFirstEventTimeoutMs: isLocalServingBackend ? 0 : spec.compat?.streamFirstEventTimeoutMs,
809
- streamIdleTimeoutMs: isLocalServingBackend
810
- ? LOCAL_OPENAI_COMPAT_STREAM_IDLE_TIMEOUT_MS
811
- : spec.compat?.streamIdleTimeoutMs,
812
- };
813
- applyCompatOverrides(compat, spec.compat);
814
- if (isXaiHost) {
815
- const canonical = xaiResponsesReasoningEffortMap(id);
816
- compat.reasoningEffortMap = { ...compat.reasoningEffortMap, ...canonical };
817
- // xhigh-capable Grok advertises unmapped `xhigh`; drop a stale clamp
818
- // from previous snapshots so 4.6 / 16-agent mode is not rewritten to `high`.
819
- for (const key of ["xhigh", "max"] as const) {
820
- if (!(key in canonical)) {
821
- delete compat.reasoningEffortMap[key];
822
- }
823
- }
824
- }
825
- if (spec.compat?.reasoningDisableMode === undefined) {
826
- compat.reasoningDisableMode = resolveReasoningDisableMode(compat.thinkingFormat);
827
- }
828
- if (spec.compat?.omitReasoningEffort === undefined && !compat.supportsReasoningEffort) {
829
- compat.omitReasoningEffort = true;
830
- }
831
- // xai-oauth cache/discovery rows written before a SKU joined the
832
- // effort-capable allowlist still carry omitReasoningEffort: true. The
833
- // allowlist is the live wire contract; do not let that stale flag hide
834
- // the picker or strip reasoning.effort.
835
- if (
836
- spec.provider === "xai-oauth" &&
837
- isGrokReasoningEffortCapable(id) &&
838
- spec.compat?.supportsReasoningEffort !== false
839
- ) {
840
- compat.supportsReasoningEffort = true;
841
- compat.omitReasoningEffort = false;
842
- }
843
- return compat;
844
- }
845
-
846
- type ResponsesOnlyCompat = Omit<ResolvedOpenAIResponsesCompat, keyof ResolvedOpenAISharedCompat>;
847
-
848
- function pickResponsesOnly(compat: ResolvedOpenAIResponsesCompat): ResponsesOnlyCompat {
849
- return {
850
- supportsLongPromptCacheRetention: compat.supportsLongPromptCacheRetention,
851
- strictResponsesPairing: compat.strictResponsesPairing,
852
- supportsImageDetailOriginal: compat.supportsImageDetailOriginal,
853
- supportsObfuscationOptOut: compat.supportsObfuscationOptOut,
854
- supportsReasoningSummary: compat.supportsReasoningSummary,
855
- isVercelGatewayHost: compat.isVercelGatewayHost,
856
- } satisfies ResponsesOnlyCompat;
857
- }
858
-
859
- export function buildOpenRouterCompat(spec: ModelSpec<"openrouter">): ResolvedOpenRouterCompat {
860
- const chat = buildOpenAICompat({
861
- ...spec,
862
- api: "openai-completions",
863
- } as ModelSpec<"openai-completions">);
864
- const responses = buildOpenAIResponsesCompat(spec);
865
- return { ...chat, ...pickResponsesOnly(responses) } as ResolvedOpenRouterCompat;
25
+ const identity = classifyModel("xai", modelId, { lenient: true });
26
+ const revision = identity.revision === undefined ? undefined : parseRevision(identity.revision);
27
+ const floor = parseRevision("4.6");
28
+ return identity.class === "xai" &&
29
+ identity.family === "grok" &&
30
+ revision !== undefined &&
31
+ floor !== undefined &&
32
+ compareRevision(revision, floor) >= 0
33
+ ? XAI_RESPONSES_MINIMAL_EFFORT_MAP
34
+ : XAI_RESPONSES_CLAMPED_EFFORT_MAP;
866
35
  }