@calliopelabs/cli 0.8.20 → 2.0.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 (402) hide show
  1. package/dist/agents/agent-config-loader.d.ts +60 -0
  2. package/dist/agents/agent-config-loader.d.ts.map +1 -0
  3. package/dist/agents/agent-config-loader.js +402 -0
  4. package/dist/agents/agent-config-loader.js.map +1 -0
  5. package/dist/agents/agent-config-presets.d.ts +10 -0
  6. package/dist/agents/agent-config-presets.d.ts.map +1 -0
  7. package/dist/agents/agent-config-presets.js +940 -0
  8. package/dist/agents/agent-config-presets.js.map +1 -0
  9. package/dist/agents/agent-config-types.d.ts +145 -0
  10. package/dist/agents/agent-config-types.d.ts.map +1 -0
  11. package/dist/agents/agent-config-types.js +12 -0
  12. package/dist/agents/agent-config-types.js.map +1 -0
  13. package/dist/{agterm → agents}/agent-detection.d.ts +1 -1
  14. package/dist/{agterm → agents}/agent-detection.d.ts.map +1 -1
  15. package/dist/{agterm → agents}/agent-detection.js +21 -5
  16. package/dist/agents/agent-detection.js.map +1 -0
  17. package/dist/agents/aggregator.d.ts +19 -0
  18. package/dist/agents/aggregator.d.ts.map +1 -0
  19. package/dist/agents/aggregator.js +141 -0
  20. package/dist/agents/aggregator.js.map +1 -0
  21. package/dist/{agterm → agents}/cli-backend.d.ts +1 -1
  22. package/dist/{agterm → agents}/cli-backend.d.ts.map +1 -1
  23. package/dist/{agterm → agents}/cli-backend.js +90 -12
  24. package/dist/agents/cli-backend.js.map +1 -0
  25. package/dist/agents/council-types.d.ts +113 -0
  26. package/dist/agents/council-types.d.ts.map +1 -0
  27. package/dist/agents/council-types.js +81 -0
  28. package/dist/agents/council-types.js.map +1 -0
  29. package/dist/agents/council.d.ts +107 -0
  30. package/dist/agents/council.d.ts.map +1 -0
  31. package/dist/agents/council.js +586 -0
  32. package/dist/agents/council.js.map +1 -0
  33. package/dist/agents/decomposer.d.ts +33 -0
  34. package/dist/agents/decomposer.d.ts.map +1 -0
  35. package/dist/agents/decomposer.js +138 -0
  36. package/dist/agents/decomposer.js.map +1 -0
  37. package/dist/agents/dynamic-tools.d.ts +52 -0
  38. package/dist/agents/dynamic-tools.d.ts.map +1 -0
  39. package/dist/agents/dynamic-tools.js +395 -0
  40. package/dist/agents/dynamic-tools.js.map +1 -0
  41. package/dist/agents/index.d.ts +29 -0
  42. package/dist/agents/index.d.ts.map +1 -0
  43. package/dist/agents/index.js +29 -0
  44. package/dist/agents/index.js.map +1 -0
  45. package/dist/agents/installer.d.ts +39 -0
  46. package/dist/agents/installer.d.ts.map +1 -0
  47. package/dist/agents/installer.js +205 -0
  48. package/dist/agents/installer.js.map +1 -0
  49. package/dist/{agterm → agents}/orchestrator.d.ts +7 -2
  50. package/dist/agents/orchestrator.d.ts.map +1 -0
  51. package/dist/{agterm → agents}/orchestrator.js +22 -2
  52. package/dist/agents/orchestrator.js.map +1 -0
  53. package/dist/agents/sdk-backend.d.ts +63 -0
  54. package/dist/agents/sdk-backend.d.ts.map +1 -0
  55. package/dist/agents/sdk-backend.js +489 -0
  56. package/dist/agents/sdk-backend.js.map +1 -0
  57. package/dist/agents/swarm-types.d.ts +83 -0
  58. package/dist/agents/swarm-types.d.ts.map +1 -0
  59. package/dist/agents/swarm-types.js +20 -0
  60. package/dist/agents/swarm-types.js.map +1 -0
  61. package/dist/agents/swarm.d.ts +74 -0
  62. package/dist/agents/swarm.d.ts.map +1 -0
  63. package/dist/agents/swarm.js +307 -0
  64. package/dist/agents/swarm.js.map +1 -0
  65. package/dist/{agterm → agents}/tools.d.ts +7 -5
  66. package/dist/agents/tools.d.ts.map +1 -0
  67. package/dist/agents/tools.js +776 -0
  68. package/dist/agents/tools.js.map +1 -0
  69. package/dist/{agterm → agents}/types.d.ts +14 -2
  70. package/dist/agents/types.d.ts.map +1 -0
  71. package/dist/{agterm → agents}/types.js +2 -2
  72. package/dist/agents/types.js.map +1 -0
  73. package/dist/api-server.d.ts +26 -0
  74. package/dist/api-server.d.ts.map +1 -0
  75. package/dist/api-server.js +230 -0
  76. package/dist/api-server.js.map +1 -0
  77. package/dist/auto-checkpoint.d.ts +35 -0
  78. package/dist/auto-checkpoint.d.ts.map +1 -0
  79. package/dist/auto-checkpoint.js +143 -0
  80. package/dist/auto-checkpoint.js.map +1 -0
  81. package/dist/auto-compressor.d.ts +44 -0
  82. package/dist/auto-compressor.d.ts.map +1 -0
  83. package/dist/auto-compressor.js +145 -0
  84. package/dist/auto-compressor.js.map +1 -0
  85. package/dist/background-jobs.d.ts +45 -0
  86. package/dist/background-jobs.d.ts.map +1 -0
  87. package/dist/background-jobs.js +122 -0
  88. package/dist/background-jobs.js.map +1 -0
  89. package/dist/bin.d.ts +6 -2
  90. package/dist/bin.d.ts.map +1 -1
  91. package/dist/bin.js +127 -24
  92. package/dist/bin.js.map +1 -1
  93. package/dist/checkpoint.d.ts +49 -0
  94. package/dist/checkpoint.d.ts.map +1 -0
  95. package/dist/checkpoint.js +219 -0
  96. package/dist/checkpoint.js.map +1 -0
  97. package/dist/circuit-breaker/breaker.d.ts +80 -0
  98. package/dist/circuit-breaker/breaker.d.ts.map +1 -0
  99. package/dist/circuit-breaker/breaker.js +408 -0
  100. package/dist/circuit-breaker/breaker.js.map +1 -0
  101. package/dist/circuit-breaker/defaults.d.ts +8 -0
  102. package/dist/circuit-breaker/defaults.d.ts.map +1 -0
  103. package/dist/circuit-breaker/defaults.js +35 -0
  104. package/dist/circuit-breaker/defaults.js.map +1 -0
  105. package/dist/circuit-breaker/index.d.ts +9 -0
  106. package/dist/circuit-breaker/index.d.ts.map +1 -0
  107. package/dist/circuit-breaker/index.js +8 -0
  108. package/dist/circuit-breaker/index.js.map +1 -0
  109. package/dist/circuit-breaker/types.d.ts +77 -0
  110. package/dist/circuit-breaker/types.d.ts.map +1 -0
  111. package/dist/circuit-breaker/types.js +8 -0
  112. package/dist/circuit-breaker/types.js.map +1 -0
  113. package/dist/circuit-breaker.d.ts +8 -0
  114. package/dist/circuit-breaker.d.ts.map +1 -0
  115. package/dist/circuit-breaker.js +7 -0
  116. package/dist/circuit-breaker.js.map +1 -0
  117. package/dist/cli/agent.d.ts +9 -0
  118. package/dist/cli/agent.d.ts.map +1 -0
  119. package/dist/cli/agent.js +262 -0
  120. package/dist/cli/agent.js.map +1 -0
  121. package/dist/cli/commands.d.ts +12 -0
  122. package/dist/cli/commands.d.ts.map +1 -0
  123. package/dist/{cli.js → cli/commands.js} +285 -422
  124. package/dist/cli/commands.js.map +1 -0
  125. package/dist/cli/index.d.ts +8 -0
  126. package/dist/cli/index.d.ts.map +1 -0
  127. package/dist/cli/index.js +222 -0
  128. package/dist/cli/index.js.map +1 -0
  129. package/dist/cli/types.d.ts +30 -0
  130. package/dist/cli/types.d.ts.map +1 -0
  131. package/dist/cli/types.js +20 -0
  132. package/dist/cli/types.js.map +1 -0
  133. package/dist/companions.d.ts +54 -0
  134. package/dist/companions.d.ts.map +1 -0
  135. package/dist/companions.js +440 -0
  136. package/dist/companions.js.map +1 -0
  137. package/dist/config.d.ts +23 -1
  138. package/dist/config.d.ts.map +1 -1
  139. package/dist/config.js +95 -22
  140. package/dist/config.js.map +1 -1
  141. package/dist/diff.d.ts +27 -0
  142. package/dist/diff.d.ts.map +1 -1
  143. package/dist/diff.js +415 -10
  144. package/dist/diff.js.map +1 -1
  145. package/dist/errors.d.ts.map +1 -1
  146. package/dist/errors.js +20 -11
  147. package/dist/errors.js.map +1 -1
  148. package/dist/git-status.d.ts +23 -0
  149. package/dist/git-status.d.ts.map +1 -0
  150. package/dist/git-status.js +92 -0
  151. package/dist/git-status.js.map +1 -0
  152. package/dist/headless.d.ts +25 -0
  153. package/dist/headless.d.ts.map +1 -0
  154. package/dist/headless.js +182 -0
  155. package/dist/headless.js.map +1 -0
  156. package/dist/hud/api.d.ts +35 -0
  157. package/dist/hud/api.d.ts.map +1 -0
  158. package/dist/hud/api.js +448 -0
  159. package/dist/hud/api.js.map +1 -0
  160. package/dist/hud/palettes.d.ts +9 -0
  161. package/dist/hud/palettes.d.ts.map +1 -0
  162. package/dist/hud/palettes.js +280 -0
  163. package/dist/hud/palettes.js.map +1 -0
  164. package/dist/hud/skins.d.ts +12 -0
  165. package/dist/hud/skins.d.ts.map +1 -0
  166. package/dist/hud/skins.js +365 -0
  167. package/dist/hud/skins.js.map +1 -0
  168. package/dist/hud/theme-packs/api.d.ts +51 -0
  169. package/dist/hud/theme-packs/api.d.ts.map +1 -0
  170. package/dist/hud/theme-packs/api.js +145 -0
  171. package/dist/hud/theme-packs/api.js.map +1 -0
  172. package/dist/hud/theme-packs/index.d.ts +18 -0
  173. package/dist/hud/theme-packs/index.d.ts.map +1 -0
  174. package/dist/hud/theme-packs/index.js +38 -0
  175. package/dist/hud/theme-packs/index.js.map +1 -0
  176. package/dist/hud/theme-packs/types.d.ts +29 -0
  177. package/dist/hud/theme-packs/types.d.ts.map +1 -0
  178. package/dist/hud/theme-packs/types.js +9 -0
  179. package/dist/hud/theme-packs/types.js.map +1 -0
  180. package/dist/hud/types.d.ts +182 -0
  181. package/dist/hud/types.d.ts.map +1 -0
  182. package/dist/hud/types.js +7 -0
  183. package/dist/hud/types.js.map +1 -0
  184. package/dist/idle-eviction.d.ts +34 -0
  185. package/dist/idle-eviction.d.ts.map +1 -0
  186. package/dist/idle-eviction.js +78 -0
  187. package/dist/idle-eviction.js.map +1 -0
  188. package/dist/index.d.ts +9 -3
  189. package/dist/index.d.ts.map +1 -1
  190. package/dist/index.js +9 -3
  191. package/dist/index.js.map +1 -1
  192. package/dist/iteration-ledger.d.ts +105 -0
  193. package/dist/iteration-ledger.d.ts.map +1 -0
  194. package/dist/iteration-ledger.js +237 -0
  195. package/dist/iteration-ledger.js.map +1 -0
  196. package/dist/markdown.d.ts.map +1 -1
  197. package/dist/markdown.js +1 -27
  198. package/dist/markdown.js.map +1 -1
  199. package/dist/mcp.d.ts +35 -0
  200. package/dist/mcp.d.ts.map +1 -1
  201. package/dist/mcp.js +291 -7
  202. package/dist/mcp.js.map +1 -1
  203. package/dist/memory.d.ts.map +1 -1
  204. package/dist/memory.js +12 -2
  205. package/dist/memory.js.map +1 -1
  206. package/dist/model-detection.d.ts +5 -0
  207. package/dist/model-detection.d.ts.map +1 -1
  208. package/dist/model-detection.js +278 -10
  209. package/dist/model-detection.js.map +1 -1
  210. package/dist/model-router.d.ts.map +1 -1
  211. package/dist/model-router.js +33 -11
  212. package/dist/model-router.js.map +1 -1
  213. package/dist/plugins.d.ts +8 -0
  214. package/dist/plugins.d.ts.map +1 -1
  215. package/dist/plugins.js +97 -6
  216. package/dist/plugins.js.map +1 -1
  217. package/dist/providers/anthropic.d.ts +10 -0
  218. package/dist/providers/anthropic.d.ts.map +1 -0
  219. package/dist/providers/anthropic.js +221 -0
  220. package/dist/providers/anthropic.js.map +1 -0
  221. package/dist/providers/bedrock.d.ts +17 -0
  222. package/dist/providers/bedrock.d.ts.map +1 -0
  223. package/dist/providers/bedrock.js +574 -0
  224. package/dist/providers/bedrock.js.map +1 -0
  225. package/dist/providers/compat.d.ts +13 -0
  226. package/dist/providers/compat.d.ts.map +1 -0
  227. package/dist/providers/compat.js +202 -0
  228. package/dist/providers/compat.js.map +1 -0
  229. package/dist/providers/google.d.ts +10 -0
  230. package/dist/providers/google.d.ts.map +1 -0
  231. package/dist/providers/google.js +203 -0
  232. package/dist/providers/google.js.map +1 -0
  233. package/dist/providers/index.d.ts +23 -0
  234. package/dist/providers/index.d.ts.map +1 -0
  235. package/dist/providers/index.js +145 -0
  236. package/dist/providers/index.js.map +1 -0
  237. package/dist/providers/ollama.d.ts +17 -0
  238. package/dist/providers/ollama.d.ts.map +1 -0
  239. package/dist/providers/ollama.js +289 -0
  240. package/dist/providers/ollama.js.map +1 -0
  241. package/dist/providers/openai.d.ts +121 -0
  242. package/dist/providers/openai.d.ts.map +1 -0
  243. package/dist/providers/openai.js +485 -0
  244. package/dist/providers/openai.js.map +1 -0
  245. package/dist/providers/types.d.ts +63 -0
  246. package/dist/providers/types.d.ts.map +1 -0
  247. package/dist/providers/types.js +164 -0
  248. package/dist/providers/types.js.map +1 -0
  249. package/dist/sandbox-native.d.ts +59 -0
  250. package/dist/sandbox-native.d.ts.map +1 -0
  251. package/dist/sandbox-native.js +292 -0
  252. package/dist/sandbox-native.js.map +1 -0
  253. package/dist/sandbox.d.ts +2 -2
  254. package/dist/sandbox.d.ts.map +1 -1
  255. package/dist/sandbox.js +59 -13
  256. package/dist/sandbox.js.map +1 -1
  257. package/dist/scope.d.ts +3 -1
  258. package/dist/scope.d.ts.map +1 -1
  259. package/dist/scope.js +13 -1
  260. package/dist/scope.js.map +1 -1
  261. package/dist/session-timeout.d.ts +31 -0
  262. package/dist/session-timeout.d.ts.map +1 -0
  263. package/dist/session-timeout.js +100 -0
  264. package/dist/session-timeout.js.map +1 -0
  265. package/dist/setup.d.ts.map +1 -1
  266. package/dist/setup.js +29 -17
  267. package/dist/setup.js.map +1 -1
  268. package/dist/smart-router.d.ts +73 -0
  269. package/dist/smart-router.d.ts.map +1 -0
  270. package/dist/smart-router.js +332 -0
  271. package/dist/smart-router.js.map +1 -0
  272. package/dist/storage.d.ts +19 -0
  273. package/dist/storage.d.ts.map +1 -1
  274. package/dist/storage.js +164 -1
  275. package/dist/storage.js.map +1 -1
  276. package/dist/streaming.d.ts +4 -0
  277. package/dist/streaming.d.ts.map +1 -1
  278. package/dist/streaming.js +12 -0
  279. package/dist/streaming.js.map +1 -1
  280. package/dist/styles.d.ts +32 -0
  281. package/dist/styles.d.ts.map +1 -1
  282. package/dist/styles.js +91 -0
  283. package/dist/styles.js.map +1 -1
  284. package/dist/summarization.d.ts +1 -1
  285. package/dist/summarization.js +4 -4
  286. package/dist/summarization.js.map +1 -1
  287. package/dist/terminal-image.d.ts +115 -0
  288. package/dist/terminal-image.d.ts.map +1 -0
  289. package/dist/terminal-image.js +766 -0
  290. package/dist/terminal-image.js.map +1 -0
  291. package/dist/terminal-recording.d.ts +55 -0
  292. package/dist/terminal-recording.d.ts.map +1 -0
  293. package/dist/terminal-recording.js +182 -0
  294. package/dist/terminal-recording.js.map +1 -0
  295. package/dist/themes.d.ts +19 -35
  296. package/dist/themes.d.ts.map +1 -1
  297. package/dist/themes.js +101 -210
  298. package/dist/themes.js.map +1 -1
  299. package/dist/tmux.d.ts +35 -0
  300. package/dist/tmux.d.ts.map +1 -0
  301. package/dist/tmux.js +106 -0
  302. package/dist/tmux.js.map +1 -0
  303. package/dist/tools.d.ts +3 -3
  304. package/dist/tools.d.ts.map +1 -1
  305. package/dist/tools.js +587 -45
  306. package/dist/tools.js.map +1 -1
  307. package/dist/trust.d.ts +53 -0
  308. package/dist/trust.d.ts.map +1 -0
  309. package/dist/trust.js +154 -0
  310. package/dist/trust.js.map +1 -0
  311. package/dist/types.d.ts +7 -3
  312. package/dist/types.d.ts.map +1 -1
  313. package/dist/types.js +70 -32
  314. package/dist/types.js.map +1 -1
  315. package/dist/ui/agent.d.ts +61 -0
  316. package/dist/ui/agent.d.ts.map +1 -0
  317. package/dist/ui/agent.js +768 -0
  318. package/dist/ui/agent.js.map +1 -0
  319. package/dist/ui/chat-input.d.ts +32 -0
  320. package/dist/ui/chat-input.d.ts.map +1 -0
  321. package/dist/ui/chat-input.js +355 -0
  322. package/dist/ui/chat-input.js.map +1 -0
  323. package/dist/ui/commands.d.ts +92 -0
  324. package/dist/ui/commands.d.ts.map +1 -0
  325. package/dist/ui/commands.js +3006 -0
  326. package/dist/ui/commands.js.map +1 -0
  327. package/dist/ui/completions.d.ts +22 -0
  328. package/dist/ui/completions.d.ts.map +1 -0
  329. package/dist/ui/completions.js +215 -0
  330. package/dist/ui/completions.js.map +1 -0
  331. package/dist/ui/components.d.ts +38 -0
  332. package/dist/ui/components.d.ts.map +1 -0
  333. package/dist/ui/components.js +422 -0
  334. package/dist/ui/components.js.map +1 -0
  335. package/dist/ui/context.d.ts +12 -0
  336. package/dist/ui/context.d.ts.map +1 -0
  337. package/dist/ui/context.js +102 -0
  338. package/dist/ui/context.js.map +1 -0
  339. package/dist/ui/error-boundary.d.ts +33 -0
  340. package/dist/ui/error-boundary.d.ts.map +1 -0
  341. package/dist/ui/error-boundary.js +94 -0
  342. package/dist/ui/error-boundary.js.map +1 -0
  343. package/dist/ui/frame.d.ts +13 -0
  344. package/dist/ui/frame.d.ts.map +1 -0
  345. package/dist/ui/frame.js +89 -0
  346. package/dist/ui/frame.js.map +1 -0
  347. package/dist/ui/index.d.ts +12 -0
  348. package/dist/ui/index.d.ts.map +1 -0
  349. package/dist/ui/index.js +928 -0
  350. package/dist/ui/index.js.map +1 -0
  351. package/dist/ui/messages.d.ts +19 -0
  352. package/dist/ui/messages.d.ts.map +1 -0
  353. package/dist/ui/messages.js +181 -0
  354. package/dist/ui/messages.js.map +1 -0
  355. package/dist/ui/modals.d.ts +52 -0
  356. package/dist/ui/modals.d.ts.map +1 -0
  357. package/dist/ui/modals.js +204 -0
  358. package/dist/ui/modals.js.map +1 -0
  359. package/dist/ui/pack-picker.d.ts +12 -0
  360. package/dist/ui/pack-picker.d.ts.map +1 -0
  361. package/dist/ui/pack-picker.js +101 -0
  362. package/dist/ui/pack-picker.js.map +1 -0
  363. package/dist/ui/status-bar.d.ts +20 -0
  364. package/dist/ui/status-bar.d.ts.map +1 -0
  365. package/dist/ui/status-bar.js +41 -0
  366. package/dist/ui/status-bar.js.map +1 -0
  367. package/dist/ui/theme-picker.d.ts +24 -0
  368. package/dist/ui/theme-picker.d.ts.map +1 -0
  369. package/dist/ui/theme-picker.js +190 -0
  370. package/dist/ui/theme-picker.js.map +1 -0
  371. package/dist/ui/types.d.ts +62 -0
  372. package/dist/ui/types.d.ts.map +1 -0
  373. package/dist/ui/types.js +7 -0
  374. package/dist/ui/types.js.map +1 -0
  375. package/dist/version-check.d.ts.map +1 -1
  376. package/dist/version-check.js +1 -9
  377. package/dist/version-check.js.map +1 -1
  378. package/package.json +8 -3
  379. package/dist/agterm/agent-detection.js.map +0 -1
  380. package/dist/agterm/cli-backend.js.map +0 -1
  381. package/dist/agterm/index.d.ts +0 -12
  382. package/dist/agterm/index.d.ts.map +0 -1
  383. package/dist/agterm/index.js +0 -15
  384. package/dist/agterm/index.js.map +0 -1
  385. package/dist/agterm/orchestrator.d.ts.map +0 -1
  386. package/dist/agterm/orchestrator.js.map +0 -1
  387. package/dist/agterm/tools.d.ts.map +0 -1
  388. package/dist/agterm/tools.js +0 -278
  389. package/dist/agterm/tools.js.map +0 -1
  390. package/dist/agterm/types.d.ts.map +0 -1
  391. package/dist/agterm/types.js.map +0 -1
  392. package/dist/cli.d.ts +0 -14
  393. package/dist/cli.d.ts.map +0 -1
  394. package/dist/cli.js.map +0 -1
  395. package/dist/providers.d.ts +0 -51
  396. package/dist/providers.d.ts.map +0 -1
  397. package/dist/providers.js +0 -1146
  398. package/dist/providers.js.map +0 -1
  399. package/dist/ui-cli.d.ts +0 -17
  400. package/dist/ui-cli.d.ts.map +0 -1
  401. package/dist/ui-cli.js +0 -3730
  402. package/dist/ui-cli.js.map +0 -1
package/dist/providers.js DELETED
@@ -1,1146 +0,0 @@
1
- /**
2
- * Calliope CLI - LLM Providers
3
- *
4
- * Handles communication with different LLM providers.
5
- */
6
- import Anthropic from '@anthropic-ai/sdk';
7
- import { GoogleGenerativeAI } from '@google/generative-ai';
8
- import OpenAI from 'openai';
9
- import * as config from './config.js';
10
- import { withRetry } from './errors.js';
11
- import { getModelContextLimit } from './model-detection.js';
12
- import { DEFAULT_MODELS } from './types.js';
13
- /**
14
- * Extract text from MessageContent
15
- */
16
- function getTextContent(content) {
17
- if (typeof content === 'string') {
18
- return content;
19
- }
20
- return content
21
- .filter(block => block.type === 'text')
22
- .map(block => block.text)
23
- .join('\n');
24
- }
25
- /**
26
- * Convert MessageContent to Anthropic content format
27
- */
28
- function toAnthropicContent(content) {
29
- if (typeof content === 'string') {
30
- return content;
31
- }
32
- return content.map(block => {
33
- if (block.type === 'text') {
34
- return { type: 'text', text: block.text };
35
- }
36
- else if (block.type === 'image') {
37
- return {
38
- type: 'image',
39
- source: {
40
- type: 'base64',
41
- media_type: block.mediaType,
42
- data: block.data,
43
- },
44
- };
45
- }
46
- return { type: 'text', text: '' };
47
- });
48
- }
49
- /**
50
- * Convert MessageContent to OpenAI content format
51
- */
52
- function toOpenAIContent(content) {
53
- if (typeof content === 'string') {
54
- return content;
55
- }
56
- return content.map(block => {
57
- if (block.type === 'text') {
58
- return { type: 'text', text: block.text };
59
- }
60
- else if (block.type === 'image') {
61
- return {
62
- type: 'image_url',
63
- image_url: {
64
- url: `data:${block.mediaType};base64,${block.data}`,
65
- },
66
- };
67
- }
68
- return { type: 'text', text: '' };
69
- });
70
- }
71
- // Constants
72
- const MAX_TOKENS = 8192;
73
- const MIN_OUTPUT_TOKENS = 1024; // Minimum output tokens to request
74
- const CONTEXT_BUFFER_PERCENT = 0.08; // 8% of context as safety buffer
75
- const CONTEXT_BUFFER_MIN = 5000; // Minimum 5k buffer
76
- // Debug logging helper
77
- const DEBUG = process.env.CALLIOPE_DEBUG === '1';
78
- function debugLog(message, ...args) {
79
- if (DEBUG)
80
- console.log(`[DEBUG] ${message}`, ...args);
81
- }
82
- /**
83
- * Estimate tokens from messages (conservative: ~3 chars per token)
84
- * Uses conservative estimation to avoid context overflow
85
- */
86
- function estimateInputTokens(messages, tools) {
87
- let totalChars = 0;
88
- for (const msg of messages) {
89
- // Add per-message overhead (role, structure, etc.)
90
- totalChars += 50;
91
- if (typeof msg.content === 'string') {
92
- totalChars += msg.content.length;
93
- }
94
- else if (Array.isArray(msg.content)) {
95
- for (const block of msg.content) {
96
- if (block.type === 'text') {
97
- totalChars += block.text.length;
98
- }
99
- else if (block.type === 'image') {
100
- // Images are roughly 85 tokens per tile (assuming ~750 tokens average)
101
- totalChars += 3000;
102
- }
103
- }
104
- }
105
- // Add overhead for tool calls in assistant messages
106
- if (msg.toolCalls) {
107
- totalChars += JSON.stringify(msg.toolCalls).length;
108
- }
109
- }
110
- // Add tool definitions overhead
111
- if (tools.length > 0) {
112
- totalChars += JSON.stringify(tools).length;
113
- }
114
- // Very conservative estimate: 2.5 characters per token
115
- // Plus 35% overhead for message structure, system prompt, and formatting
116
- return Math.ceil((totalChars / 2.5) * 1.35);
117
- }
118
- /**
119
- * Calculate dynamic max_tokens based on available context space
120
- */
121
- function calculateMaxTokens(provider, model, messages, tools) {
122
- const contextLimit = getModelContextLimit(provider, model);
123
- const estimatedInput = estimateInputTokens(messages, tools);
124
- // Use percentage-based buffer with minimum floor
125
- const buffer = Math.max(CONTEXT_BUFFER_MIN, Math.ceil(contextLimit * CONTEXT_BUFFER_PERCENT));
126
- const available = contextLimit - estimatedInput - buffer;
127
- debugLog(`Context calculation: limit=${contextLimit}, input≈${estimatedInput}, buffer=${buffer}, available=${available}`);
128
- // Ensure we have at least MIN_OUTPUT_TOKENS, up to MAX_TOKENS
129
- if (available < MIN_OUTPUT_TOKENS) {
130
- debugLog(`WARNING: Very limited output space (${available}), using minimum ${MIN_OUTPUT_TOKENS}`);
131
- return MIN_OUTPUT_TOKENS;
132
- }
133
- return Math.min(MAX_TOKENS, available);
134
- }
135
- /**
136
- * Check if context needs summarization based on actual token usage
137
- * Call this with the input_tokens from the last API response
138
- */
139
- export function needsSummarization(provider, model, actualInputTokens) {
140
- const contextLimit = getModelContextLimit(provider, model);
141
- const threshold = contextLimit * 0.85; // Trigger summarization at 85% full
142
- return actualInputTokens >= threshold;
143
- }
144
- /**
145
- * Get context health info
146
- */
147
- export function getContextHealth(provider, model, actualInputTokens) {
148
- const limit = getModelContextLimit(provider, model);
149
- const percent = Math.round((actualInputTokens / limit) * 100);
150
- return {
151
- limit,
152
- used: actualInputTokens,
153
- percent,
154
- needsSummarization: actualInputTokens >= limit * 0.85,
155
- };
156
- }
157
- /**
158
- * Estimate context usage before making a request (for pre-request summarization)
159
- * Uses conservative estimation since we don't have actual token counts yet
160
- */
161
- export function estimateContextUsage(provider, model, messages, tools) {
162
- const estimated = estimateInputTokens(messages, tools);
163
- const limit = getModelContextLimit(provider, model);
164
- const percent = Math.round((estimated / limit) * 100);
165
- return {
166
- estimated,
167
- limit,
168
- percent,
169
- needsSummarization: estimated >= limit * 0.80, // More aggressive threshold for estimates
170
- };
171
- }
172
- // ============================================================================
173
- // LLM Response Validation
174
- // ============================================================================
175
- /** Maximum allowed content length (1MB) to prevent memory issues */
176
- const MAX_CONTENT_LENGTH = 1024 * 1024;
177
- /**
178
- * Validate and sanitize LLM response
179
- */
180
- function validateLLMResponse(response) {
181
- // Ensure content is a string
182
- if (response.content === null || response.content === undefined) {
183
- response.content = '';
184
- }
185
- else if (typeof response.content !== 'string') {
186
- response.content = String(response.content);
187
- }
188
- // Truncate if too long to prevent memory issues
189
- if (response.content.length > MAX_CONTENT_LENGTH) {
190
- debugLog('Response content truncated from', response.content.length, 'to', MAX_CONTENT_LENGTH);
191
- response.content = response.content.slice(0, MAX_CONTENT_LENGTH) + '\n... [truncated]';
192
- }
193
- // Validate tool calls if present
194
- if (response.toolCalls) {
195
- response.toolCalls = response.toolCalls.filter(call => {
196
- if (!call.id || typeof call.id !== 'string') {
197
- debugLog('Invalid tool call: missing or invalid id', call);
198
- return false;
199
- }
200
- if (!call.name || typeof call.name !== 'string') {
201
- debugLog('Invalid tool call: missing or invalid name', call);
202
- return false;
203
- }
204
- if (call.arguments === null || call.arguments === undefined) {
205
- call.arguments = {};
206
- }
207
- return true;
208
- });
209
- if (response.toolCalls.length === 0) {
210
- response.toolCalls = undefined;
211
- }
212
- }
213
- // Ensure valid finish reason
214
- if (!['stop', 'tool_use', 'length', 'error'].includes(response.finishReason)) {
215
- response.finishReason = 'stop';
216
- }
217
- return response;
218
- }
219
- // ============================================================================
220
- /**
221
- * Models that require the Responses API instead of Chat Completions
222
- * These are OpenAI's newer reasoning models that only work with /v1/responses
223
- */
224
- const RESPONSES_API_MODELS = [
225
- 'o3',
226
- 'o3-mini',
227
- 'o3-pro',
228
- 'o4-mini',
229
- 'gpt-5',
230
- ];
231
- /**
232
- * Check if a model requires the Responses API
233
- */
234
- function requiresResponsesAPI(model) {
235
- return RESPONSES_API_MODELS.some(m => model.startsWith(m));
236
- }
237
- // API base URLs for OpenAI-compatible providers
238
- const PROVIDER_BASE_URLS = {
239
- openrouter: 'https://openrouter.ai/api/v1',
240
- together: 'https://api.together.xyz/v1',
241
- groq: 'https://api.groq.com/openai/v1',
242
- fireworks: 'https://api.fireworks.ai/inference/v1',
243
- mistral: 'https://api.mistral.ai/v1',
244
- ai21: 'https://api.ai21.com/studio/v1',
245
- huggingface: 'https://api-inference.huggingface.co/v1',
246
- };
247
- /**
248
- * Convert messages to OpenAI format
249
- */
250
- function toOpenAIMessages(messages) {
251
- return messages.map(m => {
252
- if (m.role === 'tool') {
253
- return {
254
- role: 'tool',
255
- tool_call_id: m.toolCallId || '',
256
- content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
257
- };
258
- }
259
- if (m.toolCalls && m.toolCalls.length > 0) {
260
- return {
261
- role: 'assistant',
262
- content: typeof m.content === 'string' ? m.content : (m.content ? JSON.stringify(m.content) : null),
263
- tool_calls: m.toolCalls.map(tc => ({
264
- id: tc.id,
265
- type: 'function',
266
- function: {
267
- name: tc.name,
268
- arguments: JSON.stringify(tc.arguments),
269
- },
270
- })),
271
- };
272
- }
273
- // Handle multi-modal content for user messages
274
- if (m.role === 'user' && Array.isArray(m.content)) {
275
- return {
276
- role: 'user',
277
- content: toOpenAIContent(m.content),
278
- };
279
- }
280
- return {
281
- role: m.role,
282
- content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
283
- };
284
- });
285
- }
286
- /**
287
- * Convert tools to OpenAI format
288
- */
289
- function toOpenAITools(tools) {
290
- return tools.map(t => ({
291
- type: 'function',
292
- function: {
293
- name: t.name,
294
- description: t.description,
295
- parameters: t.parameters,
296
- },
297
- }));
298
- }
299
- /**
300
- * Parse tool calls from OpenAI response
301
- */
302
- function parseOpenAIToolCalls(toolCalls) {
303
- if (!toolCalls)
304
- return [];
305
- const result = [];
306
- for (const tc of toolCalls) {
307
- let parsedArgs = {};
308
- try {
309
- parsedArgs = JSON.parse(tc.function.arguments);
310
- }
311
- catch (error) {
312
- const parseError = error instanceof SyntaxError ? error.message : 'Unknown parse error';
313
- throw new Error(`Invalid tool arguments from LLM: ${parseError}. Raw: ${tc.function.arguments.substring(0, 200)}`);
314
- }
315
- result.push({
316
- id: tc.id,
317
- name: tc.function.name,
318
- arguments: parsedArgs,
319
- });
320
- }
321
- return result;
322
- }
323
- /**
324
- * Get available providers based on configured API keys
325
- */
326
- export function getAvailableProviders() {
327
- const providers = [];
328
- if (config.getApiKey('anthropic'))
329
- providers.push('anthropic');
330
- if (config.getApiKey('google'))
331
- providers.push('google');
332
- if (config.getApiKey('openai'))
333
- providers.push('openai');
334
- if (config.getApiKey('openrouter'))
335
- providers.push('openrouter');
336
- if (config.getApiKey('together'))
337
- providers.push('together');
338
- if (config.getApiKey('groq'))
339
- providers.push('groq');
340
- if (config.getApiKey('mistral'))
341
- providers.push('mistral');
342
- if (config.getBaseUrl('ollama'))
343
- providers.push('ollama');
344
- if (config.getApiKey('ai21'))
345
- providers.push('ai21');
346
- if (config.getApiKey('huggingface'))
347
- providers.push('huggingface');
348
- if (config.getBaseUrl('litellm'))
349
- providers.push('litellm');
350
- return providers;
351
- }
352
- /**
353
- * Select the best available provider
354
- */
355
- export function selectProvider(preferred) {
356
- if (preferred !== 'auto') {
357
- // For Ollama/LiteLLM, check base URL instead of API key
358
- if (preferred === 'ollama' || preferred === 'litellm') {
359
- if (config.getBaseUrl(preferred))
360
- return preferred;
361
- }
362
- else {
363
- const key = config.getApiKey(preferred);
364
- if (key)
365
- return preferred;
366
- }
367
- }
368
- // Auto-select: prefer Anthropic > OpenAI > Google > others
369
- const priority = ['anthropic', 'openai', 'google', 'mistral', 'openrouter', 'together', 'groq', 'ollama', 'litellm'];
370
- for (const p of priority) {
371
- if (p === 'ollama' || p === 'litellm') {
372
- if (config.getBaseUrl(p))
373
- return p;
374
- }
375
- else if (config.getApiKey(p)) {
376
- return p;
377
- }
378
- }
379
- throw new Error('No API keys configured. Run `calliope --setup` to configure.');
380
- }
381
- /**
382
- * Chat with the selected provider (with automatic retry)
383
- */
384
- export async function chat(provider, messages, tools, model, onToken, onRetry) {
385
- const actualProvider = selectProvider(provider);
386
- const actualModel = model || DEFAULT_MODELS[actualProvider];
387
- const doChat = async () => {
388
- let response;
389
- switch (actualProvider) {
390
- case 'anthropic':
391
- response = await chatAnthropic(messages, tools, actualModel, onToken);
392
- break;
393
- case 'google':
394
- response = await chatGoogle(messages, tools, actualModel);
395
- break;
396
- case 'openai':
397
- response = await chatOpenAI(messages, tools, actualModel, onToken);
398
- break;
399
- case 'openrouter':
400
- case 'together':
401
- case 'groq':
402
- case 'fireworks':
403
- case 'mistral':
404
- case 'ai21':
405
- case 'huggingface':
406
- case 'ollama':
407
- case 'litellm':
408
- response = await chatOpenAICompatible(actualProvider, messages, tools, actualModel, onToken);
409
- break;
410
- default:
411
- throw new Error(`Provider ${actualProvider} not implemented`);
412
- }
413
- // Validate and sanitize response before returning
414
- return validateLLMResponse(response);
415
- };
416
- // Wrap with retry logic
417
- return withRetry(doChat, {
418
- maxRetries: 2,
419
- initialDelayMs: 1000,
420
- onRetry: onRetry,
421
- });
422
- }
423
- /**
424
- * Chat with Anthropic Claude
425
- */
426
- async function chatAnthropic(messages, tools, model, onToken) {
427
- const apiKey = config.getApiKey('anthropic');
428
- if (!apiKey)
429
- throw new Error('Anthropic API key not configured');
430
- const client = new Anthropic({ apiKey });
431
- // Extract system message
432
- const systemMessage = messages.find(m => m.role === 'system');
433
- const chatMessages = messages.filter(m => m.role !== 'system');
434
- // Convert to Anthropic format
435
- const anthropicMessages = chatMessages.map(m => {
436
- if (m.role === 'tool') {
437
- return {
438
- role: 'user',
439
- content: [{
440
- type: 'tool_result',
441
- tool_use_id: m.toolCallId || '',
442
- content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
443
- }],
444
- };
445
- }
446
- if (m.toolCalls && m.toolCalls.length > 0) {
447
- const textContent = typeof m.content === 'string' ? m.content :
448
- (Array.isArray(m.content) ? m.content.filter(b => b.type === 'text').map(b => b.text).join('\n') : '');
449
- return {
450
- role: 'assistant',
451
- content: [
452
- ...(textContent ? [{ type: 'text', text: textContent }] : []),
453
- ...m.toolCalls.map(tc => ({
454
- type: 'tool_use',
455
- id: tc.id,
456
- name: tc.name,
457
- input: tc.arguments,
458
- })),
459
- ],
460
- };
461
- }
462
- // Handle multi-modal content for user messages
463
- if (m.role === 'user' && Array.isArray(m.content)) {
464
- return {
465
- role: 'user',
466
- content: toAnthropicContent(m.content),
467
- };
468
- }
469
- const content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content);
470
- return {
471
- role: m.role,
472
- // Anthropic requires non-empty content for all non-final messages
473
- content: content || '(continued)',
474
- };
475
- });
476
- // Convert tools to Anthropic format
477
- const anthropicTools = tools.map(t => ({
478
- name: t.name,
479
- description: t.description,
480
- input_schema: t.parameters,
481
- }));
482
- // Calculate dynamic max_tokens based on available context space
483
- const dynamicMaxTokens = calculateMaxTokens('anthropic', model, messages, tools);
484
- debugLog(`Anthropic request: model=${model}, max_tokens=${dynamicMaxTokens}`);
485
- // Use streaming if callback provided - handles both text and tool calls
486
- if (onToken) {
487
- let content = '';
488
- let inputTokens = 0;
489
- let outputTokens = 0;
490
- const toolCalls = [];
491
- let currentToolId = '';
492
- let currentToolName = '';
493
- let currentToolInput = '';
494
- let finishReason = 'stop';
495
- try {
496
- const stream = await client.messages.stream({
497
- model,
498
- max_tokens: dynamicMaxTokens,
499
- system: systemMessage ? getTextContent(systemMessage.content) : '',
500
- messages: anthropicMessages,
501
- tools: anthropicTools.length > 0 ? anthropicTools : undefined,
502
- });
503
- for await (const event of stream) {
504
- if (event.type === 'content_block_start') {
505
- if (event.content_block.type === 'tool_use') {
506
- currentToolId = event.content_block.id;
507
- currentToolName = event.content_block.name;
508
- currentToolInput = '';
509
- }
510
- }
511
- else if (event.type === 'content_block_delta') {
512
- if (event.delta.type === 'text_delta') {
513
- const text = event.delta.text;
514
- content += text;
515
- onToken(text);
516
- }
517
- else if (event.delta.type === 'input_json_delta') {
518
- currentToolInput += event.delta.partial_json;
519
- }
520
- }
521
- else if (event.type === 'content_block_stop') {
522
- if (currentToolId && currentToolName) {
523
- try {
524
- toolCalls.push({
525
- id: currentToolId,
526
- name: currentToolName,
527
- arguments: JSON.parse(currentToolInput || '{}'),
528
- });
529
- }
530
- catch {
531
- toolCalls.push({
532
- id: currentToolId,
533
- name: currentToolName,
534
- arguments: {},
535
- });
536
- }
537
- currentToolId = '';
538
- currentToolName = '';
539
- currentToolInput = '';
540
- }
541
- }
542
- else if (event.type === 'message_delta') {
543
- if (event.usage) {
544
- outputTokens = event.usage.output_tokens;
545
- }
546
- if (event.delta.stop_reason === 'tool_use') {
547
- finishReason = 'tool_use';
548
- }
549
- else if (event.delta.stop_reason === 'max_tokens') {
550
- finishReason = 'length';
551
- }
552
- }
553
- else if (event.type === 'message_start' && event.message.usage) {
554
- inputTokens = event.message.usage.input_tokens;
555
- }
556
- }
557
- return {
558
- content,
559
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
560
- finishReason,
561
- usage: { inputTokens, outputTokens },
562
- };
563
- }
564
- catch (streamError) {
565
- // Fall back to non-streaming on error
566
- console.error('Anthropic streaming failed, falling back:', streamError);
567
- }
568
- }
569
- // Non-streaming request
570
- const response = await client.messages.create({
571
- model,
572
- max_tokens: dynamicMaxTokens,
573
- system: systemMessage ? getTextContent(systemMessage.content) : '',
574
- messages: anthropicMessages,
575
- tools: anthropicTools.length > 0 ? anthropicTools : undefined,
576
- });
577
- // Parse response
578
- let content = '';
579
- const toolCalls = [];
580
- for (const block of response.content) {
581
- if (block.type === 'text') {
582
- content += block.text;
583
- }
584
- else if (block.type === 'tool_use') {
585
- toolCalls.push({
586
- id: block.id,
587
- name: block.name,
588
- arguments: block.input,
589
- });
590
- }
591
- }
592
- // Map Anthropic stop reasons to our finish reasons
593
- let finishReason = 'stop';
594
- if (response.stop_reason === 'tool_use') {
595
- finishReason = 'tool_use';
596
- }
597
- else if (response.stop_reason === 'max_tokens') {
598
- finishReason = 'length';
599
- }
600
- return {
601
- content,
602
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
603
- finishReason,
604
- usage: {
605
- inputTokens: response.usage.input_tokens,
606
- outputTokens: response.usage.output_tokens,
607
- },
608
- };
609
- }
610
- /**
611
- * Chat with Google Gemini
612
- */
613
- async function chatGoogle(messages, tools, model) {
614
- const apiKey = config.getApiKey('google');
615
- if (!apiKey)
616
- throw new Error('Google API key not configured');
617
- const genAI = new GoogleGenerativeAI(apiKey);
618
- const genModel = genAI.getGenerativeModel({ model });
619
- // Build history (exclude last message)
620
- const history = messages.slice(0, -1).filter(m => m.role !== 'system').map(m => ({
621
- role: m.role === 'assistant' ? 'model' : 'user',
622
- parts: [{ text: getTextContent(m.content) }],
623
- }));
624
- if (messages.length === 0) {
625
- throw new Error('No messages provided');
626
- }
627
- const lastMessage = messages[messages.length - 1];
628
- const systemMessage = messages.find(m => m.role === 'system');
629
- const chat = genModel.startChat({
630
- history,
631
- systemInstruction: systemMessage ? getTextContent(systemMessage.content) : undefined,
632
- });
633
- // Convert last message to Gemini format (with image support)
634
- const lastMessageParts = [];
635
- if (typeof lastMessage.content === 'string') {
636
- lastMessageParts.push({ text: lastMessage.content });
637
- }
638
- else {
639
- for (const block of lastMessage.content) {
640
- if (block.type === 'text') {
641
- lastMessageParts.push({ text: block.text });
642
- }
643
- else if (block.type === 'image') {
644
- lastMessageParts.push({
645
- inlineData: {
646
- mimeType: block.mediaType,
647
- data: block.data,
648
- },
649
- });
650
- }
651
- }
652
- }
653
- const result = await chat.sendMessage(lastMessageParts);
654
- const response = result.response;
655
- const text = response.text();
656
- // Check for function calls
657
- const toolCalls = [];
658
- const candidates = response.candidates || [];
659
- for (const candidate of candidates) {
660
- for (const part of candidate.content?.parts || []) {
661
- if ('functionCall' in part && part.functionCall) {
662
- toolCalls.push({
663
- id: `gemini_${Date.now()}_${Math.random().toString(36).slice(2)}`,
664
- name: part.functionCall.name,
665
- arguments: part.functionCall.args,
666
- });
667
- }
668
- }
669
- }
670
- return {
671
- content: text,
672
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
673
- finishReason: toolCalls.length > 0 ? 'tool_use' : 'stop',
674
- };
675
- }
676
- /**
677
- * Convert messages to Responses API input format
678
- */
679
- function toResponsesInput(messages) {
680
- const input = [];
681
- for (const m of messages) {
682
- if (m.role === 'system') {
683
- // System messages become developer messages in Responses API
684
- input.push({
685
- role: 'developer',
686
- content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
687
- });
688
- }
689
- else if (m.role === 'tool') {
690
- // Tool results become function_call_output items
691
- input.push({
692
- type: 'function_call_output',
693
- call_id: m.toolCallId || '',
694
- output: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
695
- });
696
- }
697
- else if (m.role === 'assistant') {
698
- // Assistant messages with tool calls
699
- if (m.toolCalls && m.toolCalls.length > 0) {
700
- // First add any text content as a message
701
- const textContent = typeof m.content === 'string' ? m.content :
702
- (Array.isArray(m.content) ? m.content.filter(b => b.type === 'text').map(b => b.text).join('\n') : '');
703
- if (textContent) {
704
- input.push({
705
- role: 'assistant',
706
- content: textContent,
707
- });
708
- }
709
- // Then add each tool call as a function_call item
710
- for (const tc of m.toolCalls) {
711
- input.push({
712
- type: 'function_call',
713
- call_id: tc.id,
714
- name: tc.name,
715
- arguments: JSON.stringify(tc.arguments),
716
- });
717
- }
718
- }
719
- else {
720
- input.push({
721
- role: 'assistant',
722
- content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
723
- });
724
- }
725
- }
726
- else if (m.role === 'user') {
727
- // Handle multi-modal content for user messages
728
- if (Array.isArray(m.content)) {
729
- const parts = [];
730
- for (const block of m.content) {
731
- if (block.type === 'text') {
732
- parts.push({ type: 'input_text', text: block.text });
733
- }
734
- else if (block.type === 'image') {
735
- parts.push({
736
- type: 'input_image',
737
- image_url: { url: `data:${block.mediaType};base64,${block.data}` },
738
- });
739
- }
740
- }
741
- input.push({ role: 'user', content: parts });
742
- }
743
- else {
744
- input.push({
745
- role: 'user',
746
- content: m.content,
747
- });
748
- }
749
- }
750
- }
751
- return input;
752
- }
753
- /**
754
- * Convert tools to Responses API format
755
- */
756
- function toResponsesTools(tools) {
757
- return tools.map(t => ({
758
- type: 'function',
759
- name: t.name,
760
- description: t.description,
761
- parameters: t.parameters,
762
- strict: false,
763
- }));
764
- }
765
- /**
766
- * Type guard for text delta events
767
- */
768
- function isTextDeltaEvent(event) {
769
- return event.type === 'response.output_text.delta';
770
- }
771
- /**
772
- * Type guard for function call done events
773
- */
774
- function isFunctionCallDoneEvent(event) {
775
- return event.type === 'response.function_call_arguments.done';
776
- }
777
- /**
778
- * Type guard for completed events
779
- */
780
- function isCompletedEvent(event) {
781
- return event.type === 'response.completed';
782
- }
783
- /**
784
- * Type guard for function call output items
785
- */
786
- function isFunctionCallOutput(item) {
787
- return item.type === 'function_call';
788
- }
789
- /**
790
- * Chat with OpenAI using the Responses API (for o3, o4-mini, etc.)
791
- */
792
- async function chatOpenAIResponses(messages, tools, model, onToken) {
793
- const apiKey = config.getApiKey('openai');
794
- if (!apiKey)
795
- throw new Error('OpenAI API key not configured');
796
- const client = new OpenAI({ apiKey });
797
- const responsesInput = toResponsesInput(messages);
798
- const responsesTools = toResponsesTools(tools);
799
- // Calculate dynamic max_tokens based on available context space
800
- const dynamicMaxTokens = calculateMaxTokens('openai', model, messages, tools);
801
- debugLog(`OpenAI Responses API request: model=${model}, max_tokens=${dynamicMaxTokens}`);
802
- // Use streaming if callback provided
803
- if (onToken) {
804
- let content = '';
805
- const toolCalls = [];
806
- let finishReason = 'stop';
807
- let inputTokens = 0;
808
- let outputTokens = 0;
809
- try {
810
- // Note: OpenAI SDK types don't fully match Responses API yet
811
- // We use our own type definitions and cast through unknown for SDK interop
812
- const streamParams = {
813
- model,
814
- input: responsesInput,
815
- tools: responsesTools.length > 0 ? responsesTools : undefined,
816
- max_output_tokens: dynamicMaxTokens,
817
- };
818
- const stream = client.responses.stream(streamParams);
819
- for await (const event of stream) {
820
- const typedEvent = event;
821
- if (isTextDeltaEvent(typedEvent)) {
822
- content += typedEvent.delta;
823
- onToken(typedEvent.delta);
824
- }
825
- else if (isFunctionCallDoneEvent(typedEvent)) {
826
- toolCalls.push({
827
- id: typedEvent.call_id || `call_${Date.now()}`,
828
- name: typedEvent.name,
829
- arguments: JSON.parse(typedEvent.arguments || '{}'),
830
- });
831
- finishReason = 'tool_use';
832
- }
833
- else if (isCompletedEvent(typedEvent)) {
834
- const response = typedEvent.response;
835
- if (response?.usage) {
836
- inputTokens = response.usage.input_tokens || 0;
837
- outputTokens = response.usage.output_tokens || 0;
838
- }
839
- if (response?.status === 'incomplete') {
840
- finishReason = 'length';
841
- }
842
- }
843
- }
844
- return {
845
- content,
846
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
847
- finishReason,
848
- usage: { inputTokens, outputTokens },
849
- };
850
- }
851
- catch (streamError) {
852
- debugLog('Responses API streaming failed, falling back to non-streaming:', streamError);
853
- }
854
- }
855
- // Non-streaming request
856
- const createParams = {
857
- model,
858
- input: responsesInput,
859
- tools: responsesTools.length > 0 ? responsesTools : undefined,
860
- max_output_tokens: dynamicMaxTokens,
861
- };
862
- const response = await client.responses.create(createParams);
863
- // Extract content and tool calls from response
864
- let content = response.output_text || '';
865
- const toolCalls = [];
866
- // Process output items for tool calls
867
- for (const item of response.output) {
868
- if (isFunctionCallOutput(item)) {
869
- toolCalls.push({
870
- id: item.call_id || item.id || `call_${Date.now()}`,
871
- name: item.name,
872
- arguments: typeof item.arguments === 'string'
873
- ? JSON.parse(item.arguments)
874
- : item.arguments,
875
- });
876
- }
877
- }
878
- // Determine finish reason
879
- let finishReason = 'stop';
880
- if (toolCalls.length > 0) {
881
- finishReason = 'tool_use';
882
- }
883
- else if (response.status === 'incomplete') {
884
- finishReason = 'length';
885
- }
886
- return {
887
- content,
888
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
889
- finishReason,
890
- usage: response.usage ? {
891
- inputTokens: response.usage.input_tokens,
892
- outputTokens: response.usage.output_tokens,
893
- } : undefined,
894
- };
895
- }
896
- /**
897
- * Chat with OpenAI
898
- */
899
- async function chatOpenAI(messages, tools, model, onToken) {
900
- // Route to Responses API for models that require it (o3, o4-mini, etc.)
901
- if (requiresResponsesAPI(model)) {
902
- return chatOpenAIResponses(messages, tools, model, onToken);
903
- }
904
- const apiKey = config.getApiKey('openai');
905
- if (!apiKey)
906
- throw new Error('OpenAI API key not configured');
907
- const client = new OpenAI({ apiKey });
908
- const openaiMessages = toOpenAIMessages(messages);
909
- const openaiTools = toOpenAITools(tools);
910
- // Calculate dynamic max_tokens based on available context space
911
- const dynamicMaxTokens = calculateMaxTokens('openai', model, messages, tools);
912
- debugLog(`OpenAI request: model=${model}, max_tokens=${dynamicMaxTokens}`);
913
- // Use streaming if callback provided
914
- // Stream text content while collecting tool calls
915
- if (onToken) {
916
- let content = '';
917
- let toolCallDeltas = {};
918
- let finishReason = 'stop';
919
- try {
920
- const stream = await client.chat.completions.create({
921
- model,
922
- messages: openaiMessages,
923
- tools: openaiTools.length > 0 ? openaiTools : undefined,
924
- max_tokens: dynamicMaxTokens,
925
- stream: true,
926
- });
927
- for await (const chunk of stream) {
928
- const choice = chunk.choices[0];
929
- if (!choice)
930
- continue;
931
- // Handle text content
932
- const textDelta = choice.delta?.content;
933
- if (textDelta) {
934
- content += textDelta;
935
- onToken(textDelta);
936
- }
937
- // Handle tool calls (collect deltas)
938
- const toolCallDelta = choice.delta?.tool_calls;
939
- if (toolCallDelta) {
940
- for (const tc of toolCallDelta) {
941
- if (!toolCallDeltas[tc.index]) {
942
- toolCallDeltas[tc.index] = { id: '', name: '', arguments: '' };
943
- }
944
- if (tc.id)
945
- toolCallDeltas[tc.index].id = tc.id;
946
- if (tc.function?.name)
947
- toolCallDeltas[tc.index].name = tc.function.name;
948
- if (tc.function?.arguments)
949
- toolCallDeltas[tc.index].arguments += tc.function.arguments;
950
- }
951
- }
952
- // Track finish reason
953
- if (choice.finish_reason === 'tool_calls') {
954
- finishReason = 'tool_use';
955
- }
956
- else if (choice.finish_reason === 'length') {
957
- finishReason = 'length';
958
- }
959
- }
960
- // Convert tool call deltas to tool calls
961
- const toolCalls = Object.values(toolCallDeltas)
962
- .filter(tc => tc.id && tc.name)
963
- .map(tc => ({
964
- id: tc.id,
965
- name: tc.name,
966
- arguments: JSON.parse(tc.arguments || '{}'),
967
- }));
968
- if (toolCalls.length > 0) {
969
- finishReason = 'tool_use';
970
- }
971
- return {
972
- content,
973
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
974
- finishReason,
975
- };
976
- }
977
- catch (streamError) {
978
- // Fall back to non-streaming on error
979
- console.error('Streaming failed, falling back to non-streaming:', streamError);
980
- }
981
- }
982
- // Non-streaming request
983
- const response = await client.chat.completions.create({
984
- model,
985
- messages: openaiMessages,
986
- tools: openaiTools.length > 0 ? openaiTools : undefined,
987
- max_tokens: dynamicMaxTokens,
988
- });
989
- if (!response.choices || response.choices.length === 0) {
990
- throw new Error('Empty response from OpenAI API');
991
- }
992
- const choice = response.choices[0];
993
- const message = choice.message;
994
- const toolCalls = parseOpenAIToolCalls(message.tool_calls);
995
- // Map OpenAI finish reasons
996
- let finishReason = 'stop';
997
- if (choice.finish_reason === 'tool_calls') {
998
- finishReason = 'tool_use';
999
- }
1000
- else if (choice.finish_reason === 'length') {
1001
- finishReason = 'length';
1002
- }
1003
- return {
1004
- content: message.content || '',
1005
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
1006
- finishReason,
1007
- usage: response.usage ? {
1008
- inputTokens: response.usage.prompt_tokens,
1009
- outputTokens: response.usage.completion_tokens,
1010
- } : undefined,
1011
- };
1012
- }
1013
- /**
1014
- * Chat with OpenAI-compatible APIs (OpenRouter, Together, Groq, Mistral, etc.)
1015
- */
1016
- async function chatOpenAICompatible(provider, messages, tools, model, onToken) {
1017
- // Ollama and LiteLLM use base URL, others use API key
1018
- let apiKey;
1019
- let baseURL;
1020
- if (provider === 'ollama') {
1021
- const ollamaBase = config.getBaseUrl('ollama') || 'http://localhost:11434';
1022
- // Append /v1 for OpenAI-compatible endpoint, unless already present
1023
- baseURL = ollamaBase.endsWith('/v1') ? ollamaBase : `${ollamaBase}/v1`;
1024
- apiKey = 'ollama'; // Ollama doesn't require a real API key
1025
- }
1026
- else if (provider === 'litellm') {
1027
- const litellmBase = config.getBaseUrl('litellm') || 'http://localhost:4000';
1028
- // Append /v1 for OpenAI-compatible endpoint, unless already present
1029
- baseURL = litellmBase.endsWith('/v1') ? litellmBase : `${litellmBase}/v1`;
1030
- apiKey = config.getApiKey('litellm') || 'litellm'; // LiteLLM may or may not require key
1031
- }
1032
- else {
1033
- apiKey = config.getApiKey(provider);
1034
- if (!apiKey)
1035
- throw new Error(`${provider} API key not configured`);
1036
- baseURL = PROVIDER_BASE_URLS[provider];
1037
- if (!baseURL)
1038
- throw new Error(`Unknown provider: ${provider}`);
1039
- }
1040
- const client = new OpenAI({ apiKey, baseURL });
1041
- const openaiMessages = toOpenAIMessages(messages);
1042
- const openaiTools = toOpenAITools(tools);
1043
- // Calculate dynamic max_tokens based on available context space
1044
- const dynamicMaxTokens = calculateMaxTokens(provider, model, messages, tools);
1045
- debugLog(`${provider} request: model=${model}, max_tokens=${dynamicMaxTokens}`);
1046
- // Use streaming if callback provided
1047
- // Stream text content while collecting tool calls
1048
- if (onToken) {
1049
- let content = '';
1050
- let toolCallDeltas = {};
1051
- let finishReason = 'stop';
1052
- try {
1053
- const stream = await client.chat.completions.create({
1054
- model,
1055
- messages: openaiMessages,
1056
- tools: openaiTools.length > 0 ? openaiTools : undefined,
1057
- max_tokens: dynamicMaxTokens,
1058
- stream: true,
1059
- });
1060
- for await (const chunk of stream) {
1061
- const choice = chunk.choices[0];
1062
- if (!choice)
1063
- continue;
1064
- // Handle text content
1065
- const textDelta = choice.delta?.content;
1066
- if (textDelta) {
1067
- content += textDelta;
1068
- onToken(textDelta);
1069
- }
1070
- // Handle tool calls (collect deltas)
1071
- const toolCallDelta = choice.delta?.tool_calls;
1072
- if (toolCallDelta) {
1073
- for (const tc of toolCallDelta) {
1074
- if (!toolCallDeltas[tc.index]) {
1075
- toolCallDeltas[tc.index] = { id: '', name: '', arguments: '' };
1076
- }
1077
- if (tc.id)
1078
- toolCallDeltas[tc.index].id = tc.id;
1079
- if (tc.function?.name)
1080
- toolCallDeltas[tc.index].name = tc.function.name;
1081
- if (tc.function?.arguments)
1082
- toolCallDeltas[tc.index].arguments += tc.function.arguments;
1083
- }
1084
- }
1085
- // Track finish reason
1086
- if (choice.finish_reason === 'tool_calls') {
1087
- finishReason = 'tool_use';
1088
- }
1089
- else if (choice.finish_reason === 'length') {
1090
- finishReason = 'length';
1091
- }
1092
- }
1093
- // Convert tool call deltas to tool calls
1094
- const toolCalls = Object.values(toolCallDeltas)
1095
- .filter(tc => tc.id && tc.name)
1096
- .map(tc => ({
1097
- id: tc.id,
1098
- name: tc.name,
1099
- arguments: JSON.parse(tc.arguments || '{}'),
1100
- }));
1101
- if (toolCalls.length > 0) {
1102
- finishReason = 'tool_use';
1103
- }
1104
- return {
1105
- content,
1106
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
1107
- finishReason,
1108
- };
1109
- }
1110
- catch (streamError) {
1111
- // Fall back to non-streaming on error
1112
- console.error('Streaming failed, falling back to non-streaming:', streamError);
1113
- }
1114
- }
1115
- // Non-streaming request
1116
- const response = await client.chat.completions.create({
1117
- model,
1118
- messages: openaiMessages,
1119
- tools: openaiTools.length > 0 ? openaiTools : undefined,
1120
- max_tokens: dynamicMaxTokens,
1121
- });
1122
- if (!response.choices || response.choices.length === 0) {
1123
- throw new Error(`Empty response from ${provider} API`);
1124
- }
1125
- const choice = response.choices[0];
1126
- const message = choice.message;
1127
- const toolCalls = parseOpenAIToolCalls(message.tool_calls);
1128
- // Map finish reasons
1129
- let finishReason = 'stop';
1130
- if (choice.finish_reason === 'tool_calls') {
1131
- finishReason = 'tool_use';
1132
- }
1133
- else if (choice.finish_reason === 'length') {
1134
- finishReason = 'length';
1135
- }
1136
- return {
1137
- content: message.content || '',
1138
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
1139
- finishReason,
1140
- usage: response.usage ? {
1141
- inputTokens: response.usage.prompt_tokens,
1142
- outputTokens: response.usage.completion_tokens,
1143
- } : undefined,
1144
- };
1145
- }
1146
- //# sourceMappingURL=providers.js.map