fss-link 1.0.40 → 1.0.45

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 (428) hide show
  1. package/dist/commands/mcp/add.test.ts +122 -0
  2. package/dist/commands/mcp/add.ts +222 -0
  3. package/dist/commands/mcp/list.test.ts +154 -0
  4. package/dist/commands/mcp/list.ts +139 -0
  5. package/dist/commands/mcp/remove.test.ts +69 -0
  6. package/dist/commands/mcp/remove.ts +60 -0
  7. package/dist/commands/mcp.test.ts +55 -0
  8. package/dist/commands/mcp.ts +27 -0
  9. package/dist/config/apiValidation.test.ts +118 -0
  10. package/dist/config/auth.test.ts +79 -0
  11. package/dist/config/auth.ts +100 -0
  12. package/dist/config/config.integration.test.ts +407 -0
  13. package/dist/config/config.test.ts +1952 -0
  14. package/dist/config/config.ts +690 -0
  15. package/dist/config/database.test.ts +96 -0
  16. package/dist/config/database.ts +752 -0
  17. package/dist/config/extension.test.ts +236 -0
  18. package/dist/config/extension.ts +180 -0
  19. package/dist/config/keyBindings.test.ts +62 -0
  20. package/dist/config/keyBindings.ts +184 -0
  21. package/dist/config/modelManager.ts +275 -0
  22. package/dist/config/providerManager.ts +244 -0
  23. package/dist/config/providerPersistence.test.ts +377 -0
  24. package/dist/config/providerPersistence.ts +105 -0
  25. package/dist/config/sandboxConfig.ts +107 -0
  26. package/dist/config/settings.test.ts +1424 -0
  27. package/dist/config/settings.ts +517 -0
  28. package/dist/config/settingsSchema.test.ts +252 -0
  29. package/dist/config/settingsSchema.ts +728 -0
  30. package/dist/config/trustedFolders.test.ts +208 -0
  31. package/dist/config/trustedFolders.ts +167 -0
  32. package/dist/gemini.test.tsx +252 -0
  33. package/dist/gemini.tsx +357 -0
  34. package/dist/generated/git-commit.ts +10 -0
  35. package/dist/index.ts +21 -0
  36. package/dist/nonInteractiveCli.test.ts +276 -0
  37. package/dist/nonInteractiveCli.ts +143 -0
  38. package/dist/package.json +87 -87
  39. package/dist/patches/is-in-ci.ts +17 -0
  40. package/dist/services/BuiltinCommandLoader.test.ts +127 -0
  41. package/dist/services/BuiltinCommandLoader.ts +95 -0
  42. package/dist/services/CommandService.test.ts +352 -0
  43. package/dist/services/CommandService.ts +103 -0
  44. package/dist/services/FileCommandLoader.test.ts +1002 -0
  45. package/dist/services/FileCommandLoader.ts +289 -0
  46. package/dist/services/McpPromptLoader.ts +231 -0
  47. package/dist/services/SearchEngineConfigProvider.ts +100 -0
  48. package/dist/services/prompt-processors/argumentProcessor.test.ts +41 -0
  49. package/dist/services/prompt-processors/argumentProcessor.ts +23 -0
  50. package/dist/services/prompt-processors/shellProcessor.test.ts +709 -0
  51. package/dist/services/prompt-processors/shellProcessor.ts +248 -0
  52. package/dist/services/prompt-processors/types.ts +44 -0
  53. package/dist/services/types.ts +24 -0
  54. package/dist/src/config/apiValidation.test.d.ts +6 -0
  55. package/dist/src/config/apiValidation.test.js +99 -0
  56. package/dist/src/config/apiValidation.test.js.map +1 -0
  57. package/dist/src/config/database.d.ts +32 -0
  58. package/dist/src/config/database.js +281 -2
  59. package/dist/src/config/database.js.map +1 -1
  60. package/dist/src/config/database.test.d.ts +6 -0
  61. package/dist/src/config/database.test.js +80 -0
  62. package/dist/src/config/database.test.js.map +1 -0
  63. package/dist/src/config/providerManager.d.ts +74 -0
  64. package/dist/src/config/providerManager.js +203 -0
  65. package/dist/src/config/providerManager.js.map +1 -0
  66. package/dist/src/config/providerPersistence.d.ts +75 -0
  67. package/dist/src/config/providerPersistence.js +55 -0
  68. package/dist/src/config/providerPersistence.js.map +1 -0
  69. package/dist/src/config/providerPersistence.test.d.ts +6 -0
  70. package/dist/src/config/providerPersistence.test.js +283 -0
  71. package/dist/src/config/providerPersistence.test.js.map +1 -0
  72. package/dist/src/config/settingsSchema.d.ts +9 -0
  73. package/dist/src/config/settingsSchema.js +9 -0
  74. package/dist/src/config/settingsSchema.js.map +1 -1
  75. package/dist/src/generated/git-commit.d.ts +1 -1
  76. package/dist/src/generated/git-commit.js +1 -1
  77. package/dist/src/services/BuiltinCommandLoader.js +2 -0
  78. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  79. package/dist/src/ui/App.js +14 -2
  80. package/dist/src/ui/App.js.map +1 -1
  81. package/dist/src/ui/commands/contextCommand.d.ts +7 -0
  82. package/dist/src/ui/commands/contextCommand.js +115 -0
  83. package/dist/src/ui/commands/contextCommand.js.map +1 -0
  84. package/dist/src/ui/components/ContextUsageDisplay.d.ts +3 -1
  85. package/dist/src/ui/components/ContextUsageDisplay.js +43 -3
  86. package/dist/src/ui/components/ContextUsageDisplay.js.map +1 -1
  87. package/dist/src/ui/components/Footer.d.ts +1 -0
  88. package/dist/src/ui/components/Footer.js +2 -2
  89. package/dist/src/ui/components/Footer.js.map +1 -1
  90. package/dist/src/ui/components/GeminiKeyDialog.d.ts +11 -0
  91. package/dist/src/ui/components/GeminiKeyDialog.js +156 -0
  92. package/dist/src/ui/components/GeminiKeyDialog.js.map +1 -0
  93. package/dist/src/ui/components/OpenAIEndpointDialog.d.ts +19 -0
  94. package/dist/src/ui/components/OpenAIEndpointDialog.js +163 -0
  95. package/dist/src/ui/components/OpenAIEndpointDialog.js.map +1 -0
  96. package/dist/src/ui/components/WelcomeBackDialog.d.ts +36 -0
  97. package/dist/src/ui/components/WelcomeBackDialog.js +109 -0
  98. package/dist/src/ui/components/WelcomeBackDialog.js.map +1 -0
  99. package/dist/src/ui/hooks/useWelcomeBack.d.ts +52 -0
  100. package/dist/src/ui/hooks/useWelcomeBack.js +214 -0
  101. package/dist/src/ui/hooks/useWelcomeBack.js.map +1 -0
  102. package/dist/src/zed-integration/schema.d.ts +1516 -1516
  103. package/dist/test-setup.ts +12 -0
  104. package/dist/test-utils/customMatchers.ts +65 -0
  105. package/dist/test-utils/mockCommandContext.test.ts +62 -0
  106. package/dist/test-utils/mockCommandContext.ts +105 -0
  107. package/dist/test-utils/render.tsx +18 -0
  108. package/dist/tsconfig.tsbuildinfo +1 -1
  109. package/dist/ui/App.test.tsx +2181 -0
  110. package/dist/ui/App.tsx +1344 -0
  111. package/dist/ui/IdeIntegrationNudge.tsx +98 -0
  112. package/dist/ui/__snapshots__/App.test.tsx.snap +124 -0
  113. package/dist/ui/colors.ts +56 -0
  114. package/dist/ui/commands/aboutCommand.test.ts +153 -0
  115. package/dist/ui/commands/aboutCommand.ts +49 -0
  116. package/dist/ui/commands/authCommand.test.ts +36 -0
  117. package/dist/ui/commands/authCommand.ts +17 -0
  118. package/dist/ui/commands/bugCommand.test.ts +114 -0
  119. package/dist/ui/commands/bugCommand.ts +92 -0
  120. package/dist/ui/commands/chatCommand.test.ts +414 -0
  121. package/dist/ui/commands/chatCommand.ts +280 -0
  122. package/dist/ui/commands/clearCommand.test.ts +100 -0
  123. package/dist/ui/commands/clearCommand.ts +29 -0
  124. package/dist/ui/commands/compressCommand.test.ts +129 -0
  125. package/dist/ui/commands/compressCommand.ts +78 -0
  126. package/dist/ui/commands/contextCommand.ts +132 -0
  127. package/dist/ui/commands/copyCommand.test.ts +296 -0
  128. package/dist/ui/commands/copyCommand.ts +67 -0
  129. package/dist/ui/commands/corgiCommand.test.ts +34 -0
  130. package/dist/ui/commands/corgiCommand.ts +16 -0
  131. package/dist/ui/commands/directoryCommand.test.tsx +185 -0
  132. package/dist/ui/commands/directoryCommand.tsx +179 -0
  133. package/dist/ui/commands/docsCommand.test.ts +99 -0
  134. package/dist/ui/commands/docsCommand.ts +42 -0
  135. package/dist/ui/commands/editorCommand.test.ts +30 -0
  136. package/dist/ui/commands/editorCommand.ts +21 -0
  137. package/dist/ui/commands/extensionsCommand.test.ts +67 -0
  138. package/dist/ui/commands/extensionsCommand.ts +46 -0
  139. package/dist/ui/commands/helpCommand.test.ts +52 -0
  140. package/dist/ui/commands/helpCommand.ts +23 -0
  141. package/dist/ui/commands/ideCommand.test.ts +255 -0
  142. package/dist/ui/commands/ideCommand.ts +283 -0
  143. package/dist/ui/commands/initCommand.test.ts +127 -0
  144. package/dist/ui/commands/initCommand.ts +117 -0
  145. package/dist/ui/commands/mcpCommand.test.ts +1057 -0
  146. package/dist/ui/commands/mcpCommand.ts +531 -0
  147. package/dist/ui/commands/memoryCommand.test.ts +344 -0
  148. package/dist/ui/commands/memoryCommand.ts +305 -0
  149. package/dist/ui/commands/privacyCommand.test.ts +38 -0
  150. package/dist/ui/commands/privacyCommand.ts +17 -0
  151. package/dist/ui/commands/quitCommand.test.ts +55 -0
  152. package/dist/ui/commands/quitCommand.ts +36 -0
  153. package/dist/ui/commands/restoreCommand.test.ts +250 -0
  154. package/dist/ui/commands/restoreCommand.ts +157 -0
  155. package/dist/ui/commands/searchEngineSetupCommand.ts +18 -0
  156. package/dist/ui/commands/settingsCommand.test.ts +36 -0
  157. package/dist/ui/commands/settingsCommand.ts +17 -0
  158. package/dist/ui/commands/setupGithubCommand.test.ts +238 -0
  159. package/dist/ui/commands/setupGithubCommand.ts +212 -0
  160. package/dist/ui/commands/speakCommand.ts +175 -0
  161. package/dist/ui/commands/statsCommand.test.ts +78 -0
  162. package/dist/ui/commands/statsCommand.ts +70 -0
  163. package/dist/ui/commands/terminalSetupCommand.test.ts +85 -0
  164. package/dist/ui/commands/terminalSetupCommand.ts +45 -0
  165. package/dist/ui/commands/themeCommand.test.ts +38 -0
  166. package/dist/ui/commands/themeCommand.ts +17 -0
  167. package/dist/ui/commands/toolsCommand.test.ts +105 -0
  168. package/dist/ui/commands/toolsCommand.ts +71 -0
  169. package/dist/ui/commands/ttsCommand.ts +143 -0
  170. package/dist/ui/commands/types.ts +204 -0
  171. package/dist/ui/commands/vimCommand.ts +25 -0
  172. package/dist/ui/commands/voiceCommand.ts +125 -0
  173. package/dist/ui/components/AboutBox.tsx +133 -0
  174. package/dist/ui/components/AsciiArt.ts +54 -0
  175. package/dist/ui/components/AuthDialog.test.tsx +334 -0
  176. package/dist/ui/components/AuthDialog.tsx +289 -0
  177. package/dist/ui/components/AuthInProgress.tsx +62 -0
  178. package/dist/ui/components/AutoAcceptIndicator.tsx +47 -0
  179. package/dist/ui/components/ConsoleSummaryDisplay.tsx +35 -0
  180. package/dist/ui/components/ContextSummaryDisplay.test.tsx +85 -0
  181. package/dist/ui/components/ContextSummaryDisplay.tsx +120 -0
  182. package/dist/ui/components/ContextUsageDisplay.tsx +77 -0
  183. package/dist/ui/components/DebugProfiler.tsx +36 -0
  184. package/dist/ui/components/DetailedMessagesDisplay.tsx +82 -0
  185. package/dist/ui/components/EditorSettingsDialog.tsx +172 -0
  186. package/dist/ui/components/FolderTrustDialog.test.tsx +36 -0
  187. package/dist/ui/components/FolderTrustDialog.tsx +74 -0
  188. package/dist/ui/components/Footer.test.tsx +159 -0
  189. package/dist/ui/components/Footer.tsx +158 -0
  190. package/dist/ui/components/GeminiKeyDialog.tsx +252 -0
  191. package/dist/ui/components/GeminiRespondingSpinner.tsx +34 -0
  192. package/dist/ui/components/Header.test.tsx +44 -0
  193. package/dist/ui/components/Header.tsx +70 -0
  194. package/dist/ui/components/Help.tsx +174 -0
  195. package/dist/ui/components/HistoryItemDisplay.test.tsx +125 -0
  196. package/dist/ui/components/HistoryItemDisplay.tsx +98 -0
  197. package/dist/ui/components/InputPrompt.test.tsx +1467 -0
  198. package/dist/ui/components/InputPrompt.tsx +641 -0
  199. package/dist/ui/components/LMStudioModelPrompt.tsx +215 -0
  200. package/dist/ui/components/LoadingIndicator.test.tsx +296 -0
  201. package/dist/ui/components/LoadingIndicator.tsx +82 -0
  202. package/dist/ui/components/MemoryUsageDisplay.tsx +36 -0
  203. package/dist/ui/components/ModelStatsDisplay.test.tsx +252 -0
  204. package/dist/ui/components/ModelStatsDisplay.tsx +197 -0
  205. package/dist/ui/components/OllamaModelPrompt.tsx +206 -0
  206. package/dist/ui/components/OpenAIEndpointDialog.tsx +261 -0
  207. package/dist/ui/components/OpenAIKeyPrompt.test.tsx +64 -0
  208. package/dist/ui/components/OpenAIKeyPrompt.tsx +197 -0
  209. package/dist/ui/components/PrepareLabel.tsx +48 -0
  210. package/dist/ui/components/SearchEngineConfigDialog.tsx +280 -0
  211. package/dist/ui/components/SessionSummaryDisplay.test.tsx +75 -0
  212. package/dist/ui/components/SessionSummaryDisplay.tsx +18 -0
  213. package/dist/ui/components/SettingsDialog.test.tsx +865 -0
  214. package/dist/ui/components/SettingsDialog.tsx +753 -0
  215. package/dist/ui/components/ShellConfirmationDialog.test.tsx +53 -0
  216. package/dist/ui/components/ShellConfirmationDialog.tsx +103 -0
  217. package/dist/ui/components/ShellModeIndicator.tsx +18 -0
  218. package/dist/ui/components/ShowMoreLines.tsx +40 -0
  219. package/dist/ui/components/StatsDisplay.test.tsx +401 -0
  220. package/dist/ui/components/StatsDisplay.tsx +273 -0
  221. package/dist/ui/components/SuggestionsDisplay.tsx +102 -0
  222. package/dist/ui/components/ThemeDialog.tsx +310 -0
  223. package/dist/ui/components/Tips.tsx +45 -0
  224. package/dist/ui/components/TodoDisplay.test.tsx +97 -0
  225. package/dist/ui/components/TodoDisplay.tsx +72 -0
  226. package/dist/ui/components/ToolStatsDisplay.test.tsx +180 -0
  227. package/dist/ui/components/ToolStatsDisplay.tsx +208 -0
  228. package/dist/ui/components/UpdateNotification.tsx +23 -0
  229. package/dist/ui/components/WelcomeBackDialog.tsx +290 -0
  230. package/dist/ui/components/__snapshots__/IDEContextDetailDisplay.test.tsx.snap +24 -0
  231. package/dist/ui/components/__snapshots__/ModelStatsDisplay.test.tsx.snap +121 -0
  232. package/dist/ui/components/__snapshots__/SessionSummaryDisplay.test.tsx.snap +30 -0
  233. package/dist/ui/components/__snapshots__/ShellConfirmationDialog.test.tsx.snap +21 -0
  234. package/dist/ui/components/__snapshots__/StatsDisplay.test.tsx.snap +264 -0
  235. package/dist/ui/components/__snapshots__/ToolStatsDisplay.test.tsx.snap +91 -0
  236. package/dist/ui/components/messages/CompressionMessage.tsx +49 -0
  237. package/dist/ui/components/messages/DiffRenderer.test.tsx +365 -0
  238. package/dist/ui/components/messages/DiffRenderer.tsx +358 -0
  239. package/dist/ui/components/messages/ErrorMessage.tsx +31 -0
  240. package/dist/ui/components/messages/GeminiMessage.tsx +43 -0
  241. package/dist/ui/components/messages/GeminiMessageContent.tsx +43 -0
  242. package/dist/ui/components/messages/InfoMessage.tsx +32 -0
  243. package/dist/ui/components/messages/ToolConfirmationMessage.test.tsx +58 -0
  244. package/dist/ui/components/messages/ToolConfirmationMessage.tsx +297 -0
  245. package/dist/ui/components/messages/ToolGroupMessage.tsx +126 -0
  246. package/dist/ui/components/messages/ToolMessage.test.tsx +183 -0
  247. package/dist/ui/components/messages/ToolMessage.tsx +296 -0
  248. package/dist/ui/components/messages/UserMessage.tsx +43 -0
  249. package/dist/ui/components/messages/UserShellMessage.tsx +25 -0
  250. package/dist/ui/components/shared/MaxSizedBox.test.tsx +425 -0
  251. package/dist/ui/components/shared/MaxSizedBox.tsx +624 -0
  252. package/dist/ui/components/shared/RadioButtonSelect.test.tsx +181 -0
  253. package/dist/ui/components/shared/RadioButtonSelect.tsx +234 -0
  254. package/dist/ui/components/shared/__snapshots__/RadioButtonSelect.test.tsx.snap +47 -0
  255. package/dist/ui/components/shared/text-buffer.test.ts +1728 -0
  256. package/dist/ui/components/shared/text-buffer.ts +2227 -0
  257. package/dist/ui/components/shared/vim-buffer-actions.test.ts +1119 -0
  258. package/dist/ui/components/shared/vim-buffer-actions.ts +814 -0
  259. package/dist/ui/constants.ts +17 -0
  260. package/dist/ui/contexts/KeypressContext.test.tsx +391 -0
  261. package/dist/ui/contexts/KeypressContext.tsx +440 -0
  262. package/dist/ui/contexts/OverflowContext.tsx +87 -0
  263. package/dist/ui/contexts/SessionContext.test.tsx +132 -0
  264. package/dist/ui/contexts/SessionContext.tsx +143 -0
  265. package/dist/ui/contexts/SettingsContext.tsx +20 -0
  266. package/dist/ui/contexts/StreamingContext.tsx +22 -0
  267. package/dist/ui/contexts/VimModeContext.tsx +79 -0
  268. package/dist/ui/editors/editorSettingsManager.ts +66 -0
  269. package/dist/ui/hooks/atCommandProcessor.test.ts +1102 -0
  270. package/dist/ui/hooks/atCommandProcessor.ts +485 -0
  271. package/dist/ui/hooks/shellCommandProcessor.test.ts +481 -0
  272. package/dist/ui/hooks/shellCommandProcessor.ts +314 -0
  273. package/dist/ui/hooks/slashCommandProcessor.test.ts +1044 -0
  274. package/dist/ui/hooks/slashCommandProcessor.ts +595 -0
  275. package/dist/ui/hooks/useAtCompletion.test.ts +497 -0
  276. package/dist/ui/hooks/useAtCompletion.ts +244 -0
  277. package/dist/ui/hooks/useAuthCommand.ts +129 -0
  278. package/dist/ui/hooks/useAutoAcceptIndicator.test.ts +300 -0
  279. package/dist/ui/hooks/useAutoAcceptIndicator.ts +52 -0
  280. package/dist/ui/hooks/useBracketedPaste.ts +37 -0
  281. package/dist/ui/hooks/useCommandCompletion.test.ts +518 -0
  282. package/dist/ui/hooks/useCommandCompletion.tsx +238 -0
  283. package/dist/ui/hooks/useCompletion.ts +128 -0
  284. package/dist/ui/hooks/useConsoleMessages.test.ts +147 -0
  285. package/dist/ui/hooks/useConsoleMessages.ts +110 -0
  286. package/dist/ui/hooks/useEditorSettings.test.ts +283 -0
  287. package/dist/ui/hooks/useEditorSettings.ts +75 -0
  288. package/dist/ui/hooks/useFocus.test.ts +119 -0
  289. package/dist/ui/hooks/useFocus.ts +48 -0
  290. package/dist/ui/hooks/useFolderTrust.test.ts +159 -0
  291. package/dist/ui/hooks/useFolderTrust.ts +72 -0
  292. package/dist/ui/hooks/useGeminiStream.test.tsx +1998 -0
  293. package/dist/ui/hooks/useGeminiStream.ts +1017 -0
  294. package/dist/ui/hooks/useGitBranchName.test.ts +280 -0
  295. package/dist/ui/hooks/useGitBranchName.ts +79 -0
  296. package/dist/ui/hooks/useHistoryManager.test.ts +202 -0
  297. package/dist/ui/hooks/useHistoryManager.ts +111 -0
  298. package/dist/ui/hooks/useInputHistory.test.ts +261 -0
  299. package/dist/ui/hooks/useInputHistory.ts +111 -0
  300. package/dist/ui/hooks/useKeypress.test.ts +280 -0
  301. package/dist/ui/hooks/useKeypress.ts +39 -0
  302. package/dist/ui/hooks/useKittyKeyboardProtocol.ts +31 -0
  303. package/dist/ui/hooks/useLoadingIndicator.test.ts +139 -0
  304. package/dist/ui/hooks/useLoadingIndicator.ts +57 -0
  305. package/dist/ui/hooks/useLogger.ts +32 -0
  306. package/dist/ui/hooks/useMessageQueue.test.ts +226 -0
  307. package/dist/ui/hooks/useMessageQueue.ts +69 -0
  308. package/dist/ui/hooks/usePhraseCycler.test.ts +145 -0
  309. package/dist/ui/hooks/usePhraseCycler.ts +198 -0
  310. package/dist/ui/hooks/usePrivacySettings.test.ts +242 -0
  311. package/dist/ui/hooks/usePrivacySettings.ts +150 -0
  312. package/dist/ui/hooks/useReactToolScheduler.ts +309 -0
  313. package/dist/ui/hooks/useRefreshMemoryCommand.ts +7 -0
  314. package/dist/ui/hooks/useReverseSearchCompletion.test.tsx +260 -0
  315. package/dist/ui/hooks/useReverseSearchCompletion.tsx +95 -0
  316. package/dist/ui/hooks/useSettingsCommand.ts +25 -0
  317. package/dist/ui/hooks/useShellHistory.test.ts +219 -0
  318. package/dist/ui/hooks/useShellHistory.ts +133 -0
  319. package/dist/ui/hooks/useShowMemoryCommand.ts +75 -0
  320. package/dist/ui/hooks/useSlashCompletion.test.ts +434 -0
  321. package/dist/ui/hooks/useSlashCompletion.ts +187 -0
  322. package/dist/ui/hooks/useStateAndRef.ts +36 -0
  323. package/dist/ui/hooks/useTerminalSize.ts +32 -0
  324. package/dist/ui/hooks/useThemeCommand.ts +110 -0
  325. package/dist/ui/hooks/useTimer.test.ts +120 -0
  326. package/dist/ui/hooks/useTimer.ts +65 -0
  327. package/dist/ui/hooks/useToolScheduler.test.ts +1123 -0
  328. package/dist/ui/hooks/useWelcomeBack.ts +253 -0
  329. package/dist/ui/hooks/vim.test.ts +1691 -0
  330. package/dist/ui/hooks/vim.ts +784 -0
  331. package/dist/ui/keyMatchers.test.ts +337 -0
  332. package/dist/ui/keyMatchers.ts +105 -0
  333. package/dist/ui/privacy/CloudFreePrivacyNotice.tsx +117 -0
  334. package/dist/ui/privacy/CloudPaidPrivacyNotice.tsx +59 -0
  335. package/dist/ui/privacy/GeminiPrivacyNotice.tsx +62 -0
  336. package/dist/ui/privacy/PrivacyNotice.tsx +42 -0
  337. package/dist/ui/semantic-colors.ts +26 -0
  338. package/dist/ui/themes/ansi-light.ts +150 -0
  339. package/dist/ui/themes/ansi.ts +159 -0
  340. package/dist/ui/themes/atom-one-dark.ts +147 -0
  341. package/dist/ui/themes/ayu-light.ts +139 -0
  342. package/dist/ui/themes/ayu.ts +113 -0
  343. package/dist/ui/themes/color-utils.test.ts +221 -0
  344. package/dist/ui/themes/color-utils.ts +231 -0
  345. package/dist/ui/themes/default-light.ts +108 -0
  346. package/dist/ui/themes/default.ts +151 -0
  347. package/dist/ui/themes/dracula.ts +124 -0
  348. package/dist/ui/themes/fss-code-dark.ts +156 -0
  349. package/dist/ui/themes/fss-dark.ts +113 -0
  350. package/dist/ui/themes/fss-light.ts +139 -0
  351. package/dist/ui/themes/github-dark.ts +147 -0
  352. package/dist/ui/themes/github-light.ts +149 -0
  353. package/dist/ui/themes/googlecode.ts +146 -0
  354. package/dist/ui/themes/no-color.ts +125 -0
  355. package/dist/ui/themes/qwen-dark.ts +118 -0
  356. package/dist/ui/themes/qwen-light.ts +144 -0
  357. package/dist/ui/themes/semantic-tokens.ts +127 -0
  358. package/dist/ui/themes/shades-of-purple.ts +352 -0
  359. package/dist/ui/themes/theme-manager.test.ts +99 -0
  360. package/dist/ui/themes/theme-manager.ts +257 -0
  361. package/dist/ui/themes/theme.test.ts +97 -0
  362. package/dist/ui/themes/theme.ts +451 -0
  363. package/dist/ui/themes/xcode.ts +154 -0
  364. package/dist/ui/types.ts +255 -0
  365. package/dist/ui/utils/CodeColorizer.tsx +217 -0
  366. package/dist/ui/utils/ConsolePatcher.ts +71 -0
  367. package/dist/ui/utils/InlineMarkdownRenderer.tsx +173 -0
  368. package/dist/ui/utils/MarkdownDisplay.test.tsx +244 -0
  369. package/dist/ui/utils/MarkdownDisplay.tsx +415 -0
  370. package/dist/ui/utils/TableRenderer.tsx +159 -0
  371. package/dist/ui/utils/__snapshots__/MarkdownDisplay.test.tsx.snap +93 -0
  372. package/dist/ui/utils/clipboardUtils.test.ts +76 -0
  373. package/dist/ui/utils/clipboardUtils.ts +149 -0
  374. package/dist/ui/utils/commandUtils.test.ts +384 -0
  375. package/dist/ui/utils/commandUtils.ts +106 -0
  376. package/dist/ui/utils/computeStats.test.ts +292 -0
  377. package/dist/ui/utils/computeStats.ts +86 -0
  378. package/dist/ui/utils/displayUtils.test.ts +58 -0
  379. package/dist/ui/utils/displayUtils.ts +32 -0
  380. package/dist/ui/utils/formatters.test.ts +72 -0
  381. package/dist/ui/utils/formatters.ts +63 -0
  382. package/dist/ui/utils/isNarrowWidth.ts +9 -0
  383. package/dist/ui/utils/kittyProtocolDetector.ts +105 -0
  384. package/dist/ui/utils/markdownUtilities.test.ts +50 -0
  385. package/dist/ui/utils/markdownUtilities.ts +125 -0
  386. package/dist/ui/utils/platformConstants.ts +52 -0
  387. package/dist/ui/utils/terminalSetup.ts +342 -0
  388. package/dist/ui/utils/textUtils.ts +40 -0
  389. package/dist/ui/utils/updateCheck.test.ts +163 -0
  390. package/dist/ui/utils/updateCheck.ts +100 -0
  391. package/dist/utils/checks.ts +28 -0
  392. package/dist/utils/cleanup.test.ts +68 -0
  393. package/dist/utils/cleanup.ts +36 -0
  394. package/dist/utils/dialogScopeUtils.ts +64 -0
  395. package/dist/utils/events.ts +14 -0
  396. package/dist/utils/gitUtils.test.ts +149 -0
  397. package/dist/utils/gitUtils.ts +116 -0
  398. package/dist/utils/handleAutoUpdate.test.ts +272 -0
  399. package/dist/utils/handleAutoUpdate.ts +145 -0
  400. package/dist/utils/installationInfo.test.ts +315 -0
  401. package/dist/utils/installationInfo.ts +176 -0
  402. package/dist/utils/package.ts +38 -0
  403. package/dist/utils/readStdin.ts +51 -0
  404. package/dist/utils/resolvePath.ts +21 -0
  405. package/dist/utils/sandbox-macos-permissive-closed.sb +32 -0
  406. package/dist/utils/sandbox-macos-permissive-open.sb +25 -0
  407. package/dist/utils/sandbox-macos-permissive-proxied.sb +37 -0
  408. package/dist/utils/sandbox-macos-restrictive-closed.sb +93 -0
  409. package/dist/utils/sandbox-macos-restrictive-open.sb +96 -0
  410. package/dist/utils/sandbox-macos-restrictive-proxied.sb +98 -0
  411. package/dist/utils/sandbox.ts +962 -0
  412. package/dist/utils/settingsUtils.test.ts +797 -0
  413. package/dist/utils/settingsUtils.ts +489 -0
  414. package/dist/utils/spawnWrapper.ts +9 -0
  415. package/dist/utils/startupWarnings.test.ts +83 -0
  416. package/dist/utils/startupWarnings.ts +40 -0
  417. package/dist/utils/updateEventEmitter.ts +13 -0
  418. package/dist/utils/userStartupWarnings.test.ts +87 -0
  419. package/dist/utils/userStartupWarnings.ts +69 -0
  420. package/dist/utils/version.ts +12 -0
  421. package/dist/validateNonInterActiveAuth.test.ts +260 -0
  422. package/dist/validateNonInterActiveAuth.ts +51 -0
  423. package/dist/vitest.config.ts +37 -0
  424. package/dist/zed-integration/acp.ts +366 -0
  425. package/dist/zed-integration/fileSystemService.ts +47 -0
  426. package/dist/zed-integration/schema.ts +466 -0
  427. package/dist/zed-integration/zedIntegration.ts +944 -0
  428. package/package.json +2 -2
@@ -0,0 +1,1017 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { type Part, type PartListUnion, FinishReason } from '@google/genai';
8
+ import {
9
+ Config,
10
+ ServerGeminiContentEvent as ContentEvent,
11
+ DEFAULT_GEMINI_FLASH_MODEL,
12
+ EditorType,
13
+ ServerGeminiErrorEvent as ErrorEvent,
14
+ GeminiClient,
15
+ ServerGeminiStreamEvent as GeminiEvent,
16
+ getErrorMessage,
17
+ GitService,
18
+ isNodeError,
19
+ logUserPrompt,
20
+ MessageSenderType,
21
+ parseAndFormatApiError,
22
+ ServerGeminiChatCompressedEvent,
23
+ GeminiEventType as ServerGeminiEventType,
24
+ ServerGeminiFinishedEvent,
25
+ ThoughtSummary,
26
+ ToolCallRequestInfo,
27
+ UnauthorizedError,
28
+ UserPromptEvent,
29
+ } from 'fss-link-core';
30
+ import { promises as fs } from 'fs';
31
+ import path from 'path';
32
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
33
+ import { useSessionStats } from '../contexts/SessionContext.js';
34
+ import {
35
+ HistoryItem,
36
+ HistoryItemToolGroup,
37
+ HistoryItemWithoutId,
38
+ MessageType,
39
+ SlashCommandProcessorResult,
40
+ StreamingState,
41
+ ToolCallStatus,
42
+ } from '../types.js';
43
+ import { isAtCommand } from '../utils/commandUtils.js';
44
+ import { findLastSafeSplitPoint } from '../utils/markdownUtilities.js';
45
+ import { handleAtCommand } from './atCommandProcessor.js';
46
+ import { useShellCommandProcessor } from './shellCommandProcessor.js';
47
+ import { UseHistoryManagerReturn } from './useHistoryManager.js';
48
+ import { useKeypress } from './useKeypress.js';
49
+ import { useLogger } from './useLogger.js';
50
+ import {
51
+ mapToDisplay as mapTrackedToolCallsToDisplay,
52
+ TrackedCancelledToolCall,
53
+ TrackedCompletedToolCall,
54
+ TrackedToolCall,
55
+ useReactToolScheduler,
56
+ } from './useReactToolScheduler.js';
57
+ import { useStateAndRef } from './useStateAndRef.js';
58
+
59
+ export function mergePartListUnions(list: PartListUnion[]): PartListUnion {
60
+ const resultParts: PartListUnion = [];
61
+ for (const item of list) {
62
+ if (Array.isArray(item)) {
63
+ resultParts.push(...item);
64
+ } else {
65
+ resultParts.push(item);
66
+ }
67
+ }
68
+ return resultParts;
69
+ }
70
+
71
+ enum StreamProcessingStatus {
72
+ Completed,
73
+ UserCancelled,
74
+ Error,
75
+ }
76
+
77
+ /**
78
+ * Manages the Gemini stream, including user input, command processing,
79
+ * API interaction, and tool call lifecycle.
80
+ */
81
+ export const useGeminiStream = (
82
+ geminiClient: GeminiClient,
83
+ history: HistoryItem[],
84
+ addItem: UseHistoryManagerReturn['addItem'],
85
+ config: Config,
86
+ onDebugMessage: (message: string) => void,
87
+ handleSlashCommand: (
88
+ cmd: PartListUnion,
89
+ ) => Promise<SlashCommandProcessorResult | false>,
90
+ shellModeActive: boolean,
91
+ getPreferredEditor: () => EditorType | undefined,
92
+ onAuthError: () => void,
93
+ performMemoryRefresh: () => Promise<void>,
94
+ modelSwitchedFromQuotaError: boolean,
95
+ setModelSwitchedFromQuotaError: React.Dispatch<React.SetStateAction<boolean>>,
96
+ onEditorClose: () => void,
97
+ onCancelSubmit: () => void,
98
+ ) => {
99
+ const [initError, setInitError] = useState<string | null>(null);
100
+ const abortControllerRef = useRef<AbortController | null>(null);
101
+ const turnCancelledRef = useRef(false);
102
+ const isSubmittingQueryRef = useRef(false);
103
+ const [isResponding, setIsResponding] = useState<boolean>(false);
104
+ const [thought, setThought] = useState<ThoughtSummary | null>(null);
105
+ const [pendingHistoryItemRef, setPendingHistoryItem] =
106
+ useStateAndRef<HistoryItemWithoutId | null>(null);
107
+ const processedMemoryToolsRef = useRef<Set<string>>(new Set());
108
+ const { startNewPrompt, getPromptCount } = useSessionStats();
109
+ const logger = useLogger();
110
+ const gitService = useMemo(() => {
111
+ if (!config.getProjectRoot()) {
112
+ return;
113
+ }
114
+ return new GitService(config.getProjectRoot());
115
+ }, [config]);
116
+
117
+ const [toolCalls, scheduleToolCalls, markToolsAsSubmitted] =
118
+ useReactToolScheduler(
119
+ async (completedToolCallsFromScheduler) => {
120
+ // This onComplete is called when ALL scheduled tools for a given batch are done.
121
+ if (completedToolCallsFromScheduler.length > 0) {
122
+ // Add the final state of these tools to the history for display.
123
+ addItem(
124
+ mapTrackedToolCallsToDisplay(
125
+ completedToolCallsFromScheduler as TrackedToolCall[],
126
+ ),
127
+ Date.now(),
128
+ );
129
+
130
+ // Handle tool response submission immediately when tools complete
131
+ await handleCompletedTools(
132
+ completedToolCallsFromScheduler as TrackedToolCall[],
133
+ );
134
+ }
135
+ },
136
+ config,
137
+ setPendingHistoryItem,
138
+ getPreferredEditor,
139
+ onEditorClose,
140
+ );
141
+
142
+ const pendingToolCallGroupDisplay = useMemo(
143
+ () =>
144
+ toolCalls.length ? mapTrackedToolCallsToDisplay(toolCalls) : undefined,
145
+ [toolCalls],
146
+ );
147
+
148
+ const loopDetectedRef = useRef(false);
149
+
150
+ const onExec = useCallback(async (done: Promise<void>) => {
151
+ setIsResponding(true);
152
+ await done;
153
+ setIsResponding(false);
154
+ }, []);
155
+ const { handleShellCommand } = useShellCommandProcessor(
156
+ addItem,
157
+ setPendingHistoryItem,
158
+ onExec,
159
+ onDebugMessage,
160
+ config,
161
+ geminiClient,
162
+ );
163
+
164
+ const streamingState = useMemo(() => {
165
+ if (toolCalls.some((tc) => tc.status === 'awaiting_approval')) {
166
+ return StreamingState.WaitingForConfirmation;
167
+ }
168
+ if (
169
+ isResponding ||
170
+ toolCalls.some(
171
+ (tc) =>
172
+ tc.status === 'executing' ||
173
+ tc.status === 'scheduled' ||
174
+ tc.status === 'validating' ||
175
+ ((tc.status === 'success' ||
176
+ tc.status === 'error' ||
177
+ tc.status === 'cancelled') &&
178
+ !(tc as TrackedCompletedToolCall | TrackedCancelledToolCall)
179
+ .responseSubmittedToGemini),
180
+ )
181
+ ) {
182
+ return StreamingState.Responding;
183
+ }
184
+ return StreamingState.Idle;
185
+ }, [isResponding, toolCalls]);
186
+
187
+ const cancelOngoingRequest = useCallback(() => {
188
+ if (streamingState !== StreamingState.Responding) {
189
+ return;
190
+ }
191
+ if (turnCancelledRef.current) {
192
+ return;
193
+ }
194
+ turnCancelledRef.current = true;
195
+ isSubmittingQueryRef.current = false;
196
+ abortControllerRef.current?.abort();
197
+ if (pendingHistoryItemRef.current) {
198
+ addItem(pendingHistoryItemRef.current, Date.now());
199
+ }
200
+ addItem(
201
+ {
202
+ type: MessageType.INFO,
203
+ text: 'Request cancelled.',
204
+ },
205
+ Date.now(),
206
+ );
207
+ setPendingHistoryItem(null);
208
+ onCancelSubmit();
209
+ setIsResponding(false);
210
+ }, [
211
+ streamingState,
212
+ addItem,
213
+ setPendingHistoryItem,
214
+ onCancelSubmit,
215
+ pendingHistoryItemRef,
216
+ ]);
217
+
218
+ useKeypress(
219
+ (key) => {
220
+ if (key.name === 'escape') {
221
+ cancelOngoingRequest();
222
+ }
223
+ },
224
+ { isActive: streamingState === StreamingState.Responding },
225
+ );
226
+
227
+ const prepareQueryForGemini = useCallback(
228
+ async (
229
+ query: PartListUnion,
230
+ userMessageTimestamp: number,
231
+ abortSignal: AbortSignal,
232
+ prompt_id: string,
233
+ ): Promise<{
234
+ queryToSend: PartListUnion | null;
235
+ shouldProceed: boolean;
236
+ }> => {
237
+ if (turnCancelledRef.current) {
238
+ return { queryToSend: null, shouldProceed: false };
239
+ }
240
+ if (typeof query === 'string' && query.trim().length === 0) {
241
+ return { queryToSend: null, shouldProceed: false };
242
+ }
243
+
244
+ let localQueryToSendToGemini: PartListUnion | null = null;
245
+
246
+ if (typeof query === 'string') {
247
+ const trimmedQuery = query.trim();
248
+ logUserPrompt(
249
+ config,
250
+ new UserPromptEvent(
251
+ trimmedQuery.length,
252
+ prompt_id,
253
+ config.getContentGeneratorConfig()?.authType,
254
+ trimmedQuery,
255
+ ),
256
+ );
257
+ onDebugMessage(`User query: '${trimmedQuery}'`);
258
+ await logger?.logMessage(MessageSenderType.USER, trimmedQuery);
259
+
260
+ // Handle UI-only commands first
261
+ const slashCommandResult = await handleSlashCommand(trimmedQuery);
262
+
263
+ if (slashCommandResult) {
264
+ switch (slashCommandResult.type) {
265
+ case 'schedule_tool': {
266
+ const { toolName, toolArgs } = slashCommandResult;
267
+ const toolCallRequest: ToolCallRequestInfo = {
268
+ callId: `${toolName}-${Date.now()}-${Math.random().toString(16).slice(2)}`,
269
+ name: toolName,
270
+ args: toolArgs,
271
+ isClientInitiated: true,
272
+ prompt_id,
273
+ };
274
+ scheduleToolCalls([toolCallRequest], abortSignal);
275
+ return { queryToSend: null, shouldProceed: false };
276
+ }
277
+ case 'submit_prompt': {
278
+ localQueryToSendToGemini = slashCommandResult.content;
279
+
280
+ return {
281
+ queryToSend: localQueryToSendToGemini,
282
+ shouldProceed: true,
283
+ };
284
+ }
285
+ case 'handled': {
286
+ return { queryToSend: null, shouldProceed: false };
287
+ }
288
+ default: {
289
+ const unreachable: never = slashCommandResult;
290
+ throw new Error(
291
+ `Unhandled slash command result type: ${unreachable}`,
292
+ );
293
+ }
294
+ }
295
+ }
296
+
297
+ if (shellModeActive && handleShellCommand(trimmedQuery, abortSignal)) {
298
+ return { queryToSend: null, shouldProceed: false };
299
+ }
300
+
301
+ // Handle @-commands (which might involve tool calls)
302
+ if (isAtCommand(trimmedQuery)) {
303
+ const atCommandResult = await handleAtCommand({
304
+ query: trimmedQuery,
305
+ config,
306
+ addItem,
307
+ onDebugMessage,
308
+ messageId: userMessageTimestamp,
309
+ signal: abortSignal,
310
+ });
311
+
312
+ // Add user's turn after @ command processing is done.
313
+ addItem(
314
+ { type: MessageType.USER, text: trimmedQuery },
315
+ userMessageTimestamp,
316
+ );
317
+
318
+ if (!atCommandResult.shouldProceed) {
319
+ return { queryToSend: null, shouldProceed: false };
320
+ }
321
+ localQueryToSendToGemini = atCommandResult.processedQuery;
322
+ } else {
323
+ // Normal query for Gemini
324
+ addItem(
325
+ { type: MessageType.USER, text: trimmedQuery },
326
+ userMessageTimestamp,
327
+ );
328
+ localQueryToSendToGemini = trimmedQuery;
329
+ }
330
+ } else {
331
+ // It's a function response (PartListUnion that isn't a string)
332
+ localQueryToSendToGemini = query;
333
+ }
334
+
335
+ if (localQueryToSendToGemini === null) {
336
+ onDebugMessage(
337
+ 'Query processing resulted in null, not sending to Gemini.',
338
+ );
339
+ return { queryToSend: null, shouldProceed: false };
340
+ }
341
+ return { queryToSend: localQueryToSendToGemini, shouldProceed: true };
342
+ },
343
+ [
344
+ config,
345
+ addItem,
346
+ onDebugMessage,
347
+ handleShellCommand,
348
+ handleSlashCommand,
349
+ logger,
350
+ shellModeActive,
351
+ scheduleToolCalls,
352
+ ],
353
+ );
354
+
355
+ // --- Stream Event Handlers ---
356
+
357
+ const handleContentEvent = useCallback(
358
+ (
359
+ eventValue: ContentEvent['value'],
360
+ currentGeminiMessageBuffer: string,
361
+ userMessageTimestamp: number,
362
+ ): string => {
363
+ if (turnCancelledRef.current) {
364
+ // Prevents additional output after a user initiated cancel.
365
+ return '';
366
+ }
367
+ let newGeminiMessageBuffer = currentGeminiMessageBuffer + eventValue;
368
+ if (
369
+ pendingHistoryItemRef.current?.type !== 'gemini' &&
370
+ pendingHistoryItemRef.current?.type !== 'gemini_content'
371
+ ) {
372
+ if (pendingHistoryItemRef.current) {
373
+ addItem(pendingHistoryItemRef.current, userMessageTimestamp);
374
+ }
375
+ setPendingHistoryItem({ type: 'gemini', text: '' });
376
+ newGeminiMessageBuffer = eventValue;
377
+ }
378
+ // Split large messages for better rendering performance. Ideally,
379
+ // we should maximize the amount of output sent to <Static />.
380
+ const splitPoint = findLastSafeSplitPoint(newGeminiMessageBuffer);
381
+ if (splitPoint === newGeminiMessageBuffer.length) {
382
+ // Update the existing message with accumulated content
383
+ setPendingHistoryItem((item) => ({
384
+ type: item?.type as 'gemini' | 'gemini_content',
385
+ text: newGeminiMessageBuffer,
386
+ }));
387
+ } else {
388
+ // This indicates that we need to split up this Gemini Message.
389
+ // Splitting a message is primarily a performance consideration. There is a
390
+ // <Static> component at the root of App.tsx which takes care of rendering
391
+ // content statically or dynamically. Everything but the last message is
392
+ // treated as static in order to prevent re-rendering an entire message history
393
+ // multiple times per-second (as streaming occurs). Prior to this change you'd
394
+ // see heavy flickering of the terminal. This ensures that larger messages get
395
+ // broken up so that there are more "statically" rendered.
396
+ const beforeText = newGeminiMessageBuffer.substring(0, splitPoint);
397
+ const afterText = newGeminiMessageBuffer.substring(splitPoint);
398
+ addItem(
399
+ {
400
+ type: pendingHistoryItemRef.current?.type as
401
+ | 'gemini'
402
+ | 'gemini_content',
403
+ text: beforeText,
404
+ },
405
+ userMessageTimestamp,
406
+ );
407
+ setPendingHistoryItem({ type: 'gemini_content', text: afterText });
408
+ newGeminiMessageBuffer = afterText;
409
+ }
410
+ return newGeminiMessageBuffer;
411
+ },
412
+ [addItem, pendingHistoryItemRef, setPendingHistoryItem],
413
+ );
414
+
415
+ const handleUserCancelledEvent = useCallback(
416
+ (userMessageTimestamp: number) => {
417
+ if (turnCancelledRef.current) {
418
+ return;
419
+ }
420
+ if (pendingHistoryItemRef.current) {
421
+ if (pendingHistoryItemRef.current.type === 'tool_group') {
422
+ const updatedTools = pendingHistoryItemRef.current.tools.map(
423
+ (tool) =>
424
+ tool.status === ToolCallStatus.Pending ||
425
+ tool.status === ToolCallStatus.Confirming ||
426
+ tool.status === ToolCallStatus.Executing
427
+ ? { ...tool, status: ToolCallStatus.Canceled }
428
+ : tool,
429
+ );
430
+ const pendingItem: HistoryItemToolGroup = {
431
+ ...pendingHistoryItemRef.current,
432
+ tools: updatedTools,
433
+ };
434
+ addItem(pendingItem, userMessageTimestamp);
435
+ } else {
436
+ addItem(pendingHistoryItemRef.current, userMessageTimestamp);
437
+ }
438
+ setPendingHistoryItem(null);
439
+ }
440
+ addItem(
441
+ { type: MessageType.INFO, text: 'User cancelled the request.' },
442
+ userMessageTimestamp,
443
+ );
444
+ setIsResponding(false);
445
+ setThought(null); // Reset thought when user cancels
446
+ },
447
+ [addItem, pendingHistoryItemRef, setPendingHistoryItem, setThought],
448
+ );
449
+
450
+ const handleErrorEvent = useCallback(
451
+ (eventValue: ErrorEvent['value'], userMessageTimestamp: number) => {
452
+ if (pendingHistoryItemRef.current) {
453
+ addItem(pendingHistoryItemRef.current, userMessageTimestamp);
454
+ setPendingHistoryItem(null);
455
+ }
456
+ addItem(
457
+ {
458
+ type: MessageType.ERROR,
459
+ text: parseAndFormatApiError(
460
+ eventValue.error,
461
+ config.getContentGeneratorConfig()?.authType,
462
+ undefined,
463
+ config.getModel(),
464
+ DEFAULT_GEMINI_FLASH_MODEL,
465
+ ),
466
+ },
467
+ userMessageTimestamp,
468
+ );
469
+ setThought(null); // Reset thought when there's an error
470
+ },
471
+ [addItem, pendingHistoryItemRef, setPendingHistoryItem, config, setThought],
472
+ );
473
+
474
+ const handleFinishedEvent = useCallback(
475
+ (event: ServerGeminiFinishedEvent, userMessageTimestamp: number) => {
476
+ const finishReason = event.value;
477
+
478
+ const finishReasonMessages: Record<FinishReason, string | undefined> = {
479
+ [FinishReason.FINISH_REASON_UNSPECIFIED]: undefined,
480
+ [FinishReason.STOP]: undefined,
481
+ [FinishReason.MAX_TOKENS]: 'Response truncated due to token limits.',
482
+ [FinishReason.SAFETY]: 'Response stopped due to safety reasons.',
483
+ [FinishReason.RECITATION]: 'Response stopped due to recitation policy.',
484
+ [FinishReason.LANGUAGE]:
485
+ 'Response stopped due to unsupported language.',
486
+ [FinishReason.BLOCKLIST]: 'Response stopped due to forbidden terms.',
487
+ [FinishReason.PROHIBITED_CONTENT]:
488
+ 'Response stopped due to prohibited content.',
489
+ [FinishReason.SPII]:
490
+ 'Response stopped due to sensitive personally identifiable information.',
491
+ [FinishReason.OTHER]: 'Response stopped for other reasons.',
492
+ [FinishReason.MALFORMED_FUNCTION_CALL]:
493
+ 'Response stopped due to malformed function call.',
494
+ [FinishReason.IMAGE_SAFETY]:
495
+ 'Response stopped due to image safety violations.',
496
+ [FinishReason.UNEXPECTED_TOOL_CALL]:
497
+ 'Response stopped due to unexpected tool call.',
498
+ };
499
+
500
+ const message = finishReasonMessages[finishReason];
501
+ if (message) {
502
+ addItem(
503
+ {
504
+ type: 'info',
505
+ text: `⚠️ ${message}`,
506
+ },
507
+ userMessageTimestamp,
508
+ );
509
+ }
510
+ },
511
+ [addItem],
512
+ );
513
+
514
+ const handleChatCompressionEvent = useCallback(
515
+ (eventValue: ServerGeminiChatCompressedEvent['value']) =>
516
+ addItem(
517
+ {
518
+ type: 'info',
519
+ text:
520
+ `IMPORTANT: This conversation approached the input token limit for ${config.getModel()}. ` +
521
+ `A compressed context will be sent for future messages (compressed from: ` +
522
+ `${eventValue?.originalTokenCount ?? 'unknown'} to ` +
523
+ `${eventValue?.newTokenCount ?? 'unknown'} tokens).`,
524
+ },
525
+ Date.now(),
526
+ ),
527
+ [addItem, config],
528
+ );
529
+
530
+ const handleMaxSessionTurnsEvent = useCallback(
531
+ () =>
532
+ addItem(
533
+ {
534
+ type: 'info',
535
+ text:
536
+ `The session has reached the maximum number of turns: ${config.getMaxSessionTurns()}. ` +
537
+ `Please update this limit in your setting.json file.`,
538
+ },
539
+ Date.now(),
540
+ ),
541
+ [addItem, config],
542
+ );
543
+
544
+ const handleSessionTokenLimitExceededEvent = useCallback(
545
+ (value: { currentTokens: number; limit: number; message: string }) =>
546
+ addItem(
547
+ {
548
+ type: 'error',
549
+ text:
550
+ `🚫 Session token limit exceeded: ${value.currentTokens.toLocaleString()} tokens > ${value.limit.toLocaleString()} limit.\n\n` +
551
+ `💡 Solutions:\n` +
552
+ ` • Start a new session: Use /clear command\n` +
553
+ ` • Increase limit: Add "sessionTokenLimit": (e.g., 128000) to your settings.json\n` +
554
+ ` • Compress history: Use /compress command to compress history`,
555
+ },
556
+ Date.now(),
557
+ ),
558
+ [addItem],
559
+ );
560
+
561
+ const handleLoopDetectedEvent = useCallback(() => {
562
+ addItem(
563
+ {
564
+ type: 'info',
565
+ text: `A potential loop was detected. This can happen due to repetitive tool calls or other model behavior. The request has been halted.`,
566
+ },
567
+ Date.now(),
568
+ );
569
+ }, [addItem]);
570
+
571
+ const processGeminiStreamEvents = useCallback(
572
+ async (
573
+ stream: AsyncIterable<GeminiEvent>,
574
+ userMessageTimestamp: number,
575
+ signal: AbortSignal,
576
+ ): Promise<StreamProcessingStatus> => {
577
+ let geminiMessageBuffer = '';
578
+ const toolCallRequests: ToolCallRequestInfo[] = [];
579
+ for await (const event of stream) {
580
+ switch (event.type) {
581
+ case ServerGeminiEventType.Thought:
582
+ setThought(event.value);
583
+ break;
584
+ case ServerGeminiEventType.Content:
585
+ geminiMessageBuffer = handleContentEvent(
586
+ event.value,
587
+ geminiMessageBuffer,
588
+ userMessageTimestamp,
589
+ );
590
+ break;
591
+ case ServerGeminiEventType.ToolCallRequest:
592
+ toolCallRequests.push(event.value);
593
+ break;
594
+ case ServerGeminiEventType.UserCancelled:
595
+ handleUserCancelledEvent(userMessageTimestamp);
596
+ break;
597
+ case ServerGeminiEventType.Error:
598
+ handleErrorEvent(event.value, userMessageTimestamp);
599
+ break;
600
+ case ServerGeminiEventType.ChatCompressed:
601
+ handleChatCompressionEvent(event.value);
602
+ break;
603
+ case ServerGeminiEventType.ToolCallConfirmation:
604
+ case ServerGeminiEventType.ToolCallResponse:
605
+ // do nothing
606
+ break;
607
+ case ServerGeminiEventType.MaxSessionTurns:
608
+ handleMaxSessionTurnsEvent();
609
+ break;
610
+ case ServerGeminiEventType.SessionTokenLimitExceeded:
611
+ handleSessionTokenLimitExceededEvent(event.value);
612
+ break;
613
+ case ServerGeminiEventType.Finished:
614
+ handleFinishedEvent(
615
+ event as ServerGeminiFinishedEvent,
616
+ userMessageTimestamp,
617
+ );
618
+ break;
619
+ case ServerGeminiEventType.LoopDetected:
620
+ // handle later because we want to move pending history to history
621
+ // before we add loop detected message to history
622
+ loopDetectedRef.current = true;
623
+ break;
624
+ default: {
625
+ // enforces exhaustive switch-case
626
+ const unreachable: never = event;
627
+ return unreachable;
628
+ }
629
+ }
630
+ }
631
+ if (toolCallRequests.length > 0) {
632
+ scheduleToolCalls(toolCallRequests, signal);
633
+ }
634
+ return StreamProcessingStatus.Completed;
635
+ },
636
+ [
637
+ handleContentEvent,
638
+ handleUserCancelledEvent,
639
+ handleErrorEvent,
640
+ scheduleToolCalls,
641
+ handleChatCompressionEvent,
642
+ handleFinishedEvent,
643
+ handleMaxSessionTurnsEvent,
644
+ handleSessionTokenLimitExceededEvent,
645
+ ],
646
+ );
647
+
648
+ const submitQuery = useCallback(
649
+ async (
650
+ query: PartListUnion,
651
+ options?: { isContinuation: boolean },
652
+ prompt_id?: string,
653
+ ) => {
654
+ // Prevent concurrent executions of submitQuery, but allow continuations
655
+ // which are part of the same logical flow (tool responses)
656
+ if (isSubmittingQueryRef.current && !options?.isContinuation) {
657
+ return;
658
+ }
659
+
660
+ if (
661
+ (streamingState === StreamingState.Responding ||
662
+ streamingState === StreamingState.WaitingForConfirmation) &&
663
+ !options?.isContinuation
664
+ )
665
+ return;
666
+
667
+ // Set the flag to indicate we're now executing
668
+ isSubmittingQueryRef.current = true;
669
+
670
+ const userMessageTimestamp = Date.now();
671
+
672
+ // Reset quota error flag when starting a new query (not a continuation)
673
+ if (!options?.isContinuation) {
674
+ setModelSwitchedFromQuotaError(false);
675
+ config.setQuotaErrorOccurred(false);
676
+ }
677
+
678
+ abortControllerRef.current = new AbortController();
679
+ const abortSignal = abortControllerRef.current.signal;
680
+ turnCancelledRef.current = false;
681
+
682
+ if (!prompt_id) {
683
+ prompt_id = config.getSessionId() + '########' + getPromptCount();
684
+ }
685
+
686
+ const { queryToSend, shouldProceed } = await prepareQueryForGemini(
687
+ query,
688
+ userMessageTimestamp,
689
+ abortSignal,
690
+ prompt_id!,
691
+ );
692
+
693
+ if (!shouldProceed || queryToSend === null) {
694
+ isSubmittingQueryRef.current = false;
695
+ return;
696
+ }
697
+
698
+ if (!options?.isContinuation) {
699
+ startNewPrompt();
700
+ setThought(null); // Reset thought when starting a new prompt
701
+ }
702
+
703
+ setIsResponding(true);
704
+ setInitError(null);
705
+
706
+ try {
707
+ const stream = geminiClient.sendMessageStream(
708
+ queryToSend,
709
+ abortSignal,
710
+ prompt_id!,
711
+ );
712
+ const processingStatus = await processGeminiStreamEvents(
713
+ stream,
714
+ userMessageTimestamp,
715
+ abortSignal,
716
+ );
717
+
718
+ if (processingStatus === StreamProcessingStatus.UserCancelled) {
719
+ isSubmittingQueryRef.current = false;
720
+ return;
721
+ }
722
+
723
+ if (pendingHistoryItemRef.current) {
724
+ addItem(pendingHistoryItemRef.current, userMessageTimestamp);
725
+ setPendingHistoryItem(null);
726
+ }
727
+ if (loopDetectedRef.current) {
728
+ loopDetectedRef.current = false;
729
+ handleLoopDetectedEvent();
730
+ }
731
+ } catch (error: unknown) {
732
+ if (error instanceof UnauthorizedError) {
733
+ onAuthError();
734
+ } else if (!isNodeError(error) || error.name !== 'AbortError') {
735
+ addItem(
736
+ {
737
+ type: MessageType.ERROR,
738
+ text: parseAndFormatApiError(
739
+ getErrorMessage(error) || 'Unknown error',
740
+ config.getContentGeneratorConfig()?.authType,
741
+ undefined,
742
+ config.getModel(),
743
+ DEFAULT_GEMINI_FLASH_MODEL,
744
+ ),
745
+ },
746
+ userMessageTimestamp,
747
+ );
748
+ }
749
+ } finally {
750
+ setIsResponding(false);
751
+ isSubmittingQueryRef.current = false;
752
+ }
753
+ },
754
+ [
755
+ streamingState,
756
+ setModelSwitchedFromQuotaError,
757
+ prepareQueryForGemini,
758
+ processGeminiStreamEvents,
759
+ pendingHistoryItemRef,
760
+ addItem,
761
+ setPendingHistoryItem,
762
+ setInitError,
763
+ geminiClient,
764
+ onAuthError,
765
+ config,
766
+ startNewPrompt,
767
+ getPromptCount,
768
+ handleLoopDetectedEvent,
769
+ ],
770
+ );
771
+
772
+ const handleCompletedTools = useCallback(
773
+ async (completedToolCallsFromScheduler: TrackedToolCall[]) => {
774
+ if (isResponding) {
775
+ return;
776
+ }
777
+
778
+ const completedAndReadyToSubmitTools =
779
+ completedToolCallsFromScheduler.filter(
780
+ (
781
+ tc: TrackedToolCall,
782
+ ): tc is TrackedCompletedToolCall | TrackedCancelledToolCall => {
783
+ const isTerminalState =
784
+ tc.status === 'success' ||
785
+ tc.status === 'error' ||
786
+ tc.status === 'cancelled';
787
+
788
+ if (isTerminalState) {
789
+ const completedOrCancelledCall = tc as
790
+ | TrackedCompletedToolCall
791
+ | TrackedCancelledToolCall;
792
+ return (
793
+ completedOrCancelledCall.response?.responseParts !== undefined
794
+ );
795
+ }
796
+ return false;
797
+ },
798
+ );
799
+
800
+ // Finalize any client-initiated tools as soon as they are done.
801
+ const clientTools = completedAndReadyToSubmitTools.filter(
802
+ (t) => t.request.isClientInitiated,
803
+ );
804
+ if (clientTools.length > 0) {
805
+ markToolsAsSubmitted(clientTools.map((t) => t.request.callId));
806
+ }
807
+
808
+ // Identify new, successful save_memory calls that we haven't processed yet.
809
+ const newSuccessfulMemorySaves = completedAndReadyToSubmitTools.filter(
810
+ (t) =>
811
+ t.request.name === 'save_memory' &&
812
+ t.status === 'success' &&
813
+ !processedMemoryToolsRef.current.has(t.request.callId),
814
+ );
815
+
816
+ if (newSuccessfulMemorySaves.length > 0) {
817
+ // Perform the refresh only if there are new ones.
818
+ void performMemoryRefresh();
819
+ // Mark them as processed so we don't do this again on the next render.
820
+ newSuccessfulMemorySaves.forEach((t) =>
821
+ processedMemoryToolsRef.current.add(t.request.callId),
822
+ );
823
+ }
824
+
825
+ const geminiTools = completedAndReadyToSubmitTools.filter(
826
+ (t) => !t.request.isClientInitiated,
827
+ );
828
+
829
+ if (geminiTools.length === 0) {
830
+ return;
831
+ }
832
+
833
+ // If all the tools were cancelled, don't submit a response to Gemini.
834
+ const allToolsCancelled = geminiTools.every(
835
+ (tc) => tc.status === 'cancelled',
836
+ );
837
+
838
+ if (allToolsCancelled) {
839
+ if (geminiClient) {
840
+ // We need to manually add the function responses to the history
841
+ // so the model knows the tools were cancelled.
842
+ const responsesToAdd = geminiTools.flatMap(
843
+ (toolCall) => toolCall.response.responseParts,
844
+ );
845
+ const combinedParts: Part[] = [];
846
+ for (const response of responsesToAdd) {
847
+ if (Array.isArray(response)) {
848
+ combinedParts.push(...response);
849
+ } else if (typeof response === 'string') {
850
+ combinedParts.push({ text: response });
851
+ } else {
852
+ combinedParts.push(response);
853
+ }
854
+ }
855
+ geminiClient.addHistory({
856
+ role: 'user',
857
+ parts: combinedParts,
858
+ });
859
+ }
860
+
861
+ const callIdsToMarkAsSubmitted = geminiTools.map(
862
+ (toolCall) => toolCall.request.callId,
863
+ );
864
+ markToolsAsSubmitted(callIdsToMarkAsSubmitted);
865
+ return;
866
+ }
867
+
868
+ const responsesToSend: PartListUnion[] = geminiTools.map(
869
+ (toolCall) => toolCall.response.responseParts,
870
+ );
871
+ const callIdsToMarkAsSubmitted = geminiTools.map(
872
+ (toolCall) => toolCall.request.callId,
873
+ );
874
+
875
+ const prompt_ids = geminiTools.map(
876
+ (toolCall) => toolCall.request.prompt_id,
877
+ );
878
+
879
+ markToolsAsSubmitted(callIdsToMarkAsSubmitted);
880
+
881
+ // Don't continue if model was switched due to quota error
882
+ if (modelSwitchedFromQuotaError) {
883
+ return;
884
+ }
885
+
886
+ submitQuery(
887
+ mergePartListUnions(responsesToSend),
888
+ {
889
+ isContinuation: true,
890
+ },
891
+ prompt_ids[0],
892
+ );
893
+ },
894
+ [
895
+ isResponding,
896
+ submitQuery,
897
+ markToolsAsSubmitted,
898
+ geminiClient,
899
+ performMemoryRefresh,
900
+ modelSwitchedFromQuotaError,
901
+ ],
902
+ );
903
+
904
+ const pendingHistoryItems = [
905
+ pendingHistoryItemRef.current,
906
+ pendingToolCallGroupDisplay,
907
+ ].filter((i) => i !== undefined && i !== null);
908
+
909
+ useEffect(() => {
910
+ const saveRestorableToolCalls = async () => {
911
+ if (!config.getCheckpointingEnabled()) {
912
+ return;
913
+ }
914
+ const restorableToolCalls = toolCalls.filter(
915
+ (toolCall) =>
916
+ (toolCall.request.name === 'replace' ||
917
+ toolCall.request.name === 'write_file') &&
918
+ toolCall.status === 'awaiting_approval',
919
+ );
920
+
921
+ if (restorableToolCalls.length > 0) {
922
+ const checkpointDir = config.getProjectTempDir()
923
+ ? path.join(config.getProjectTempDir(), 'checkpoints')
924
+ : undefined;
925
+
926
+ if (!checkpointDir) {
927
+ return;
928
+ }
929
+
930
+ try {
931
+ await fs.mkdir(checkpointDir, { recursive: true });
932
+ } catch (error) {
933
+ if (!isNodeError(error) || error.code !== 'EEXIST') {
934
+ onDebugMessage(
935
+ `Failed to create checkpoint directory: ${getErrorMessage(error)}`,
936
+ );
937
+ return;
938
+ }
939
+ }
940
+
941
+ for (const toolCall of restorableToolCalls) {
942
+ const filePath = toolCall.request.args['file_path'] as string;
943
+ if (!filePath) {
944
+ onDebugMessage(
945
+ `Skipping restorable tool call due to missing file_path: ${toolCall.request.name}`,
946
+ );
947
+ continue;
948
+ }
949
+
950
+ try {
951
+ let commitHash = await gitService?.createFileSnapshot(
952
+ `Snapshot for ${toolCall.request.name}`,
953
+ );
954
+
955
+ if (!commitHash) {
956
+ commitHash = await gitService?.getCurrentCommitHash();
957
+ }
958
+
959
+ if (!commitHash) {
960
+ onDebugMessage(
961
+ `Failed to create snapshot for ${filePath}. Skipping restorable tool call.`,
962
+ );
963
+ continue;
964
+ }
965
+
966
+ const timestamp = new Date()
967
+ .toISOString()
968
+ .replace(/:/g, '-')
969
+ .replace(/\./g, '_');
970
+ const toolName = toolCall.request.name;
971
+ const fileName = path.basename(filePath);
972
+ const toolCallWithSnapshotFileName = `${timestamp}-${fileName}-${toolName}.json`;
973
+ const clientHistory = await geminiClient?.getHistory();
974
+ const toolCallWithSnapshotFilePath = path.join(
975
+ checkpointDir,
976
+ toolCallWithSnapshotFileName,
977
+ );
978
+
979
+ await fs.writeFile(
980
+ toolCallWithSnapshotFilePath,
981
+ JSON.stringify(
982
+ {
983
+ history,
984
+ clientHistory,
985
+ toolCall: {
986
+ name: toolCall.request.name,
987
+ args: toolCall.request.args,
988
+ },
989
+ commitHash,
990
+ filePath,
991
+ },
992
+ null,
993
+ 2,
994
+ ),
995
+ );
996
+ } catch (error) {
997
+ onDebugMessage(
998
+ `Failed to write restorable tool call file: ${getErrorMessage(
999
+ error,
1000
+ )}`,
1001
+ );
1002
+ }
1003
+ }
1004
+ }
1005
+ };
1006
+ saveRestorableToolCalls();
1007
+ }, [toolCalls, config, onDebugMessage, gitService, history, geminiClient]);
1008
+
1009
+ return {
1010
+ streamingState,
1011
+ submitQuery,
1012
+ initError,
1013
+ pendingHistoryItems,
1014
+ thought,
1015
+ cancelOngoingRequest,
1016
+ };
1017
+ };