@office-ai/aioncli-core 0.1.21 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (349) hide show
  1. package/dist/index.d.ts +3 -0
  2. package/dist/index.js +3 -0
  3. package/dist/index.js.map +1 -1
  4. package/dist/src/code_assist/converter.d.ts +3 -2
  5. package/dist/src/code_assist/converter.js +1 -0
  6. package/dist/src/code_assist/converter.js.map +1 -1
  7. package/dist/src/code_assist/oauth2.d.ts +2 -0
  8. package/dist/src/code_assist/oauth2.js +47 -25
  9. package/dist/src/code_assist/oauth2.js.map +1 -1
  10. package/dist/src/code_assist/oauth2.test.js +99 -8
  11. package/dist/src/code_assist/oauth2.test.js.map +1 -1
  12. package/dist/src/code_assist/server.js +1 -1
  13. package/dist/src/code_assist/server.js.map +1 -1
  14. package/dist/src/code_assist/setup.js +48 -17
  15. package/dist/src/code_assist/setup.js.map +1 -1
  16. package/dist/src/code_assist/setup.test.js +114 -8
  17. package/dist/src/code_assist/setup.test.js.map +1 -1
  18. package/dist/src/config/config.d.ts +27 -8
  19. package/dist/src/config/config.js +54 -25
  20. package/dist/src/config/config.js.map +1 -1
  21. package/dist/src/config/config.test.js +109 -1
  22. package/dist/src/config/config.test.js.map +1 -1
  23. package/dist/src/core/client.d.ts +13 -15
  24. package/dist/src/core/client.js +215 -54
  25. package/dist/src/core/client.js.map +1 -1
  26. package/dist/src/core/client.test.js +631 -36
  27. package/dist/src/core/client.test.js.map +1 -1
  28. package/dist/src/core/contentGenerator.js +20 -12
  29. package/dist/src/core/contentGenerator.js.map +1 -1
  30. package/dist/src/core/contentGenerator.test.js +39 -15
  31. package/dist/src/core/contentGenerator.test.js.map +1 -1
  32. package/dist/src/core/coreToolScheduler.d.ts +2 -1
  33. package/dist/src/core/coreToolScheduler.js +26 -4
  34. package/dist/src/core/coreToolScheduler.js.map +1 -1
  35. package/dist/src/core/coreToolScheduler.test.js +230 -71
  36. package/dist/src/core/coreToolScheduler.test.js.map +1 -1
  37. package/dist/src/core/geminiChat.js +1 -1
  38. package/dist/src/core/geminiChat.js.map +1 -1
  39. package/dist/src/core/logger.d.ts +22 -1
  40. package/dist/src/core/logger.js +103 -17
  41. package/dist/src/core/logger.js.map +1 -1
  42. package/dist/src/core/logger.test.js +86 -20
  43. package/dist/src/core/logger.test.js.map +1 -1
  44. package/dist/src/core/loggingContentGenerator.d.ts +1 -0
  45. package/dist/src/core/loggingContentGenerator.js +7 -1
  46. package/dist/src/core/loggingContentGenerator.js.map +1 -1
  47. package/dist/src/core/nonInteractiveToolExecutor.d.ts +2 -2
  48. package/dist/src/core/nonInteractiveToolExecutor.js +11 -3
  49. package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -1
  50. package/dist/src/core/nonInteractiveToolExecutor.test.js +95 -46
  51. package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
  52. package/dist/src/core/openaiContentGenerator.js +71 -25
  53. package/dist/src/core/openaiContentGenerator.js.map +1 -1
  54. package/dist/src/core/openaiContentGenerator.test.js +1 -1
  55. package/dist/src/core/openaiContentGenerator.test.js.map +1 -1
  56. package/dist/src/core/prompts.js +4 -4
  57. package/dist/src/core/prompts.js.map +1 -1
  58. package/dist/src/core/subagent.js +5 -5
  59. package/dist/src/core/subagent.js.map +1 -1
  60. package/dist/src/core/subagent.test.js +3 -3
  61. package/dist/src/core/subagent.test.js.map +1 -1
  62. package/dist/src/generated/git-commit.d.ts +7 -0
  63. package/dist/src/generated/git-commit.js +10 -0
  64. package/dist/src/generated/git-commit.js.map +1 -0
  65. package/dist/src/ide/constants.d.ts +6 -0
  66. package/dist/src/ide/constants.js +7 -0
  67. package/dist/src/ide/constants.js.map +1 -0
  68. package/dist/src/ide/detect-ide.d.ts +12 -2
  69. package/dist/src/ide/detect-ide.js +64 -5
  70. package/dist/src/ide/detect-ide.js.map +1 -1
  71. package/dist/src/ide/detect-ide.test.d.ts +6 -0
  72. package/dist/src/ide/detect-ide.test.js +65 -0
  73. package/dist/src/ide/detect-ide.test.js.map +1 -0
  74. package/dist/src/ide/ide-client.d.ts +9 -1
  75. package/dist/src/ide/ide-client.js +113 -30
  76. package/dist/src/ide/ide-client.js.map +1 -1
  77. package/dist/src/ide/ide-client.test.d.ts +6 -0
  78. package/dist/src/ide/ide-client.test.js +43 -0
  79. package/dist/src/ide/ide-client.test.js.map +1 -0
  80. package/dist/src/ide/ide-installer.js +23 -34
  81. package/dist/src/ide/ide-installer.js.map +1 -1
  82. package/dist/src/ide/ide-installer.test.js +6 -8
  83. package/dist/src/ide/ide-installer.test.js.map +1 -1
  84. package/dist/src/ide/ideContext.d.ts +6 -6
  85. package/dist/src/ide/process-utils.d.ts +19 -0
  86. package/dist/src/ide/process-utils.js +140 -0
  87. package/dist/src/ide/process-utils.js.map +1 -0
  88. package/dist/src/index.d.ts +6 -1
  89. package/dist/src/index.js +6 -1
  90. package/dist/src/index.js.map +1 -1
  91. package/dist/src/mcp/google-auth-provider.js +9 -0
  92. package/dist/src/mcp/google-auth-provider.js.map +1 -1
  93. package/dist/src/mcp/google-auth-provider.test.js +45 -10
  94. package/dist/src/mcp/google-auth-provider.test.js.map +1 -1
  95. package/dist/src/mcp/oauth-provider.d.ts +0 -1
  96. package/dist/src/mcp/oauth-provider.js +176 -59
  97. package/dist/src/mcp/oauth-provider.js.map +1 -1
  98. package/dist/src/mcp/oauth-provider.test.js +132 -62
  99. package/dist/src/mcp/oauth-provider.test.js.map +1 -1
  100. package/dist/src/mcp/oauth-utils.d.ts +3 -1
  101. package/dist/src/mcp/oauth-utils.js +50 -12
  102. package/dist/src/mcp/oauth-utils.js.map +1 -1
  103. package/dist/src/mcp/oauth-utils.test.js +17 -2
  104. package/dist/src/mcp/oauth-utils.test.js.map +1 -1
  105. package/dist/src/mocks/msw.d.ts +6 -0
  106. package/dist/src/mocks/msw.js +8 -0
  107. package/dist/src/mocks/msw.js.map +1 -0
  108. package/dist/src/services/chatRecordingService.d.ts +150 -0
  109. package/dist/src/services/chatRecordingService.js +318 -0
  110. package/dist/src/services/chatRecordingService.js.map +1 -0
  111. package/dist/src/services/chatRecordingService.test.d.ts +6 -0
  112. package/dist/src/services/chatRecordingService.test.js +288 -0
  113. package/dist/src/services/chatRecordingService.test.js.map +1 -0
  114. package/dist/src/services/fileSystemService.d.ts +31 -0
  115. package/dist/src/services/fileSystemService.js +18 -0
  116. package/dist/src/services/fileSystemService.js.map +1 -0
  117. package/dist/src/services/fileSystemService.test.d.ts +6 -0
  118. package/dist/src/services/fileSystemService.test.js +41 -0
  119. package/dist/src/services/fileSystemService.test.js.map +1 -0
  120. package/dist/src/services/loopDetectionService.js +9 -10
  121. package/dist/src/services/loopDetectionService.js.map +1 -1
  122. package/dist/src/services/loopDetectionService.test.js +52 -0
  123. package/dist/src/services/loopDetectionService.test.js.map +1 -1
  124. package/dist/src/services/shellExecutionService.d.ts +8 -10
  125. package/dist/src/services/shellExecutionService.js +289 -133
  126. package/dist/src/services/shellExecutionService.js.map +1 -1
  127. package/dist/src/services/shellExecutionService.test.js +274 -30
  128. package/dist/src/services/shellExecutionService.test.js.map +1 -1
  129. package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +61 -17
  130. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +211 -233
  131. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
  132. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.d.ts +11 -0
  133. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +250 -94
  134. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
  135. package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +9 -2
  136. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +19 -9
  137. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
  138. package/dist/src/telemetry/constants.d.ts +1 -0
  139. package/dist/src/telemetry/constants.js +1 -0
  140. package/dist/src/telemetry/constants.js.map +1 -1
  141. package/dist/src/telemetry/index.d.ts +2 -2
  142. package/dist/src/telemetry/index.js +2 -2
  143. package/dist/src/telemetry/index.js.map +1 -1
  144. package/dist/src/telemetry/integration.test.circular.js +1 -0
  145. package/dist/src/telemetry/integration.test.circular.js.map +1 -1
  146. package/dist/src/telemetry/loggers.d.ts +3 -1
  147. package/dist/src/telemetry/loggers.js +39 -6
  148. package/dist/src/telemetry/loggers.js.map +1 -1
  149. package/dist/src/telemetry/loggers.test.js +37 -7
  150. package/dist/src/telemetry/loggers.test.js.map +1 -1
  151. package/dist/src/telemetry/metrics.d.ts +5 -1
  152. package/dist/src/telemetry/metrics.js +23 -9
  153. package/dist/src/telemetry/metrics.js.map +1 -1
  154. package/dist/src/telemetry/metrics.test.js +31 -1
  155. package/dist/src/telemetry/metrics.test.js.map +1 -1
  156. package/dist/src/telemetry/sdk.d.ts +1 -1
  157. package/dist/src/telemetry/sdk.js +80 -44
  158. package/dist/src/telemetry/sdk.js.map +1 -1
  159. package/dist/src/telemetry/sdk.test.d.ts +6 -0
  160. package/dist/src/telemetry/sdk.test.js +82 -0
  161. package/dist/src/telemetry/sdk.test.js.map +1 -0
  162. package/dist/src/telemetry/telemetry.test.js +2 -2
  163. package/dist/src/telemetry/telemetry.test.js.map +1 -1
  164. package/dist/src/telemetry/types.d.ts +41 -14
  165. package/dist/src/telemetry/types.js +52 -23
  166. package/dist/src/telemetry/types.js.map +1 -1
  167. package/dist/src/telemetry/uiTelemetry.d.ts +4 -0
  168. package/dist/src/telemetry/uiTelemetry.js +14 -1
  169. package/dist/src/telemetry/uiTelemetry.js.map +1 -1
  170. package/dist/src/telemetry/uiTelemetry.test.js +45 -9
  171. package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
  172. package/dist/src/test-utils/config.d.ts +16 -0
  173. package/dist/src/test-utils/config.js +32 -0
  174. package/dist/src/test-utils/config.js.map +1 -0
  175. package/dist/src/test-utils/tools.d.ts +29 -8
  176. package/dist/src/test-utils/tools.js +80 -16
  177. package/dist/src/test-utils/tools.js.map +1 -1
  178. package/dist/src/tools/edit.d.ts +1 -1
  179. package/dist/src/tools/edit.js +23 -19
  180. package/dist/src/tools/edit.js.map +1 -1
  181. package/dist/src/tools/edit.test.js +11 -3
  182. package/dist/src/tools/edit.test.js.map +1 -1
  183. package/dist/src/tools/glob.d.ts +1 -1
  184. package/dist/src/tools/glob.js +15 -16
  185. package/dist/src/tools/glob.js.map +1 -1
  186. package/dist/src/tools/glob.test.js +20 -0
  187. package/dist/src/tools/glob.test.js.map +1 -1
  188. package/dist/src/tools/grep.d.ts +1 -1
  189. package/dist/src/tools/grep.js +7 -13
  190. package/dist/src/tools/grep.js.map +1 -1
  191. package/dist/src/tools/ls.d.ts +4 -23
  192. package/dist/src/tools/ls.js +77 -79
  193. package/dist/src/tools/ls.js.map +1 -1
  194. package/dist/src/tools/ls.test.js +62 -34
  195. package/dist/src/tools/ls.test.js.map +1 -1
  196. package/dist/src/tools/mcp-client-manager.d.ts +38 -0
  197. package/dist/src/tools/mcp-client-manager.js +74 -0
  198. package/dist/src/tools/mcp-client-manager.js.map +1 -0
  199. package/dist/src/tools/mcp-client-manager.test.d.ts +6 -0
  200. package/dist/src/tools/mcp-client-manager.test.js +39 -0
  201. package/dist/src/tools/mcp-client-manager.test.js.map +1 -0
  202. package/dist/src/tools/mcp-client.d.ts +43 -0
  203. package/dist/src/tools/mcp-client.js +157 -11
  204. package/dist/src/tools/mcp-client.js.map +1 -1
  205. package/dist/src/tools/mcp-client.test.js +62 -276
  206. package/dist/src/tools/mcp-client.test.js.map +1 -1
  207. package/dist/src/tools/mcp-tool.d.ts +6 -13
  208. package/dist/src/tools/mcp-tool.js +62 -34
  209. package/dist/src/tools/mcp-tool.js.map +1 -1
  210. package/dist/src/tools/mcp-tool.test.js +118 -59
  211. package/dist/src/tools/mcp-tool.test.js.map +1 -1
  212. package/dist/src/tools/memoryTool.d.ts +9 -13
  213. package/dist/src/tools/memoryTool.js +122 -121
  214. package/dist/src/tools/memoryTool.js.map +1 -1
  215. package/dist/src/tools/memoryTool.test.js +38 -18
  216. package/dist/src/tools/memoryTool.test.js.map +1 -1
  217. package/dist/src/tools/read-file.d.ts +1 -1
  218. package/dist/src/tools/read-file.js +11 -14
  219. package/dist/src/tools/read-file.js.map +1 -1
  220. package/dist/src/tools/read-file.test.js +8 -0
  221. package/dist/src/tools/read-file.test.js.map +1 -1
  222. package/dist/src/tools/read-many-files.d.ts +3 -5
  223. package/dist/src/tools/read-many-files.js +121 -105
  224. package/dist/src/tools/read-many-files.js.map +1 -1
  225. package/dist/src/tools/read-many-files.test.js +94 -37
  226. package/dist/src/tools/read-many-files.test.js.map +1 -1
  227. package/dist/src/tools/shell.d.ts +4 -6
  228. package/dist/src/tools/shell.js +120 -124
  229. package/dist/src/tools/shell.js.map +1 -1
  230. package/dist/src/tools/shell.test.js +63 -65
  231. package/dist/src/tools/shell.test.js.map +1 -1
  232. package/dist/src/tools/tool-error.d.ts +1 -0
  233. package/dist/src/tools/tool-error.js +1 -0
  234. package/dist/src/tools/tool-error.js.map +1 -1
  235. package/dist/src/tools/tool-registry.d.ts +14 -18
  236. package/dist/src/tools/tool-registry.js +73 -106
  237. package/dist/src/tools/tool-registry.js.map +1 -1
  238. package/dist/src/tools/tool-registry.test.js +24 -192
  239. package/dist/src/tools/tool-registry.test.js.map +1 -1
  240. package/dist/src/tools/tools.d.ts +33 -89
  241. package/dist/src/tools/tools.js +76 -119
  242. package/dist/src/tools/tools.js.map +1 -1
  243. package/dist/src/tools/tools.test.js +91 -2
  244. package/dist/src/tools/tools.test.js.map +1 -1
  245. package/dist/src/tools/web-fetch.d.ts +4 -7
  246. package/dist/src/tools/web-fetch.js +58 -64
  247. package/dist/src/tools/web-fetch.js.map +1 -1
  248. package/dist/src/tools/web-fetch.test.js +8 -4
  249. package/dist/src/tools/web-fetch.test.js.map +1 -1
  250. package/dist/src/tools/web-search.d.ts +4 -5
  251. package/dist/src/tools/web-search.js +47 -51
  252. package/dist/src/tools/web-search.js.map +1 -1
  253. package/dist/src/tools/web-search.test.d.ts +6 -0
  254. package/dist/src/tools/web-search.test.js +139 -0
  255. package/dist/src/tools/web-search.test.js.map +1 -0
  256. package/dist/src/tools/write-file.d.ts +15 -10
  257. package/dist/src/tools/write-file.js +134 -145
  258. package/dist/src/tools/write-file.js.map +1 -1
  259. package/dist/src/tools/write-file.test.js +82 -127
  260. package/dist/src/tools/write-file.test.js.map +1 -1
  261. package/dist/src/utils/browser.js +4 -3
  262. package/dist/src/utils/browser.js.map +1 -1
  263. package/dist/src/utils/editCorrector.js +21 -22
  264. package/dist/src/utils/editCorrector.js.map +1 -1
  265. package/dist/src/utils/editor.js +1 -1
  266. package/dist/src/utils/editor.js.map +1 -1
  267. package/dist/src/utils/editor.test.js +10 -10
  268. package/dist/src/utils/editor.test.js.map +1 -1
  269. package/dist/src/utils/environmentContext.js +2 -2
  270. package/dist/src/utils/environmentContext.js.map +1 -1
  271. package/dist/src/utils/environmentContext.test.js +3 -2
  272. package/dist/src/utils/environmentContext.test.js.map +1 -1
  273. package/dist/src/utils/errorParsing.d.ts +8 -0
  274. package/dist/src/utils/errorParsing.js +93 -0
  275. package/dist/src/utils/errorParsing.js.map +1 -0
  276. package/dist/src/utils/errorParsing.test.d.ts +6 -0
  277. package/dist/src/utils/errorParsing.test.js +172 -0
  278. package/dist/src/utils/errorParsing.test.js.map +1 -0
  279. package/dist/src/utils/fileUtils.d.ts +2 -1
  280. package/dist/src/utils/fileUtils.js +3 -3
  281. package/dist/src/utils/fileUtils.js.map +1 -1
  282. package/dist/src/utils/fileUtils.test.js +18 -17
  283. package/dist/src/utils/fileUtils.test.js.map +1 -1
  284. package/dist/src/utils/filesearch/crawler.d.ts +15 -0
  285. package/dist/src/utils/filesearch/crawler.js +50 -0
  286. package/dist/src/utils/filesearch/crawler.js.map +1 -0
  287. package/dist/src/utils/filesearch/crawler.test.d.ts +6 -0
  288. package/dist/src/utils/filesearch/crawler.test.js +468 -0
  289. package/dist/src/utils/filesearch/crawler.test.js.map +1 -0
  290. package/dist/src/utils/filesearch/fileSearch.d.ts +9 -53
  291. package/dist/src/utils/filesearch/fileSearch.js +63 -118
  292. package/dist/src/utils/filesearch/fileSearch.js.map +1 -1
  293. package/dist/src/utils/filesearch/fileSearch.test.js +95 -197
  294. package/dist/src/utils/filesearch/fileSearch.test.js.map +1 -1
  295. package/dist/src/utils/filesearch/ignore.d.ts +7 -0
  296. package/dist/src/utils/filesearch/ignore.js +25 -0
  297. package/dist/src/utils/filesearch/ignore.js.map +1 -1
  298. package/dist/src/utils/filesearch/ignore.test.js +89 -2
  299. package/dist/src/utils/filesearch/ignore.test.js.map +1 -1
  300. package/dist/src/utils/filesearch/result-cache.d.ts +1 -2
  301. package/dist/src/utils/filesearch/result-cache.js +1 -3
  302. package/dist/src/utils/filesearch/result-cache.js.map +1 -1
  303. package/dist/src/utils/filesearch/result-cache.test.js +3 -4
  304. package/dist/src/utils/filesearch/result-cache.test.js.map +1 -1
  305. package/dist/src/utils/getPty.d.ts +19 -0
  306. package/dist/src/utils/getPty.js +23 -0
  307. package/dist/src/utils/getPty.js.map +1 -0
  308. package/dist/src/utils/memoryDiscovery.js +3 -3
  309. package/dist/src/utils/memoryDiscovery.js.map +1 -1
  310. package/dist/src/utils/memoryDiscovery.test.js +3 -2
  311. package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
  312. package/dist/src/utils/memoryImportProcessor.js +3 -7
  313. package/dist/src/utils/memoryImportProcessor.js.map +1 -1
  314. package/dist/src/utils/memoryImportProcessor.test.js +17 -20
  315. package/dist/src/utils/memoryImportProcessor.test.js.map +1 -1
  316. package/dist/src/utils/nextSpeakerChecker.js +3 -4
  317. package/dist/src/utils/nextSpeakerChecker.js.map +1 -1
  318. package/dist/src/utils/paths.d.ts +7 -0
  319. package/dist/src/utils/paths.js +15 -0
  320. package/dist/src/utils/paths.js.map +1 -1
  321. package/dist/src/utils/paths.test.js +74 -2
  322. package/dist/src/utils/paths.test.js.map +1 -1
  323. package/dist/src/utils/quotaErrorDetection.d.ts +1 -5
  324. package/dist/src/utils/quotaErrorDetection.js.map +1 -1
  325. package/dist/src/utils/schemaValidator.d.ts +1 -8
  326. package/dist/src/utils/schemaValidator.js +1 -32
  327. package/dist/src/utils/schemaValidator.js.map +1 -1
  328. package/dist/src/utils/secure-browser-launcher.js +4 -3
  329. package/dist/src/utils/secure-browser-launcher.js.map +1 -1
  330. package/dist/src/utils/shell-utils.d.ts +39 -0
  331. package/dist/src/utils/shell-utils.js +72 -2
  332. package/dist/src/utils/shell-utils.js.map +1 -1
  333. package/dist/src/utils/shell-utils.test.js +132 -4
  334. package/dist/src/utils/shell-utils.test.js.map +1 -1
  335. package/dist/src/utils/systemEncoding.js +1 -1
  336. package/dist/src/utils/systemEncoding.js.map +1 -1
  337. package/dist/src/utils/systemEncoding.test.js +23 -23
  338. package/dist/src/utils/systemEncoding.test.js.map +1 -1
  339. package/dist/src/utils/user_account.js +58 -48
  340. package/dist/src/utils/user_account.js.map +1 -1
  341. package/dist/src/utils/user_account.test.js +76 -9
  342. package/dist/src/utils/user_account.test.js.map +1 -1
  343. package/dist/src/utils/workspaceContext.d.ts +22 -7
  344. package/dist/src/utils/workspaceContext.js +81 -55
  345. package/dist/src/utils/workspaceContext.js.map +1 -1
  346. package/dist/src/utils/workspaceContext.test.js +221 -137
  347. package/dist/src/utils/workspaceContext.test.js.map +1 -1
  348. package/dist/tsconfig.tsbuildinfo +1 -1
  349. package/package.json +21 -8
@@ -15,6 +15,7 @@ import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
15
15
  import { setSimulate429 } from '../utils/testUtils.js';
16
16
  import { tokenLimit } from './tokenLimits.js';
17
17
  import { ideContext } from '../ide/ideContext.js';
18
+ import { ClearcutLogger } from '../telemetry/clearcut-logger/clearcut-logger.js';
18
19
  // --- Mocks ---
19
20
  const mockChatCreateFn = vi.fn();
20
21
  const mockGenerateContentFn = vi.fn();
@@ -151,7 +152,7 @@ describe('Gemini Client (client.ts)', () => {
151
152
  getContentGeneratorConfig: vi
152
153
  .fn()
153
154
  .mockReturnValue(contentGeneratorConfig),
154
- getToolRegistry: vi.fn().mockResolvedValue(mockToolRegistry),
155
+ getToolRegistry: vi.fn().mockReturnValue(mockToolRegistry),
155
156
  getModel: vi.fn().mockReturnValue('test-model'),
156
157
  getEmbeddingModel: vi.fn().mockReturnValue('test-embedding-model'),
157
158
  getApiKey: vi.fn().mockReturnValue('test-key'),
@@ -170,12 +171,14 @@ describe('Gemini Client (client.ts)', () => {
170
171
  getUsageStatisticsEnabled: vi.fn().mockReturnValue(true),
171
172
  getIdeModeFeature: vi.fn().mockReturnValue(false),
172
173
  getIdeMode: vi.fn().mockReturnValue(true),
174
+ getDebugMode: vi.fn().mockReturnValue(false),
173
175
  getWorkspaceContext: vi.fn().mockReturnValue({
174
176
  getDirectories: vi.fn().mockReturnValue(['/test/dir']),
175
177
  }),
176
178
  getGeminiClient: vi.fn(),
177
179
  setFallbackMode: vi.fn(),
178
180
  getChatCompression: vi.fn().mockReturnValue(undefined),
181
+ getSkipNextSpeakerCheck: vi.fn().mockReturnValue(false),
179
182
  };
180
183
  const MockedConfig = vi.mocked(Config, true);
181
184
  MockedConfig.mockImplementation(() => mockConfigObject);
@@ -315,7 +318,7 @@ describe('Gemini Client (client.ts)', () => {
315
318
  systemInstruction: getCoreSystemPrompt(''),
316
319
  temperature: 0,
317
320
  topP: 1,
318
- responseSchema: schema,
321
+ responseJsonSchema: schema,
319
322
  responseMimeType: 'application/json',
320
323
  },
321
324
  contents,
@@ -341,7 +344,7 @@ describe('Gemini Client (client.ts)', () => {
341
344
  temperature: 0.9,
342
345
  topP: 1, // from default
343
346
  topK: 20,
344
- responseSchema: schema,
347
+ responseJsonSchema: schema,
345
348
  responseMimeType: 'application/json',
346
349
  },
347
350
  contents,
@@ -353,7 +356,6 @@ describe('Gemini Client (client.ts)', () => {
353
356
  const mockChat = {
354
357
  addHistory: vi.fn(),
355
358
  };
356
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
357
359
  client['chat'] = mockChat;
358
360
  const newContent = {
359
361
  role: 'user',
@@ -419,6 +421,33 @@ describe('Gemini Client (client.ts)', () => {
419
421
  expect(result).toBeNull();
420
422
  expect(newChat).toBe(initialChat);
421
423
  });
424
+ it('logs a telemetry event when compressing', async () => {
425
+ vi.spyOn(ClearcutLogger.prototype, 'logChatCompressionEvent');
426
+ const MOCKED_TOKEN_LIMIT = 1000;
427
+ const MOCKED_CONTEXT_PERCENTAGE_THRESHOLD = 0.5;
428
+ vi.mocked(tokenLimit).mockReturnValue(MOCKED_TOKEN_LIMIT);
429
+ vi.spyOn(client['config'], 'getChatCompression').mockReturnValue({
430
+ contextPercentageThreshold: MOCKED_CONTEXT_PERCENTAGE_THRESHOLD,
431
+ });
432
+ mockGetHistory.mockReturnValue([
433
+ { role: 'user', parts: [{ text: '...history...' }] },
434
+ ]);
435
+ const originalTokenCount = MOCKED_TOKEN_LIMIT * MOCKED_CONTEXT_PERCENTAGE_THRESHOLD;
436
+ const newTokenCount = 100;
437
+ mockCountTokens
438
+ .mockResolvedValueOnce({ totalTokens: originalTokenCount }) // First call for the check
439
+ .mockResolvedValueOnce({ totalTokens: newTokenCount }); // Second call for the new history
440
+ // Mock the summary response from the chat
441
+ mockSendMessage.mockResolvedValue({
442
+ role: 'model',
443
+ parts: [{ text: 'This is a summary.' }],
444
+ });
445
+ await client.tryCompressChat('prompt-id-3');
446
+ expect(ClearcutLogger.prototype.logChatCompressionEvent).toHaveBeenCalledWith(expect.objectContaining({
447
+ tokens_before: originalTokenCount,
448
+ tokens_after: newTokenCount,
449
+ }));
450
+ });
422
451
  it('should trigger summarization if token count is at threshold with contextPercentageThreshold setting', async () => {
423
452
  const MOCKED_TOKEN_LIMIT = 1000;
424
453
  const MOCKED_CONTEXT_PERCENTAGE_THRESHOLD = 0.5;
@@ -529,7 +558,7 @@ describe('Gemini Client (client.ts)', () => {
529
558
  });
530
559
  });
531
560
  describe('sendMessageStream', () => {
532
- it('should include IDE context when ideModeFeature is enabled', async () => {
561
+ it('should include editor context when ideMode is enabled', async () => {
533
562
  // Arrange
534
563
  vi.mocked(ideContext.getIdeContext).mockReturnValue({
535
564
  workspaceState: {
@@ -552,7 +581,7 @@ describe('Gemini Client (client.ts)', () => {
552
581
  ],
553
582
  },
554
583
  });
555
- vi.spyOn(client['config'], 'getIdeModeFeature').mockReturnValue(true);
584
+ vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
556
585
  const mockStream = (async function* () {
557
586
  yield { type: 'content', value: 'Hello' };
558
587
  })();
@@ -576,27 +605,35 @@ describe('Gemini Client (client.ts)', () => {
576
605
  // Assert
577
606
  expect(ideContext.getIdeContext).toHaveBeenCalled();
578
607
  const expectedContext = `
579
- This is the file that the user is looking at:
580
- - Path: /path/to/active/file.ts
581
- This is the cursor position in the file:
582
- - Cursor Position: Line 5, Character 10
583
- This is the selected text in the file:
584
- - hello
585
- Here are some other files the user has open, with the most recent at the top:
586
- - /path/to/recent/file1.ts
587
- - /path/to/recent/file2.ts
608
+ Here is the user's editor context as a JSON object. This is for your information only.
609
+ \`\`\`json
610
+ ${JSON.stringify({
611
+ activeFile: {
612
+ path: '/path/to/active/file.ts',
613
+ cursor: {
614
+ line: 5,
615
+ character: 10,
616
+ },
617
+ selectedText: 'hello',
618
+ },
619
+ otherOpenFiles: ['/path/to/recent/file1.ts', '/path/to/recent/file2.ts'],
620
+ }, null, 2)}
621
+ \`\`\`
588
622
  `.trim();
589
- const expectedRequest = [{ text: expectedContext }, ...initialRequest];
590
- expect(mockTurnRunFn).toHaveBeenCalledWith(expectedRequest, expect.any(Object));
623
+ const expectedRequest = [{ text: expectedContext }];
624
+ expect(mockChat.addHistory).toHaveBeenCalledWith({
625
+ role: 'user',
626
+ parts: expectedRequest,
627
+ });
591
628
  });
592
- it('should not add context if ideModeFeature is enabled but no open files', async () => {
629
+ it('should not add context if ideMode is enabled but no open files', async () => {
593
630
  // Arrange
594
631
  vi.mocked(ideContext.getIdeContext).mockReturnValue({
595
632
  workspaceState: {
596
633
  openFiles: [],
597
634
  },
598
635
  });
599
- vi.spyOn(client['config'], 'getIdeModeFeature').mockReturnValue(true);
636
+ vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
600
637
  const mockStream = (async function* () {
601
638
  yield { type: 'content', value: 'Hello' };
602
639
  })();
@@ -621,7 +658,7 @@ Here are some other files the user has open, with the most recent at the top:
621
658
  expect(ideContext.getIdeContext).toHaveBeenCalled();
622
659
  expect(mockTurnRunFn).toHaveBeenCalledWith(initialRequest, expect.any(Object));
623
660
  });
624
- it('should add context if ideModeFeature is enabled and there is one active file', async () => {
661
+ it('should add context if ideMode is enabled and there is one active file', async () => {
625
662
  // Arrange
626
663
  vi.mocked(ideContext.getIdeContext).mockReturnValue({
627
664
  workspaceState: {
@@ -636,7 +673,7 @@ Here are some other files the user has open, with the most recent at the top:
636
673
  ],
637
674
  },
638
675
  });
639
- vi.spyOn(client['config'], 'getIdeModeFeature').mockReturnValue(true);
676
+ vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
640
677
  const mockStream = (async function* () {
641
678
  yield { type: 'content', value: 'Hello' };
642
679
  })();
@@ -660,17 +697,27 @@ Here are some other files the user has open, with the most recent at the top:
660
697
  // Assert
661
698
  expect(ideContext.getIdeContext).toHaveBeenCalled();
662
699
  const expectedContext = `
663
- This is the file that the user is looking at:
664
- - Path: /path/to/active/file.ts
665
- This is the cursor position in the file:
666
- - Cursor Position: Line 5, Character 10
667
- This is the selected text in the file:
668
- - hello
700
+ Here is the user's editor context as a JSON object. This is for your information only.
701
+ \`\`\`json
702
+ ${JSON.stringify({
703
+ activeFile: {
704
+ path: '/path/to/active/file.ts',
705
+ cursor: {
706
+ line: 5,
707
+ character: 10,
708
+ },
709
+ selectedText: 'hello',
710
+ },
711
+ }, null, 2)}
712
+ \`\`\`
669
713
  `.trim();
670
- const expectedRequest = [{ text: expectedContext }, ...initialRequest];
671
- expect(mockTurnRunFn).toHaveBeenCalledWith(expectedRequest, expect.any(Object));
714
+ const expectedRequest = [{ text: expectedContext }];
715
+ expect(mockChat.addHistory).toHaveBeenCalledWith({
716
+ role: 'user',
717
+ parts: expectedRequest,
718
+ });
672
719
  });
673
- it('should add context if ideModeFeature is enabled and there are open files but no active file', async () => {
720
+ it('should add context if ideMode is enabled and there are open files but no active file', async () => {
674
721
  // Arrange
675
722
  vi.mocked(ideContext.getIdeContext).mockReturnValue({
676
723
  workspaceState: {
@@ -686,7 +733,7 @@ This is the selected text in the file:
686
733
  ],
687
734
  },
688
735
  });
689
- vi.spyOn(client['config'], 'getIdeModeFeature').mockReturnValue(true);
736
+ vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
690
737
  const mockStream = (async function* () {
691
738
  yield { type: 'content', value: 'Hello' };
692
739
  })();
@@ -710,12 +757,18 @@ This is the selected text in the file:
710
757
  // Assert
711
758
  expect(ideContext.getIdeContext).toHaveBeenCalled();
712
759
  const expectedContext = `
713
- Here are some files the user has open, with the most recent at the top:
714
- - /path/to/recent/file1.ts
715
- - /path/to/recent/file2.ts
760
+ Here is the user's editor context as a JSON object. This is for your information only.
761
+ \`\`\`json
762
+ ${JSON.stringify({
763
+ otherOpenFiles: ['/path/to/recent/file1.ts', '/path/to/recent/file2.ts'],
764
+ }, null, 2)}
765
+ \`\`\`
716
766
  `.trim();
717
- const expectedRequest = [{ text: expectedContext }, ...initialRequest];
718
- expect(mockTurnRunFn).toHaveBeenCalledWith(expectedRequest, expect.any(Object));
767
+ const expectedRequest = [{ text: expectedContext }];
768
+ expect(mockChat.addHistory).toHaveBeenCalledWith({
769
+ role: 'user',
770
+ parts: expectedRequest,
771
+ });
719
772
  });
720
773
  it('should return the turn instance after the stream is complete', async () => {
721
774
  // Arrange
@@ -918,6 +971,488 @@ Here are some files the user has open, with the most recent at the top:
918
971
  console.log(`Infinite loop protection working: checkNextSpeaker called ${callCount} times, ` +
919
972
  `${eventCount} events generated (properly bounded by MAX_TURNS)`);
920
973
  });
974
+ describe('Editor context delta', () => {
975
+ const mockStream = (async function* () {
976
+ yield { type: 'content', value: 'Hello' };
977
+ })();
978
+ beforeEach(() => {
979
+ client['forceFullIdeContext'] = false; // Reset before each delta test
980
+ vi.spyOn(client, 'tryCompressChat').mockResolvedValue(null);
981
+ vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
982
+ mockTurnRunFn.mockReturnValue(mockStream);
983
+ const mockChat = {
984
+ addHistory: vi.fn(),
985
+ setHistory: vi.fn(),
986
+ sendMessage: vi.fn().mockResolvedValue({ text: 'summary' }),
987
+ // Assume history is not empty for delta checks
988
+ getHistory: vi
989
+ .fn()
990
+ .mockReturnValue([
991
+ { role: 'user', parts: [{ text: 'previous message' }] },
992
+ ]),
993
+ };
994
+ client['chat'] = mockChat;
995
+ const mockGenerator = {
996
+ countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
997
+ generateContent: mockGenerateContentFn,
998
+ };
999
+ client['contentGenerator'] = mockGenerator;
1000
+ });
1001
+ const testCases = [
1002
+ {
1003
+ description: 'sends delta when active file changes',
1004
+ previousActiveFile: {
1005
+ path: '/path/to/old/file.ts',
1006
+ cursor: { line: 5, character: 10 },
1007
+ selectedText: 'hello',
1008
+ },
1009
+ currentActiveFile: {
1010
+ path: '/path/to/active/file.ts',
1011
+ cursor: { line: 5, character: 10 },
1012
+ selectedText: 'hello',
1013
+ },
1014
+ shouldSendContext: true,
1015
+ },
1016
+ {
1017
+ description: 'sends delta when cursor line changes',
1018
+ previousActiveFile: {
1019
+ path: '/path/to/active/file.ts',
1020
+ cursor: { line: 1, character: 10 },
1021
+ selectedText: 'hello',
1022
+ },
1023
+ currentActiveFile: {
1024
+ path: '/path/to/active/file.ts',
1025
+ cursor: { line: 5, character: 10 },
1026
+ selectedText: 'hello',
1027
+ },
1028
+ shouldSendContext: true,
1029
+ },
1030
+ {
1031
+ description: 'sends delta when cursor character changes',
1032
+ previousActiveFile: {
1033
+ path: '/path/to/active/file.ts',
1034
+ cursor: { line: 5, character: 1 },
1035
+ selectedText: 'hello',
1036
+ },
1037
+ currentActiveFile: {
1038
+ path: '/path/to/active/file.ts',
1039
+ cursor: { line: 5, character: 10 },
1040
+ selectedText: 'hello',
1041
+ },
1042
+ shouldSendContext: true,
1043
+ },
1044
+ {
1045
+ description: 'sends delta when selected text changes',
1046
+ previousActiveFile: {
1047
+ path: '/path/to/active/file.ts',
1048
+ cursor: { line: 5, character: 10 },
1049
+ selectedText: 'world',
1050
+ },
1051
+ currentActiveFile: {
1052
+ path: '/path/to/active/file.ts',
1053
+ cursor: { line: 5, character: 10 },
1054
+ selectedText: 'hello',
1055
+ },
1056
+ shouldSendContext: true,
1057
+ },
1058
+ {
1059
+ description: 'sends delta when selected text is added',
1060
+ previousActiveFile: {
1061
+ path: '/path/to/active/file.ts',
1062
+ cursor: { line: 5, character: 10 },
1063
+ },
1064
+ currentActiveFile: {
1065
+ path: '/path/to/active/file.ts',
1066
+ cursor: { line: 5, character: 10 },
1067
+ selectedText: 'hello',
1068
+ },
1069
+ shouldSendContext: true,
1070
+ },
1071
+ {
1072
+ description: 'sends delta when selected text is removed',
1073
+ previousActiveFile: {
1074
+ path: '/path/to/active/file.ts',
1075
+ cursor: { line: 5, character: 10 },
1076
+ selectedText: 'hello',
1077
+ },
1078
+ currentActiveFile: {
1079
+ path: '/path/to/active/file.ts',
1080
+ cursor: { line: 5, character: 10 },
1081
+ },
1082
+ shouldSendContext: true,
1083
+ },
1084
+ {
1085
+ description: 'does not send context when nothing changes',
1086
+ previousActiveFile: {
1087
+ path: '/path/to/active/file.ts',
1088
+ cursor: { line: 5, character: 10 },
1089
+ selectedText: 'hello',
1090
+ },
1091
+ currentActiveFile: {
1092
+ path: '/path/to/active/file.ts',
1093
+ cursor: { line: 5, character: 10 },
1094
+ selectedText: 'hello',
1095
+ },
1096
+ shouldSendContext: false,
1097
+ },
1098
+ ];
1099
+ it.each(testCases)('$description', async ({ previousActiveFile, currentActiveFile, shouldSendContext, }) => {
1100
+ // Setup previous context
1101
+ client['lastSentIdeContext'] = {
1102
+ workspaceState: {
1103
+ openFiles: [
1104
+ {
1105
+ path: previousActiveFile.path,
1106
+ cursor: previousActiveFile.cursor,
1107
+ selectedText: previousActiveFile.selectedText,
1108
+ isActive: true,
1109
+ timestamp: Date.now() - 1000,
1110
+ },
1111
+ ],
1112
+ },
1113
+ };
1114
+ // Setup current context
1115
+ vi.mocked(ideContext.getIdeContext).mockReturnValue({
1116
+ workspaceState: {
1117
+ openFiles: [
1118
+ { ...currentActiveFile, isActive: true, timestamp: Date.now() },
1119
+ ],
1120
+ },
1121
+ });
1122
+ const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-delta');
1123
+ for await (const _ of stream) {
1124
+ // consume stream
1125
+ }
1126
+ const mockChat = client['chat'];
1127
+ if (shouldSendContext) {
1128
+ expect(mockChat.addHistory).toHaveBeenCalledWith(expect.objectContaining({
1129
+ parts: expect.arrayContaining([
1130
+ expect.objectContaining({
1131
+ text: expect.stringContaining("Here is a summary of changes in the user's editor context"),
1132
+ }),
1133
+ ]),
1134
+ }));
1135
+ }
1136
+ else {
1137
+ expect(mockChat.addHistory).not.toHaveBeenCalled();
1138
+ }
1139
+ });
1140
+ it('sends full context when history is cleared, even if editor state is unchanged', async () => {
1141
+ const activeFile = {
1142
+ path: '/path/to/active/file.ts',
1143
+ cursor: { line: 5, character: 10 },
1144
+ selectedText: 'hello',
1145
+ };
1146
+ // Setup previous context
1147
+ client['lastSentIdeContext'] = {
1148
+ workspaceState: {
1149
+ openFiles: [
1150
+ {
1151
+ path: activeFile.path,
1152
+ cursor: activeFile.cursor,
1153
+ selectedText: activeFile.selectedText,
1154
+ isActive: true,
1155
+ timestamp: Date.now() - 1000,
1156
+ },
1157
+ ],
1158
+ },
1159
+ };
1160
+ // Setup current context (same as previous)
1161
+ vi.mocked(ideContext.getIdeContext).mockReturnValue({
1162
+ workspaceState: {
1163
+ openFiles: [
1164
+ { ...activeFile, isActive: true, timestamp: Date.now() },
1165
+ ],
1166
+ },
1167
+ });
1168
+ // Make history empty
1169
+ const mockChat = client['chat'];
1170
+ mockChat.getHistory.mockReturnValue([]);
1171
+ const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-history-cleared');
1172
+ for await (const _ of stream) {
1173
+ // consume stream
1174
+ }
1175
+ expect(mockChat.addHistory).toHaveBeenCalledWith(expect.objectContaining({
1176
+ parts: expect.arrayContaining([
1177
+ expect.objectContaining({
1178
+ text: expect.stringContaining("Here is the user's editor context"),
1179
+ }),
1180
+ ]),
1181
+ }));
1182
+ // Also verify it's the full context, not a delta.
1183
+ const call = mockChat.addHistory.mock.calls[0][0];
1184
+ const contextText = call.parts[0].text;
1185
+ const contextJson = JSON.parse(contextText.match(/```json\n(.*)\n```/s)[1]);
1186
+ expect(contextJson).toHaveProperty('activeFile');
1187
+ expect(contextJson.activeFile.path).toBe('/path/to/active/file.ts');
1188
+ });
1189
+ });
1190
+ describe('IDE context with pending tool calls', () => {
1191
+ let mockChat;
1192
+ beforeEach(() => {
1193
+ vi.spyOn(client, 'tryCompressChat').mockResolvedValue(null);
1194
+ const mockStream = (async function* () {
1195
+ yield { type: 'content', value: 'response' };
1196
+ })();
1197
+ mockTurnRunFn.mockReturnValue(mockStream);
1198
+ mockChat = {
1199
+ addHistory: vi.fn(),
1200
+ getHistory: vi.fn().mockReturnValue([]), // Default empty history
1201
+ setHistory: vi.fn(),
1202
+ sendMessage: vi.fn().mockResolvedValue({ text: 'summary' }),
1203
+ };
1204
+ client['chat'] = mockChat;
1205
+ const mockGenerator = {
1206
+ countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
1207
+ };
1208
+ client['contentGenerator'] = mockGenerator;
1209
+ vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
1210
+ vi.mocked(ideContext.getIdeContext).mockReturnValue({
1211
+ workspaceState: {
1212
+ openFiles: [{ path: '/path/to/file.ts', timestamp: Date.now() }],
1213
+ },
1214
+ });
1215
+ });
1216
+ it('should NOT add IDE context when a tool call is pending', async () => {
1217
+ // Arrange: History ends with a functionCall from the model
1218
+ const historyWithPendingCall = [
1219
+ { role: 'user', parts: [{ text: 'Please use a tool.' }] },
1220
+ {
1221
+ role: 'model',
1222
+ parts: [{ functionCall: { name: 'some_tool', args: {} } }],
1223
+ },
1224
+ ];
1225
+ vi.mocked(mockChat.getHistory).mockReturnValue(historyWithPendingCall);
1226
+ // Act: Simulate sending the tool's response back
1227
+ const stream = client.sendMessageStream([
1228
+ {
1229
+ functionResponse: {
1230
+ name: 'some_tool',
1231
+ response: { success: true },
1232
+ },
1233
+ },
1234
+ ], new AbortController().signal, 'prompt-id-tool-response');
1235
+ for await (const _ of stream) {
1236
+ // consume stream to complete the call
1237
+ }
1238
+ // Assert: The IDE context message should NOT have been added to the history.
1239
+ expect(mockChat.addHistory).not.toHaveBeenCalledWith(expect.objectContaining({
1240
+ parts: expect.arrayContaining([
1241
+ expect.objectContaining({
1242
+ text: expect.stringContaining("user's editor context"),
1243
+ }),
1244
+ ]),
1245
+ }));
1246
+ });
1247
+ it('should add IDE context when no tool call is pending', async () => {
1248
+ // Arrange: History is normal, no pending calls
1249
+ const normalHistory = [
1250
+ { role: 'user', parts: [{ text: 'A normal message.' }] },
1251
+ { role: 'model', parts: [{ text: 'A normal response.' }] },
1252
+ ];
1253
+ vi.mocked(mockChat.getHistory).mockReturnValue(normalHistory);
1254
+ // Act
1255
+ const stream = client.sendMessageStream([{ text: 'Another normal message' }], new AbortController().signal, 'prompt-id-normal');
1256
+ for await (const _ of stream) {
1257
+ // consume stream
1258
+ }
1259
+ // Assert: The IDE context message SHOULD have been added.
1260
+ expect(mockChat.addHistory).toHaveBeenCalledWith(expect.objectContaining({
1261
+ role: 'user',
1262
+ parts: expect.arrayContaining([
1263
+ expect.objectContaining({
1264
+ text: expect.stringContaining("user's editor context"),
1265
+ }),
1266
+ ]),
1267
+ }));
1268
+ });
1269
+ it('should send the latest IDE context on the next message after a skipped context', async () => {
1270
+ // --- Step 1: A tool call is pending, context should be skipped ---
1271
+ // Arrange: History ends with a functionCall
1272
+ const historyWithPendingCall = [
1273
+ { role: 'user', parts: [{ text: 'Please use a tool.' }] },
1274
+ {
1275
+ role: 'model',
1276
+ parts: [{ functionCall: { name: 'some_tool', args: {} } }],
1277
+ },
1278
+ ];
1279
+ vi.mocked(mockChat.getHistory).mockReturnValue(historyWithPendingCall);
1280
+ // Arrange: Set the initial IDE context
1281
+ const initialIdeContext = {
1282
+ workspaceState: {
1283
+ openFiles: [{ path: '/path/to/fileA.ts', timestamp: Date.now() }],
1284
+ },
1285
+ };
1286
+ vi.mocked(ideContext.getIdeContext).mockReturnValue(initialIdeContext);
1287
+ // Act: Send the tool response
1288
+ let stream = client.sendMessageStream([
1289
+ {
1290
+ functionResponse: {
1291
+ name: 'some_tool',
1292
+ response: { success: true },
1293
+ },
1294
+ },
1295
+ ], new AbortController().signal, 'prompt-id-tool-response');
1296
+ for await (const _ of stream) {
1297
+ /* consume */
1298
+ }
1299
+ // Assert: The initial context was NOT sent
1300
+ expect(mockChat.addHistory).not.toHaveBeenCalledWith(expect.objectContaining({
1301
+ parts: expect.arrayContaining([
1302
+ expect.objectContaining({
1303
+ text: expect.stringContaining("user's editor context"),
1304
+ }),
1305
+ ]),
1306
+ }));
1307
+ // --- Step 2: A new message is sent, latest context should be included ---
1308
+ // Arrange: The model has responded to the tool, and the user is sending a new message.
1309
+ const historyAfterToolResponse = [
1310
+ ...historyWithPendingCall,
1311
+ {
1312
+ role: 'user',
1313
+ parts: [
1314
+ {
1315
+ functionResponse: {
1316
+ name: 'some_tool',
1317
+ response: { success: true },
1318
+ },
1319
+ },
1320
+ ],
1321
+ },
1322
+ { role: 'model', parts: [{ text: 'The tool ran successfully.' }] },
1323
+ ];
1324
+ vi.mocked(mockChat.getHistory).mockReturnValue(historyAfterToolResponse);
1325
+ vi.mocked(mockChat.addHistory).mockClear(); // Clear previous calls for the next assertion
1326
+ // Arrange: The IDE context has now changed
1327
+ const newIdeContext = {
1328
+ workspaceState: {
1329
+ openFiles: [{ path: '/path/to/fileB.ts', timestamp: Date.now() }],
1330
+ },
1331
+ };
1332
+ vi.mocked(ideContext.getIdeContext).mockReturnValue(newIdeContext);
1333
+ // Act: Send a new, regular user message
1334
+ stream = client.sendMessageStream([{ text: 'Thanks!' }], new AbortController().signal, 'prompt-id-final');
1335
+ for await (const _ of stream) {
1336
+ /* consume */
1337
+ }
1338
+ // Assert: The NEW context was sent as a FULL context because there was no previously sent context.
1339
+ const addHistoryCalls = vi.mocked(mockChat.addHistory).mock.calls;
1340
+ const contextCall = addHistoryCalls.find((call) => JSON.stringify(call[0]).includes("user's editor context"));
1341
+ expect(contextCall).toBeDefined();
1342
+ expect(JSON.stringify(contextCall[0])).toContain("Here is the user's editor context as a JSON object");
1343
+ // Check that the sent context is the new one (fileB.ts)
1344
+ expect(JSON.stringify(contextCall[0])).toContain('fileB.ts');
1345
+ // Check that the sent context is NOT the old one (fileA.ts)
1346
+ expect(JSON.stringify(contextCall[0])).not.toContain('fileA.ts');
1347
+ });
1348
+ it('should send a context DELTA on the next message after a skipped context', async () => {
1349
+ // --- Step 0: Establish an initial context ---
1350
+ vi.mocked(mockChat.getHistory).mockReturnValue([]); // Start with empty history
1351
+ const contextA = {
1352
+ workspaceState: {
1353
+ openFiles: [
1354
+ {
1355
+ path: '/path/to/fileA.ts',
1356
+ isActive: true,
1357
+ timestamp: Date.now(),
1358
+ },
1359
+ ],
1360
+ },
1361
+ };
1362
+ vi.mocked(ideContext.getIdeContext).mockReturnValue(contextA);
1363
+ // Act: Send a regular message to establish the initial context
1364
+ let stream = client.sendMessageStream([{ text: 'Initial message' }], new AbortController().signal, 'prompt-id-initial');
1365
+ for await (const _ of stream) {
1366
+ /* consume */
1367
+ }
1368
+ // Assert: Full context for fileA.ts was sent and stored.
1369
+ const initialCall = vi.mocked(mockChat.addHistory).mock.calls[0][0];
1370
+ expect(JSON.stringify(initialCall)).toContain("user's editor context as a JSON object");
1371
+ expect(JSON.stringify(initialCall)).toContain('fileA.ts');
1372
+ // This implicitly tests that `lastSentIdeContext` is now set internally by the client.
1373
+ vi.mocked(mockChat.addHistory).mockClear();
1374
+ // --- Step 1: A tool call is pending, context should be skipped ---
1375
+ const historyWithPendingCall = [
1376
+ { role: 'user', parts: [{ text: 'Please use a tool.' }] },
1377
+ {
1378
+ role: 'model',
1379
+ parts: [{ functionCall: { name: 'some_tool', args: {} } }],
1380
+ },
1381
+ ];
1382
+ vi.mocked(mockChat.getHistory).mockReturnValue(historyWithPendingCall);
1383
+ // Arrange: IDE context changes, but this should be skipped
1384
+ const contextB = {
1385
+ workspaceState: {
1386
+ openFiles: [
1387
+ {
1388
+ path: '/path/to/fileB.ts',
1389
+ isActive: true,
1390
+ timestamp: Date.now(),
1391
+ },
1392
+ ],
1393
+ },
1394
+ };
1395
+ vi.mocked(ideContext.getIdeContext).mockReturnValue(contextB);
1396
+ // Act: Send the tool response
1397
+ stream = client.sendMessageStream([
1398
+ {
1399
+ functionResponse: {
1400
+ name: 'some_tool',
1401
+ response: { success: true },
1402
+ },
1403
+ },
1404
+ ], new AbortController().signal, 'prompt-id-tool-response');
1405
+ for await (const _ of stream) {
1406
+ /* consume */
1407
+ }
1408
+ // Assert: No context was sent
1409
+ expect(mockChat.addHistory).not.toHaveBeenCalled();
1410
+ // --- Step 2: A new message is sent, latest context DELTA should be included ---
1411
+ const historyAfterToolResponse = [
1412
+ ...historyWithPendingCall,
1413
+ {
1414
+ role: 'user',
1415
+ parts: [
1416
+ {
1417
+ functionResponse: {
1418
+ name: 'some_tool',
1419
+ response: { success: true },
1420
+ },
1421
+ },
1422
+ ],
1423
+ },
1424
+ { role: 'model', parts: [{ text: 'The tool ran successfully.' }] },
1425
+ ];
1426
+ vi.mocked(mockChat.getHistory).mockReturnValue(historyAfterToolResponse);
1427
+ // Arrange: The IDE context has changed again
1428
+ const contextC = {
1429
+ workspaceState: {
1430
+ openFiles: [
1431
+ // fileA is now closed, fileC is open
1432
+ {
1433
+ path: '/path/to/fileC.ts',
1434
+ isActive: true,
1435
+ timestamp: Date.now(),
1436
+ },
1437
+ ],
1438
+ },
1439
+ };
1440
+ vi.mocked(ideContext.getIdeContext).mockReturnValue(contextC);
1441
+ // Act: Send a new, regular user message
1442
+ stream = client.sendMessageStream([{ text: 'Thanks!' }], new AbortController().signal, 'prompt-id-final');
1443
+ for await (const _ of stream) {
1444
+ /* consume */
1445
+ }
1446
+ // Assert: The DELTA context was sent
1447
+ const finalCall = vi.mocked(mockChat.addHistory).mock.calls[0][0];
1448
+ expect(JSON.stringify(finalCall)).toContain('summary of changes');
1449
+ // The delta should reflect fileA being closed and fileC being opened.
1450
+ expect(JSON.stringify(finalCall)).toContain('filesClosed');
1451
+ expect(JSON.stringify(finalCall)).toContain('fileA.ts');
1452
+ expect(JSON.stringify(finalCall)).toContain('activeFileChanged');
1453
+ expect(JSON.stringify(finalCall)).toContain('fileC.ts');
1454
+ });
1455
+ });
921
1456
  });
922
1457
  describe('generateContent', () => {
923
1458
  it('should use current model from config for content generation', async () => {
@@ -1004,5 +1539,65 @@ Here are some files the user has open, with the most recent at the top:
1004
1539
  expect(mockFallbackHandler).toHaveBeenCalledWith(currentModel, fallbackModel, undefined);
1005
1540
  });
1006
1541
  });
1542
+ describe('setHistory', () => {
1543
+ it('should strip thought signatures when stripThoughts is true', () => {
1544
+ const mockChat = {
1545
+ setHistory: vi.fn(),
1546
+ };
1547
+ client['chat'] = mockChat;
1548
+ const historyWithThoughts = [
1549
+ {
1550
+ role: 'user',
1551
+ parts: [{ text: 'hello' }],
1552
+ },
1553
+ {
1554
+ role: 'model',
1555
+ parts: [
1556
+ { text: 'thinking...', thoughtSignature: 'thought-123' },
1557
+ {
1558
+ functionCall: { name: 'test', args: {} },
1559
+ thoughtSignature: 'thought-456',
1560
+ },
1561
+ ],
1562
+ },
1563
+ ];
1564
+ client.setHistory(historyWithThoughts, { stripThoughts: true });
1565
+ const expectedHistory = [
1566
+ {
1567
+ role: 'user',
1568
+ parts: [{ text: 'hello' }],
1569
+ },
1570
+ {
1571
+ role: 'model',
1572
+ parts: [
1573
+ { text: 'thinking...' },
1574
+ { functionCall: { name: 'test', args: {} } },
1575
+ ],
1576
+ },
1577
+ ];
1578
+ expect(mockChat.setHistory).toHaveBeenCalledWith(expectedHistory);
1579
+ });
1580
+ it('should not strip thought signatures when stripThoughts is false', () => {
1581
+ const mockChat = {
1582
+ setHistory: vi.fn(),
1583
+ };
1584
+ client['chat'] = mockChat;
1585
+ const historyWithThoughts = [
1586
+ {
1587
+ role: 'user',
1588
+ parts: [{ text: 'hello' }],
1589
+ },
1590
+ {
1591
+ role: 'model',
1592
+ parts: [
1593
+ { text: 'thinking...', thoughtSignature: 'thought-123' },
1594
+ { text: 'ok', thoughtSignature: 'thought-456' },
1595
+ ],
1596
+ },
1597
+ ];
1598
+ client.setHistory(historyWithThoughts, { stripThoughts: false });
1599
+ expect(mockChat.setHistory).toHaveBeenCalledWith(historyWithThoughts);
1600
+ });
1601
+ });
1007
1602
  });
1008
1603
  //# sourceMappingURL=client.test.js.map