@gitkraken/core-gitlens 0.5.109 → 0.5.111

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 (416) hide show
  1. package/CHANGELOG.md +48 -1
  2. package/dist/git/cache.d.ts +111 -6
  3. package/dist/git/cache.d.ts.map +1 -1
  4. package/dist/git/cache.js +312 -43
  5. package/dist/git/cache.js.map +1 -1
  6. package/dist/git/context.d.ts +11 -0
  7. package/dist/git/context.d.ts.map +1 -1
  8. package/dist/git/context.js.map +1 -1
  9. package/dist/git/models/fileStatus.d.ts +1 -0
  10. package/dist/git/models/fileStatus.d.ts.map +1 -1
  11. package/dist/git/models/fileStatus.js +1 -0
  12. package/dist/git/models/fileStatus.js.map +1 -1
  13. package/dist/git/models/issue.d.ts +4 -0
  14. package/dist/git/models/issue.d.ts.map +1 -1
  15. package/dist/git/models/mergeConflicts.d.ts +2 -0
  16. package/dist/git/models/mergeConflicts.d.ts.map +1 -1
  17. package/dist/git/models/pullRequest.d.ts +119 -2
  18. package/dist/git/models/pullRequest.d.ts.map +1 -1
  19. package/dist/git/models/pullRequest.js +27 -1
  20. package/dist/git/models/pullRequest.js.map +1 -1
  21. package/dist/git/models/repository.d.ts +32 -5
  22. package/dist/git/models/repository.d.ts.map +1 -1
  23. package/dist/git/models/repository.js +58 -12
  24. package/dist/git/models/repository.js.map +1 -1
  25. package/dist/git/models/statusFile.d.ts.map +1 -1
  26. package/dist/git/models/statusFile.js +3 -0
  27. package/dist/git/models/statusFile.js.map +1 -1
  28. package/dist/git/providers/branches.d.ts +2 -0
  29. package/dist/git/providers/branches.d.ts.map +1 -1
  30. package/dist/git/providers/commits.d.ts +8 -0
  31. package/dist/git/providers/commits.d.ts.map +1 -1
  32. package/dist/git/providers/config.d.ts +19 -1
  33. package/dist/git/providers/config.d.ts.map +1 -1
  34. package/dist/git/providers/operations.d.ts +1 -0
  35. package/dist/git/providers/operations.d.ts.map +1 -1
  36. package/dist/git/providers/pausedOperations.d.ts +2 -0
  37. package/dist/git/providers/pausedOperations.d.ts.map +1 -1
  38. package/dist/git/providers/status.d.ts +1 -0
  39. package/dist/git/providers/status.d.ts.map +1 -1
  40. package/dist/git/utils/config.utils.d.ts +3 -0
  41. package/dist/git/utils/config.utils.d.ts.map +1 -0
  42. package/dist/git/utils/config.utils.js +8 -0
  43. package/dist/git/utils/config.utils.js.map +1 -0
  44. package/dist/git/utils/issue.utils.d.ts.map +1 -1
  45. package/dist/git/utils/issue.utils.js +2 -0
  46. package/dist/git/utils/issue.utils.js.map +1 -1
  47. package/dist/git/utils/pausedOperationStatus.utils.d.ts +14 -0
  48. package/dist/git/utils/pausedOperationStatus.utils.d.ts.map +1 -1
  49. package/dist/git/utils/pausedOperationStatus.utils.js +23 -0
  50. package/dist/git/utils/pausedOperationStatus.utils.js.map +1 -1
  51. package/dist/git/utils/pullRequest.utils.d.ts +24 -1
  52. package/dist/git/utils/pullRequest.utils.d.ts.map +1 -1
  53. package/dist/git/utils/pullRequest.utils.js +71 -0
  54. package/dist/git/utils/pullRequest.utils.js.map +1 -1
  55. package/dist/git/watching/changeEvent.d.ts +6 -1
  56. package/dist/git/watching/changeEvent.d.ts.map +1 -1
  57. package/dist/git/watching/classifyChange.d.ts.map +1 -1
  58. package/dist/git/watching/classifyChange.js +14 -1
  59. package/dist/git/watching/classifyChange.js.map +1 -1
  60. package/dist/git/watching/watchService.d.ts.map +1 -1
  61. package/dist/git/watching/watchService.js +18 -3
  62. package/dist/git/watching/watchService.js.map +1 -1
  63. package/dist/git/watching/watchSession.d.ts +8 -1
  64. package/dist/git/watching/watchSession.d.ts.map +1 -1
  65. package/dist/git/watching/watchSession.js +38 -6
  66. package/dist/git/watching/watchSession.js.map +1 -1
  67. package/dist/git/watching/watcherPatterns.d.ts +2 -2
  68. package/dist/git/watching/watcherPatterns.d.ts.map +1 -1
  69. package/dist/git/watching/watcherPatterns.js +1 -1
  70. package/dist/git/watching/watcherPatterns.js.map +1 -1
  71. package/dist/git-cli/exec/git.d.ts +1 -1
  72. package/dist/git-cli/exec/git.d.ts.map +1 -1
  73. package/dist/git-cli/exec/git.js +13 -3
  74. package/dist/git-cli/exec/git.js.map +1 -1
  75. package/dist/git-cli/providers/branches.d.ts +19 -0
  76. package/dist/git-cli/providers/branches.d.ts.map +1 -1
  77. package/dist/git-cli/providers/branches.js +282 -78
  78. package/dist/git-cli/providers/branches.js.map +1 -1
  79. package/dist/git-cli/providers/commits.d.ts +4 -0
  80. package/dist/git-cli/providers/commits.d.ts.map +1 -1
  81. package/dist/git-cli/providers/commits.js +84 -11
  82. package/dist/git-cli/providers/commits.js.map +1 -1
  83. package/dist/git-cli/providers/config.d.ts +42 -0
  84. package/dist/git-cli/providers/config.d.ts.map +1 -1
  85. package/dist/git-cli/providers/config.js +186 -5
  86. package/dist/git-cli/providers/config.js.map +1 -1
  87. package/dist/git-cli/providers/contributors.d.ts.map +1 -1
  88. package/dist/git-cli/providers/contributors.js +1 -1
  89. package/dist/git-cli/providers/contributors.js.map +1 -1
  90. package/dist/git-cli/providers/graph.d.ts +2 -2
  91. package/dist/git-cli/providers/graph.d.ts.map +1 -1
  92. package/dist/git-cli/providers/graph.js +27 -9
  93. package/dist/git-cli/providers/graph.js.map +1 -1
  94. package/dist/git-cli/providers/operations.d.ts.map +1 -1
  95. package/dist/git-cli/providers/operations.js +40 -12
  96. package/dist/git-cli/providers/operations.js.map +1 -1
  97. package/dist/git-cli/providers/pausedOperations.d.ts +5 -0
  98. package/dist/git-cli/providers/pausedOperations.d.ts.map +1 -1
  99. package/dist/git-cli/providers/pausedOperations.js +87 -16
  100. package/dist/git-cli/providers/pausedOperations.js.map +1 -1
  101. package/dist/git-cli/providers/refs.d.ts +5 -3
  102. package/dist/git-cli/providers/refs.d.ts.map +1 -1
  103. package/dist/git-cli/providers/refs.js +33 -4
  104. package/dist/git-cli/providers/refs.js.map +1 -1
  105. package/dist/git-cli/providers/status.d.ts +1 -0
  106. package/dist/git-cli/providers/status.d.ts.map +1 -1
  107. package/dist/git-cli/providers/status.js +18 -3
  108. package/dist/git-cli/providers/status.js.map +1 -1
  109. package/dist/git-cli/providers/worktrees.d.ts.map +1 -1
  110. package/dist/git-cli/providers/worktrees.js +4 -0
  111. package/dist/git-cli/providers/worktrees.js.map +1 -1
  112. package/dist/git-cli/service.d.ts.map +1 -1
  113. package/dist/git-cli/service.js.map +1 -1
  114. package/dist/plus/agents/providers/claudeCodeProvider.d.ts +39 -1
  115. package/dist/plus/agents/providers/claudeCodeProvider.d.ts.map +1 -1
  116. package/dist/plus/agents/providers/claudeCodeProvider.js +978 -71
  117. package/dist/plus/agents/providers/claudeCodeProvider.js.map +1 -1
  118. package/dist/plus/agents/providers/claudeCodeTranscript.d.ts +24 -2
  119. package/dist/plus/agents/providers/claudeCodeTranscript.d.ts.map +1 -1
  120. package/dist/plus/agents/providers/claudeCodeTranscript.js +84 -9
  121. package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
  122. package/dist/plus/agents/types.d.ts +37 -2
  123. package/dist/plus/agents/types.d.ts.map +1 -1
  124. package/dist/plus/agents/types.js +2 -0
  125. package/dist/plus/agents/types.js.map +1 -1
  126. package/dist/plus/ai/errors.d.ts +10 -0
  127. package/dist/plus/ai/errors.d.ts.map +1 -1
  128. package/dist/plus/ai/errors.js +29 -0
  129. package/dist/plus/ai/errors.js.map +1 -1
  130. package/dist/plus/ai/models/model.d.ts +4 -0
  131. package/dist/plus/ai/models/model.d.ts.map +1 -1
  132. package/dist/plus/ai/models/model.js +51 -6
  133. package/dist/plus/ai/models/model.js.map +1 -1
  134. package/dist/plus/ai/models/promptTemplates.d.ts +1 -1
  135. package/dist/plus/ai/models/promptTemplates.d.ts.map +1 -1
  136. package/dist/plus/ai/models/provider.d.ts +60 -2
  137. package/dist/plus/ai/models/provider.d.ts.map +1 -1
  138. package/dist/plus/ai/prompts.d.ts +12 -0
  139. package/dist/plus/ai/prompts.d.ts.map +1 -1
  140. package/dist/plus/ai/prompts.js +177 -61
  141. package/dist/plus/ai/prompts.js.map +1 -1
  142. package/dist/plus/ai/providers/anthropicProvider.d.ts +10 -2
  143. package/dist/plus/ai/providers/anthropicProvider.d.ts.map +1 -1
  144. package/dist/plus/ai/providers/anthropicProvider.js +139 -46
  145. package/dist/plus/ai/providers/anthropicProvider.js.map +1 -1
  146. package/dist/plus/ai/providers/deepSeekProvider.d.ts +2 -0
  147. package/dist/plus/ai/providers/deepSeekProvider.d.ts.map +1 -1
  148. package/dist/plus/ai/providers/deepSeekProvider.js +10 -21
  149. package/dist/plus/ai/providers/deepSeekProvider.js.map +1 -1
  150. package/dist/plus/ai/providers/geminiProvider.d.ts +1 -0
  151. package/dist/plus/ai/providers/geminiProvider.d.ts.map +1 -1
  152. package/dist/plus/ai/providers/geminiProvider.js +19 -0
  153. package/dist/plus/ai/providers/geminiProvider.js.map +1 -1
  154. package/dist/plus/ai/providers/gitkrakenProvider.d.ts +5 -1
  155. package/dist/plus/ai/providers/gitkrakenProvider.d.ts.map +1 -1
  156. package/dist/plus/ai/providers/gitkrakenProvider.js +118 -3
  157. package/dist/plus/ai/providers/gitkrakenProvider.js.map +1 -1
  158. package/dist/plus/ai/providers/huggingFaceProvider.d.ts +1 -0
  159. package/dist/plus/ai/providers/huggingFaceProvider.d.ts.map +1 -1
  160. package/dist/plus/ai/providers/huggingFaceProvider.js +5 -0
  161. package/dist/plus/ai/providers/huggingFaceProvider.js.map +1 -1
  162. package/dist/plus/ai/providers/mistralProvider.d.ts +3 -1
  163. package/dist/plus/ai/providers/mistralProvider.d.ts.map +1 -1
  164. package/dist/plus/ai/providers/mistralProvider.js +99 -19
  165. package/dist/plus/ai/providers/mistralProvider.js.map +1 -1
  166. package/dist/plus/ai/providers/ollamaProvider.d.ts +4 -3
  167. package/dist/plus/ai/providers/ollamaProvider.d.ts.map +1 -1
  168. package/dist/plus/ai/providers/ollamaProvider.js +38 -3
  169. package/dist/plus/ai/providers/ollamaProvider.js.map +1 -1
  170. package/dist/plus/ai/providers/openAICompatibleProvider.d.ts +1 -0
  171. package/dist/plus/ai/providers/openAICompatibleProvider.d.ts.map +1 -1
  172. package/dist/plus/ai/providers/openAICompatibleProvider.js +4 -0
  173. package/dist/plus/ai/providers/openAICompatibleProvider.js.map +1 -1
  174. package/dist/plus/ai/providers/openAICompatibleProviderBase.d.ts +154 -5
  175. package/dist/plus/ai/providers/openAICompatibleProviderBase.d.ts.map +1 -1
  176. package/dist/plus/ai/providers/openAICompatibleProviderBase.js +259 -10
  177. package/dist/plus/ai/providers/openAICompatibleProviderBase.js.map +1 -1
  178. package/dist/plus/ai/providers/openRouterProvider.d.ts +4 -0
  179. package/dist/plus/ai/providers/openRouterProvider.d.ts.map +1 -1
  180. package/dist/plus/ai/providers/openRouterProvider.js +9 -0
  181. package/dist/plus/ai/providers/openRouterProvider.js.map +1 -1
  182. package/dist/plus/ai/providers/openaiProvider.d.ts +1 -0
  183. package/dist/plus/ai/providers/openaiProvider.d.ts.map +1 -1
  184. package/dist/plus/ai/providers/openaiProvider.js +1 -0
  185. package/dist/plus/ai/providers/openaiProvider.js.map +1 -1
  186. package/dist/plus/ai/providers/responseFormatCache.d.ts +8 -0
  187. package/dist/plus/ai/providers/responseFormatCache.d.ts.map +1 -0
  188. package/dist/plus/ai/providers/responseFormatCache.js +25 -0
  189. package/dist/plus/ai/providers/responseFormatCache.js.map +1 -0
  190. package/dist/plus/ai/providers/xaiProvider.d.ts +1 -0
  191. package/dist/plus/ai/providers/xaiProvider.d.ts.map +1 -1
  192. package/dist/plus/ai/providers/xaiProvider.js +10 -1
  193. package/dist/plus/ai/providers/xaiProvider.js.map +1 -1
  194. package/dist/plus/ai/utils/ai.utils.d.ts +3 -0
  195. package/dist/plus/ai/utils/ai.utils.d.ts.map +1 -1
  196. package/dist/plus/ai/utils/ai.utils.js +13 -0
  197. package/dist/plus/ai/utils/ai.utils.js.map +1 -1
  198. package/dist/plus/ai/utils/results.utils.d.ts +16 -2
  199. package/dist/plus/ai/utils/results.utils.d.ts.map +1 -1
  200. package/dist/plus/ai/utils/results.utils.js +204 -28
  201. package/dist/plus/ai/utils/results.utils.js.map +1 -1
  202. package/dist/plus/git-github/api/github.d.ts +92 -4
  203. package/dist/plus/git-github/api/github.d.ts.map +1 -1
  204. package/dist/plus/git-github/api/github.js +324 -20
  205. package/dist/plus/git-github/api/github.js.map +1 -1
  206. package/dist/plus/git-github/api/pullRequestSearchQuery.d.ts +27 -1
  207. package/dist/plus/git-github/api/pullRequestSearchQuery.d.ts.map +1 -1
  208. package/dist/plus/git-github/api/pullRequestSearchQuery.js +73 -6
  209. package/dist/plus/git-github/api/pullRequestSearchQuery.js.map +1 -1
  210. package/dist/plus/git-github/models.d.ts +33 -5
  211. package/dist/plus/git-github/models.d.ts.map +1 -1
  212. package/dist/plus/git-github/models.js +43 -4
  213. package/dist/plus/git-github/models.js.map +1 -1
  214. package/dist/plus/git-github/providers/github/status.d.ts +1 -0
  215. package/dist/plus/git-github/providers/github/status.d.ts.map +1 -1
  216. package/dist/plus/git-github/providers/github/status.js.map +1 -1
  217. package/dist/plus/integrations/context.d.ts +4 -0
  218. package/dist/plus/integrations/context.d.ts.map +1 -1
  219. package/dist/plus/integrations/index.d.ts.map +1 -1
  220. package/dist/plus/integrations/index.js +2 -0
  221. package/dist/plus/integrations/index.js.map +1 -1
  222. package/dist/plus/integrations/integrationService.d.ts +16 -1
  223. package/dist/plus/integrations/integrationService.d.ts.map +1 -1
  224. package/dist/plus/integrations/integrationService.js +9 -1
  225. package/dist/plus/integrations/integrationService.js.map +1 -1
  226. package/dist/plus/integrations/manager.d.ts +45 -5
  227. package/dist/plus/integrations/manager.d.ts.map +1 -1
  228. package/dist/plus/integrations/models/gitHostIntegration.d.ts +18 -3
  229. package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -1
  230. package/dist/plus/integrations/models/gitHostIntegration.js +30 -2
  231. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  232. package/dist/plus/integrations/models/integration.d.ts +4 -2
  233. package/dist/plus/integrations/models/integration.d.ts.map +1 -1
  234. package/dist/plus/integrations/models/integration.js +2 -2
  235. package/dist/plus/integrations/models/integration.js.map +1 -1
  236. package/dist/plus/integrations/providerFilters.d.ts +1 -1
  237. package/dist/plus/integrations/providerFilters.d.ts.map +1 -1
  238. package/dist/plus/integrations/providers/accounts.d.ts +6 -0
  239. package/dist/plus/integrations/providers/accounts.d.ts.map +1 -0
  240. package/dist/plus/integrations/providers/accounts.js +34 -0
  241. package/dist/plus/integrations/providers/accounts.js.map +1 -0
  242. package/dist/plus/integrations/providers/azure/models.d.ts.map +1 -1
  243. package/dist/plus/integrations/providers/azure/models.js +3 -0
  244. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  245. package/dist/plus/integrations/providers/bitbucket/models.d.ts.map +1 -1
  246. package/dist/plus/integrations/providers/bitbucket/models.js +1 -0
  247. package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
  248. package/dist/plus/integrations/providers/github.d.ts +23 -2
  249. package/dist/plus/integrations/providers/github.d.ts.map +1 -1
  250. package/dist/plus/integrations/providers/github.js +169 -138
  251. package/dist/plus/integrations/providers/github.js.map +1 -1
  252. package/dist/plus/integrations/providers/gitlab.d.ts +9 -0
  253. package/dist/plus/integrations/providers/gitlab.d.ts.map +1 -1
  254. package/dist/plus/integrations/providers/gitlab.js +23 -4
  255. package/dist/plus/integrations/providers/gitlab.js.map +1 -1
  256. package/dist/plus/integrations/providers/models.d.ts +14 -32
  257. package/dist/plus/integrations/providers/models.d.ts.map +1 -1
  258. package/dist/plus/integrations/providers/models.js +66 -109
  259. package/dist/plus/integrations/providers/models.js.map +1 -1
  260. package/dist/plus/integrations/providers/pullRequestReviews.d.ts +50 -0
  261. package/dist/plus/integrations/providers/pullRequestReviews.d.ts.map +1 -0
  262. package/dist/plus/integrations/providers/pullRequestReviews.js +99 -0
  263. package/dist/plus/integrations/providers/pullRequestReviews.js.map +1 -0
  264. package/dist/plus/integrations/reads/broaden.d.ts.map +1 -1
  265. package/dist/plus/integrations/reads/broaden.js +6 -1
  266. package/dist/plus/integrations/reads/broaden.js.map +1 -1
  267. package/dist/plus/integrations/reads/counts.d.ts +55 -0
  268. package/dist/plus/integrations/reads/counts.d.ts.map +1 -1
  269. package/dist/plus/integrations/reads/counts.js +146 -2
  270. package/dist/plus/integrations/reads/counts.js.map +1 -1
  271. package/dist/plus/integrations/reads/drains.d.ts +7 -1
  272. package/dist/plus/integrations/reads/drains.d.ts.map +1 -1
  273. package/dist/plus/integrations/reads/drains.js +20 -6
  274. package/dist/plus/integrations/reads/drains.js.map +1 -1
  275. package/dist/plus/integrations/reads/filters.d.ts.map +1 -1
  276. package/dist/plus/integrations/reads/filters.js +22 -0
  277. package/dist/plus/integrations/reads/filters.js.map +1 -1
  278. package/dist/plus/integrations/reads/hierarchy.d.ts.map +1 -1
  279. package/dist/plus/integrations/reads/hierarchy.js +3 -1
  280. package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
  281. package/dist/plus/integrations/reads/issueTracker.d.ts.map +1 -1
  282. package/dist/plus/integrations/reads/issueTracker.js +8 -3
  283. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  284. package/dist/plus/integrations/reads/issues.d.ts.map +1 -1
  285. package/dist/plus/integrations/reads/issues.js +9 -4
  286. package/dist/plus/integrations/reads/issues.js.map +1 -1
  287. package/dist/plus/integrations/reads/pullRequests.d.ts.map +1 -1
  288. package/dist/plus/integrations/reads/pullRequests.js +9 -4
  289. package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
  290. package/dist/plus/integrations/reads/searchPullRequests.d.ts +4 -3
  291. package/dist/plus/integrations/reads/searchPullRequests.d.ts.map +1 -1
  292. package/dist/plus/integrations/reads/searchPullRequests.js +6 -4
  293. package/dist/plus/integrations/reads/searchPullRequests.js.map +1 -1
  294. package/dist/plus/integrations/reads/sweeps.js +1 -1
  295. package/dist/plus/integrations/reads/sweeps.js.map +1 -1
  296. package/dist/plus/integrations/reads/warnings.d.ts +2 -2
  297. package/dist/plus/integrations/reads/warnings.d.ts.map +1 -1
  298. package/dist/plus/integrations/reads/warnings.js +11 -2
  299. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  300. package/dist/plus/integrations/results.d.ts +6 -1
  301. package/dist/plus/integrations/results.d.ts.map +1 -1
  302. package/dist/plus/integrations/results.js.map +1 -1
  303. package/dist/utils/env/browser/base64.d.ts +1 -1
  304. package/dist/utils/env/browser/base64.d.ts.map +1 -1
  305. package/dist/utils/env/node/base64.d.ts +1 -1
  306. package/dist/utils/env/node/base64.d.ts.map +1 -1
  307. package/dist/utils/env/node/base64.js +2 -0
  308. package/dist/utils/env/node/base64.js.map +1 -1
  309. package/dist/utils/keys/chord.d.ts +52 -0
  310. package/dist/utils/keys/chord.d.ts.map +1 -0
  311. package/dist/utils/keys/chord.js +135 -0
  312. package/dist/utils/keys/chord.js.map +1 -0
  313. package/dist/utils/keys/keybinding.d.ts +53 -0
  314. package/dist/utils/keys/keybinding.d.ts.map +1 -0
  315. package/dist/utils/keys/keybinding.js +58 -0
  316. package/dist/utils/keys/keybinding.js.map +1 -0
  317. package/dist/utils/path.d.ts +11 -0
  318. package/dist/utils/path.d.ts.map +1 -1
  319. package/dist/utils/path.js +13 -0
  320. package/dist/utils/path.js.map +1 -1
  321. package/docs/integrations.md +76 -10
  322. package/docs/kepler-read-api-parity.md +9 -0
  323. package/package.json +13 -5
  324. package/src/git/cache.ts +383 -47
  325. package/src/git/context.ts +11 -0
  326. package/src/git/models/fileStatus.ts +1 -0
  327. package/src/git/models/issue.ts +4 -0
  328. package/src/git/models/mergeConflicts.ts +3 -2
  329. package/src/git/models/pullRequest.ts +117 -1
  330. package/src/git/models/repository.ts +58 -17
  331. package/src/git/models/statusFile.ts +3 -0
  332. package/src/git/providers/branches.ts +6 -0
  333. package/src/git/providers/commits.ts +11 -0
  334. package/src/git/providers/config.ts +18 -0
  335. package/src/git/providers/operations.ts +1 -0
  336. package/src/git/providers/pausedOperations.ts +2 -0
  337. package/src/git/providers/status.ts +1 -0
  338. package/src/git/utils/config.utils.ts +8 -0
  339. package/src/git/utils/issue.utils.ts +2 -0
  340. package/src/git/utils/pausedOperationStatus.utils.ts +28 -0
  341. package/src/git/utils/pullRequest.utils.ts +86 -0
  342. package/src/git/watching/changeEvent.ts +6 -1
  343. package/src/git/watching/classifyChange.ts +15 -1
  344. package/src/git/watching/watchService.ts +20 -3
  345. package/src/git/watching/watchSession.ts +41 -6
  346. package/src/git/watching/watcherPatterns.ts +1 -1
  347. package/src/git-cli/exec/git.ts +13 -3
  348. package/src/git-cli/providers/branches.ts +384 -132
  349. package/src/git-cli/providers/commits.ts +106 -15
  350. package/src/git-cli/providers/config.ts +229 -8
  351. package/src/git-cli/providers/contributors.ts +5 -1
  352. package/src/git-cli/providers/graph.ts +26 -13
  353. package/src/git-cli/providers/operations.ts +45 -20
  354. package/src/git-cli/providers/pausedOperations.ts +109 -17
  355. package/src/git-cli/providers/refs.ts +33 -3
  356. package/src/git-cli/providers/status.ts +25 -4
  357. package/src/git-cli/providers/worktrees.ts +5 -0
  358. package/src/git-cli/service.ts +0 -1
  359. package/src/plus/agents/providers/claudeCodeProvider.ts +1130 -87
  360. package/src/plus/agents/providers/claudeCodeTranscript.ts +95 -9
  361. package/src/plus/agents/types.ts +46 -2
  362. package/src/plus/ai/errors.ts +30 -0
  363. package/src/plus/ai/models/model.ts +56 -6
  364. package/src/plus/ai/models/promptTemplates.ts +1 -1
  365. package/src/plus/ai/models/provider.ts +69 -2
  366. package/src/plus/ai/prompts.ts +186 -61
  367. package/src/plus/ai/providers/anthropicProvider.ts +155 -47
  368. package/src/plus/ai/providers/deepSeekProvider.ts +11 -21
  369. package/src/plus/ai/providers/geminiProvider.ts +19 -0
  370. package/src/plus/ai/providers/gitkrakenProvider.ts +126 -3
  371. package/src/plus/ai/providers/huggingFaceProvider.ts +6 -0
  372. package/src/plus/ai/providers/mistralProvider.ts +102 -19
  373. package/src/plus/ai/providers/ollamaProvider.ts +55 -4
  374. package/src/plus/ai/providers/openAICompatibleProvider.ts +5 -0
  375. package/src/plus/ai/providers/openAICompatibleProviderBase.ts +360 -17
  376. package/src/plus/ai/providers/openRouterProvider.ts +17 -0
  377. package/src/plus/ai/providers/openaiProvider.ts +1 -0
  378. package/src/plus/ai/providers/responseFormatCache.ts +39 -0
  379. package/src/plus/ai/providers/xaiProvider.ts +10 -1
  380. package/src/plus/ai/utils/ai.utils.ts +14 -0
  381. package/src/plus/ai/utils/results.utils.ts +255 -31
  382. package/src/plus/git-github/api/github.ts +467 -23
  383. package/src/plus/git-github/api/pullRequestSearchQuery.ts +81 -7
  384. package/src/plus/git-github/models.ts +77 -7
  385. package/src/plus/git-github/providers/github/status.ts +1 -0
  386. package/src/plus/integrations/context.ts +4 -0
  387. package/src/plus/integrations/index.ts +2 -0
  388. package/src/plus/integrations/integrationService.ts +25 -2
  389. package/src/plus/integrations/manager.ts +50 -5
  390. package/src/plus/integrations/models/gitHostIntegration.ts +54 -2
  391. package/src/plus/integrations/models/integration.ts +24 -13
  392. package/src/plus/integrations/providerFilters.ts +6 -1
  393. package/src/plus/integrations/providers/accounts.ts +38 -0
  394. package/src/plus/integrations/providers/azure/models.ts +3 -0
  395. package/src/plus/integrations/providers/bitbucket/models.ts +1 -0
  396. package/src/plus/integrations/providers/github.ts +218 -145
  397. package/src/plus/integrations/providers/gitlab.ts +30 -20
  398. package/src/plus/integrations/providers/models.ts +99 -127
  399. package/src/plus/integrations/providers/pullRequestReviews.ts +122 -0
  400. package/src/plus/integrations/reads/broaden.ts +6 -1
  401. package/src/plus/integrations/reads/counts.ts +273 -2
  402. package/src/plus/integrations/reads/drains.ts +19 -6
  403. package/src/plus/integrations/reads/filters.ts +22 -0
  404. package/src/plus/integrations/reads/hierarchy.ts +3 -1
  405. package/src/plus/integrations/reads/issueTracker.ts +9 -3
  406. package/src/plus/integrations/reads/issues.ts +9 -4
  407. package/src/plus/integrations/reads/pullRequests.ts +9 -4
  408. package/src/plus/integrations/reads/searchPullRequests.ts +12 -4
  409. package/src/plus/integrations/reads/sweeps.ts +1 -1
  410. package/src/plus/integrations/reads/warnings.ts +12 -1
  411. package/src/plus/integrations/results.ts +6 -1
  412. package/src/utils/env/browser/base64.ts +1 -1
  413. package/src/utils/env/node/base64.ts +3 -1
  414. package/src/utils/keys/chord.ts +184 -0
  415. package/src/utils/keys/keybinding.ts +116 -0
  416. package/src/utils/path.ts +14 -0
@@ -1,13 +1,30 @@
1
1
  import { CancellationError, isCancellationError } from '../../../utils/cancellation.js';
2
2
  import { uuid } from '../../../utils/crypto.js';
3
- import { getLoggableName } from '../../../utils/logger.js';
3
+ import { getLoggableName, Logger } from '../../../utils/logger.js';
4
4
  import { maybeStartScopedLogger } from '../../../utils/logger.scoped.js';
5
5
  import type { AIProviders } from '../constants.js';
6
6
  import { AIError, AIErrorReason } from '../errors.js';
7
7
  import type { AIActionType, AIModel, AIProviderDescriptor } from '../models/model.js';
8
- import type { AIChatMessage, AIChatMessageRole, AIProvider, AIProviderResponse } from '../models/provider.js';
8
+ import type {
9
+ AIChatMessage,
10
+ AIChatMessageRole,
11
+ AIFinishReason,
12
+ AIProvider,
13
+ AIProviderResponse,
14
+ AIResponseFormat,
15
+ AIToolCall,
16
+ AIToolDefinition,
17
+ JSONSchema,
18
+ } from '../models/provider.js';
9
19
  import { getActionName, getReducedMaxInputTokens, getValidatedTemperature } from '../utils/ai.utils.js';
10
20
  import type { AIProviderContext } from './context.js';
21
+ import { isResponseFormatRejected, rememberResponseFormatRejection } from './responseFormatCache.js';
22
+
23
+ // Native-format rejection wording varies by provider ("response_format", "structured outputs",
24
+ // Gemini's "Json mode is not enabled", and its OpenAI-compat layer's `generation_config.response_schema`
25
+ // path in INVALID_ARGUMENT bodies, ...)
26
+ const formatRejectionRegex =
27
+ /response[_ ]?(?:format|schema)|structured[_ ]?output|output_config|json_schema|json[_ ]?mode/i;
11
28
 
12
29
  export abstract class OpenAICompatibleProviderBase<T extends AIProviders> implements AIProvider<T> {
13
30
  constructor(protected readonly context: AIProviderContext) {}
@@ -54,6 +71,14 @@ export abstract class OpenAICompatibleProviderBase<T extends AIProviders> implem
54
71
 
55
72
  abstract getModels(): Promise<readonly AIModel<T>[]>;
56
73
 
74
+ protected isResponseFormatSessionRejected(model: AIModel<T>, responseFormat: AIResponseFormat): boolean {
75
+ return isResponseFormatRejected(this.id, model, responseFormat);
76
+ }
77
+
78
+ protected rememberResponseFormatRejection(model: AIModel<T>, responseFormat: AIResponseFormat): void {
79
+ rememberResponseFormatRejection(this.id, model, responseFormat);
80
+ }
81
+
57
82
  protected abstract getUrl(_model: AIModel<T>): string | undefined;
58
83
 
59
84
  protected getHeaders<TAction extends AIActionType>(
@@ -76,11 +101,13 @@ export abstract class OpenAICompatibleProviderBase<T extends AIProviders> implem
76
101
  action: TAction,
77
102
  model: AIModel<T>,
78
103
  apiKey: string,
79
- getMessages: (maxInputTokens: number, retries: number) => Promise<AIChatMessage[]>,
104
+ getMessages: (maxInputTokens: number, retries: number) => Promise<AIChatMessage<AIChatMessageRole>[]>,
80
105
  options: {
81
106
  signal: AbortSignal;
82
107
  modelOptions?: { outputTokens?: number; temperature?: number };
83
108
  conversationId?: string;
109
+ responseFormat?: AIResponseFormat;
110
+ tools?: readonly AIToolDefinition[];
84
111
  },
85
112
  ): Promise<AIProviderResponse<void> | undefined> {
86
113
  using scope = maybeStartScopedLogger(`${getLoggableName(this)}.sendRequest`);
@@ -92,8 +119,10 @@ export abstract class OpenAICompatibleProviderBase<T extends AIProviders> implem
92
119
  apiKey,
93
120
  getMessages,
94
121
  options.modelOptions,
122
+ options.responseFormat,
95
123
  options.signal,
96
124
  options.conversationId,
125
+ options.tools,
97
126
  );
98
127
  return result;
99
128
  } catch (ex) {
@@ -116,18 +145,27 @@ export abstract class OpenAICompatibleProviderBase<T extends AIProviders> implem
116
145
  action: TAction,
117
146
  model: AIModel<T>,
118
147
  apiKey: string,
119
- messages: (maxInputTokens: number, retries: number) => Promise<AIChatMessage[]>,
148
+ messages: (maxInputTokens: number, retries: number) => Promise<AIChatMessage<AIChatMessageRole>[]>,
120
149
  modelOptions?: { outputTokens?: number; temperature?: number },
150
+ responseFormat?: AIResponseFormat,
121
151
  signal?: AbortSignal,
122
152
  conversationId?: string,
153
+ tools?: readonly AIToolDefinition[],
123
154
  ): Promise<AIProviderResponse<void>> {
124
155
  let retries = 0;
125
156
  let maxInputTokens = model.maxTokens.input;
157
+ // Latched when the provider rejects the `tools` field, so the retry drops tools entirely and
158
+ // the caller can stop offering them for the rest of the session.
159
+ let toolsRejected = false;
126
160
 
127
161
  while (true) {
162
+ const useTools = tools?.length && !toolsRejected ? tools : undefined;
163
+ const { messages: msgs, system } = this.extractSystemPrompt(await messages(maxInputTokens, retries));
128
164
  const request: ChatCompletionRequest = {
129
165
  model: model.id,
130
- ...this.extractSystemPrompt(await messages(maxInputTokens, retries)),
166
+ messages: this.serializeMessages(msgs),
167
+ ...(system != null ? { system: system } : undefined),
168
+ ...(useTools != null ? this.serializeTools(useTools) : undefined),
131
169
  stream: false,
132
170
  max_completion_tokens: model.maxTokens.output
133
171
  ? Math.min(modelOptions?.outputTokens ?? Infinity, model.maxTokens.output)
@@ -138,22 +176,98 @@ export abstract class OpenAICompatibleProviderBase<T extends AIProviders> implem
138
176
  this.context.defaultTemperature,
139
177
  ),
140
178
  };
179
+ if (
180
+ responseFormat != null &&
181
+ !this.isResponseFormatSessionRejected(model, responseFormat) &&
182
+ this.supportsResponseFormat(model)
183
+ ) {
184
+ this.applyResponseFormat(request, model, responseFormat);
185
+ }
186
+
187
+ let rsp = await this.fetchCore(action, model, apiKey, request, signal, conversationId);
188
+ // Read once and forward to `handleFetchFailure` so the error body isn't decoded twice
189
+ let errorBody: string | undefined;
190
+ if (!rsp.ok && (request.response_format != null || request.output_config != null)) {
191
+ // If the native format was rejected (stale model snapshot, older Azure api-version,
192
+ // proxy converter gaps, OpenRouter's require_parameters matching no endpoint), strip
193
+ // it and resend the same request — the prompt still describes the JSON. Resending
194
+ // directly avoids re-running the (non-idempotent) prompt build and its side effects
195
+ // Leave it `undefined` (not '') when the read fails, so `handleFetchFailure` falls
196
+ // back to reading the still-unconsumed response instead of parsing an empty string
197
+ errorBody = await rsp
198
+ .clone()
199
+ .text()
200
+ .catch(() => undefined);
201
+ if (this.isResponseFormatRejection(rsp.status, errorBody ?? '')) {
202
+ Logger.warn(
203
+ `(${this.name}) ${model.id}: native response format was rejected (${rsp.status}); retrying without it`,
204
+ );
205
+ const rejected = responseFormat;
206
+ responseFormat = undefined;
207
+ delete request.response_format;
208
+ delete request.output_config;
209
+ delete request.provider;
210
+ rsp = await this.fetchCore(action, model, apiKey, request, signal, conversationId);
211
+ errorBody = undefined;
212
+ // Memoize only when stripping fixed it — proof the format (not e.g. an oversized
213
+ // prompt behind the same wrapped status) caused the failure
214
+ if (rsp.ok && rejected != null) {
215
+ this.rememberResponseFormatRejection(model, rejected);
216
+ }
217
+ }
218
+ }
141
219
 
142
- const rsp = await this.fetchCore(action, model, apiKey, request, signal, conversationId);
143
220
  if (!rsp.ok) {
144
- const result = await this.handleFetchFailure(rsp, action, model, retries, maxInputTokens);
221
+ const result = await this.handleFetchFailure(
222
+ rsp,
223
+ action,
224
+ model,
225
+ retries,
226
+ maxInputTokens,
227
+ errorBody,
228
+ useTools != null,
229
+ );
145
230
  if (result.retry) {
146
231
  maxInputTokens = result.maxInputTokens;
232
+ if (result.withoutTools) {
233
+ // Say so explicitly: the caller degrades silently by design, so the only remaining
234
+ // evidence would be `toolCalls=0` — indistinguishable from a model that simply chose
235
+ // not to consult the repository. That ambiguity is the whole reason to log it.
236
+ Logger.warn(
237
+ `(${this.name}) ${model.id}: tools were rejected (${rsp.status}); retrying without them — repository consultation is unavailable for the rest of this session`,
238
+ );
239
+ toolsRejected = true;
240
+ }
147
241
  retries++;
148
242
  continue;
149
243
  }
150
244
  }
151
245
 
152
246
  const data: ChatCompletionResponse = (await rsp.json()) as ChatCompletionResponse;
247
+ const toolCalls = this.parseToolCalls(data);
248
+ // A tool-call-only turn carries no text, which `extractContent` correctly reports as '' — it
249
+ // selects the text block by type, so a `tool_use` (or thinking) block is never mistaken for one.
250
+ // The calls ride alongside on `toolCalls` below.
251
+ const content = this.extractContent(data);
252
+ // Some providers/proxies (e.g. OpenRouter) report upstream failures as an OK response
253
+ // carrying an error envelope and no choices — surface that instead of empty content
254
+ if (!content && toolCalls == null && data.error != null) {
255
+ if (data.error.code === 429 || data.error.code === '429') {
256
+ throw new AIError(
257
+ AIErrorReason.RateLimitOrFundsExceeded,
258
+ new Error(`(${this.name}) ${data.error.message || 'Rate limit exceeded'}`),
259
+ );
260
+ }
261
+ throw new Error(`(${this.name}) ${data.error.message || 'Unknown error'}`);
262
+ }
263
+
153
264
  const result: AIProviderResponse<void> = {
154
265
  id: data.id ?? uuid(),
155
- content: data.choices?.[0].message.content?.trim() ?? data.content?.[0]?.text?.trim() ?? '',
266
+ content: content,
267
+ finishReason: this.extractFinishReason(data),
156
268
  model: model,
269
+ ...(toolCalls?.length ? { toolCalls: toolCalls } : undefined),
270
+ ...(toolsRejected ? { toolsRejected: true } : undefined),
157
271
  usage: {
158
272
  promptTokens: data.usage?.prompt_tokens ?? data.usage?.input_tokens,
159
273
  completionTokens: data.usage?.completion_tokens ?? data.usage?.output_tokens,
@@ -185,13 +299,196 @@ export abstract class OpenAICompatibleProviderBase<T extends AIProviders> implem
185
299
  return { messages: messages };
186
300
  }
187
301
 
302
+ /** Whether the given model accepts a native response-format/schema field; when false the
303
+ * prompt's JSON description is the only format control (the request is unchanged).
304
+ * Dynamic-catalog providers (GitKraken, OpenRouter) populate
305
+ * {@link AIModel.supportsStructuredOutputs}; static providers override with id-based gates */
306
+ protected supportsResponseFormat(model: AIModel<T>): boolean {
307
+ return model.supportsStructuredOutputs ?? true;
308
+ }
309
+
310
+ /**
311
+ * Whether a failed response reads as a rejection of the `tools` field rather than a problem with the
312
+ * prompt — the trigger for one retry without tools, so a tool-using feature degrades to single-shot
313
+ * instead of failing outright.
314
+ *
315
+ * Providers that wrap upstream rejections in their own envelope (e.g. GitKraken) must override this:
316
+ * the wrapper's prose names neither "tool" nor "function", so the text match here cannot see through
317
+ * it and the fallback would never fire.
318
+ */
319
+ protected isToolsRejection(status: number, message: string | undefined): boolean {
320
+ if (status !== 400 || !message) return false;
321
+
322
+ const m = message.toLowerCase();
323
+ return (
324
+ (m.includes('tool') || m.includes('function')) &&
325
+ (m.includes('unsupported') ||
326
+ m.includes('not supported') ||
327
+ m.includes('unknown') ||
328
+ m.includes('unrecognized') ||
329
+ m.includes('invalid') ||
330
+ m.includes('unexpected'))
331
+ );
332
+ }
333
+
334
+ /** Whether a failed response means the native response format itself was rejected — the
335
+ * trigger for stripping it and retrying via the prompt-described JSON. Providers that wrap
336
+ * upstream rejections in their own envelope (e.g. GitKraken) extend this */
337
+ protected isResponseFormatRejection(status: number, body: string): boolean {
338
+ return (
339
+ (status === 400 && formatRejectionRegex.test(body)) ||
340
+ // OpenRouter answers 404 when `require_parameters` matches no serving endpoint
341
+ (status === 404 && /no endpoints found/i.test(body))
342
+ );
343
+ }
344
+
345
+ /** Translates the canonical response format onto the request; called only when
346
+ * {@link supportsResponseFormat} allows it. Base emits the OpenAI `response_format.json_schema`
347
+ * envelope. Overrides must REPLACE this for providers with a different native field
348
+ * (e.g. Anthropic `output_config`) — never leave both on the request. */
349
+ protected applyResponseFormat(
350
+ request: ChatCompletionRequest,
351
+ _model: AIModel<T>,
352
+ responseFormat: AIResponseFormat,
353
+ ): void {
354
+ request.response_format = {
355
+ type: 'json_schema',
356
+ json_schema: {
357
+ name: responseFormat.name,
358
+ strict: true,
359
+ schema: responseFormat.schema,
360
+ },
361
+ };
362
+ }
363
+
364
+ // Finds the text block by type rather than position — with some Anthropic models (e.g. Sonnet 5,
365
+ // where thinking runs by default) a thinking block can precede the text block
366
+ protected extractContent(data: ChatCompletionResponse): string {
367
+ return (
368
+ data.choices?.[0]?.message?.content?.trim() ??
369
+ data.content?.find(b => b.type === 'text')?.text?.trim() ??
370
+ ''
371
+ );
372
+ }
373
+
374
+ /** Normalizes abnormal completion signals (all arrive as HTTP 200) so consumers can
375
+ * distinguish truncated/filtered/refused output from a malformed-but-complete reply */
376
+ protected extractFinishReason(data: ChatCompletionResponse): AIFinishReason | undefined {
377
+ const choice = data.choices?.[0];
378
+ if (choice != null) {
379
+ // Truthy check — some OpenAI-compatible servers serialize `"refusal": ""` on normal replies
380
+ if (choice.message?.refusal) return 'refusal';
381
+ if (choice.finish_reason === 'length') return 'length';
382
+ if (choice.finish_reason === 'content_filter') return 'content_filter';
383
+ return undefined;
384
+ }
385
+
386
+ switch (data.stop_reason) {
387
+ // `model_context_window_exceeded` means the context filled before the output limit — the
388
+ // reply is truncated all the same
389
+ case 'max_tokens':
390
+ case 'model_context_window_exceeded':
391
+ return 'length';
392
+ case 'refusal':
393
+ return 'refusal';
394
+ default:
395
+ return undefined;
396
+ }
397
+ }
398
+
399
+ /**
400
+ * Maps messages onto the provider's wire shape. The default is OpenAI Chat Completions: assistant
401
+ * tool calls go in `tool_calls` with `arguments` JSON-stringified, and tool results are `tool`-role
402
+ * entries keyed by `tool_call_id`. Providers with a different tool encoding (e.g. Anthropic's
403
+ * content blocks) override this.
404
+ */
405
+ protected serializeMessages(messages: AIChatMessage<AIChatMessageRole>[]): unknown[] {
406
+ return messages.map(m => {
407
+ if (m.role === 'tool') {
408
+ // A tool result is only addressable by the id of the call it answers. Without one, send it
409
+ // as plain text — the same shape used for providers that can't carry tool calls at all —
410
+ // rather than a `tool_call_id: undefined` the provider rejects with nothing to trace it to.
411
+ if (m.toolCallId == null) return { role: 'user', content: m.content };
412
+
413
+ return { role: 'tool', tool_call_id: m.toolCallId, content: m.content };
414
+ }
415
+
416
+ if (m.role === 'assistant' && m.toolCalls?.length) {
417
+ return {
418
+ role: 'assistant',
419
+ // OpenAI requires `content` to be present, but allows null on a tool-call-only turn
420
+ content: m.content || null,
421
+ tool_calls: m.toolCalls.map(c => ({
422
+ id: c.id,
423
+ type: 'function',
424
+ function: { name: c.name, arguments: JSON.stringify(c.args) },
425
+ // Only the GitKraken proxy mints these, so the field can only ever be present on a
426
+ // conversation with that proxy — no other provider sees it.
427
+ ...(c.providerSignature != null ? { thought_signature: c.providerSignature } : undefined),
428
+ })),
429
+ };
430
+ }
431
+
432
+ return { role: m.role, content: m.content };
433
+ });
434
+ }
435
+
436
+ /** Returns the tool-related request fields. The default is the OpenAI `function` envelope. */
437
+ protected serializeTools(tools: readonly AIToolDefinition[]): { tools: unknown[] } {
438
+ return {
439
+ tools: tools.map(t => ({
440
+ type: 'function',
441
+ function: { name: t.name, description: t.description, parameters: t.parameters },
442
+ })),
443
+ };
444
+ }
445
+
446
+ /**
447
+ * Extracts tool calls from a response, handling both the OpenAI shape
448
+ * (`choices[].message.tool_calls`, whose `arguments` is a JSON string) and the Anthropic shape
449
+ * (`content[]` blocks of type `tool_use`) — mirroring how {@link fetch} already reads content
450
+ * from both.
451
+ */
452
+ protected parseToolCalls(data: ChatCompletionResponse): AIToolCall[] | undefined {
453
+ const calls: AIToolCall[] = [];
454
+
455
+ for (const call of data.choices?.[0]?.message?.tool_calls ?? []) {
456
+ let args: Record<string, unknown> = {};
457
+ try {
458
+ // Providers can emit '' for a no-argument call
459
+ args = call.function.arguments ? (JSON.parse(call.function.arguments) as Record<string, unknown>) : {};
460
+ } catch {
461
+ // Leave args empty — the tool dispatcher reports the resulting validation error back to
462
+ // the model, which is more recoverable than failing the whole request here.
463
+ }
464
+
465
+ calls.push({
466
+ id: call.id,
467
+ name: call.function.name,
468
+ args: args,
469
+ ...(call.thought_signature != null ? { providerSignature: call.thought_signature } : undefined),
470
+ });
471
+ }
472
+
473
+ for (const block of data.content ?? []) {
474
+ if (block.type !== 'tool_use' || block.id == null || block.name == null) continue;
475
+
476
+ calls.push({ id: block.id, name: block.name, args: block.input ?? {} });
477
+ }
478
+
479
+ return calls.length ? calls : undefined;
480
+ }
481
+
188
482
  protected async handleFetchFailure<TAction extends AIActionType>(
189
483
  rsp: Response,
190
484
  _action: TAction,
191
485
  model: AIModel<T>,
192
486
  retries: number,
193
487
  maxInputTokens: number,
194
- ): Promise<{ retry: true; maxInputTokens: number }> {
488
+ /** The already-read error body, when the caller had to read it (avoids a second decode) */
489
+ body?: string,
490
+ sentTools?: boolean,
491
+ ): Promise<{ retry: true; maxInputTokens: number; withoutTools?: boolean }> {
195
492
  if (rsp.status === 404) {
196
493
  throw new AIError(
197
494
  AIErrorReason.Unauthorized,
@@ -209,7 +506,9 @@ export abstract class OpenAICompatibleProviderBase<T extends AIProviders> implem
209
506
 
210
507
  let json;
211
508
  try {
212
- json = (await rsp.json()) as { error?: { code: string; message: string } } | undefined;
509
+ json = (body != null ? JSON.parse(body) : await rsp.json()) as
510
+ | { error?: { code: string; message: string } }
511
+ | undefined;
213
512
  } catch {}
214
513
 
215
514
  if (Array.isArray(json)) {
@@ -226,6 +525,13 @@ export abstract class OpenAICompatibleProviderBase<T extends AIProviders> implem
226
525
  );
227
526
  }
228
527
 
528
+ // A provider that advertises the OpenAI shape but rejects `tools` (an older or partial
529
+ // implementation) gets one retry without them, so the caller degrades to text-only instead of
530
+ // failing outright.
531
+ if (sentTools && this.isToolsRejection(rsp.status, json?.error?.message)) {
532
+ return { retry: true, maxInputTokens: maxInputTokens, withoutTools: true };
533
+ }
534
+
229
535
  throw new Error(`(${this.name}) ${rsp.status}: ${json?.error?.message || rsp.statusText}`);
230
536
  }
231
537
 
@@ -242,11 +548,23 @@ export abstract class OpenAICompatibleProviderBase<T extends AIProviders> implem
242
548
  throw new Error(`(${this.name}) ${getActionName(action)}: No URL configured`);
243
549
  }
244
550
 
551
+ const body = JSON.stringify(request);
552
+
553
+ // A rejected request is otherwise undiagnosable: a proxying backend reports only its own
554
+ // "upstream AI provider error (<provider> HTTP <status>)", so which field the upstream objected to
555
+ // can only be inferred. Log the request's shape — which fields were sent, and how much — but not
556
+ // the messages, which carry the user's source code.
557
+ Logger.trace(
558
+ `(${this.name}) ${getActionName(action)} → ${model.id}: fields=[${Object.keys(request)
559
+ .sort()
560
+ .join(',')}] bytes=${body.length}`,
561
+ );
562
+
245
563
  try {
246
564
  return await this.context.fetch(url, {
247
565
  headers: await this.getHeaders(action, apiKey, model, url, conversationId),
248
566
  method: 'POST',
249
- body: JSON.stringify(request),
567
+ body: body,
250
568
  signal: signal,
251
569
  });
252
570
  } catch (ex) {
@@ -256,13 +574,17 @@ export abstract class OpenAICompatibleProviderBase<T extends AIProviders> implem
256
574
  }
257
575
  }
258
576
 
259
- interface ChatCompletionRequest {
577
+ export interface ChatCompletionRequest {
260
578
  model: string;
261
- messages: AIChatMessage<AIChatMessageRole>[];
579
+ /** Provider-shaped messages from {@link OpenAICompatibleProviderBase.serializeMessages} */
580
+ messages: unknown[];
262
581
 
263
582
  /** Anthropic carries the initial system prompt here instead of as a `system`-role message */
264
583
  system?: string;
265
584
 
585
+ /** Tool definitions, shaped by {@link OpenAICompatibleProviderBase.serializeTools} */
586
+ tools?: unknown[];
587
+
266
588
  /** @deprecated but used by Anthropic & Gemini */
267
589
  max_tokens?: number;
268
590
  /** Currently can't be used for Anthropic & Gemini */
@@ -274,9 +596,16 @@ interface ChatCompletionRequest {
274
596
 
275
597
  /** Not supported by many models/providers */
276
598
  reasoning_effort?: 'low' | 'medium' | 'high';
599
+
600
+ /** OpenAI-compatible structured output (set via {@link OpenAICompatibleProviderBase.applyResponseFormat}) */
601
+ response_format?: { type: 'json_schema'; json_schema: { name: string; strict: boolean; schema: JSONSchema } };
602
+ /** Anthropic-native structured output (set only by the Anthropic override) */
603
+ output_config?: { format: { type: 'json_schema'; schema: JSONSchema } };
604
+ /** OpenRouter-only routing constraint (set only by the OpenRouter override) */
605
+ provider?: { require_parameters: boolean };
277
606
  }
278
607
 
279
- interface ChatCompletionResponse {
608
+ export interface ChatCompletionResponse {
280
609
  id: string;
281
610
  model: string;
282
611
  /** OpenAI compatible output */
@@ -285,12 +614,26 @@ interface ChatCompletionResponse {
285
614
  message: {
286
615
  role: string;
287
616
  content: string | null;
288
- refusal: string | null;
617
+ /** OpenAI-only; most OpenAI-compatible servers omit it entirely */
618
+ refusal?: string | null;
619
+ tool_calls?: {
620
+ id: string;
621
+ type: string;
622
+ function: { name: string; arguments: string };
623
+ /** GitKraken proxy only — an opaque provider reasoning token to round-trip */
624
+ thought_signature?: string;
625
+ }[];
289
626
  };
290
627
  finish_reason: string;
291
628
  }[];
292
- /** Anthropic output */
293
- content?: { type: 'text'; text: string }[];
629
+ /** Anthropic output (non-text blocks, e.g. thinking, carry no text). `tool_use` blocks add the
630
+ * call fields, which is why `id`/`name` are optional here — {@link parseToolCalls} narrows on
631
+ * `type` and then checks them. */
632
+ content?: { type: string; text?: string; id?: string; name?: string; input?: Record<string, unknown> }[];
633
+ /** Error envelope some providers/proxies return with an OK status (e.g. OpenRouter upstream failures) */
634
+ error?: { message?: string; code?: number | string } | null;
635
+ /** Anthropic stop reason */
636
+ stop_reason?: string;
294
637
  usage: {
295
638
  /** OpenAI compatible */
296
639
  prompt_tokens?: number;
@@ -1,6 +1,8 @@
1
1
  import { isCancellationError } from '../../../utils/cancellation.js';
2
2
  import { openRouterProviderDescriptor as provider } from '../constants.js';
3
3
  import type { AIActionType, AIModel } from '../models/model.js';
4
+ import type { AIResponseFormat } from '../models/provider.js';
5
+ import type { ChatCompletionRequest } from './openAICompatibleProviderBase.js';
4
6
  import { OpenAICompatibleProviderBase } from './openAICompatibleProviderBase.js';
5
7
 
6
8
  type OpenRouterModel = AIModel<typeof provider.id>;
@@ -8,6 +10,7 @@ type OpenRouterModel = AIModel<typeof provider.id>;
8
10
  export class OpenRouterProvider extends OpenAICompatibleProviderBase<typeof provider.id> {
9
11
  readonly id = provider.id;
10
12
  readonly name = provider.name;
13
+ readonly supportsTools = true;
11
14
  protected readonly descriptor = provider;
12
15
  protected readonly config = {
13
16
  keyUrl: 'https://openrouter.ai/keys',
@@ -40,6 +43,7 @@ export class OpenRouterProvider extends OpenAICompatibleProviderBase<typeof prov
40
43
  top_provider: {
41
44
  max_completion_tokens?: number;
42
45
  };
46
+ supported_parameters?: string[];
43
47
  }[];
44
48
  };
45
49
 
@@ -55,10 +59,23 @@ export class OpenRouterProvider extends OpenAICompatibleProviderBase<typeof prov
55
59
  },
56
60
  provider: provider,
57
61
  temperature: null,
62
+ // OpenRouter hard-errors when `response_format` reaches a model that doesn't advertise support
63
+ supportsStructuredOutputs: m.supported_parameters?.includes('structured_outputs') ?? false,
58
64
  }) satisfies OpenRouterModel,
59
65
  );
60
66
  }
61
67
 
68
+ protected override applyResponseFormat(
69
+ request: ChatCompletionRequest,
70
+ model: AIModel<typeof provider.id>,
71
+ responseFormat: AIResponseFormat,
72
+ ): void {
73
+ super.applyResponseFormat(request, model, responseFormat);
74
+ // `supported_parameters` is a union across the providers serving a model — restrict routing
75
+ // to providers that actually support every parameter sent
76
+ request.provider = { require_parameters: true };
77
+ }
78
+
62
79
  protected getUrl(_model: AIModel<typeof provider.id>): string {
63
80
  return 'https://openrouter.ai/api/v1/chat/completions';
64
81
  }
@@ -10,6 +10,7 @@ const models: OpenAIModel[] = openAIModels(provider);
10
10
  export class OpenAIProvider extends OpenAICompatibleProviderBase<typeof provider.id> {
11
11
  readonly id = provider.id;
12
12
  readonly name = provider.name;
13
+ readonly supportsTools = true;
13
14
  protected readonly descriptor = provider;
14
15
  protected readonly config = {
15
16
  keyUrl: 'https://platform.openai.com/account/api-keys',
@@ -0,0 +1,39 @@
1
+ import type { AIModel } from '../models/model.js';
2
+ import type { AIResponseFormat } from '../models/provider.js';
3
+
4
+ /**
5
+ * `providerId:modelId:schemaName` keys whose native format was rejected. Module-scoped because the
6
+ * service constructs a fresh provider instance per request, so instance state wouldn't survive to
7
+ * skip the doomed probe. Keyed per schema: e.g. GitKraken-Gemini rejects the null-union review
8
+ * schemas but happily enforces the commits schema on the same model.
9
+ *
10
+ * Lives in its own (dependency-free) module so the AI service can clear it without statically
11
+ * importing a provider implementation — every provider is loaded lazily into the `ai` chunk.
12
+ */
13
+ const rejectedResponseFormats = new Set<string>();
14
+
15
+ function getKey(providerId: string, model: AIModel, responseFormat: AIResponseFormat): string {
16
+ return `${providerId}:${model.id}:${responseFormat.name}`;
17
+ }
18
+
19
+ export function isResponseFormatRejected(
20
+ providerId: string,
21
+ model: AIModel,
22
+ responseFormat: AIResponseFormat,
23
+ ): boolean {
24
+ return rejectedResponseFormats.has(getKey(providerId, model, responseFormat));
25
+ }
26
+
27
+ export function rememberResponseFormatRejection(
28
+ providerId: string,
29
+ model: AIModel,
30
+ responseFormat: AIResponseFormat,
31
+ ): void {
32
+ rejectedResponseFormats.add(getKey(providerId, model, responseFormat));
33
+ }
34
+
35
+ /** Drops the remembered rejections; shares the provider model-list cache's lifetime, so a
36
+ * misclassified rejection can't outlive the capability data it was inferred from */
37
+ export function clearResponseFormatRejections(): void {
38
+ rejectedResponseFormats.clear();
39
+ }
@@ -4,6 +4,12 @@ import { OpenAICompatibleProviderBase } from './openAICompatibleProviderBase.js'
4
4
 
5
5
  type XAIModel = AIModel<typeof provider.id>;
6
6
  const models: XAIModel[] = [
7
+ {
8
+ id: 'grok-4.6',
9
+ name: 'Grok 4.6',
10
+ maxTokens: { input: 500000, output: 65536 }, // xAI publishes no output cap; mirrors Grok 4.5 to bound cost
11
+ provider: provider,
12
+ },
7
13
  {
8
14
  id: 'grok-4.5',
9
15
  name: 'Grok 4.5',
@@ -13,7 +19,7 @@ const models: XAIModel[] = [
13
19
  {
14
20
  id: 'grok-4.3',
15
21
  name: 'Grok 4.3',
16
- maxTokens: { input: 1048576, output: 65536 },
22
+ maxTokens: { input: 1000000, output: 65536 },
17
23
  provider: provider,
18
24
  default: true,
19
25
  },
@@ -23,12 +29,15 @@ const models: XAIModel[] = [
23
29
  maxTokens: { input: 131072, output: 4096 },
24
30
  provider: provider,
25
31
  hidden: true,
32
+ // Structured outputs require grok-2-1212 or newer; grok-beta predates that floor
33
+ supportsStructuredOutputs: false,
26
34
  },
27
35
  ];
28
36
 
29
37
  export class XAIProvider extends OpenAICompatibleProviderBase<typeof provider.id> {
30
38
  readonly id = provider.id;
31
39
  readonly name = provider.name;
40
+ readonly supportsTools = true;
32
41
  protected readonly descriptor = provider;
33
42
  protected readonly config = {
34
43
  keyUrl: 'https://console.x.ai/',
@@ -1,4 +1,10 @@
1
1
  import type { AIActionType, AIModel } from '../models/model.js';
2
+ import type { AIFinishReason } from '../models/provider.js';
3
+
4
+ /** Whether the model declined to fulfill the request (refused or content-filtered) */
5
+ export function didModelDecline(finishReason: AIFinishReason | undefined): boolean {
6
+ return finishReason === 'refusal' || finishReason === 'content_filter';
7
+ }
2
8
 
3
9
  export function getActionName(action: AIActionType): string {
4
10
  switch (action) {
@@ -32,6 +38,14 @@ export function getValidatedTemperature(
32
38
  modelTemperature?: number | null,
33
39
  defaultTemperature?: number,
34
40
  ): number | undefined {
41
+ // `temperature: null` on the model is a capability statement, not a default: the model rejects the
42
+ // parameter (reasoning models, Anthropic Opus 4.7+/Sonnet 5, Gemini 3.x) or the catalog can't
43
+ // promise it accepts one (every GitKraken-proxy and OpenRouter model). It therefore has to win over
44
+ // a caller-supplied value — callers merge as `modelOptions?.temperature ?? model.temperature`, and
45
+ // `0 ?? null` is `0`, so an explicit 0 would otherwise defeat the opt-out and be sent anyway.
46
+ // It also covers upstreams the id check below can't see: a proxy model is `openai:gpt-5.6-…`, not
47
+ // `gpt-5.6-…`, so `startsWith` misses it while the upstream still 400s on a non-default value.
48
+ if (model.temperature === null) return undefined;
35
49
  if (modelTemperature === null) return undefined;
36
50
  // GPT5 doesn't support anything but the default temperature
37
51
  if (model.id.startsWith('gpt-5')) return undefined;