@sayknow-cli/ai 0.2.2

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 (361) hide show
  1. package/CHANGELOG.md +2788 -0
  2. package/README.md +1183 -0
  3. package/dist/types/api-registry.d.ts +30 -0
  4. package/dist/types/auth-broker/client.d.ts +66 -0
  5. package/dist/types/auth-broker/index.d.ts +5 -0
  6. package/dist/types/auth-broker/refresher.d.ts +25 -0
  7. package/dist/types/auth-broker/remote-store.d.ts +96 -0
  8. package/dist/types/auth-broker/server.d.ts +32 -0
  9. package/dist/types/auth-broker/types.d.ts +105 -0
  10. package/dist/types/auth-broker/wire-schemas.d.ts +412 -0
  11. package/dist/types/auth-gateway/http.d.ts +39 -0
  12. package/dist/types/auth-gateway/index.d.ts +3 -0
  13. package/dist/types/auth-gateway/server.d.ts +17 -0
  14. package/dist/types/auth-gateway/types.d.ts +115 -0
  15. package/dist/types/auth-storage.d.ts +660 -0
  16. package/dist/types/cli.d.ts +2 -0
  17. package/dist/types/index.d.ts +51 -0
  18. package/dist/types/model-cache.d.ts +17 -0
  19. package/dist/types/model-manager.d.ts +62 -0
  20. package/dist/types/model-thinking.d.ts +74 -0
  21. package/dist/types/models.d.ts +12 -0
  22. package/dist/types/provider-details.d.ts +24 -0
  23. package/dist/types/provider-models/bundled-references.d.ts +4 -0
  24. package/dist/types/provider-models/descriptors.d.ts +48 -0
  25. package/dist/types/provider-models/google.d.ts +20 -0
  26. package/dist/types/provider-models/index.d.ts +5 -0
  27. package/dist/types/provider-models/ollama.d.ts +7 -0
  28. package/dist/types/provider-models/openai-compat.d.ts +244 -0
  29. package/dist/types/provider-models/special.d.ts +16 -0
  30. package/dist/types/providers/amazon-bedrock.d.ts +60 -0
  31. package/dist/types/providers/anthropic-messages-server-schema.d.ts +450 -0
  32. package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
  33. package/dist/types/providers/anthropic.d.ts +198 -0
  34. package/dist/types/providers/aws-credentials.d.ts +43 -0
  35. package/dist/types/providers/aws-eventstream.d.ts +38 -0
  36. package/dist/types/providers/aws-sigv4.d.ts +55 -0
  37. package/dist/types/providers/azure-openai-responses.d.ts +15 -0
  38. package/dist/types/providers/composer-discipline.d.ts +26 -0
  39. package/dist/types/providers/cursor/gen/agent_pb.d.ts +13022 -0
  40. package/dist/types/providers/cursor.d.ts +44 -0
  41. package/dist/types/providers/error-message.d.ts +27 -0
  42. package/dist/types/providers/github-copilot-headers.d.ts +40 -0
  43. package/dist/types/providers/gitlab-duo.d.ts +27 -0
  44. package/dist/types/providers/google-auth.d.ts +24 -0
  45. package/dist/types/providers/google-gemini-cli.d.ts +72 -0
  46. package/dist/types/providers/google-gemini-headers.d.ts +18 -0
  47. package/dist/types/providers/google-shared.d.ts +173 -0
  48. package/dist/types/providers/google-types.d.ts +138 -0
  49. package/dist/types/providers/google-vertex.d.ts +7 -0
  50. package/dist/types/providers/google.d.ts +4 -0
  51. package/dist/types/providers/grammar.d.ts +1 -0
  52. package/dist/types/providers/kimi.d.ts +27 -0
  53. package/dist/types/providers/mock.d.ts +175 -0
  54. package/dist/types/providers/ollama.d.ts +41 -0
  55. package/dist/types/providers/openai-anthropic-shim.d.ts +31 -0
  56. package/dist/types/providers/openai-chat-server-schema.d.ts +815 -0
  57. package/dist/types/providers/openai-chat-server.d.ts +16 -0
  58. package/dist/types/providers/openai-codex/constants.d.ts +26 -0
  59. package/dist/types/providers/openai-codex/request-transformer.d.ts +49 -0
  60. package/dist/types/providers/openai-codex/response-handler.d.ts +17 -0
  61. package/dist/types/providers/openai-codex-responses.d.ts +67 -0
  62. package/dist/types/providers/openai-completions-compat.d.ts +27 -0
  63. package/dist/types/providers/openai-completions.d.ts +33 -0
  64. package/dist/types/providers/openai-request-transform.d.ts +4 -0
  65. package/dist/types/providers/openai-responses-server-schema.d.ts +392 -0
  66. package/dist/types/providers/openai-responses-server.d.ts +17 -0
  67. package/dist/types/providers/openai-responses-shared.d.ts +89 -0
  68. package/dist/types/providers/openai-responses.d.ts +32 -0
  69. package/dist/types/providers/pi-native-client.d.ts +13 -0
  70. package/dist/types/providers/pi-native-server.d.ts +68 -0
  71. package/dist/types/providers/register-builtins.d.ts +31 -0
  72. package/dist/types/providers/synthetic.d.ts +26 -0
  73. package/dist/types/providers/transform-messages.d.ts +14 -0
  74. package/dist/types/providers/vision-guard.d.ts +8 -0
  75. package/dist/types/rate-limit-utils.d.ts +19 -0
  76. package/dist/types/stream.d.ts +43 -0
  77. package/dist/types/types.d.ts +811 -0
  78. package/dist/types/usage/claude.d.ts +3 -0
  79. package/dist/types/usage/gemini.d.ts +2 -0
  80. package/dist/types/usage/github-copilot.d.ts +7 -0
  81. package/dist/types/usage/google-antigravity.d.ts +2 -0
  82. package/dist/types/usage/grok-cli.d.ts +10 -0
  83. package/dist/types/usage/kimi.d.ts +2 -0
  84. package/dist/types/usage/minimax-code.d.ts +2 -0
  85. package/dist/types/usage/openai-codex.d.ts +3 -0
  86. package/dist/types/usage/shared.d.ts +1 -0
  87. package/dist/types/usage/zai.d.ts +2 -0
  88. package/dist/types/usage.d.ts +258 -0
  89. package/dist/types/utils/abort.d.ts +19 -0
  90. package/dist/types/utils/anthropic-auth.d.ts +31 -0
  91. package/dist/types/utils/discovery/antigravity.d.ts +61 -0
  92. package/dist/types/utils/discovery/codex.d.ts +38 -0
  93. package/dist/types/utils/discovery/cursor.d.ts +23 -0
  94. package/dist/types/utils/discovery/gemini.d.ts +25 -0
  95. package/dist/types/utils/discovery/index.d.ts +4 -0
  96. package/dist/types/utils/discovery/openai-compatible.d.ts +74 -0
  97. package/dist/types/utils/event-stream.d.ts +33 -0
  98. package/dist/types/utils/fireworks-model-id.d.ts +10 -0
  99. package/dist/types/utils/foundry.d.ts +1 -0
  100. package/dist/types/utils/h2-fetch.d.ts +22 -0
  101. package/dist/types/utils/http-inspector.d.ts +35 -0
  102. package/dist/types/utils/idle-iterator.d.ts +67 -0
  103. package/dist/types/utils/json-parse.d.ts +10 -0
  104. package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +18 -0
  105. package/dist/types/utils/oauth/anthropic.d.ts +22 -0
  106. package/dist/types/utils/oauth/api-key-login.d.ts +35 -0
  107. package/dist/types/utils/oauth/api-key-validation.d.ts +27 -0
  108. package/dist/types/utils/oauth/callback-server.d.ts +60 -0
  109. package/dist/types/utils/oauth/cerebras.d.ts +1 -0
  110. package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +18 -0
  111. package/dist/types/utils/oauth/cursor.d.ts +15 -0
  112. package/dist/types/utils/oauth/deepseek.d.ts +10 -0
  113. package/dist/types/utils/oauth/firepass.d.ts +1 -0
  114. package/dist/types/utils/oauth/fireworks.d.ts +1 -0
  115. package/dist/types/utils/oauth/github-copilot.d.ts +38 -0
  116. package/dist/types/utils/oauth/gitlab-duo.d.ts +3 -0
  117. package/dist/types/utils/oauth/google-antigravity.d.ts +11 -0
  118. package/dist/types/utils/oauth/google-gemini-cli.d.ts +10 -0
  119. package/dist/types/utils/oauth/google-oauth-shared.d.ts +28 -0
  120. package/dist/types/utils/oauth/huggingface.d.ts +19 -0
  121. package/dist/types/utils/oauth/index.d.ts +38 -0
  122. package/dist/types/utils/oauth/kagi.d.ts +17 -0
  123. package/dist/types/utils/oauth/kilo.d.ts +5 -0
  124. package/dist/types/utils/oauth/kimi.d.ts +21 -0
  125. package/dist/types/utils/oauth/litellm.d.ts +18 -0
  126. package/dist/types/utils/oauth/lm-studio.d.ts +17 -0
  127. package/dist/types/utils/oauth/minimax-code.d.ts +28 -0
  128. package/dist/types/utils/oauth/moonshot.d.ts +1 -0
  129. package/dist/types/utils/oauth/nanogpt.d.ts +1 -0
  130. package/dist/types/utils/oauth/nvidia.d.ts +18 -0
  131. package/dist/types/utils/oauth/ollama-cloud.d.ts +2 -0
  132. package/dist/types/utils/oauth/ollama.d.ts +18 -0
  133. package/dist/types/utils/oauth/openai-codex.d.ts +21 -0
  134. package/dist/types/utils/oauth/opencode.d.ts +18 -0
  135. package/dist/types/utils/oauth/parallel.d.ts +17 -0
  136. package/dist/types/utils/oauth/perplexity.d.ts +9 -0
  137. package/dist/types/utils/oauth/pkce.d.ts +8 -0
  138. package/dist/types/utils/oauth/qianfan.d.ts +17 -0
  139. package/dist/types/utils/oauth/qwen-portal.d.ts +19 -0
  140. package/dist/types/utils/oauth/synthetic.d.ts +1 -0
  141. package/dist/types/utils/oauth/tavily.d.ts +17 -0
  142. package/dist/types/utils/oauth/together.d.ts +1 -0
  143. package/dist/types/utils/oauth/types.d.ts +45 -0
  144. package/dist/types/utils/oauth/venice.d.ts +18 -0
  145. package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +18 -0
  146. package/dist/types/utils/oauth/vllm.d.ts +16 -0
  147. package/dist/types/utils/oauth/xai.d.ts +30 -0
  148. package/dist/types/utils/oauth/xiaomi.d.ts +25 -0
  149. package/dist/types/utils/oauth/zai.d.ts +18 -0
  150. package/dist/types/utils/oauth/zenmux.d.ts +1 -0
  151. package/dist/types/utils/overflow.d.ts +54 -0
  152. package/dist/types/utils/parse-bind.d.ts +23 -0
  153. package/dist/types/utils/provider-response.d.ts +3 -0
  154. package/dist/types/utils/retry-after.d.ts +3 -0
  155. package/dist/types/utils/retry-budget.d.ts +1 -0
  156. package/dist/types/utils/retry.d.ts +26 -0
  157. package/dist/types/utils/schema/adapt.d.ts +24 -0
  158. package/dist/types/utils/schema/compatibility.d.ts +30 -0
  159. package/dist/types/utils/schema/dereference.d.ts +11 -0
  160. package/dist/types/utils/schema/draft.d.ts +10 -0
  161. package/dist/types/utils/schema/equality.d.ts +4 -0
  162. package/dist/types/utils/schema/fields.d.ts +49 -0
  163. package/dist/types/utils/schema/index.d.ts +13 -0
  164. package/dist/types/utils/schema/json-schema-validator.d.ts +12 -0
  165. package/dist/types/utils/schema/meta-validator.d.ts +2 -0
  166. package/dist/types/utils/schema/normalize.d.ts +93 -0
  167. package/dist/types/utils/schema/spill.d.ts +8 -0
  168. package/dist/types/utils/schema/stamps.d.ts +25 -0
  169. package/dist/types/utils/schema/types.d.ts +4 -0
  170. package/dist/types/utils/schema/wire.d.ts +54 -0
  171. package/dist/types/utils/schema/zod-decontaminate.d.ts +31 -0
  172. package/dist/types/utils/sse-debug.d.ts +10 -0
  173. package/dist/types/utils/tool-call-healing.d.ts +71 -0
  174. package/dist/types/utils/tool-choice-capability.d.ts +41 -0
  175. package/dist/types/utils/tool-choice.d.ts +50 -0
  176. package/dist/types/utils/validation.d.ts +17 -0
  177. package/dist/types/utils.d.ts +34 -0
  178. package/package.json +146 -0
  179. package/src/api-registry.ts +96 -0
  180. package/src/auth-broker/client.ts +358 -0
  181. package/src/auth-broker/index.ts +5 -0
  182. package/src/auth-broker/refresher.ts +127 -0
  183. package/src/auth-broker/remote-store.ts +623 -0
  184. package/src/auth-broker/server.ts +644 -0
  185. package/src/auth-broker/types.ts +127 -0
  186. package/src/auth-broker/wire-schemas.ts +200 -0
  187. package/src/auth-gateway/http.ts +194 -0
  188. package/src/auth-gateway/index.ts +3 -0
  189. package/src/auth-gateway/server.ts +717 -0
  190. package/src/auth-gateway/types.ts +134 -0
  191. package/src/auth-storage.ts +4179 -0
  192. package/src/cli.ts +263 -0
  193. package/src/index.ts +56 -0
  194. package/src/model-cache.ts +129 -0
  195. package/src/model-manager.ts +486 -0
  196. package/src/model-thinking.ts +772 -0
  197. package/src/models.json +75437 -0
  198. package/src/models.json.d.ts +9 -0
  199. package/src/models.ts +82 -0
  200. package/src/prompts/turn-aborted-guidance.md +4 -0
  201. package/src/provider-details.ts +90 -0
  202. package/src/provider-models/bundled-references.ts +38 -0
  203. package/src/provider-models/descriptors.ts +327 -0
  204. package/src/provider-models/google.ts +91 -0
  205. package/src/provider-models/index.ts +5 -0
  206. package/src/provider-models/ollama.ts +153 -0
  207. package/src/provider-models/openai-compat.ts +2352 -0
  208. package/src/provider-models/special.ts +67 -0
  209. package/src/providers/amazon-bedrock.ts +937 -0
  210. package/src/providers/anthropic-messages-server-schema.ts +229 -0
  211. package/src/providers/anthropic-messages-server.ts +677 -0
  212. package/src/providers/anthropic.ts +2940 -0
  213. package/src/providers/aws-credentials.ts +501 -0
  214. package/src/providers/aws-eventstream.ts +185 -0
  215. package/src/providers/aws-sigv4.ts +218 -0
  216. package/src/providers/azure-openai-responses.ts +379 -0
  217. package/src/providers/composer-discipline.ts +41 -0
  218. package/src/providers/cursor/gen/agent_pb.ts +15274 -0
  219. package/src/providers/cursor/proto/agent.proto +3526 -0
  220. package/src/providers/cursor/proto/buf.gen.yaml +6 -0
  221. package/src/providers/cursor/proto/buf.yaml +17 -0
  222. package/src/providers/cursor.ts +2671 -0
  223. package/src/providers/error-message.ts +21 -0
  224. package/src/providers/github-copilot-headers.ts +140 -0
  225. package/src/providers/gitlab-duo.ts +372 -0
  226. package/src/providers/google-auth.ts +252 -0
  227. package/src/providers/google-gemini-cli.ts +856 -0
  228. package/src/providers/google-gemini-headers.ts +41 -0
  229. package/src/providers/google-shared.ts +951 -0
  230. package/src/providers/google-types.ts +167 -0
  231. package/src/providers/google-vertex.ts +88 -0
  232. package/src/providers/google.ts +41 -0
  233. package/src/providers/grammar.ts +70 -0
  234. package/src/providers/kimi.ts +52 -0
  235. package/src/providers/mock.ts +500 -0
  236. package/src/providers/ollama.ts +603 -0
  237. package/src/providers/openai-anthropic-shim.ts +138 -0
  238. package/src/providers/openai-chat-server-schema.ts +243 -0
  239. package/src/providers/openai-chat-server.ts +635 -0
  240. package/src/providers/openai-codex/constants.ts +43 -0
  241. package/src/providers/openai-codex/request-transformer.ts +161 -0
  242. package/src/providers/openai-codex/response-handler.ts +81 -0
  243. package/src/providers/openai-codex-responses.ts +2774 -0
  244. package/src/providers/openai-completions-compat.ts +289 -0
  245. package/src/providers/openai-completions.ts +1935 -0
  246. package/src/providers/openai-request-transform.ts +136 -0
  247. package/src/providers/openai-responses-server-schema.ts +290 -0
  248. package/src/providers/openai-responses-server.ts +1190 -0
  249. package/src/providers/openai-responses-shared.ts +800 -0
  250. package/src/providers/openai-responses.ts +738 -0
  251. package/src/providers/pi-native-client.ts +227 -0
  252. package/src/providers/pi-native-server.ts +210 -0
  253. package/src/providers/register-builtins.ts +411 -0
  254. package/src/providers/synthetic.ts +50 -0
  255. package/src/providers/transform-messages.ts +319 -0
  256. package/src/providers/vision-guard.ts +31 -0
  257. package/src/rate-limit-utils.ts +93 -0
  258. package/src/stream.ts +960 -0
  259. package/src/types.ts +967 -0
  260. package/src/usage/claude.ts +431 -0
  261. package/src/usage/gemini.ts +250 -0
  262. package/src/usage/github-copilot.ts +421 -0
  263. package/src/usage/google-antigravity.ts +201 -0
  264. package/src/usage/grok-cli.ts +163 -0
  265. package/src/usage/kimi.ts +271 -0
  266. package/src/usage/minimax-code.ts +31 -0
  267. package/src/usage/openai-codex.ts +503 -0
  268. package/src/usage/shared.ts +10 -0
  269. package/src/usage/zai.ts +247 -0
  270. package/src/usage.ts +183 -0
  271. package/src/utils/abort.ts +51 -0
  272. package/src/utils/anthropic-auth.ts +87 -0
  273. package/src/utils/discovery/antigravity.ts +261 -0
  274. package/src/utils/discovery/codex.ts +371 -0
  275. package/src/utils/discovery/cursor.ts +306 -0
  276. package/src/utils/discovery/gemini.ts +248 -0
  277. package/src/utils/discovery/index.ts +4 -0
  278. package/src/utils/discovery/openai-compatible.ts +230 -0
  279. package/src/utils/event-stream.ts +172 -0
  280. package/src/utils/fireworks-model-id.ts +30 -0
  281. package/src/utils/foundry.ts +8 -0
  282. package/src/utils/h2-fetch.ts +60 -0
  283. package/src/utils/http-inspector.ts +255 -0
  284. package/src/utils/idle-iterator.ts +257 -0
  285. package/src/utils/json-parse.ts +148 -0
  286. package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
  287. package/src/utils/oauth/anthropic.ts +200 -0
  288. package/src/utils/oauth/api-key-login.ts +87 -0
  289. package/src/utils/oauth/api-key-validation.ts +92 -0
  290. package/src/utils/oauth/callback-server.ts +281 -0
  291. package/src/utils/oauth/cerebras.ts +16 -0
  292. package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
  293. package/src/utils/oauth/cursor.ts +157 -0
  294. package/src/utils/oauth/deepseek.ts +53 -0
  295. package/src/utils/oauth/firepass.ts +24 -0
  296. package/src/utils/oauth/fireworks.ts +15 -0
  297. package/src/utils/oauth/github-copilot.ts +362 -0
  298. package/src/utils/oauth/gitlab-duo.ts +123 -0
  299. package/src/utils/oauth/google-antigravity.ts +200 -0
  300. package/src/utils/oauth/google-gemini-cli.ts +256 -0
  301. package/src/utils/oauth/google-oauth-shared.ts +110 -0
  302. package/src/utils/oauth/huggingface.ts +62 -0
  303. package/src/utils/oauth/index.ts +469 -0
  304. package/src/utils/oauth/kagi.ts +47 -0
  305. package/src/utils/oauth/kilo.ts +87 -0
  306. package/src/utils/oauth/kimi.ts +254 -0
  307. package/src/utils/oauth/litellm.ts +47 -0
  308. package/src/utils/oauth/lm-studio.ts +38 -0
  309. package/src/utils/oauth/minimax-code.ts +78 -0
  310. package/src/utils/oauth/moonshot.ts +16 -0
  311. package/src/utils/oauth/nanogpt.ts +15 -0
  312. package/src/utils/oauth/nvidia.ts +70 -0
  313. package/src/utils/oauth/oauth.html +199 -0
  314. package/src/utils/oauth/ollama-cloud.ts +28 -0
  315. package/src/utils/oauth/ollama.ts +47 -0
  316. package/src/utils/oauth/openai-codex.ts +299 -0
  317. package/src/utils/oauth/opencode.ts +49 -0
  318. package/src/utils/oauth/parallel.ts +46 -0
  319. package/src/utils/oauth/perplexity.ts +206 -0
  320. package/src/utils/oauth/pkce.ts +18 -0
  321. package/src/utils/oauth/qianfan.ts +58 -0
  322. package/src/utils/oauth/qwen-portal.ts +60 -0
  323. package/src/utils/oauth/synthetic.ts +16 -0
  324. package/src/utils/oauth/tavily.ts +46 -0
  325. package/src/utils/oauth/together.ts +16 -0
  326. package/src/utils/oauth/types.ts +99 -0
  327. package/src/utils/oauth/venice.ts +59 -0
  328. package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
  329. package/src/utils/oauth/vllm.ts +40 -0
  330. package/src/utils/oauth/xai.ts +246 -0
  331. package/src/utils/oauth/xiaomi.ts +199 -0
  332. package/src/utils/oauth/zai.ts +60 -0
  333. package/src/utils/oauth/zenmux.ts +15 -0
  334. package/src/utils/overflow.ts +137 -0
  335. package/src/utils/parse-bind.ts +54 -0
  336. package/src/utils/provider-response.ts +30 -0
  337. package/src/utils/retry-after.ts +110 -0
  338. package/src/utils/retry-budget.ts +4 -0
  339. package/src/utils/retry.ts +54 -0
  340. package/src/utils/schema/CONSTRAINTS.md +164 -0
  341. package/src/utils/schema/adapt.ts +36 -0
  342. package/src/utils/schema/compatibility.ts +435 -0
  343. package/src/utils/schema/dereference.ts +98 -0
  344. package/src/utils/schema/draft.ts +341 -0
  345. package/src/utils/schema/equality.ts +97 -0
  346. package/src/utils/schema/fields.ts +190 -0
  347. package/src/utils/schema/index.ts +13 -0
  348. package/src/utils/schema/json-schema-validator.ts +577 -0
  349. package/src/utils/schema/meta-validator.ts +167 -0
  350. package/src/utils/schema/normalize.ts +1588 -0
  351. package/src/utils/schema/spill.ts +43 -0
  352. package/src/utils/schema/stamps.ts +97 -0
  353. package/src/utils/schema/types.ts +11 -0
  354. package/src/utils/schema/wire.ts +213 -0
  355. package/src/utils/schema/zod-decontaminate.ts +331 -0
  356. package/src/utils/sse-debug.ts +289 -0
  357. package/src/utils/tool-call-healing.ts +271 -0
  358. package/src/utils/tool-choice-capability.ts +220 -0
  359. package/src/utils/tool-choice.ts +99 -0
  360. package/src/utils/validation.ts +1019 -0
  361. package/src/utils.ts +178 -0
@@ -0,0 +1,603 @@
1
+ import { extractHttpStatusFromError, fetchWithRetry } from "@sayknow-cli/utils";
2
+ import { getEnvApiKey } from "../stream";
3
+ import type {
4
+ Api,
5
+ AssistantMessage,
6
+ Context,
7
+ DeveloperMessage,
8
+ Message,
9
+ Model,
10
+ StreamFunction,
11
+ StreamOptions,
12
+ Tool,
13
+ ToolChoice,
14
+ ToolResultMessage,
15
+ UserMessage,
16
+ } from "../types";
17
+ import { normalizeSystemPrompts } from "../utils";
18
+ import { AssistantMessageEventStream } from "../utils/event-stream";
19
+ import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
20
+ import { parseStreamingJson } from "../utils/json-parse";
21
+ import { resolveRetryBudget } from "../utils/retry-budget";
22
+ import { toolWireSchema } from "../utils/schema/wire";
23
+ import {
24
+ isForcedToolChoiceUnsupportedError,
25
+ markToolChoiceIncapability,
26
+ resolveToolChoice,
27
+ } from "../utils/tool-choice-capability";
28
+ import { transformMessages } from "./transform-messages";
29
+
30
+ export interface OllamaChatOptions extends StreamOptions {
31
+ reasoning?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
32
+ toolChoice?: ToolChoice;
33
+ }
34
+
35
+ type OllamaFunctionTool = {
36
+ type: "function";
37
+ function: {
38
+ name: string;
39
+ description: string;
40
+ parameters: Record<string, unknown>;
41
+ };
42
+ };
43
+
44
+ type OllamaMessage = {
45
+ role: "system" | "user" | "assistant" | "tool";
46
+ content: string;
47
+ images?: string[];
48
+ thinking?: string;
49
+ tool_calls?: Array<{
50
+ type: "function";
51
+ function: {
52
+ index?: number;
53
+ name: string;
54
+ arguments: Record<string, unknown>;
55
+ };
56
+ }>;
57
+ tool_name?: string;
58
+ };
59
+
60
+ type OllamaChatChunk = {
61
+ message?: {
62
+ role?: string;
63
+ content?: string;
64
+ thinking?: string;
65
+ tool_calls?: Array<{
66
+ type?: string;
67
+ function?: {
68
+ index?: number;
69
+ name?: string;
70
+ arguments?: Record<string, unknown> | string;
71
+ };
72
+ }>;
73
+ };
74
+ done?: boolean;
75
+ done_reason?: string;
76
+ prompt_eval_count?: number;
77
+ eval_count?: number;
78
+ };
79
+
80
+ type InternalToolCallBlock = AssistantMessage["content"][number] & {
81
+ type: "toolCall";
82
+ partialJson?: string;
83
+ };
84
+
85
+ function normalizeBaseUrl(baseUrl?: string): string {
86
+ const value = baseUrl?.trim();
87
+ if (!value) {
88
+ return "https://ollama.com";
89
+ }
90
+ const trimmed = value.endsWith("/") ? value.slice(0, -1) : value;
91
+ return trimmed.endsWith("/api") ? trimmed.slice(0, -4) : trimmed;
92
+ }
93
+
94
+ function mapReasoning(reasoning: OllamaChatOptions["reasoning"]): boolean | "low" | "medium" | "high" | undefined {
95
+ switch (reasoning) {
96
+ case "minimal":
97
+ case "low":
98
+ return "low";
99
+ case "medium":
100
+ return "medium";
101
+ case "high":
102
+ case "xhigh":
103
+ case "max":
104
+ return "high";
105
+ default:
106
+ return undefined;
107
+ }
108
+ }
109
+
110
+ function mapToolChoice(toolChoice: ToolChoice | undefined): "auto" | "none" | "required" | undefined {
111
+ if (!toolChoice || toolChoice === "auto") {
112
+ return undefined;
113
+ }
114
+ if (toolChoice === "none") {
115
+ return "none";
116
+ }
117
+ if (toolChoice === "required" || toolChoice === "any") {
118
+ return "required";
119
+ }
120
+ if (typeof toolChoice === "object") {
121
+ return "required";
122
+ }
123
+ return undefined;
124
+ }
125
+
126
+ function getNamedToolChoiceName(toolChoice: ToolChoice | undefined): string | undefined {
127
+ if (!toolChoice || typeof toolChoice === "string") {
128
+ return undefined;
129
+ }
130
+ if ("function" in toolChoice) {
131
+ return toolChoice.function.name;
132
+ }
133
+ return toolChoice.name;
134
+ }
135
+
136
+ function selectToolsForToolChoice(tools: Tool[] | undefined, toolChoice: ToolChoice | undefined): Tool[] | undefined {
137
+ const toolName = getNamedToolChoiceName(toolChoice);
138
+ if (!toolName || !tools) {
139
+ return tools;
140
+ }
141
+ for (const tool of tools) {
142
+ if (tool.name === toolName) {
143
+ return [tool];
144
+ }
145
+ }
146
+ return [];
147
+ }
148
+
149
+ function toPlainContent(content: string | Array<{ type: "text" | "image"; text?: string; data?: string }>): {
150
+ content: string;
151
+ images?: string[];
152
+ } {
153
+ if (typeof content === "string") {
154
+ return { content };
155
+ }
156
+ const textParts: string[] = [];
157
+ const images: string[] = [];
158
+ for (const block of content) {
159
+ if (block.type === "text" && typeof block.text === "string") {
160
+ textParts.push(block.text);
161
+ }
162
+ if (block.type === "image" && typeof block.data === "string") {
163
+ images.push(block.data);
164
+ }
165
+ }
166
+ return {
167
+ content: textParts.join("\n"),
168
+ ...(images.length > 0 ? { images } : {}),
169
+ };
170
+ }
171
+
172
+ function convertMessage(message: Message): OllamaMessage {
173
+ if (message.role === "user") {
174
+ const converted = toPlainContent(message.content as UserMessage["content"]);
175
+ return { role: "user", ...converted };
176
+ }
177
+ if (message.role === "developer") {
178
+ const converted = toPlainContent(message.content as DeveloperMessage["content"]);
179
+ return { role: "system", ...converted };
180
+ }
181
+ if (message.role === "toolResult") {
182
+ const converted = toPlainContent(message.content as ToolResultMessage["content"]);
183
+ return {
184
+ role: "tool",
185
+ tool_name: message.toolName,
186
+ ...converted,
187
+ };
188
+ }
189
+ const text: string[] = [];
190
+ const thinking: string[] = [];
191
+ const toolCalls: NonNullable<OllamaMessage["tool_calls"]> = [];
192
+ for (const block of message.content) {
193
+ if (block.type === "text") {
194
+ text.push(block.text);
195
+ continue;
196
+ }
197
+ if (block.type === "thinking") {
198
+ thinking.push(block.thinking);
199
+ continue;
200
+ }
201
+ if (block.type === "toolCall") {
202
+ toolCalls.push({
203
+ type: "function",
204
+ function: {
205
+ name: block.name,
206
+ arguments: block.arguments,
207
+ },
208
+ });
209
+ }
210
+ }
211
+ return {
212
+ role: "assistant",
213
+ content: text.join("\n"),
214
+ ...(thinking.length > 0 ? { thinking: thinking.join("\n") } : {}),
215
+ ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
216
+ };
217
+ }
218
+
219
+ function convertMessages(model: Model<"ollama-chat">, context: Context): OllamaMessage[] {
220
+ const messages: Message[] = [];
221
+ // Emit one developer message per ordered system prompt. The wire role is mapped to "system"
222
+ // by `convertMessage`, but keeping the prompts separate preserves prefix-cache stability:
223
+ // if only the trailing prompt changes between calls, the leading system messages keep
224
+ // their identical token prefix so KV-cache reuse covers them.
225
+ for (const systemPrompt of normalizeSystemPrompts(context.systemPrompt)) {
226
+ messages.push({
227
+ role: "developer",
228
+ content: systemPrompt,
229
+ timestamp: Date.now(),
230
+ });
231
+ }
232
+ messages.push(...context.messages);
233
+ const isCloud = model.provider === "ollama-cloud";
234
+ return transformMessages(messages, model).map(msg => {
235
+ const converted = convertMessage(msg);
236
+ // Ollama cloud rejects requests when assistant history messages contain the `thinking`
237
+ // field — it's valid in model responses but not accepted as a history input. Strip it
238
+ // to prevent HTTP 400 errors. Local Ollama instances are unaffected.
239
+ if (isCloud && converted.role === "assistant" && converted.thinking) {
240
+ const { thinking: _t, ...rest } = converted;
241
+ return rest;
242
+ }
243
+ return converted;
244
+ });
245
+ }
246
+
247
+ function convertTools(tools: Tool[] | undefined): OllamaFunctionTool[] | undefined {
248
+ if (!tools || tools.length === 0) {
249
+ return undefined;
250
+ }
251
+ return tools.map(tool => ({
252
+ type: "function",
253
+ function: {
254
+ name: tool.name,
255
+ description: tool.description,
256
+ parameters: toolWireSchema(tool),
257
+ },
258
+ }));
259
+ }
260
+
261
+ export function createChatBody(model: Model<"ollama-chat">, context: Context, options: OllamaChatOptions | undefined) {
262
+ const think = mapReasoning(options?.reasoning);
263
+ const resolved = resolveToolChoice(model, options?.toolChoice);
264
+ const toolChoice = mapToolChoice(resolved.resolvedChoice);
265
+
266
+ // Ollama's wire protocol has no named forcing — a named request rides
267
+ // `tool_choice: "required"` with the tool list narrowed to the target
268
+ // (issue #1236). Narrow whenever the resolved choice still forces a tool.
269
+ const shouldNarrowToNamedTool =
270
+ resolved.requestedLevel === "named" &&
271
+ (resolved.resolvedLevel === "named" || resolved.resolvedLevel === "required") &&
272
+ resolved.targetToolName !== undefined;
273
+ const selectedTools =
274
+ shouldNarrowToNamedTool && resolved.targetToolName
275
+ ? selectToolsForToolChoice(context.tools, { type: "tool", name: resolved.targetToolName })
276
+ : context.tools;
277
+ const tools = convertTools(selectedTools);
278
+ return {
279
+ model: model.id,
280
+ messages: convertMessages(model, context),
281
+ ...(tools ? { tools } : {}),
282
+ ...(think !== undefined ? { think } : {}),
283
+ ...(toolChoice !== undefined ? { tool_choice: toolChoice } : {}),
284
+ ...(options?.maxTokens !== undefined ? { options: { num_predict: options.maxTokens } } : {}),
285
+ stream: true,
286
+ };
287
+ }
288
+
289
+ async function* iterateNdjson(stream: ReadableStream<Uint8Array>): AsyncGenerator<OllamaChatChunk> {
290
+ const reader = stream.getReader();
291
+ const decoder = new TextDecoder();
292
+ let buffer = "";
293
+ while (true) {
294
+ const { done, value } = await reader.read();
295
+ if (done) {
296
+ break;
297
+ }
298
+ buffer += decoder.decode(value, { stream: true });
299
+ while (true) {
300
+ const newlineIndex = buffer.indexOf("\n");
301
+ if (newlineIndex < 0) {
302
+ break;
303
+ }
304
+ const line = buffer.slice(0, newlineIndex).trim();
305
+ buffer = buffer.slice(newlineIndex + 1);
306
+ if (!line) {
307
+ continue;
308
+ }
309
+ yield JSON.parse(line) as OllamaChatChunk;
310
+ }
311
+ }
312
+ buffer += decoder.decode();
313
+ const tail = buffer.trim();
314
+ if (tail) {
315
+ yield JSON.parse(tail) as OllamaChatChunk;
316
+ }
317
+ }
318
+
319
+ function createEmptyOutput(model: Model<"ollama-chat">): AssistantMessage {
320
+ return {
321
+ role: "assistant",
322
+ content: [],
323
+ api: "ollama-chat" as Api,
324
+ provider: model.provider,
325
+ model: model.id,
326
+ usage: {
327
+ input: 0,
328
+ output: 0,
329
+ cacheRead: 0,
330
+ cacheWrite: 0,
331
+ totalTokens: 0,
332
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
333
+ },
334
+ stopReason: "stop",
335
+ timestamp: Date.now(),
336
+ };
337
+ }
338
+
339
+ function endThinkingBlock(stream: AssistantMessageEventStream, output: AssistantMessage, index: number): void {
340
+ const block = output.content[index];
341
+ if (block?.type === "thinking") {
342
+ stream.push({ type: "thinking_end", contentIndex: index, content: block.thinking, partial: output });
343
+ }
344
+ }
345
+
346
+ function endTextBlock(stream: AssistantMessageEventStream, output: AssistantMessage, index: number): void {
347
+ const block = output.content[index];
348
+ if (block?.type === "text") {
349
+ stream.push({ type: "text_end", contentIndex: index, content: block.text, partial: output });
350
+ }
351
+ }
352
+
353
+ function endToolCallBlock(stream: AssistantMessageEventStream, output: AssistantMessage, index: number): void {
354
+ const block = output.content[index];
355
+ if (block?.type !== "toolCall") {
356
+ return;
357
+ }
358
+ const toolCall = block as InternalToolCallBlock;
359
+ if (toolCall.partialJson) {
360
+ toolCall.arguments = parseStreamingJson<Record<string, unknown>>(toolCall.partialJson);
361
+ delete toolCall.partialJson;
362
+ }
363
+ stream.push({ type: "toolcall_end", contentIndex: index, toolCall, partial: output });
364
+ }
365
+
366
+ function mapDoneReason(doneReason: string | undefined, output: AssistantMessage): AssistantMessage["stopReason"] {
367
+ if (doneReason === "length") {
368
+ return "length";
369
+ }
370
+ if (doneReason === "tool_calls") {
371
+ return "toolUse";
372
+ }
373
+ if (doneReason === undefined && output.content.some(block => block.type === "toolCall")) {
374
+ return "toolUse";
375
+ }
376
+ return "stop";
377
+ }
378
+
379
+ const OLLAMA_RETRY_DELAYS_MS = [2_000, 5_000, 10_000];
380
+
381
+ export const streamOllama: StreamFunction<"ollama-chat"> = (
382
+ model: Model<"ollama-chat">,
383
+ context: Context,
384
+ options: OllamaChatOptions,
385
+ ): AssistantMessageEventStream => {
386
+ const stream = new AssistantMessageEventStream();
387
+ void (async () => {
388
+ const startTime = Date.now();
389
+ let firstTokenTime: number | undefined;
390
+ const output = createEmptyOutput(model);
391
+ let rawRequestDump: RawHttpRequestDump | undefined;
392
+ let activeThinkingIndex: number | undefined;
393
+ let activeTextIndex: number | undefined;
394
+ const activeToolIndices = new Set<number>();
395
+ try {
396
+ const apiKey = options.apiKey || getEnvApiKey(model.provider);
397
+ if (!apiKey) {
398
+ throw new Error(`No API key for provider: ${model.provider}`);
399
+ }
400
+ const baseUrl = normalizeBaseUrl(model.baseUrl);
401
+ let body = createChatBody(model, context, options);
402
+ const sentForcedToolChoice = body.tool_choice === "required";
403
+ const replacementPayload = await options.onPayload?.(body, model);
404
+ if (replacementPayload !== undefined) {
405
+ body = replacementPayload as typeof body;
406
+ }
407
+ rawRequestDump = {
408
+ provider: model.provider,
409
+ api: model.api,
410
+ model: model.id,
411
+ method: "POST",
412
+ url: `${baseUrl}/api/chat`,
413
+ body,
414
+ };
415
+ let response = await fetchWithRetry(`${baseUrl}/api/chat`, {
416
+ method: "POST",
417
+ headers: {
418
+ ...model.headers,
419
+ ...options.headers,
420
+ Authorization: `Bearer ${apiKey}`,
421
+ "Content-Type": "application/json",
422
+ },
423
+ body: JSON.stringify(body),
424
+ signal: options.signal,
425
+ maxAttempts: resolveRetryBudget(options.requestMaxRetries, 4) + 1,
426
+ defaultDelayMs: OLLAMA_RETRY_DELAYS_MS,
427
+ fetch: options.fetch,
428
+ });
429
+ if (!response.ok && sentForcedToolChoice) {
430
+ const error = new Error(
431
+ `HTTP ${response.status} from ${baseUrl}/api/chat: ${await response.text().catch(() => "")}`,
432
+ );
433
+ (error as Error & { status?: number }).status = response.status;
434
+ if (firstTokenTime === undefined && isForcedToolChoiceUnsupportedError(error, true)) {
435
+ markToolChoiceIncapability(model, "auto", error.message);
436
+ stream.push({
437
+ type: "toolChoiceIncapability",
438
+ api: model.api,
439
+ provider: model.provider,
440
+ model: model.id,
441
+ requestedLevel: "required",
442
+ resolvedLevel: "auto",
443
+ reason: error.message,
444
+ registryKey: resolveToolChoice(model, options?.toolChoice).registryKey,
445
+ });
446
+ body = { ...body };
447
+ delete (body as { tool_choice?: unknown }).tool_choice;
448
+ rawRequestDump = { ...rawRequestDump, body };
449
+ response = await fetchWithRetry(`${baseUrl}/api/chat`, {
450
+ method: "POST",
451
+ headers: {
452
+ ...model.headers,
453
+ ...options.headers,
454
+ Authorization: `Bearer ${apiKey}`,
455
+ "Content-Type": "application/json",
456
+ },
457
+ body: JSON.stringify(body),
458
+ signal: options.signal,
459
+ maxAttempts: 1,
460
+ defaultDelayMs: OLLAMA_RETRY_DELAYS_MS,
461
+ fetch: options.fetch,
462
+ });
463
+ } else {
464
+ throw error;
465
+ }
466
+ }
467
+ if (!response.ok) {
468
+ throw new Error(`HTTP ${response.status} from ${baseUrl}/api/chat`);
469
+ }
470
+ if (!response.body) {
471
+ throw new Error("Ollama returned an empty response body");
472
+ }
473
+ stream.push({ type: "start", partial: output });
474
+ for await (const chunk of iterateNdjson(response.body)) {
475
+ if (chunk.message?.thinking) {
476
+ if (activeTextIndex !== undefined) {
477
+ endTextBlock(stream, output, activeTextIndex);
478
+ activeTextIndex = undefined;
479
+ }
480
+ if (activeThinkingIndex === undefined) {
481
+ output.content.push({ type: "thinking", thinking: "" });
482
+ activeThinkingIndex = output.content.length - 1;
483
+ stream.push({ type: "thinking_start", contentIndex: activeThinkingIndex, partial: output });
484
+ }
485
+ const block = output.content[activeThinkingIndex];
486
+ if (block?.type === "thinking") {
487
+ block.thinking += chunk.message.thinking;
488
+ stream.push({
489
+ type: "thinking_delta",
490
+ contentIndex: activeThinkingIndex,
491
+ delta: chunk.message.thinking,
492
+ partial: output,
493
+ });
494
+ }
495
+ if (!firstTokenTime) {
496
+ firstTokenTime = Date.now();
497
+ }
498
+ }
499
+ if (chunk.message?.content) {
500
+ if (activeThinkingIndex !== undefined) {
501
+ endThinkingBlock(stream, output, activeThinkingIndex);
502
+ activeThinkingIndex = undefined;
503
+ }
504
+ if (activeTextIndex === undefined) {
505
+ output.content.push({ type: "text", text: "" });
506
+ activeTextIndex = output.content.length - 1;
507
+ stream.push({ type: "text_start", contentIndex: activeTextIndex, partial: output });
508
+ }
509
+ const block = output.content[activeTextIndex];
510
+ if (block?.type === "text") {
511
+ block.text += chunk.message.content;
512
+ stream.push({
513
+ type: "text_delta",
514
+ contentIndex: activeTextIndex,
515
+ delta: chunk.message.content,
516
+ partial: output,
517
+ });
518
+ }
519
+ if (!firstTokenTime) {
520
+ firstTokenTime = Date.now();
521
+ }
522
+ }
523
+ if (chunk.message?.tool_calls?.length) {
524
+ if (activeThinkingIndex !== undefined) {
525
+ endThinkingBlock(stream, output, activeThinkingIndex);
526
+ activeThinkingIndex = undefined;
527
+ }
528
+ if (activeTextIndex !== undefined) {
529
+ endTextBlock(stream, output, activeTextIndex);
530
+ activeTextIndex = undefined;
531
+ }
532
+ for (const call of chunk.message.tool_calls) {
533
+ const name = call.function?.name ?? "unknown_tool";
534
+ const rawArgs = call.function?.arguments;
535
+ const partialJson = typeof rawArgs === "string" ? rawArgs : JSON.stringify(rawArgs ?? {});
536
+ const toolCall: InternalToolCallBlock = {
537
+ type: "toolCall",
538
+ id: `ollama:${output.content.length}:${name}`,
539
+ name,
540
+ arguments: parseStreamingJson<Record<string, unknown>>(partialJson),
541
+ partialJson,
542
+ };
543
+ output.content.push(toolCall);
544
+ const index = output.content.length - 1;
545
+ activeToolIndices.add(index);
546
+ stream.push({ type: "toolcall_start", contentIndex: index, partial: output });
547
+ stream.push({
548
+ type: "toolcall_delta",
549
+ contentIndex: index,
550
+ delta: partialJson,
551
+ partial: output,
552
+ });
553
+ if (!firstTokenTime) {
554
+ firstTokenTime = Date.now();
555
+ }
556
+ }
557
+ }
558
+ if (chunk.done) {
559
+ if (activeThinkingIndex !== undefined) {
560
+ endThinkingBlock(stream, output, activeThinkingIndex);
561
+ activeThinkingIndex = undefined;
562
+ }
563
+ if (activeTextIndex !== undefined) {
564
+ endTextBlock(stream, output, activeTextIndex);
565
+ activeTextIndex = undefined;
566
+ }
567
+ for (const index of activeToolIndices) {
568
+ endToolCallBlock(stream, output, index);
569
+ }
570
+ activeToolIndices.clear();
571
+ output.stopReason = mapDoneReason(chunk.done_reason, output);
572
+ output.usage.input = chunk.prompt_eval_count ?? 0;
573
+ output.usage.output = chunk.eval_count ?? 0;
574
+ output.usage.totalTokens = output.usage.input + output.usage.output;
575
+ }
576
+ }
577
+ output.duration = Date.now() - startTime;
578
+ if (firstTokenTime) {
579
+ output.ttft = firstTokenTime - startTime;
580
+ }
581
+ const doneReason =
582
+ output.stopReason === "length" ? "length" : output.stopReason === "toolUse" ? "toolUse" : "stop";
583
+ stream.push({ type: "done", reason: doneReason, message: output });
584
+ stream.end();
585
+ } catch (error) {
586
+ for (const block of output.content) {
587
+ if (block.type === "toolCall") {
588
+ delete (block as InternalToolCallBlock).partialJson;
589
+ }
590
+ }
591
+ output.stopReason = options.signal?.aborted ? "aborted" : "error";
592
+ output.errorStatus = extractHttpStatusFromError(error);
593
+ output.errorMessage = await finalizeErrorMessage(error, rawRequestDump);
594
+ output.duration = Date.now() - startTime;
595
+ if (firstTokenTime) {
596
+ output.ttft = firstTokenTime - startTime;
597
+ }
598
+ stream.push({ type: "error", reason: output.stopReason, error: output });
599
+ stream.end();
600
+ }
601
+ })();
602
+ return stream;
603
+ };