@vib-rato/ai 0.16.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 (525) hide show
  1. package/CHANGELOG.md +3354 -0
  2. package/README.md +1194 -0
  3. package/dist/types/adapter-internals/provider-safety-stop.d.ts +45 -0
  4. package/dist/types/api-registry.d.ts +30 -0
  5. package/dist/types/auth-broker/client.d.ts +80 -0
  6. package/dist/types/auth-broker/index.d.ts +5 -0
  7. package/dist/types/auth-broker/redact.d.ts +7 -0
  8. package/dist/types/auth-broker/refresher.d.ts +25 -0
  9. package/dist/types/auth-broker/remote-store.d.ts +153 -0
  10. package/dist/types/auth-broker/server.d.ts +32 -0
  11. package/dist/types/auth-broker/types.d.ts +132 -0
  12. package/dist/types/auth-broker/wire-schemas.d.ts +555 -0
  13. package/dist/types/auth-gateway/http.d.ts +40 -0
  14. package/dist/types/auth-gateway/index.d.ts +3 -0
  15. package/dist/types/auth-gateway/server.d.ts +70 -0
  16. package/dist/types/auth-gateway/types.d.ts +129 -0
  17. package/dist/types/auth-storage.d.ts +1074 -0
  18. package/dist/types/cli.d.ts +2 -0
  19. package/dist/types/codex-tools.d.ts +4 -0
  20. package/dist/types/context-cap-policy.d.ts +68 -0
  21. package/dist/types/core.d.ts +35 -0
  22. package/dist/types/index.d.ts +55 -0
  23. package/dist/types/model-cache.d.ts +24 -0
  24. package/dist/types/model-manager.d.ts +77 -0
  25. package/dist/types/model-pricing.d.ts +3 -0
  26. package/dist/types/model-retirements.d.ts +6 -0
  27. package/dist/types/model-thinking.d.ts +100 -0
  28. package/dist/types/models.d.ts +21 -0
  29. package/dist/types/openai-completions-compat.d.ts +34 -0
  30. package/dist/types/provider-details.d.ts +24 -0
  31. package/dist/types/provider-models/bundled-references.d.ts +4 -0
  32. package/dist/types/provider-models/descriptors.d.ts +48 -0
  33. package/dist/types/provider-models/google.d.ts +20 -0
  34. package/dist/types/provider-models/index.d.ts +5 -0
  35. package/dist/types/provider-models/ollama.d.ts +7 -0
  36. package/dist/types/provider-models/openai-compat.d.ts +293 -0
  37. package/dist/types/provider-models/special.d.ts +29 -0
  38. package/dist/types/providers/amazon-bedrock.d.ts +60 -0
  39. package/dist/types/providers/anthropic-messages-server-schema.d.ts +450 -0
  40. package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
  41. package/dist/types/providers/anthropic.d.ts +280 -0
  42. package/dist/types/providers/aws-credential-config.d.ts +19 -0
  43. package/dist/types/providers/aws-credentials.d.ts +43 -0
  44. package/dist/types/providers/aws-eventstream.d.ts +38 -0
  45. package/dist/types/providers/aws-sigv4.d.ts +55 -0
  46. package/dist/types/providers/azure-openai-responses.d.ts +22 -0
  47. package/dist/types/providers/composer-discipline.d.ts +32 -0
  48. package/dist/types/providers/cursor/client-version.d.ts +10 -0
  49. package/dist/types/providers/cursor/exec-modern.d.ts +98 -0
  50. package/dist/types/providers/cursor/gen/agent_pb.d.ts +16769 -0
  51. package/dist/types/providers/cursor-pi-args.d.ts +119 -0
  52. package/dist/types/providers/cursor.d.ts +72 -0
  53. package/dist/types/providers/dashscope-token-plan-headers.d.ts +57 -0
  54. package/dist/types/providers/error-message.d.ts +27 -0
  55. package/dist/types/providers/github-copilot-headers.d.ts +40 -0
  56. package/dist/types/providers/gitlab-duo.d.ts +27 -0
  57. package/dist/types/providers/google-auth.d.ts +26 -0
  58. package/dist/types/providers/google-gemini-cli.d.ts +75 -0
  59. package/dist/types/providers/google-gemini-headers.d.ts +43 -0
  60. package/dist/types/providers/google-shared.d.ts +183 -0
  61. package/dist/types/providers/google-types.d.ts +138 -0
  62. package/dist/types/providers/google-vertex.d.ts +11 -0
  63. package/dist/types/providers/google.d.ts +4 -0
  64. package/dist/types/providers/grammar.d.ts +1 -0
  65. package/dist/types/providers/kimi.d.ts +27 -0
  66. package/dist/types/providers/kiro-api-key.d.ts +50 -0
  67. package/dist/types/providers/kiro-codewhisperer.d.ts +11 -0
  68. package/dist/types/providers/mock.d.ts +189 -0
  69. package/dist/types/providers/ollama.d.ts +41 -0
  70. package/dist/types/providers/openai-anthropic-shim.d.ts +31 -0
  71. package/dist/types/providers/openai-bounded-rate-limits.d.ts +3 -0
  72. package/dist/types/providers/openai-chat-server-schema.d.ts +1733 -0
  73. package/dist/types/providers/openai-chat-server.d.ts +16 -0
  74. package/dist/types/providers/openai-codex/constants.d.ts +26 -0
  75. package/dist/types/providers/openai-codex/request-transformer.d.ts +50 -0
  76. package/dist/types/providers/openai-codex/response-handler.d.ts +18 -0
  77. package/dist/types/providers/openai-codex-responses.d.ts +71 -0
  78. package/dist/types/providers/openai-completions-compat.d.ts +6 -0
  79. package/dist/types/providers/openai-completions.d.ts +35 -0
  80. package/dist/types/providers/openai-opencodex-responses.d.ts +10 -0
  81. package/dist/types/providers/openai-request-transform.d.ts +4 -0
  82. package/dist/types/providers/openai-responses-server-schema.d.ts +392 -0
  83. package/dist/types/providers/openai-responses-server.d.ts +17 -0
  84. package/dist/types/providers/openai-responses-shared.d.ts +106 -0
  85. package/dist/types/providers/openai-responses.d.ts +37 -0
  86. package/dist/types/providers/pi-native-client.d.ts +29 -0
  87. package/dist/types/providers/pi-native-server.d.ts +60 -0
  88. package/dist/types/providers/register-builtins.d.ts +59 -0
  89. package/dist/types/providers/synthetic.d.ts +26 -0
  90. package/dist/types/providers/transform-messages.d.ts +33 -0
  91. package/dist/types/providers/vision-guard.d.ts +8 -0
  92. package/dist/types/rate-limit-utils.d.ts +19 -0
  93. package/dist/types/stream.d.ts +45 -0
  94. package/dist/types/types.d.ts +1073 -0
  95. package/dist/types/usage/claude.d.ts +3 -0
  96. package/dist/types/usage/gemini.d.ts +2 -0
  97. package/dist/types/usage/github-copilot.d.ts +7 -0
  98. package/dist/types/usage/google-antigravity.d.ts +2 -0
  99. package/dist/types/usage/grok-cli.d.ts +17 -0
  100. package/dist/types/usage/kimi.d.ts +4 -0
  101. package/dist/types/usage/minimax-code.d.ts +2 -0
  102. package/dist/types/usage/openai-codex.d.ts +3 -0
  103. package/dist/types/usage/shared.d.ts +1 -0
  104. package/dist/types/usage/zai.d.ts +2 -0
  105. package/dist/types/usage.d.ts +264 -0
  106. package/dist/types/utils/abort.d.ts +19 -0
  107. package/dist/types/utils/anthropic-auth.d.ts +39 -0
  108. package/dist/types/utils/block-symbols.d.ts +6 -0
  109. package/dist/types/utils/discovery/antigravity.d.ts +67 -0
  110. package/dist/types/utils/discovery/codex.d.ts +38 -0
  111. package/dist/types/utils/discovery/cursor.d.ts +49 -0
  112. package/dist/types/utils/discovery/gemini.d.ts +25 -0
  113. package/dist/types/utils/discovery/index.d.ts +4 -0
  114. package/dist/types/utils/discovery/openai-compatible.d.ts +81 -0
  115. package/dist/types/utils/event-stream.d.ts +41 -0
  116. package/dist/types/utils/fallback-transport.d.ts +110 -0
  117. package/dist/types/utils/fireworks-model-id.d.ts +10 -0
  118. package/dist/types/utils/foundry.d.ts +11 -0
  119. package/dist/types/utils/h2-fetch.d.ts +22 -0
  120. package/dist/types/utils/http-inspector.d.ts +59 -0
  121. package/dist/types/utils/idle-iterator.d.ts +122 -0
  122. package/dist/types/utils/json-parse.d.ts +98 -0
  123. package/dist/types/utils/oauth/alibaba-token-plan.d.ts +19 -0
  124. package/dist/types/utils/oauth/anthropic.d.ts +41 -0
  125. package/dist/types/utils/oauth/api-key-login.d.ts +38 -0
  126. package/dist/types/utils/oauth/api-key-validation.d.ts +39 -0
  127. package/dist/types/utils/oauth/bizrouter.d.ts +1 -0
  128. package/dist/types/utils/oauth/callback-server.d.ts +80 -0
  129. package/dist/types/utils/oauth/cerebras.d.ts +1 -0
  130. package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +18 -0
  131. package/dist/types/utils/oauth/commandcode.d.ts +1 -0
  132. package/dist/types/utils/oauth/cursor.d.ts +15 -0
  133. package/dist/types/utils/oauth/deepinfra.d.ts +1 -0
  134. package/dist/types/utils/oauth/deepseek.d.ts +10 -0
  135. package/dist/types/utils/oauth/firepass.d.ts +1 -0
  136. package/dist/types/utils/oauth/fireworks.d.ts +1 -0
  137. package/dist/types/utils/oauth/fugu.d.ts +1 -0
  138. package/dist/types/utils/oauth/github-copilot.d.ts +38 -0
  139. package/dist/types/utils/oauth/gitlab-duo.d.ts +3 -0
  140. package/dist/types/utils/oauth/glm-zcode.d.ts +71 -0
  141. package/dist/types/utils/oauth/google-antigravity.d.ts +11 -0
  142. package/dist/types/utils/oauth/google-gemini-cli.d.ts +10 -0
  143. package/dist/types/utils/oauth/google-oauth-shared.d.ts +28 -0
  144. package/dist/types/utils/oauth/huggingface.d.ts +19 -0
  145. package/dist/types/utils/oauth/index.d.ts +39 -0
  146. package/dist/types/utils/oauth/kagi.d.ts +17 -0
  147. package/dist/types/utils/oauth/kilo.d.ts +5 -0
  148. package/dist/types/utils/oauth/kimi.d.ts +17 -0
  149. package/dist/types/utils/oauth/kiro.d.ts +71 -0
  150. package/dist/types/utils/oauth/litellm.d.ts +18 -0
  151. package/dist/types/utils/oauth/lm-studio.d.ts +17 -0
  152. package/dist/types/utils/oauth/mara.d.ts +1 -0
  153. package/dist/types/utils/oauth/minimax-code.d.ts +28 -0
  154. package/dist/types/utils/oauth/moonshot.d.ts +1 -0
  155. package/dist/types/utils/oauth/nanogpt.d.ts +1 -0
  156. package/dist/types/utils/oauth/nvidia.d.ts +18 -0
  157. package/dist/types/utils/oauth/ollama-cloud.d.ts +2 -0
  158. package/dist/types/utils/oauth/ollama.d.ts +18 -0
  159. package/dist/types/utils/oauth/openai-codex.d.ts +21 -0
  160. package/dist/types/utils/oauth/opencode.d.ts +18 -0
  161. package/dist/types/utils/oauth/opengateway.d.ts +1 -0
  162. package/dist/types/utils/oauth/openrouter.d.ts +1 -0
  163. package/dist/types/utils/oauth/parallel.d.ts +17 -0
  164. package/dist/types/utils/oauth/perplexity.d.ts +4 -0
  165. package/dist/types/utils/oauth/pkce.d.ts +8 -0
  166. package/dist/types/utils/oauth/qianfan.d.ts +17 -0
  167. package/dist/types/utils/oauth/qwen-portal.d.ts +19 -0
  168. package/dist/types/utils/oauth/sglang.d.ts +16 -0
  169. package/dist/types/utils/oauth/synthetic.d.ts +1 -0
  170. package/dist/types/utils/oauth/tavily.d.ts +17 -0
  171. package/dist/types/utils/oauth/together.d.ts +1 -0
  172. package/dist/types/utils/oauth/types.d.ts +56 -0
  173. package/dist/types/utils/oauth/venice.d.ts +18 -0
  174. package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +18 -0
  175. package/dist/types/utils/oauth/vllm.d.ts +16 -0
  176. package/dist/types/utils/oauth/xai.d.ts +30 -0
  177. package/dist/types/utils/oauth/xiaomi.d.ts +25 -0
  178. package/dist/types/utils/oauth/zai.d.ts +18 -0
  179. package/dist/types/utils/oauth/zenmux.d.ts +1 -0
  180. package/dist/types/utils/overflow.d.ts +14 -0
  181. package/dist/types/utils/parse-bind.d.ts +26 -0
  182. package/dist/types/utils/provider-response.d.ts +6 -0
  183. package/dist/types/utils/provider-safety-stop.d.ts +8 -0
  184. package/dist/types/utils/proxy.d.ts +7 -0
  185. package/dist/types/utils/retry-after.d.ts +3 -0
  186. package/dist/types/utils/retry-budget.d.ts +1 -0
  187. package/dist/types/utils/retry.d.ts +29 -0
  188. package/dist/types/utils/schema/adapt.d.ts +24 -0
  189. package/dist/types/utils/schema/compatibility.d.ts +30 -0
  190. package/dist/types/utils/schema/dereference.d.ts +11 -0
  191. package/dist/types/utils/schema/draft.d.ts +10 -0
  192. package/dist/types/utils/schema/equality.d.ts +4 -0
  193. package/dist/types/utils/schema/fields.d.ts +49 -0
  194. package/dist/types/utils/schema/index.d.ts +14 -0
  195. package/dist/types/utils/schema/json-schema-validator.d.ts +12 -0
  196. package/dist/types/utils/schema/meta-validator.d.ts +2 -0
  197. package/dist/types/utils/schema/normalize.d.ts +93 -0
  198. package/dist/types/utils/schema/root-combinator.d.ts +12 -0
  199. package/dist/types/utils/schema/spill.d.ts +8 -0
  200. package/dist/types/utils/schema/stamps.d.ts +25 -0
  201. package/dist/types/utils/schema/types.d.ts +4 -0
  202. package/dist/types/utils/schema/wire.d.ts +54 -0
  203. package/dist/types/utils/schema/zod-decontaminate.d.ts +31 -0
  204. package/dist/types/utils/sse-debug.d.ts +10 -0
  205. package/dist/types/utils/tool-call-healing.d.ts +80 -0
  206. package/dist/types/utils/tool-choice-capability.d.ts +57 -0
  207. package/dist/types/utils/tool-choice.d.ts +50 -0
  208. package/dist/types/utils/validation.d.ts +17 -0
  209. package/dist/types/utils.d.ts +117 -0
  210. package/package.json +152 -0
  211. package/src/adapter-internals/provider-safety-stop.d.ts +45 -0
  212. package/src/adapter-internals/provider-safety-stop.ts +156 -0
  213. package/src/api-registry.d.ts +30 -0
  214. package/src/api-registry.ts +96 -0
  215. package/src/auth-broker/client.ts +444 -0
  216. package/src/auth-broker/index.ts +5 -0
  217. package/src/auth-broker/redact.ts +39 -0
  218. package/src/auth-broker/refresher.ts +130 -0
  219. package/src/auth-broker/remote-store.ts +1576 -0
  220. package/src/auth-broker/server.ts +764 -0
  221. package/src/auth-broker/types.ts +164 -0
  222. package/src/auth-broker/wire-schemas.ts +261 -0
  223. package/src/auth-gateway/http.ts +198 -0
  224. package/src/auth-gateway/index.ts +3 -0
  225. package/src/auth-gateway/server.ts +1315 -0
  226. package/src/auth-gateway/types.ts +160 -0
  227. package/src/auth-storage.ts +7312 -0
  228. package/src/cli.ts +269 -0
  229. package/src/codex-tools.d.ts +4 -0
  230. package/src/codex-tools.ts +24 -0
  231. package/src/context-cap-policy.d.ts +68 -0
  232. package/src/context-cap-policy.ts +123 -0
  233. package/src/core.ts +44 -0
  234. package/src/index.ts +61 -0
  235. package/src/model-cache.ts +236 -0
  236. package/src/model-manager.ts +744 -0
  237. package/src/model-pricing.d.ts +3 -0
  238. package/src/model-pricing.ts +68 -0
  239. package/src/model-retirements.d.ts +6 -0
  240. package/src/model-retirements.ts +19 -0
  241. package/src/model-thinking.d.ts +100 -0
  242. package/src/model-thinking.ts +1054 -0
  243. package/src/models.d.ts +21 -0
  244. package/src/models.json +94672 -0
  245. package/src/models.json.d.ts +9 -0
  246. package/src/models.ts +126 -0
  247. package/src/openai-completions-compat.d.ts +34 -0
  248. package/src/openai-completions-compat.ts +383 -0
  249. package/src/prompts/composer-bash-policy-recovery.md +1 -0
  250. package/src/prompts/cursor-composer-bash-policy-recovery.md +1 -0
  251. package/src/prompts/cursor-composer-edit-discipline.md +7 -0
  252. package/src/prompts/turn-aborted-guidance.md +4 -0
  253. package/src/provider-details.ts +90 -0
  254. package/src/provider-models/bundled-references.ts +38 -0
  255. package/src/provider-models/descriptors.ts +392 -0
  256. package/src/provider-models/google.ts +92 -0
  257. package/src/provider-models/index.ts +5 -0
  258. package/src/provider-models/ollama.ts +159 -0
  259. package/src/provider-models/openai-compat.ts +2920 -0
  260. package/src/provider-models/special.ts +185 -0
  261. package/src/providers/amazon-bedrock.d.ts +60 -0
  262. package/src/providers/amazon-bedrock.ts +939 -0
  263. package/src/providers/anthropic-messages-server-schema.ts +229 -0
  264. package/src/providers/anthropic-messages-server.ts +839 -0
  265. package/src/providers/anthropic.d.ts +280 -0
  266. package/src/providers/anthropic.ts +4421 -0
  267. package/src/providers/aws-credential-config.d.ts +19 -0
  268. package/src/providers/aws-credential-config.ts +179 -0
  269. package/src/providers/aws-credentials.d.ts +43 -0
  270. package/src/providers/aws-credentials.ts +457 -0
  271. package/src/providers/aws-eventstream.d.ts +38 -0
  272. package/src/providers/aws-eventstream.ts +185 -0
  273. package/src/providers/aws-sigv4.d.ts +55 -0
  274. package/src/providers/aws-sigv4.ts +218 -0
  275. package/src/providers/azure-openai-responses.d.ts +22 -0
  276. package/src/providers/azure-openai-responses.ts +447 -0
  277. package/src/providers/composer-discipline.d.ts +32 -0
  278. package/src/providers/composer-discipline.ts +95 -0
  279. package/src/providers/cursor/client-version.d.ts +10 -0
  280. package/src/providers/cursor/client-version.ts +10 -0
  281. package/src/providers/cursor/exec-modern.d.ts +98 -0
  282. package/src/providers/cursor/exec-modern.ts +497 -0
  283. package/src/providers/cursor/gen/agent_pb.d.ts +16769 -0
  284. package/src/providers/cursor/gen/agent_pb.ts +19780 -0
  285. package/src/providers/cursor/proto/agent.proto +4533 -0
  286. package/src/providers/cursor/proto/buf.gen.yaml +6 -0
  287. package/src/providers/cursor/proto/buf.yaml +17 -0
  288. package/src/providers/cursor-pi-args.d.ts +119 -0
  289. package/src/providers/cursor-pi-args.ts +187 -0
  290. package/src/providers/cursor.d.ts +72 -0
  291. package/src/providers/cursor.ts +3396 -0
  292. package/src/providers/dashscope-token-plan-headers.d.ts +57 -0
  293. package/src/providers/dashscope-token-plan-headers.ts +84 -0
  294. package/src/providers/error-message.d.ts +27 -0
  295. package/src/providers/error-message.ts +21 -0
  296. package/src/providers/github-copilot-headers.d.ts +40 -0
  297. package/src/providers/github-copilot-headers.ts +140 -0
  298. package/src/providers/gitlab-duo.d.ts +27 -0
  299. package/src/providers/gitlab-duo.ts +393 -0
  300. package/src/providers/google-auth.d.ts +26 -0
  301. package/src/providers/google-auth.ts +262 -0
  302. package/src/providers/google-gemini-cli.d.ts +75 -0
  303. package/src/providers/google-gemini-cli.ts +969 -0
  304. package/src/providers/google-gemini-headers.d.ts +43 -0
  305. package/src/providers/google-gemini-headers.ts +100 -0
  306. package/src/providers/google-shared.d.ts +183 -0
  307. package/src/providers/google-shared.ts +1105 -0
  308. package/src/providers/google-types.d.ts +138 -0
  309. package/src/providers/google-types.ts +167 -0
  310. package/src/providers/google-vertex.d.ts +11 -0
  311. package/src/providers/google-vertex.ts +124 -0
  312. package/src/providers/google.d.ts +4 -0
  313. package/src/providers/google.ts +41 -0
  314. package/src/providers/grammar.d.ts +1 -0
  315. package/src/providers/grammar.ts +70 -0
  316. package/src/providers/kimi.d.ts +27 -0
  317. package/src/providers/kimi.ts +52 -0
  318. package/src/providers/kiro-api-key.d.ts +50 -0
  319. package/src/providers/kiro-api-key.ts +786 -0
  320. package/src/providers/kiro-codewhisperer.d.ts +11 -0
  321. package/src/providers/kiro-codewhisperer.ts +600 -0
  322. package/src/providers/mock.ts +526 -0
  323. package/src/providers/ollama.d.ts +41 -0
  324. package/src/providers/ollama.ts +645 -0
  325. package/src/providers/openai-anthropic-shim.d.ts +31 -0
  326. package/src/providers/openai-anthropic-shim.ts +156 -0
  327. package/src/providers/openai-bounded-rate-limits.d.ts +3 -0
  328. package/src/providers/openai-bounded-rate-limits.ts +57 -0
  329. package/src/providers/openai-chat-server-schema.ts +254 -0
  330. package/src/providers/openai-chat-server.ts +724 -0
  331. package/src/providers/openai-codex/constants.d.ts +26 -0
  332. package/src/providers/openai-codex/constants.ts +43 -0
  333. package/src/providers/openai-codex/request-transformer.d.ts +50 -0
  334. package/src/providers/openai-codex/request-transformer.ts +219 -0
  335. package/src/providers/openai-codex/response-handler.d.ts +18 -0
  336. package/src/providers/openai-codex/response-handler.ts +111 -0
  337. package/src/providers/openai-codex-responses.d.ts +71 -0
  338. package/src/providers/openai-codex-responses.ts +3288 -0
  339. package/src/providers/openai-completions-compat.d.ts +6 -0
  340. package/src/providers/openai-completions-compat.ts +6 -0
  341. package/src/providers/openai-completions.d.ts +35 -0
  342. package/src/providers/openai-completions.ts +2294 -0
  343. package/src/providers/openai-opencodex-responses.ts +174 -0
  344. package/src/providers/openai-request-transform.d.ts +4 -0
  345. package/src/providers/openai-request-transform.ts +136 -0
  346. package/src/providers/openai-responses-server-schema.ts +290 -0
  347. package/src/providers/openai-responses-server.ts +1268 -0
  348. package/src/providers/openai-responses-shared.d.ts +106 -0
  349. package/src/providers/openai-responses-shared.ts +1253 -0
  350. package/src/providers/openai-responses.d.ts +37 -0
  351. package/src/providers/openai-responses.ts +989 -0
  352. package/src/providers/pi-native-client.d.ts +29 -0
  353. package/src/providers/pi-native-client.ts +243 -0
  354. package/src/providers/pi-native-server.ts +488 -0
  355. package/src/providers/register-builtins.d.ts +59 -0
  356. package/src/providers/register-builtins.ts +544 -0
  357. package/src/providers/synthetic.d.ts +26 -0
  358. package/src/providers/synthetic.ts +50 -0
  359. package/src/providers/transform-messages.d.ts +33 -0
  360. package/src/providers/transform-messages.ts +408 -0
  361. package/src/providers/vision-guard.d.ts +8 -0
  362. package/src/providers/vision-guard.ts +31 -0
  363. package/src/rate-limit-utils.d.ts +19 -0
  364. package/src/rate-limit-utils.ts +102 -0
  365. package/src/stream.d.ts +45 -0
  366. package/src/stream.ts +1306 -0
  367. package/src/types.d.ts +1073 -0
  368. package/src/types.ts +1305 -0
  369. package/src/usage/claude.ts +449 -0
  370. package/src/usage/gemini.ts +250 -0
  371. package/src/usage/github-copilot.ts +421 -0
  372. package/src/usage/google-antigravity.ts +201 -0
  373. package/src/usage/grok-cli.ts +259 -0
  374. package/src/usage/kimi.ts +285 -0
  375. package/src/usage/minimax-code.ts +31 -0
  376. package/src/usage/openai-codex.ts +503 -0
  377. package/src/usage/shared.ts +10 -0
  378. package/src/usage/zai.ts +247 -0
  379. package/src/usage.ts +190 -0
  380. package/src/utils/abort.d.ts +19 -0
  381. package/src/utils/abort.ts +51 -0
  382. package/src/utils/anthropic-auth.ts +95 -0
  383. package/src/utils/block-symbols.d.ts +6 -0
  384. package/src/utils/block-symbols.ts +11 -0
  385. package/src/utils/discovery/antigravity.ts +275 -0
  386. package/src/utils/discovery/codex.ts +362 -0
  387. package/src/utils/discovery/cursor.ts +388 -0
  388. package/src/utils/discovery/gemini.ts +248 -0
  389. package/src/utils/discovery/index.ts +4 -0
  390. package/src/utils/discovery/openai-compatible.ts +379 -0
  391. package/src/utils/event-stream.d.ts +41 -0
  392. package/src/utils/event-stream.ts +269 -0
  393. package/src/utils/fallback-transport.d.ts +110 -0
  394. package/src/utils/fallback-transport.ts +411 -0
  395. package/src/utils/fireworks-model-id.d.ts +10 -0
  396. package/src/utils/fireworks-model-id.ts +30 -0
  397. package/src/utils/foundry.d.ts +11 -0
  398. package/src/utils/foundry.ts +18 -0
  399. package/src/utils/h2-fetch.ts +60 -0
  400. package/src/utils/http-inspector.d.ts +59 -0
  401. package/src/utils/http-inspector.ts +380 -0
  402. package/src/utils/idle-iterator.d.ts +122 -0
  403. package/src/utils/idle-iterator.ts +410 -0
  404. package/src/utils/json-parse.d.ts +98 -0
  405. package/src/utils/json-parse.ts +607 -0
  406. package/src/utils/oauth/alibaba-token-plan.ts +60 -0
  407. package/src/utils/oauth/anthropic.ts +233 -0
  408. package/src/utils/oauth/api-key-login.ts +98 -0
  409. package/src/utils/oauth/api-key-validation.ts +344 -0
  410. package/src/utils/oauth/bizrouter.ts +15 -0
  411. package/src/utils/oauth/callback-server.d.ts +80 -0
  412. package/src/utils/oauth/callback-server.ts +359 -0
  413. package/src/utils/oauth/cerebras.ts +16 -0
  414. package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
  415. package/src/utils/oauth/commandcode.ts +17 -0
  416. package/src/utils/oauth/cursor.ts +157 -0
  417. package/src/utils/oauth/deepinfra.ts +15 -0
  418. package/src/utils/oauth/deepseek.ts +53 -0
  419. package/src/utils/oauth/firepass.ts +24 -0
  420. package/src/utils/oauth/fireworks.ts +15 -0
  421. package/src/utils/oauth/fugu.ts +15 -0
  422. package/src/utils/oauth/github-copilot.d.ts +38 -0
  423. package/src/utils/oauth/github-copilot.ts +362 -0
  424. package/src/utils/oauth/gitlab-duo.ts +123 -0
  425. package/src/utils/oauth/glm-zcode.d.ts +71 -0
  426. package/src/utils/oauth/glm-zcode.ts +433 -0
  427. package/src/utils/oauth/google-antigravity.ts +200 -0
  428. package/src/utils/oauth/google-gemini-cli.ts +256 -0
  429. package/src/utils/oauth/google-oauth-shared.ts +110 -0
  430. package/src/utils/oauth/huggingface.ts +62 -0
  431. package/src/utils/oauth/index.ts +558 -0
  432. package/src/utils/oauth/kagi.ts +47 -0
  433. package/src/utils/oauth/kilo.ts +87 -0
  434. package/src/utils/oauth/kimi.d.ts +17 -0
  435. package/src/utils/oauth/kimi.ts +275 -0
  436. package/src/utils/oauth/kiro.ts +448 -0
  437. package/src/utils/oauth/litellm.ts +47 -0
  438. package/src/utils/oauth/lm-studio.ts +38 -0
  439. package/src/utils/oauth/mara.ts +16 -0
  440. package/src/utils/oauth/minimax-code.ts +78 -0
  441. package/src/utils/oauth/moonshot.ts +16 -0
  442. package/src/utils/oauth/nanogpt.ts +15 -0
  443. package/src/utils/oauth/nvidia.ts +70 -0
  444. package/src/utils/oauth/oauth.html +199 -0
  445. package/src/utils/oauth/ollama-cloud.ts +28 -0
  446. package/src/utils/oauth/ollama.ts +47 -0
  447. package/src/utils/oauth/openai-codex.ts +299 -0
  448. package/src/utils/oauth/opencode.ts +49 -0
  449. package/src/utils/oauth/opengateway.ts +15 -0
  450. package/src/utils/oauth/openrouter.ts +16 -0
  451. package/src/utils/oauth/parallel.ts +46 -0
  452. package/src/utils/oauth/perplexity.ts +225 -0
  453. package/src/utils/oauth/pkce.ts +18 -0
  454. package/src/utils/oauth/qianfan.ts +58 -0
  455. package/src/utils/oauth/qwen-portal.ts +60 -0
  456. package/src/utils/oauth/sglang.ts +42 -0
  457. package/src/utils/oauth/synthetic.ts +15 -0
  458. package/src/utils/oauth/tavily.ts +46 -0
  459. package/src/utils/oauth/together.ts +16 -0
  460. package/src/utils/oauth/types.d.ts +56 -0
  461. package/src/utils/oauth/types.ts +122 -0
  462. package/src/utils/oauth/venice.ts +59 -0
  463. package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
  464. package/src/utils/oauth/vllm.ts +42 -0
  465. package/src/utils/oauth/xai.ts +246 -0
  466. package/src/utils/oauth/xiaomi.ts +199 -0
  467. package/src/utils/oauth/zai.ts +60 -0
  468. package/src/utils/oauth/zenmux.ts +15 -0
  469. package/src/utils/overflow.ts +275 -0
  470. package/src/utils/parse-bind.ts +81 -0
  471. package/src/utils/provider-response.d.ts +6 -0
  472. package/src/utils/provider-response.ts +30 -0
  473. package/src/utils/provider-safety-stop.ts +8 -0
  474. package/src/utils/proxy.d.ts +7 -0
  475. package/src/utils/proxy.ts +652 -0
  476. package/src/utils/retry-after.d.ts +3 -0
  477. package/src/utils/retry-after.ts +110 -0
  478. package/src/utils/retry-budget.d.ts +1 -0
  479. package/src/utils/retry-budget.ts +4 -0
  480. package/src/utils/retry.d.ts +29 -0
  481. package/src/utils/retry.ts +67 -0
  482. package/src/utils/schema/CONSTRAINTS.md +164 -0
  483. package/src/utils/schema/adapt.d.ts +24 -0
  484. package/src/utils/schema/adapt.ts +36 -0
  485. package/src/utils/schema/compatibility.d.ts +30 -0
  486. package/src/utils/schema/compatibility.ts +435 -0
  487. package/src/utils/schema/dereference.d.ts +11 -0
  488. package/src/utils/schema/dereference.ts +98 -0
  489. package/src/utils/schema/draft.d.ts +10 -0
  490. package/src/utils/schema/draft.ts +341 -0
  491. package/src/utils/schema/equality.d.ts +4 -0
  492. package/src/utils/schema/equality.ts +97 -0
  493. package/src/utils/schema/fields.d.ts +49 -0
  494. package/src/utils/schema/fields.ts +190 -0
  495. package/src/utils/schema/index.d.ts +14 -0
  496. package/src/utils/schema/index.ts +14 -0
  497. package/src/utils/schema/json-schema-validator.d.ts +12 -0
  498. package/src/utils/schema/json-schema-validator.ts +577 -0
  499. package/src/utils/schema/meta-validator.d.ts +2 -0
  500. package/src/utils/schema/meta-validator.ts +167 -0
  501. package/src/utils/schema/normalize.d.ts +93 -0
  502. package/src/utils/schema/normalize.ts +1588 -0
  503. package/src/utils/schema/root-combinator.d.ts +12 -0
  504. package/src/utils/schema/root-combinator.ts +143 -0
  505. package/src/utils/schema/spill.d.ts +8 -0
  506. package/src/utils/schema/spill.ts +43 -0
  507. package/src/utils/schema/stamps.d.ts +25 -0
  508. package/src/utils/schema/stamps.ts +97 -0
  509. package/src/utils/schema/types.d.ts +4 -0
  510. package/src/utils/schema/types.ts +11 -0
  511. package/src/utils/schema/wire.d.ts +54 -0
  512. package/src/utils/schema/wire.ts +213 -0
  513. package/src/utils/schema/zod-decontaminate.d.ts +31 -0
  514. package/src/utils/schema/zod-decontaminate.ts +331 -0
  515. package/src/utils/sse-debug.d.ts +10 -0
  516. package/src/utils/sse-debug.ts +289 -0
  517. package/src/utils/tool-call-healing.d.ts +80 -0
  518. package/src/utils/tool-call-healing.ts +298 -0
  519. package/src/utils/tool-choice-capability.d.ts +57 -0
  520. package/src/utils/tool-choice-capability.ts +633 -0
  521. package/src/utils/tool-choice.d.ts +50 -0
  522. package/src/utils/tool-choice.ts +99 -0
  523. package/src/utils/validation.ts +1080 -0
  524. package/src/utils.d.ts +117 -0
  525. package/src/utils.ts +523 -0
@@ -0,0 +1,744 @@
1
+ import { sanitizeText } from "@vib-rato/utils";
2
+ import { applyFinalCodexGpt56ContextCap } from "./context-cap-policy";
3
+ import { insertModelCacheIfAbsent, readModelCache, updateModelCacheIfUnchanged, writeModelCache } from "./model-cache";
4
+ import { isRetiredModel, isRetiredModelKey } from "./model-retirements";
5
+ import { applyGeneratedModelPolicies, enrichModelThinking } from "./model-thinking";
6
+ import { type GeneratedProvider, getBundledModels } from "./models";
7
+ import type { Api, Model, Provider } from "./types";
8
+ import { isSafeCatalogModelId } from "./utils/discovery/openai-compatible";
9
+
10
+ const DEFAULT_CACHE_TTL_MS = 2 * 60 * 60 * 1000;
11
+ const NON_AUTHORITATIVE_RETRY_MS = 5 * 60 * 1000;
12
+
13
+ // Coalescing guards the implicit cold-start strategy only: an explicit
14
+ // "online" refresh owns its fetch so a newer probe can always supersede an
15
+ // older in-flight refresh (the registry pins that ordering), and callers never
16
+ // block behind a refresh whose completion they may be expected to unblock.
17
+ // A legacy or missing row has no dynamic-ID marker, so concurrent cold-start
18
+ // resolutions would otherwise all fetch before the first one can publish it.
19
+ const legacyDynamicRefreshes = new Map<string, Promise<void>>();
20
+
21
+ function legacyDynamicRefreshKey(providerId: Provider, cacheDbPath: string | undefined, provenance: string): string {
22
+ return `${cacheDbPath ?? "<default>"}\0${providerId}\0${provenance}`;
23
+ }
24
+
25
+ /**
26
+ * Controls when dynamic endpoint models should be fetched.
27
+ */
28
+ export type ModelRefreshStrategy = "online" | "offline" | "online-if-uncached";
29
+
30
+ /**
31
+ * Hook for loading and mapping models.dev fallback data into canonical model objects.
32
+ */
33
+ export interface ModelsDevFallback<TApi extends Api = Api, TPayload = unknown> {
34
+ /** Fetches raw fallback payload (for example from models.dev). */
35
+ fetch(): Promise<TPayload>;
36
+ /** Maps payload into provider models. */
37
+ map(payload: TPayload, providerId: Provider): readonly Model<TApi>[];
38
+ }
39
+
40
+ /**
41
+ * Configuration for provider model resolution.
42
+ */
43
+ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload = unknown> {
44
+ /** Provider id used for static lookup and cache namespacing. */
45
+ providerId: Provider;
46
+ /** Optional static list override. When omitted, bundled models.json is used. */
47
+ staticModels?: readonly Model<TApi>[];
48
+ /** Optional override for the cache database path. Default: <agent-dir>/models.db. */
49
+ cacheDbPath?: string;
50
+ /** Maximum cache age in milliseconds before considered stale. Default: 24h. */
51
+ cacheTtlMs?: number;
52
+ /** Optional dynamic endpoint fetcher. */
53
+ fetchDynamicModels?: () => Promise<readonly Model<TApi>[] | null>;
54
+ /** Optional models.dev fallback hook. */
55
+ modelsDev?: ModelsDevFallback<TApi, TModelsDevPayload>;
56
+ /** Clock override for deterministic tests. */
57
+ now?: () => number;
58
+ /** Optional guard that must permit cache publication. Default: writes are permitted. */
59
+ canPublishCache?: () => boolean;
60
+ /** Credential-and-endpoint identity required to reuse dynamic catalog IDs. */
61
+ cacheDynamicModelProvenance?: string;
62
+ }
63
+
64
+ /**
65
+ * Resolution result.
66
+ *
67
+ * `stale` is false when the resolved catalog is authoritative for the selected provider:
68
+ * - dynamic endpoint data was fetched in this call,
69
+ * - a still-fresh authoritative cache was reused in `online-if-uncached` mode, or
70
+ * - the provider has no dynamic fetcher configured.
71
+ */
72
+ export interface ModelResolutionResult<TApi extends Api = Api> {
73
+ models: Model<TApi>[];
74
+ stale: boolean;
75
+ /** Whether the cache row consulted for this resolution was still within its TTL. */
76
+ cacheFresh: boolean;
77
+ /** Whether the consulted cache row was authoritative. */
78
+ cacheAuthoritative: boolean;
79
+ /** Whether this resolution successfully fetched dynamic models. */
80
+ fetched: boolean;
81
+ /**
82
+ * IDs returned by a current authoritative dynamic provider catalog. This is
83
+ * deliberately distinct from `models`, which merges static and cached data.
84
+ */
85
+ dynamicModelIds?: readonly string[];
86
+ }
87
+
88
+ /**
89
+ * Stateful facade over provider model resolution.
90
+ */
91
+ export interface ModelManager<TApi extends Api = Api> {
92
+ refresh(strategy?: ModelRefreshStrategy): Promise<ModelResolutionResult<TApi>>;
93
+ }
94
+
95
+ /**
96
+ * Creates a reusable provider model manager.
97
+ */
98
+ export function createModelManager<TApi extends Api = Api, TModelsDevPayload = unknown>(
99
+ options: ModelManagerOptions<TApi, TModelsDevPayload>,
100
+ ): ModelManager<TApi> {
101
+ return {
102
+ refresh(strategy: ModelRefreshStrategy = "online-if-uncached") {
103
+ return resolveProviderModels(options, strategy);
104
+ },
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Cheap fast path for trusted model sources (bundled literals, our own cache rows).
110
+ * Skips per-field validation; only guards against catastrophically corrupt rows.
111
+ */
112
+ function passModelList<TApi extends Api>(value: unknown): Model<TApi>[] {
113
+ if (!Array.isArray(value)) {
114
+ return [];
115
+ }
116
+ const out: Model<TApi>[] = [];
117
+ for (const item of value) {
118
+ if (item === null || typeof item !== "object") {
119
+ continue;
120
+ }
121
+ const candidate = item as { id?: unknown; provider?: unknown };
122
+ if (!isSafeCatalogModelId(candidate.id)) {
123
+ continue;
124
+ }
125
+ if (typeof candidate.provider === "string" && isRetiredModelKey(candidate.provider, candidate.id)) {
126
+ continue;
127
+ }
128
+ const model = enrichModelThinking(item as Model<TApi>);
129
+ out.push({ ...model, name: sanitizeModelDisplayName(model.name, model.id) });
130
+ }
131
+ applyGeneratedModelPolicies(out as Model<Api>[]);
132
+ return applyFinalCodexGpt56ContextCap(out);
133
+ }
134
+
135
+ /**
136
+ * Resolves provider models with source precedence:
137
+ * static -> models.dev -> cache -> dynamic.
138
+ *
139
+ * Later sources override earlier ones by model id.
140
+ */
141
+ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPayload = unknown>(
142
+ options: ModelManagerOptions<TApi, TModelsDevPayload>,
143
+ strategy: ModelRefreshStrategy = "online-if-uncached",
144
+ ): Promise<ModelResolutionResult<TApi>> {
145
+ const provenance = options.cacheDynamicModelProvenance;
146
+ const now = options.now ?? Date.now;
147
+ const ttlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
148
+ const legacyCache = readModelCache<TApi>(options.providerId, ttlMs, now, options.cacheDbPath);
149
+ if (
150
+ strategy !== "online-if-uncached" ||
151
+ typeof options.fetchDynamicModels !== "function" ||
152
+ provenance === undefined ||
153
+ (legacyCache !== null && legacyCache.dynamicModelIds !== undefined)
154
+ ) {
155
+ return resolveProviderModelsUncoalesced(options, strategy);
156
+ }
157
+
158
+ const refreshKey = legacyDynamicRefreshKey(options.providerId, options.cacheDbPath, provenance);
159
+ const inFlightRefresh = legacyDynamicRefreshes.get(refreshKey);
160
+ if (inFlightRefresh) {
161
+ await inFlightRefresh;
162
+ return resolveProviderModelsUncoalesced(options, strategy);
163
+ }
164
+
165
+ const refreshCompletion = Promise.withResolvers<void>();
166
+ legacyDynamicRefreshes.set(refreshKey, refreshCompletion.promise);
167
+ try {
168
+ return await resolveProviderModelsUncoalesced(options, strategy);
169
+ } finally {
170
+ if (legacyDynamicRefreshes.get(refreshKey) === refreshCompletion.promise) {
171
+ legacyDynamicRefreshes.delete(refreshKey);
172
+ }
173
+ refreshCompletion.resolve();
174
+ }
175
+ }
176
+
177
+ async function resolveProviderModelsUncoalesced<TApi extends Api = Api, TModelsDevPayload = unknown>(
178
+ options: ModelManagerOptions<TApi, TModelsDevPayload>,
179
+ strategy: ModelRefreshStrategy = "online-if-uncached",
180
+ ): Promise<ModelResolutionResult<TApi>> {
181
+ const now = options.now ?? Date.now;
182
+ const ttlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
183
+ const dbPath = options.cacheDbPath;
184
+ const staticModels = passModelList<TApi>(
185
+ options.staticModels ?? getBundledModels(options.providerId as GeneratedProvider),
186
+ );
187
+ const cache = readModelCache<TApi>(options.providerId, ttlMs, now, dbPath);
188
+ const dynamicFetcher = options.fetchDynamicModels;
189
+ const hasDynamicFetcher = typeof dynamicFetcher === "function";
190
+ const cacheDynamicModelIdsCurrent =
191
+ cache?.dynamicModelIds !== undefined &&
192
+ cache.dynamicModelProvenance !== undefined &&
193
+ cache.dynamicModelProvenance === options.cacheDynamicModelProvenance;
194
+ const requiresBoundDynamicCache = hasDynamicFetcher && options.cacheDynamicModelProvenance !== undefined;
195
+ const cacheProvenanceMismatch =
196
+ cache !== null &&
197
+ (cache.dynamicModelIds !== undefined ? !cacheDynamicModelIdsCurrent : requiresBoundDynamicCache);
198
+ // A provider that supplies cache provenance has opted into live-catalog
199
+ // authority. Rows written before that provider enabled discovery have no
200
+ // dynamic IDs, so they must be synchronized once rather than suppressing
201
+ // the first live fetch for their full TTL.
202
+ const cacheNeedsInitialDynamicRefresh =
203
+ hasDynamicFetcher &&
204
+ options.cacheDynamicModelProvenance !== undefined &&
205
+ !options.cacheDynamicModelProvenance.startsWith("vibrato:non-cacheable-") &&
206
+ cache?.dynamicModelIds === undefined;
207
+ const hasAuthoritativeCache =
208
+ !hasDynamicFetcher ||
209
+ ((cache?.authoritative ?? false) &&
210
+ (!cacheNeedsInitialDynamicRefresh && requiresBoundDynamicCache
211
+ ? cacheDynamicModelIdsCurrent
212
+ : cache?.dynamicModelIds === undefined || cacheDynamicModelIdsCurrent));
213
+ const cacheAgeMs = cache ? now() - cache.updatedAt : Number.POSITIVE_INFINITY;
214
+ const shouldFetchFromNetwork =
215
+ // A bound row whose dynamic IDs are defined but stale belongs to a
216
+ // different discovery context: force the validation fetch immediately,
217
+ // on every non-offline visit, until a row for this context lands.
218
+ // A row with no dynamic IDs (a failed-fetch tombstone, or a legacy
219
+ // provenance-less row) cannot prove foreignness, never serves its
220
+ // models in a bound context, and is non-authoritative — so its refetch
221
+ // cadence follows the standard non-authoritative retry backoff instead
222
+ // of hammering a failing endpoint on every provider-tab visit.
223
+ (cacheNeedsInitialDynamicRefresh || (cacheProvenanceMismatch && cache?.dynamicModelIds !== undefined)) &&
224
+ strategy !== "offline"
225
+ ? true
226
+ : shouldFetchRemoteSources(strategy, cache?.fresh ?? false, hasAuthoritativeCache, cacheAgeMs);
227
+ const staticFingerprint = fingerprintStatic(staticModels);
228
+
229
+ // Cold-start fast path: when a fresh, authoritative cache exists, the network
230
+ // fetch is skipped, AND the static catalog slice is byte-identical to what
231
+ // was merged in last time, the cache row IS the authoritative merge result.
232
+ // Re-running `mergeDynamicModels(static, cache)` would just rebuild the same
233
+ // objects (~800ms in the steady-state cold-start profile for `vib -p hi`).
234
+ if (
235
+ !shouldFetchFromNetwork &&
236
+ cache?.fresh &&
237
+ hasAuthoritativeCache &&
238
+ cache.staticFingerprint === staticFingerprint &&
239
+ cache.staticFingerprint.length > 0
240
+ ) {
241
+ const cachedModels = passModelList<TApi>(cache.models);
242
+ if (!hasStaticTransportDrift(staticModels, cachedModels)) {
243
+ return {
244
+ models: cachedModels,
245
+ stale: false,
246
+ cacheFresh: true,
247
+ cacheAuthoritative: true,
248
+ fetched: false,
249
+ dynamicModelIds:
250
+ strategy === "online-if-uncached" && cacheDynamicModelIdsCurrent ? cache.dynamicModelIds : undefined,
251
+ };
252
+ }
253
+ const repairedModels = mergeDynamicModels(staticModels, cachedModels);
254
+ if (options.canPublishCache?.() ?? true) {
255
+ writeModelCache(
256
+ options.providerId,
257
+ now(),
258
+ repairedModels,
259
+ true,
260
+ staticFingerprint,
261
+ dbPath,
262
+ cache.dynamicModelIds,
263
+ cache.dynamicModelProvenance,
264
+ );
265
+ }
266
+ return {
267
+ models: repairedModels,
268
+ stale: false,
269
+ cacheFresh: true,
270
+ cacheAuthoritative: true,
271
+ fetched: false,
272
+ dynamicModelIds:
273
+ strategy === "online-if-uncached" && cacheDynamicModelIdsCurrent ? cache.dynamicModelIds : undefined,
274
+ };
275
+ }
276
+
277
+ const [fetchedModelsDevModels, fetchedDynamicModels] = shouldFetchFromNetwork
278
+ ? await Promise.all([fetchModelsDev(options), dynamicFetcher ? fetchDynamicModels(dynamicFetcher) : null])
279
+ : [null, null];
280
+ const modelsDevModels = normalizeModelList<TApi>(fetchedModelsDevModels ?? []);
281
+ const shouldUseFreshCacheAsAuthoritative =
282
+ strategy === "online-if-uncached" && (cache?.fresh ?? false) && hasAuthoritativeCache;
283
+ const dynamicFetchSucceeded = fetchedDynamicModels !== null;
284
+ // Stale-while-error fallback: cached dynamic rows may only serve when their
285
+ // provenance still matches the current request context. A fetch forced by a
286
+ // provenance mismatch means the cache belongs to a different discovery
287
+ // context (e.g. another tenant's header), so a failed refetch must fail
288
+ // closed on those rows instead of surfacing them as selectable models. The
289
+ // explicit "offline" strategy keeps serving last-known rows regardless:
290
+ // local-only mode has no network attempt whose failure would warrant
291
+ // withholding them.
292
+ const cacheModelsServeCurrentContext = !cacheProvenanceMismatch || strategy === "offline";
293
+ let cacheModels =
294
+ dynamicFetchSucceeded || !cacheModelsServeCurrentContext ? [] : normalizeModelList<TApi>(cache?.models ?? []);
295
+ const dynamicModels = fetchedDynamicModels ?? [];
296
+ const mergedWithCache = mergeDynamicModels(mergeModelSources(staticModels, modelsDevModels), cacheModels);
297
+ const models = applyFinalCodexGpt56ContextCap(mergeDynamicModels(mergedWithCache, dynamicModels));
298
+ const dynamicAuthoritative =
299
+ !hasDynamicFetcher ||
300
+ dynamicFetchSucceeded ||
301
+ (shouldUseFreshCacheAsAuthoritative && cacheModelsServeCurrentContext);
302
+ if (shouldFetchFromNetwork) {
303
+ if (dynamicFetchSucceeded) {
304
+ const snapshotModels = applyFinalCodexGpt56ContextCap(
305
+ mergeDynamicModels(mergeModelSources(staticModels, modelsDevModels), dynamicModels),
306
+ );
307
+ if (options.canPublishCache?.() ?? true) {
308
+ writeModelCache(
309
+ options.providerId,
310
+ now(),
311
+ snapshotModels,
312
+ true,
313
+ staticFingerprint,
314
+ dbPath,
315
+ dynamicModels.map(model => model.id),
316
+ options.cacheDynamicModelProvenance,
317
+ );
318
+ }
319
+ } else {
320
+ // Dynamic fetch failed — update cache with a non-authoritative snapshot so
321
+ // stale state remains visible while retry backoff still applies. Re-read
322
+ // the current row and trust only that row's provenance and dynamic IDs,
323
+ // not the initial cacheProvenanceMismatch snapshot. A concurrent writer
324
+ // may have replaced the provider row; if the latest row is unbound or
325
+ // belongs to another context, use no latest fallback and do not
326
+ // overwrite or downgrade it.
327
+ const latestCache = readModelCache<TApi>(options.providerId, ttlMs, now, dbPath);
328
+ const latestCacheMatchesCurrentContext =
329
+ options.cacheDynamicModelProvenance === undefined
330
+ ? latestCache !== null && latestCache.dynamicModelIds === undefined
331
+ : cacheRowMatchesBoundDynamicProvenance(latestCache, options.cacheDynamicModelProvenance);
332
+ const latestCacheIsLegacy =
333
+ cache?.dynamicModelIds === undefined && latestCache !== null && latestCache.dynamicModelIds === undefined;
334
+ // An unbound context (the provider supplies no dynamic provenance) has no
335
+ // foreign-context risk, so a legacy row keeps serving its models through a
336
+ // failed refetch instead of blanking the catalog and overwriting the row
337
+ // with an empty snapshot.
338
+ const latestCacheServesLegacyRow = latestCacheIsLegacy && options.cacheDynamicModelProvenance === undefined;
339
+ const fallbackCacheModels =
340
+ (latestCacheMatchesCurrentContext || latestCacheServesLegacyRow) && latestCache !== null
341
+ ? normalizeModelList<TApi>(latestCache.models)
342
+ : [];
343
+ cacheModels = fallbackCacheModels;
344
+ if (options.canPublishCache?.() ?? true) {
345
+ const snapshotModels = applyFinalCodexGpt56ContextCap(
346
+ mergeDynamicModels(mergeModelSources(staticModels, modelsDevModels), fallbackCacheModels),
347
+ );
348
+ if (latestCache !== null && (latestCacheMatchesCurrentContext || latestCacheIsLegacy)) {
349
+ const updated = updateModelCacheIfUnchanged(
350
+ options.providerId,
351
+ latestCache.updatedAt,
352
+ latestCache.dynamicModelIds,
353
+ latestCache.dynamicModelProvenance,
354
+ latestCache.models,
355
+ now(),
356
+ snapshotModels,
357
+ false,
358
+ staticFingerprint,
359
+ dbPath,
360
+ options.cacheDynamicModelProvenance === undefined ? undefined : [],
361
+ options.cacheDynamicModelProvenance,
362
+ );
363
+ if (!updated) cacheModels = [];
364
+ } else if (latestCache == null) {
365
+ const inserted = insertModelCacheIfAbsent(
366
+ options.providerId,
367
+ now(),
368
+ snapshotModels,
369
+ false,
370
+ staticFingerprint,
371
+ dbPath,
372
+ options.cacheDynamicModelProvenance === undefined ? undefined : [],
373
+ options.cacheDynamicModelProvenance,
374
+ );
375
+ if (!inserted) cacheModels = [];
376
+ }
377
+ }
378
+ }
379
+ }
380
+ const returnedModels =
381
+ shouldFetchFromNetwork && !dynamicFetchSucceeded
382
+ ? applyFinalCodexGpt56ContextCap(
383
+ mergeDynamicModels(mergeModelSources(staticModels, modelsDevModels), cacheModels),
384
+ )
385
+ : models;
386
+ return {
387
+ models: returnedModels,
388
+ stale: !dynamicAuthoritative,
389
+ cacheFresh: cache?.fresh ?? false,
390
+ cacheAuthoritative: cache?.authoritative ?? false,
391
+ fetched: shouldFetchFromNetwork && dynamicFetchSucceeded,
392
+ dynamicModelIds: dynamicFetchSucceeded
393
+ ? dynamicModels.map(model => model.id)
394
+ : shouldUseFreshCacheAsAuthoritative
395
+ ? cacheDynamicModelIdsCurrent
396
+ ? cache?.dynamicModelIds
397
+ : undefined
398
+ : undefined,
399
+ };
400
+ }
401
+
402
+ async function fetchModelsDev<TApi extends Api, TModelsDevPayload>(
403
+ options: ModelManagerOptions<TApi, TModelsDevPayload>,
404
+ ): Promise<Model<TApi>[] | null> {
405
+ if (!options.modelsDev) {
406
+ return null;
407
+ }
408
+
409
+ try {
410
+ const payload = await options.modelsDev.fetch();
411
+ return normalizeModelList<TApi>(options.modelsDev.map(payload, options.providerId));
412
+ } catch {
413
+ return null;
414
+ }
415
+ }
416
+
417
+ async function fetchDynamicModels<TApi extends Api>(
418
+ fetcher: () => Promise<readonly Model<TApi>[] | null>,
419
+ ): Promise<Model<TApi>[] | null> {
420
+ try {
421
+ const models = await fetcher();
422
+ if (models === null) {
423
+ return null;
424
+ }
425
+ return normalizeModelList<TApi>(models);
426
+ } catch {
427
+ return null;
428
+ }
429
+ }
430
+
431
+ function cacheRowMatchesBoundDynamicProvenance<
432
+ TCache extends {
433
+ models: readonly { id: string }[];
434
+ dynamicModelIds?: readonly string[];
435
+ dynamicModelProvenance?: string;
436
+ },
437
+ >(
438
+ cache: TCache | null | undefined,
439
+ expectedProvenance: string | undefined,
440
+ ): cache is TCache & { dynamicModelIds: readonly string[]; dynamicModelProvenance: string } {
441
+ if (
442
+ cache == null ||
443
+ cache.dynamicModelIds === undefined ||
444
+ cache.dynamicModelProvenance === undefined ||
445
+ expectedProvenance === undefined ||
446
+ cache.dynamicModelProvenance !== expectedProvenance
447
+ ) {
448
+ return false;
449
+ }
450
+ const modelIds = new Set(cache.models.map(model => model.id));
451
+ return cache.dynamicModelIds.every(id => modelIds.has(id));
452
+ }
453
+
454
+ function shouldFetchRemoteSources(
455
+ strategy: ModelRefreshStrategy,
456
+ hasFreshCache: boolean,
457
+ hasAuthoritativeCache: boolean,
458
+ cacheAgeMs: number,
459
+ ): boolean {
460
+ if (strategy === "offline") {
461
+ return false;
462
+ }
463
+ if (strategy === "online") {
464
+ return true;
465
+ }
466
+ // online-if-uncached: skip fetch if cache is fresh.
467
+ // For non-authoritative caches (dynamic fetch previously failed),
468
+ // use a shorter retry interval instead of retrying every startup.
469
+ if (!hasFreshCache) {
470
+ return true;
471
+ }
472
+ if (!hasAuthoritativeCache) {
473
+ return cacheAgeMs >= NON_AUTHORITATIVE_RETRY_MS;
474
+ }
475
+ return false;
476
+ }
477
+
478
+ function hasStaticTransportDrift<TApi extends Api>(
479
+ staticModels: readonly Model<TApi>[],
480
+ cachedModels: readonly Model<TApi>[],
481
+ ): boolean {
482
+ if (staticModels.length === 0 || cachedModels.length === 0) return false;
483
+ const cachedById = new Map(cachedModels.map(model => [model.id, model]));
484
+ for (const staticModel of staticModels) {
485
+ const cachedModel = cachedById.get(staticModel.id);
486
+ if (!cachedModel) continue;
487
+ if (cachedModel.api !== staticModel.api) return true;
488
+ }
489
+ return false;
490
+ }
491
+
492
+ function mergeModelSources<TApi extends Api>(...sources: readonly (readonly Model<TApi>[])[]): Model<TApi>[] {
493
+ // Strip out empty/missing sources up front. The hot path is `(static, [])`
494
+ // (modelsDev disabled / failed) — a single non-empty source means we can
495
+ // skip the Map churn entirely and just hand back the array.
496
+ const nonEmpty = sources.filter(source => source.length > 0);
497
+ if (nonEmpty.length === 0) return [];
498
+ if (nonEmpty.length === 1) return [...nonEmpty[0]];
499
+ const merged = new Map<string, Model<TApi>>();
500
+ for (const source of nonEmpty) {
501
+ for (const model of source) {
502
+ if (!model?.id) continue;
503
+ merged.set(model.id, model);
504
+ }
505
+ }
506
+ return Array.from(merged.values());
507
+ }
508
+
509
+ function mergeDynamicModels<TApi extends Api>(
510
+ baseModels: readonly Model<TApi>[],
511
+ dynamicModels: readonly Model<TApi>[],
512
+ ): Model<TApi>[] {
513
+ // Empty-side fast paths: `mergeDynamicModels(base, [])` is the common shape
514
+ // after we've already merged the first pair, and `(...)` with no base
515
+ // happens for providers without static catalogs.
516
+ if (dynamicModels.length === 0) return baseModels.length === 0 ? [] : [...baseModels];
517
+ if (baseModels.length === 0) return [...dynamicModels];
518
+ const merged = new Map<string, Model<TApi>>(baseModels.map(model => [model.id, model]));
519
+ for (const dynamicModel of dynamicModels) {
520
+ if (!dynamicModel?.id) {
521
+ continue;
522
+ }
523
+ const existingModel = merged.get(dynamicModel.id);
524
+ if (!existingModel) {
525
+ merged.set(dynamicModel.id, dynamicModel);
526
+ continue;
527
+ }
528
+ merged.set(dynamicModel.id, mergeDynamicModel(existingModel, dynamicModel));
529
+ }
530
+ return Array.from(merged.values());
531
+ }
532
+
533
+ /**
534
+ * Stable, low-collision fingerprint of a static catalog slice. Cached by
535
+ * reference so repeat calls in the same process (e.g. multiple cold-start
536
+ * arms calling `resolveProviderModels` with the same `staticModels` array)
537
+ * skip the JSON+hash work after the first call.
538
+ */
539
+ const kStaticFingerprint = Symbol("model-manager.staticFingerprint");
540
+ type ModelArrayWithFingerprint = readonly Model<Api>[] & { [kStaticFingerprint]?: string };
541
+ function fingerprintStatic<TApi extends Api>(models: readonly Model<TApi>[]): string {
542
+ if (models.length === 0) return "empty";
543
+ const tagged = models as ModelArrayWithFingerprint;
544
+ const cached = tagged[kStaticFingerprint];
545
+ if (cached !== undefined) return cached;
546
+ // `Bun.hash` returns a `bigint`; base36 keeps the string short for the
547
+ // SQLite column without sacrificing distinguishability.
548
+ const fingerprint = Bun.hash(JSON.stringify(models)).toString(36);
549
+ tagged[kStaticFingerprint] = fingerprint;
550
+ return fingerprint;
551
+ }
552
+
553
+ function mergeDynamicModel<TApi extends Api>(existingModel: Model<TApi>, dynamicModel: Model<TApi>): Model<TApi> {
554
+ const supportsImage = existingModel.input.includes("image") || dynamicModel.input.includes("image");
555
+ // The static catalog is authoritative for transport: `api` (and its
556
+ // api-specific `baseUrl`). Dynamic discovery enumerates ids via a single
557
+ // hardcoded api (e.g. fetchOpenAICompatibleModels always tags
558
+ // `openai-completions`), so spreading it blindly would clobber catalog
559
+ // entries that route through a different format — e.g. opencode-go
560
+ // qwen3.7-max is `anthropic-messages` but would be downgraded to
561
+ // `openai-completions` and 401 with `not supported for format oa-compat`
562
+ // (issue #489). Keep the existing api, and only take the dynamic baseUrl
563
+ // when the api matches (same transport, same URL shape).
564
+ const baseUrl = existingModel.api === dynamicModel.api ? dynamicModel.baseUrl : existingModel.baseUrl;
565
+ const merged = enrichModelThinking({
566
+ ...existingModel,
567
+ ...dynamicModel,
568
+ api: existingModel.api,
569
+ baseUrl,
570
+ name: sanitizeModelDisplayName(
571
+ preferDiscoveryName(dynamicModel.name, existingModel.name, dynamicModel.id),
572
+ dynamicModel.id,
573
+ ),
574
+ reasoning: existingModel.reasoning || dynamicModel.reasoning,
575
+ input: supportsImage ? ["text", "image"] : ["text"],
576
+ cost: {
577
+ input: preferDiscoveryCost(dynamicModel.cost.input, existingModel.cost.input),
578
+ output: preferDiscoveryCost(dynamicModel.cost.output, existingModel.cost.output),
579
+ cacheRead: preferDiscoveryCost(dynamicModel.cost.cacheRead, existingModel.cost.cacheRead),
580
+ cacheWrite: preferDiscoveryCost(dynamicModel.cost.cacheWrite, existingModel.cost.cacheWrite),
581
+ },
582
+ contextWindow: preferDiscoveryLimit(dynamicModel.contextWindow, existingModel.contextWindow),
583
+ maxTokens: preferDiscoveryLimit(dynamicModel.maxTokens, existingModel.maxTokens),
584
+ headers: dynamicModel.headers ? { ...existingModel.headers, ...dynamicModel.headers } : existingModel.headers,
585
+ compat: dynamicModel.compat ?? existingModel.compat,
586
+ contextPromotionTarget: dynamicModel.contextPromotionTarget ?? existingModel.contextPromotionTarget,
587
+ });
588
+ const policyModels = [merged as Model<Api>];
589
+ applyGeneratedModelPolicies(policyModels);
590
+ return policyModels[0] as Model<TApi>;
591
+ }
592
+
593
+ function preferDiscoveryCost(discoveryCost: number, fallbackCost: number): number {
594
+ if (Number.isFinite(discoveryCost) && discoveryCost > 0) {
595
+ return discoveryCost;
596
+ }
597
+ return fallbackCost;
598
+ }
599
+
600
+ function preferDiscoveryName(discoveryName: string, fallbackName: string, modelId: string): string {
601
+ const normalizedDiscoveryName = discoveryName.trim();
602
+ if (normalizedDiscoveryName.length === 0) {
603
+ return fallbackName;
604
+ }
605
+ if (normalizedDiscoveryName === modelId && fallbackName !== modelId) {
606
+ return fallbackName;
607
+ }
608
+ return normalizedDiscoveryName;
609
+ }
610
+
611
+ const MODEL_DISPLAY_NAME_MAX_LENGTH = 200;
612
+
613
+ function sanitizeModelDisplayName(name: string, modelId: string): string {
614
+ const sanitizedName = sanitizeText(name).replace(/\s+/g, " ").trim().slice(0, MODEL_DISPLAY_NAME_MAX_LENGTH);
615
+ if (sanitizedName.length > 0) return sanitizedName;
616
+ const sanitizedId = sanitizeText(modelId).replace(/\s+/g, " ").trim().slice(0, MODEL_DISPLAY_NAME_MAX_LENGTH);
617
+ return sanitizedId || "Unnamed model";
618
+ }
619
+
620
+ function preferDiscoveryLimit(discoveryLimit: number, fallbackLimit: number): number {
621
+ if (!Number.isFinite(discoveryLimit) || discoveryLimit <= 0) {
622
+ return fallbackLimit;
623
+ }
624
+ if (discoveryLimit === 4096 && fallbackLimit > discoveryLimit) {
625
+ return fallbackLimit;
626
+ }
627
+ return discoveryLimit;
628
+ }
629
+
630
+ function normalizeModelList<TApi extends Api>(value: unknown): Model<TApi>[] {
631
+ if (!Array.isArray(value)) {
632
+ return [];
633
+ }
634
+ const models: Model<TApi>[] = [];
635
+ for (const item of value) {
636
+ if (isModelLike(item) && !isRetiredModel(item)) {
637
+ const model = enrichModelThinking(item as Model<TApi>);
638
+ model.longContextPricing = undefined;
639
+ models.push({ ...model, name: sanitizeModelDisplayName(model.name, model.id) });
640
+ }
641
+ }
642
+ applyGeneratedModelPolicies(models as Model<Api>[]);
643
+ return applyFinalCodexGpt56ContextCap(models);
644
+ }
645
+
646
+ function isModelLike(value: unknown): value is Model<Api> {
647
+ if (!isRecord(value)) {
648
+ return false;
649
+ }
650
+ const v = value as {
651
+ id?: unknown;
652
+ name?: unknown;
653
+ api?: unknown;
654
+ provider?: unknown;
655
+ baseUrl?: unknown;
656
+ reasoning?: unknown;
657
+ input?: unknown;
658
+ cost?: unknown;
659
+ contextWindow?: unknown;
660
+ maxTokens?: unknown;
661
+ };
662
+ if (!isSafeCatalogModelId(v.id)) {
663
+ return false;
664
+ }
665
+ if (typeof v.name !== "string" || v.name.length === 0) {
666
+ return false;
667
+ }
668
+ if (typeof v.api !== "string" || v.api.length === 0) {
669
+ return false;
670
+ }
671
+ if (typeof v.provider !== "string" || v.provider.length === 0) {
672
+ return false;
673
+ }
674
+ if (typeof v.baseUrl !== "string" || v.baseUrl.length === 0) {
675
+ return false;
676
+ }
677
+ if (typeof v.reasoning !== "boolean") {
678
+ return false;
679
+ }
680
+ if (!isModelInputArray(v.input)) {
681
+ return false;
682
+ }
683
+ if (!isModelCost(v.cost)) {
684
+ return false;
685
+ }
686
+ // Finite positive: NaN > 0 is false, +Infinity < Infinity is false.
687
+ const cw = v.contextWindow;
688
+ if (typeof cw !== "number" || !(cw > 0 && cw < Infinity)) {
689
+ return false;
690
+ }
691
+ const mt = v.maxTokens;
692
+ if (typeof mt !== "number" || !(mt > 0 && mt < Infinity)) {
693
+ return false;
694
+ }
695
+ return true;
696
+ }
697
+
698
+ function isRecord(value: unknown): value is Record<string, unknown> {
699
+ return typeof value === "object" && value !== null;
700
+ }
701
+
702
+ function isModelInputArray(value: unknown): value is ("text" | "image")[] {
703
+ if (!Array.isArray(value) || value.length === 0) {
704
+ return false;
705
+ }
706
+ for (let i = 0; i < value.length; i++) {
707
+ const item = value[i];
708
+ if (item !== "text" && item !== "image") {
709
+ return false;
710
+ }
711
+ }
712
+ return true;
713
+ }
714
+
715
+ function isModelCost(value: unknown): value is Model<Api>["cost"] {
716
+ if (!isRecord(value)) {
717
+ return false;
718
+ }
719
+ const c = value as {
720
+ input?: unknown;
721
+ output?: unknown;
722
+ cacheRead?: unknown;
723
+ cacheWrite?: unknown;
724
+ };
725
+ // Finite (NaN-safe): -Infinity < x < Infinity rejects NaN and both infinities.
726
+ // Preserves original behavior: 0 and negatives remain valid.
727
+ const ci = c.input;
728
+ if (typeof ci !== "number" || !(ci > -Infinity && ci < Infinity)) {
729
+ return false;
730
+ }
731
+ const co = c.output;
732
+ if (typeof co !== "number" || !(co > -Infinity && co < Infinity)) {
733
+ return false;
734
+ }
735
+ const cr = c.cacheRead;
736
+ if (typeof cr !== "number" || !(cr > -Infinity && cr < Infinity)) {
737
+ return false;
738
+ }
739
+ const cw = c.cacheWrite;
740
+ if (typeof cw !== "number" || !(cw > -Infinity && cw < Infinity)) {
741
+ return false;
742
+ }
743
+ return true;
744
+ }