@machina.ai/cell-cli-core 1.0.13-rc9 → 1.0.21-rc1

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