@sunub/obsidian-mcp-server 0.3.4 → 0.3.9

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 (323) hide show
  1. package/build/config.d.ts +2 -27
  2. package/build/config.js +11 -31
  3. package/build/config.js.map +1 -1
  4. package/build/db.js +3 -0
  5. package/build/db.js.map +1 -1
  6. package/build/index.js +28 -6
  7. package/build/index.js.map +1 -1
  8. package/build/server.js +16 -13
  9. package/build/server.js.map +1 -1
  10. package/build/setup.d.ts +4 -0
  11. package/build/setup.js +114 -0
  12. package/build/setup.js.map +1 -0
  13. package/build/tools/create_document_with_properties/index.d.ts +5 -5
  14. package/build/tools/create_document_with_properties/index.js +2 -2
  15. package/build/tools/create_document_with_properties/index.js.map +1 -1
  16. package/build/tools/create_document_with_properties/params.d.ts +8 -8
  17. package/build/tools/create_document_with_properties/params.js +1 -1
  18. package/build/tools/create_document_with_properties/params.js.map +1 -1
  19. package/build/tools/generate_property/index.d.ts +1 -1
  20. package/build/tools/generate_property/index.js +1 -1
  21. package/build/tools/generate_property/index.js.map +1 -1
  22. package/build/tools/generate_property/params.d.ts +4 -4
  23. package/build/tools/index.d.ts +8 -8
  24. package/build/tools/index.js +5 -5
  25. package/build/tools/index.js.map +1 -1
  26. package/build/tools/organize_attachments/index.d.ts +1 -1
  27. package/build/tools/organize_attachments/index.js +2 -2
  28. package/build/tools/organize_attachments/index.js.map +1 -1
  29. package/build/tools/vault/index.d.ts +2 -2
  30. package/build/tools/vault/index.js +7 -8
  31. package/build/tools/vault/index.js.map +1 -1
  32. package/build/tools/vault/params.d.ts +4 -4
  33. package/build/tools/vault/types/collect_context.d.ts +58 -58
  34. package/build/tools/vault/types/collect_context.js +1 -1
  35. package/build/tools/vault/types/collect_context.js.map +1 -1
  36. package/build/tools/vault/types/list_all.d.ts +42 -42
  37. package/build/tools/vault/types/list_all.js +2 -2
  38. package/build/tools/vault/types/list_all.js.map +1 -1
  39. package/build/tools/vault/types/read_specific.d.ts +6 -6
  40. package/build/tools/vault/types/read_specific.js +1 -1
  41. package/build/tools/vault/types/read_specific.js.map +1 -1
  42. package/build/tools/vault/types/search.d.ts +70 -70
  43. package/build/tools/vault/utils/actions/collect_context.d.ts +1 -1
  44. package/build/tools/vault/utils/actions/collect_context.js +4 -4
  45. package/build/tools/vault/utils/actions/collect_context.js.map +1 -1
  46. package/build/tools/vault/utils/actions/list_all.d.ts +1 -1
  47. package/build/tools/vault/utils/actions/list_all.js +1 -1
  48. package/build/tools/vault/utils/actions/list_all.js.map +1 -1
  49. package/build/tools/vault/utils/actions/load_memory.d.ts +1 -1
  50. package/build/tools/vault/utils/actions/load_memory.js +3 -3
  51. package/build/tools/vault/utils/actions/load_memory.js.map +1 -1
  52. package/build/tools/vault/utils/actions/read.d.ts +1 -1
  53. package/build/tools/vault/utils/actions/read.js +1 -1
  54. package/build/tools/vault/utils/actions/read.js.map +1 -1
  55. package/build/tools/vault/utils/actions/search.d.ts +1 -1
  56. package/build/tools/vault/utils/actions/search.js +25 -15
  57. package/build/tools/vault/utils/actions/search.js.map +1 -1
  58. package/build/tools/vault/utils/shared.d.ts +1 -1
  59. package/build/tools/vault/utils.d.ts +7 -8
  60. package/build/tools/vault/utils.js +7 -8
  61. package/build/tools/vault/utils.js.map +1 -1
  62. package/build/tools/write_property/index.d.ts +5 -5
  63. package/build/tools/write_property/index.js +1 -1
  64. package/build/tools/write_property/index.js.map +1 -1
  65. package/build/tools/write_property/params.d.ts +8 -8
  66. package/build/utils/DirectoryWalker.d.ts +1 -1
  67. package/build/utils/Embedder.d.ts +11 -0
  68. package/build/utils/Embedder.js +46 -0
  69. package/build/utils/Embedder.js.map +1 -0
  70. package/build/utils/Indexer.d.ts +4 -2
  71. package/build/utils/Indexer.js +19 -2
  72. package/build/utils/Indexer.js.map +1 -1
  73. package/build/utils/LLMClient.js +16 -0
  74. package/build/utils/LLMClient.js.map +1 -1
  75. package/build/utils/LocalReranker.d.ts +17 -0
  76. package/build/utils/LocalReranker.js +71 -0
  77. package/build/utils/LocalReranker.js.map +1 -0
  78. package/build/utils/RAGIndexer.d.ts +10 -1
  79. package/build/utils/RAGIndexer.js +59 -27
  80. package/build/utils/RAGIndexer.js.map +1 -1
  81. package/build/utils/RerankerClient.d.ts +6 -6
  82. package/build/utils/RerankerClient.js +20 -1
  83. package/build/utils/RerankerClient.js.map +1 -1
  84. package/build/utils/VaultManger/VaultManager.d.ts +38 -2
  85. package/build/utils/VaultManger/VaultManager.js +268 -13
  86. package/build/utils/VaultManger/VaultManager.js.map +1 -1
  87. package/build/utils/VaultManger/index.d.ts +3 -3
  88. package/build/utils/VaultManger/index.js +2 -2
  89. package/build/utils/VaultManger/index.js.map +1 -1
  90. package/build/utils/VaultManger/types.d.ts +1 -1
  91. package/build/utils/VaultWatcher.d.ts +1 -1
  92. package/build/utils/VaultWatcher.js +12 -55
  93. package/build/utils/VaultWatcher.js.map +1 -1
  94. package/build/utils/VectorDB.js +31 -6
  95. package/build/utils/VectorDB.js.map +1 -1
  96. package/build/utils/constants.d.ts +3 -0
  97. package/build/utils/constants.js +14 -0
  98. package/build/utils/constants.js.map +1 -0
  99. package/build/utils/getVaultManager.d.ts +1 -1
  100. package/build/utils/getVaultManager.js +1 -1
  101. package/build/utils/getVaultManager.js.map +1 -1
  102. package/build/utils/processor/MatterParser.d.ts +1 -1
  103. package/build/utils/processor/MatterParser.js +1 -1
  104. package/build/utils/processor/MatterParser.js.map +1 -1
  105. package/build/utils/processor/types.d.ts +4 -0
  106. package/build/utils/processor/types.js +1 -0
  107. package/build/utils/processor/types.js.map +1 -1
  108. package/package.json +16 -49
  109. package/LICENSE +0 -15
  110. package/README.md +0 -512
  111. package/build/cli/AppContainer.d.ts +0 -1
  112. package/build/cli/AppContainer.js +0 -197
  113. package/build/cli/AppContainer.js.map +0 -1
  114. package/build/cli/app.d.ts +0 -1
  115. package/build/cli/app.js +0 -7
  116. package/build/cli/app.js.map +0 -1
  117. package/build/cli/colors.d.ts +0 -15
  118. package/build/cli/colors.js +0 -15
  119. package/build/cli/colors.js.map +0 -1
  120. package/build/cli/commands/types.d.ts +0 -19
  121. package/build/cli/commands/types.js +0 -11
  122. package/build/cli/commands/types.js.map +0 -1
  123. package/build/cli/config/SettingContext.d.ts +0 -1
  124. package/build/cli/config/SettingContext.js +0 -2
  125. package/build/cli/config/SettingContext.js.map +0 -1
  126. package/build/cli/config/mcpServersConfig.d.ts +0 -30
  127. package/build/cli/config/mcpServersConfig.js +0 -119
  128. package/build/cli/config/mcpServersConfig.js.map +0 -1
  129. package/build/cli/config/settingSchema.d.ts +0 -180
  130. package/build/cli/config/settingSchema.js +0 -128
  131. package/build/cli/config/settingSchema.js.map +0 -1
  132. package/build/cli/constants.d.ts +0 -47
  133. package/build/cli/constants.js +0 -101
  134. package/build/cli/constants.js.map +0 -1
  135. package/build/cli/context/InputContext.d.ts +0 -12
  136. package/build/cli/context/InputContext.js +0 -10
  137. package/build/cli/context/InputContext.js.map +0 -1
  138. package/build/cli/context/KeypressContext.d.ts +0 -30
  139. package/build/cli/context/KeypressContext.js +0 -200
  140. package/build/cli/context/KeypressContext.js.map +0 -1
  141. package/build/cli/context/KeypressContext.util.d.ts +0 -7
  142. package/build/cli/context/KeypressContext.util.js +0 -474
  143. package/build/cli/context/KeypressContext.util.js.map +0 -1
  144. package/build/cli/hooks/useAtCompletion.d.ts +0 -12
  145. package/build/cli/hooks/useAtCompletion.js +0 -5
  146. package/build/cli/hooks/useAtCompletion.js.map +0 -1
  147. package/build/cli/hooks/useCommandCompletion.d.ts +0 -59
  148. package/build/cli/hooks/useCommandCompletion.js +0 -344
  149. package/build/cli/hooks/useCommandCompletion.js.map +0 -1
  150. package/build/cli/hooks/useCompletion.d.ts +0 -22
  151. package/build/cli/hooks/useCompletion.js +0 -84
  152. package/build/cli/hooks/useCompletion.js.map +0 -1
  153. package/build/cli/hooks/useDispatcher.d.ts +0 -6
  154. package/build/cli/hooks/useDispatcher.js +0 -286
  155. package/build/cli/hooks/useDispatcher.js.map +0 -1
  156. package/build/cli/hooks/useFocus.d.ts +0 -2
  157. package/build/cli/hooks/useFocus.js +0 -3
  158. package/build/cli/hooks/useFocus.js.map +0 -1
  159. package/build/cli/hooks/useInputHistory.d.ts +0 -24
  160. package/build/cli/hooks/useInputHistory.js +0 -159
  161. package/build/cli/hooks/useInputHistory.js.map +0 -1
  162. package/build/cli/hooks/useKeyMatchers.d.ts +0 -17
  163. package/build/cli/hooks/useKeyMatchers.js +0 -13
  164. package/build/cli/hooks/useKeyMatchers.js.map +0 -1
  165. package/build/cli/hooks/useKeypress.d.ts +0 -19
  166. package/build/cli/hooks/useKeypress.js +0 -28
  167. package/build/cli/hooks/useKeypress.js.map +0 -1
  168. package/build/cli/hooks/useLlmStream.d.ts +0 -12
  169. package/build/cli/hooks/useLlmStream.js +0 -365
  170. package/build/cli/hooks/useLlmStream.js.map +0 -1
  171. package/build/cli/hooks/useMcpClient.d.ts +0 -10
  172. package/build/cli/hooks/useMcpClient.js +0 -100
  173. package/build/cli/hooks/useMcpClient.js.map +0 -1
  174. package/build/cli/hooks/useMcpManager.d.ts +0 -22
  175. package/build/cli/hooks/useMcpManager.js +0 -86
  176. package/build/cli/hooks/useMcpManager.js.map +0 -1
  177. package/build/cli/hooks/usePromptCompletion.d.ts +0 -19
  178. package/build/cli/hooks/usePromptCompletion.js +0 -13
  179. package/build/cli/hooks/usePromptCompletion.js.map +0 -1
  180. package/build/cli/hooks/useRagContext.d.ts +0 -6
  181. package/build/cli/hooks/useRagContext.js +0 -83
  182. package/build/cli/hooks/useRagContext.js.map +0 -1
  183. package/build/cli/hooks/useShellCompletion.d.ts +0 -18
  184. package/build/cli/hooks/useShellCompletion.js +0 -10
  185. package/build/cli/hooks/useShellCompletion.js.map +0 -1
  186. package/build/cli/hooks/useSlashCompletion.d.ts +0 -21
  187. package/build/cli/hooks/useSlashCompletion.js +0 -11
  188. package/build/cli/hooks/useSlashCompletion.js.map +0 -1
  189. package/build/cli/hooks/useTerminalSize.d.ts +0 -9
  190. package/build/cli/hooks/useTerminalSize.js +0 -26
  191. package/build/cli/hooks/useTerminalSize.js.map +0 -1
  192. package/build/cli/index.d.ts +0 -1
  193. package/build/cli/index.js +0 -39
  194. package/build/cli/index.js.map +0 -1
  195. package/build/cli/key/input.d.ts +0 -5
  196. package/build/cli/key/input.js +0 -9
  197. package/build/cli/key/input.js.map +0 -1
  198. package/build/cli/key/keyBindings.d.ts +0 -152
  199. package/build/cli/key/keyBindings.js +0 -630
  200. package/build/cli/key/keyBindings.js.map +0 -1
  201. package/build/cli/key/keyMatchers.d.ts +0 -27
  202. package/build/cli/key/keyMatchers.js +0 -33
  203. package/build/cli/key/keyMatchers.js.map +0 -1
  204. package/build/cli/key/mouse.d.ts +0 -23
  205. package/build/cli/key/mouse.js +0 -155
  206. package/build/cli/key/mouse.js.map +0 -1
  207. package/build/cli/key/text-buffer-bestcase.d.ts +0 -867
  208. package/build/cli/key/text-buffer-bestcase.js +0 -2754
  209. package/build/cli/key/text-buffer-bestcase.js.map +0 -1
  210. package/build/cli/key/text-buffer.d.ts +0 -509
  211. package/build/cli/key/text-buffer.js +0 -918
  212. package/build/cli/key/text-buffer.js.map +0 -1
  213. package/build/cli/main.d.ts +0 -1
  214. package/build/cli/main.js +0 -20
  215. package/build/cli/main.js.map +0 -1
  216. package/build/cli/reference/HistoryItemDisplay.d.ts +0 -23
  217. package/build/cli/reference/HistoryItemDisplay.js +0 -63
  218. package/build/cli/reference/HistoryItemDisplay.js.map +0 -1
  219. package/build/cli/reference/InputContext.d.ts +0 -17
  220. package/build/cli/reference/InputContext.js +0 -16
  221. package/build/cli/reference/InputContext.js.map +0 -1
  222. package/build/cli/reference/KerypressContext.d.ts +0 -37
  223. package/build/cli/reference/KerypressContext.js +0 -791
  224. package/build/cli/reference/KerypressContext.js.map +0 -1
  225. package/build/cli/reference/MainContent.d.ts +0 -6
  226. package/build/cli/reference/MainContent.js +0 -207
  227. package/build/cli/reference/MainContent.js.map +0 -1
  228. package/build/cli/reference/UIStateContext.d.ts +0 -188
  229. package/build/cli/reference/UIStateContext.js +0 -16
  230. package/build/cli/reference/UIStateContext.js.map +0 -1
  231. package/build/cli/reference/text-buffer.d.ts +0 -867
  232. package/build/cli/reference/text-buffer.js +0 -2754
  233. package/build/cli/reference/text-buffer.js.map +0 -1
  234. package/build/cli/reference/useGeminiStream.d.ts +0 -42
  235. package/build/cli/reference/useGeminiStream.js +0 -1404
  236. package/build/cli/reference/useGeminiStream.js.map +0 -1
  237. package/build/cli/services/McpClientService.d.ts +0 -32
  238. package/build/cli/services/McpClientService.js +0 -123
  239. package/build/cli/services/McpClientService.js.map +0 -1
  240. package/build/cli/services/McpManager.d.ts +0 -28
  241. package/build/cli/services/McpManager.js +0 -146
  242. package/build/cli/services/McpManager.js.map +0 -1
  243. package/build/cli/theme/builtin/default-dark.d.ts +0 -7
  244. package/build/cli/theme/builtin/default-dark.js +0 -143
  245. package/build/cli/theme/builtin/default-dark.js.map +0 -1
  246. package/build/cli/theme/builtin/no-color.d.ts +0 -7
  247. package/build/cli/theme/builtin/no-color.js +0 -125
  248. package/build/cli/theme/builtin/no-color.js.map +0 -1
  249. package/build/cli/theme/builtin/tokyonight-dark.d.ts +0 -7
  250. package/build/cli/theme/builtin/tokyonight-dark.js +0 -146
  251. package/build/cli/theme/builtin/tokyonight-dark.js.map +0 -1
  252. package/build/cli/theme/semantic-colors.d.ts +0 -2
  253. package/build/cli/theme/semantic-colors.js +0 -19
  254. package/build/cli/theme/semantic-colors.js.map +0 -1
  255. package/build/cli/theme/semantic-tokens.d.ts +0 -42
  256. package/build/cli/theme/semantic-tokens.js +0 -77
  257. package/build/cli/theme/semantic-tokens.js.map +0 -1
  258. package/build/cli/theme/theme-manager.d.ts +0 -32
  259. package/build/cli/theme/theme-manager.js +0 -150
  260. package/build/cli/theme/theme-manager.js.map +0 -1
  261. package/build/cli/theme/theme.d.ts +0 -107
  262. package/build/cli/theme/theme.js +0 -333
  263. package/build/cli/theme/theme.js.map +0 -1
  264. package/build/cli/tmp.d.ts +0 -1
  265. package/build/cli/tmp.js +0 -27
  266. package/build/cli/tmp.js.map +0 -1
  267. package/build/cli/types.d.ts +0 -72
  268. package/build/cli/types.js +0 -8
  269. package/build/cli/types.js.map +0 -1
  270. package/build/cli/ui/ConnectionStatus.d.ts +0 -13
  271. package/build/cli/ui/ConnectionStatus.js +0 -24
  272. package/build/cli/ui/ConnectionStatus.js.map +0 -1
  273. package/build/cli/ui/HelpCommands.d.ts +0 -5
  274. package/build/cli/ui/HelpCommands.js +0 -11
  275. package/build/cli/ui/HelpCommands.js.map +0 -1
  276. package/build/cli/ui/HistoryItemDisplay.d.ts +0 -9
  277. package/build/cli/ui/HistoryItemDisplay.js +0 -38
  278. package/build/cli/ui/HistoryItemDisplay.js.map +0 -1
  279. package/build/cli/ui/InputPrompt-bestcase.d.ts +0 -63
  280. package/build/cli/ui/InputPrompt-bestcase.js +0 -1301
  281. package/build/cli/ui/InputPrompt-bestcase.js.map +0 -1
  282. package/build/cli/ui/InputPrompt.d.ts +0 -12
  283. package/build/cli/ui/InputPrompt.js +0 -145
  284. package/build/cli/ui/InputPrompt.js.map +0 -1
  285. package/build/cli/ui/InputPrompt.tmp.d.ts +0 -7
  286. package/build/cli/ui/InputPrompt.tmp.js +0 -215
  287. package/build/cli/ui/InputPrompt.tmp.js.map +0 -1
  288. package/build/cli/ui/MainContent.d.ts +0 -10
  289. package/build/cli/ui/MainContent.js +0 -24
  290. package/build/cli/ui/MainContent.js.map +0 -1
  291. package/build/cli/ui/MouseContext.d.ts +0 -1
  292. package/build/cli/ui/MouseContext.js +0 -2
  293. package/build/cli/ui/MouseContext.js.map +0 -1
  294. package/build/cli/ui/SuggestionsDisplay.d.ts +0 -25
  295. package/build/cli/ui/SuggestionsDisplay.js +0 -45
  296. package/build/cli/ui/SuggestionsDisplay.js.map +0 -1
  297. package/build/cli/ui/ThinkingIndicator.d.ts +0 -2
  298. package/build/cli/ui/ThinkingIndicator.js +0 -16
  299. package/build/cli/ui/ThinkingIndicator.js.map +0 -1
  300. package/build/cli/ui/layout/DefaultAppLayout.d.ts +0 -2
  301. package/build/cli/ui/layout/DefaultAppLayout.js +0 -6
  302. package/build/cli/ui/layout/DefaultAppLayout.js.map +0 -1
  303. package/build/cli/ui/shared/ExpandableText.d.ts +0 -14
  304. package/build/cli/ui/shared/ExpandableText.js +0 -19
  305. package/build/cli/ui/shared/ExpandableText.js.map +0 -1
  306. package/build/cli/utils/cache.d.ts +0 -21
  307. package/build/cli/utils/cache.js +0 -56
  308. package/build/cli/utils/cache.js.map +0 -1
  309. package/build/cli/utils/commandUtils.d.ts +0 -11
  310. package/build/cli/utils/commandUtils.js +0 -16
  311. package/build/cli/utils/commandUtils.js.map +0 -1
  312. package/build/cli/utils/debugLogger.d.ts +0 -12
  313. package/build/cli/utils/debugLogger.js +0 -54
  314. package/build/cli/utils/debugLogger.js.map +0 -1
  315. package/build/cli/utils/historyStorage.d.ts +0 -13
  316. package/build/cli/utils/historyStorage.js +0 -64
  317. package/build/cli/utils/historyStorage.js.map +0 -1
  318. package/build/cli/utils/textUtil.d.ts +0 -68
  319. package/build/cli/utils/textUtil.js +0 -236
  320. package/build/cli/utils/textUtil.js.map +0 -1
  321. package/build/tools/vault/utils/actions/search_semantic.d.ts +0 -3
  322. package/build/tools/vault/utils/actions/search_semantic.js +0 -71
  323. package/build/tools/vault/utils/actions/search_semantic.js.map +0 -1
@@ -1,1404 +0,0 @@
1
- // @ts-nocheck
2
- /**
3
- * @license
4
- * Copyright 2025 Google LLC
5
- * SPDX-License-Identifier: Apache-2.0
6
- */
7
- import { promises as fs } from "node:fs";
8
- import path from "node:path";
9
- import { ACTIVATE_SKILL_TOOL_NAME, ApprovalMode, buildToolVisibilityContext, buildUserSteeringHintPrompt, ConversationFinishedEvent, CoreEvent, CoreToolCallStatus, coreEvents, DEFAULT_GEMINI_FLASH_MODEL, debugLogger, EDIT_TOOL_NAMES, GeminiCliOperation, GitService, getErrorMessage, getPlanModeExitMessage, isBackgroundExecutionData, isNodeError, isRenderedInHistory, Kind, logConversationFinishedEvent, logUserPrompt, MessageBusType, MessageSenderType, parseAndFormatApiError, processRestorableToolCalls, promptIdContext, recordToolCallInteractions, runInDevTraceSpan, GeminiEventType as ServerGeminiEventType, ToolConfirmationOutcome, ToolErrorType, tokenLimit, UnauthorizedError, UPDATE_TOPIC_DISPLAY_NAME, UPDATE_TOPIC_TOOL_NAME, UserPromptEvent, ValidationRequiredError, } from "@google/gemini-cli-core";
10
- import { FinishReason } from "@google/genai";
11
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
12
- import { isCompactTool } from "../components/messages/ToolGroupMessage.js";
13
- import { SHELL_COMMAND_NAME } from "../constants.js";
14
- import { useSessionStats } from "../contexts/SessionContext.js";
15
- import { theme } from "../semantic-colors.js";
16
- import { MessageType, mapCoreStatusToDisplayStatus, StreamingState, ToolCallStatus, } from "../types.js";
17
- import { getToolGroupBorderAppearance } from "../utils/borderStyles.js";
18
- import { isAtCommand, isSlashCommand } from "../utils/commandUtils.js";
19
- import { getInlineThinkingMode } from "../utils/inlineThinkingMode.js";
20
- import { findLastSafeSplitPoint } from "../utils/markdownUtilities.js";
21
- import { handleAtCommand } from "./atCommandProcessor.js";
22
- import { mapToDisplay as mapTrackedToolCallsToDisplay } from "./toolMapping.js";
23
- import { useExecutionLifecycle } from "./useExecutionLifecycle.js";
24
- import { useKeypress } from "./useKeypress.js";
25
- import { useLogger } from "./useLogger.js";
26
- import { useStateAndRef } from "./useStateAndRef.js";
27
- import { useToolScheduler, } from "./useToolScheduler.js";
28
- const isTopicTool = (name) => name === UPDATE_TOPIC_TOOL_NAME || name === UPDATE_TOPIC_DISPLAY_NAME;
29
- var StreamProcessingStatus;
30
- (function (StreamProcessingStatus) {
31
- StreamProcessingStatus[StreamProcessingStatus["Completed"] = 0] = "Completed";
32
- StreamProcessingStatus[StreamProcessingStatus["UserCancelled"] = 1] = "UserCancelled";
33
- StreamProcessingStatus[StreamProcessingStatus["Error"] = 2] = "Error";
34
- })(StreamProcessingStatus || (StreamProcessingStatus = {}));
35
- const SUPPRESSED_TOOL_ERRORS_NOTE = 'Some internal tool attempts failed before this final error. Press F12 for diagnostics, or run /settings and change "Error Verbosity" to full for details.';
36
- const LOW_VERBOSITY_FAILURE_NOTE = 'This request failed. Press F12 for diagnostics, or run /settings and change "Error Verbosity" to full for full details.';
37
- function getBackgroundedToolInfo(toolCall) {
38
- const response = toolCall.response;
39
- const rawData = response?.data;
40
- if (!isBackgroundExecutionData(rawData)) {
41
- return undefined;
42
- }
43
- if (rawData.pid === undefined) {
44
- return undefined;
45
- }
46
- return {
47
- pid: rawData.pid,
48
- command: rawData.command ?? toolCall.request.name,
49
- initialOutput: rawData.initialOutput ?? "",
50
- };
51
- }
52
- function isBackgroundableExecutingToolCall(toolCall) {
53
- return (toolCall.status === CoreToolCallStatus.Executing &&
54
- typeof toolCall.pid === "number");
55
- }
56
- function showCitations(settings) {
57
- const enabled = settings.merged.ui.showCitations;
58
- if (enabled !== undefined) {
59
- return enabled;
60
- }
61
- return true;
62
- }
63
- /**
64
- * Calculates the current streaming state based on tool call status and responding flag.
65
- */
66
- function calculateStreamingState(isResponding, toolCalls) {
67
- if (toolCalls.some((tc) => tc.status === CoreToolCallStatus.AwaitingApproval)) {
68
- return StreamingState.WaitingForConfirmation;
69
- }
70
- const isAnyToolActive = toolCalls.some((tc) => {
71
- // These statuses indicate active processing
72
- if (tc.status === CoreToolCallStatus.Executing ||
73
- tc.status === CoreToolCallStatus.Scheduled ||
74
- tc.status === CoreToolCallStatus.Validating) {
75
- return true;
76
- }
77
- // Terminal statuses (success, error, cancelled) still count as "Responding"
78
- // if the result hasn't been submitted back to Gemini yet.
79
- if (tc.status === CoreToolCallStatus.Success ||
80
- tc.status === CoreToolCallStatus.Error ||
81
- tc.status === CoreToolCallStatus.Cancelled) {
82
- return !tc
83
- .responseSubmittedToGemini;
84
- }
85
- return false;
86
- });
87
- if (isResponding || isAnyToolActive) {
88
- return StreamingState.Responding;
89
- }
90
- return StreamingState.Idle;
91
- }
92
- /**
93
- * Manages the Gemini stream, including user input, command processing,
94
- * API interaction, and tool call lifecycle.
95
- */
96
- export const useGeminiStream = (geminiClient, history, addItem, config, settings, onDebugMessage, handleSlashCommand, shellModeActive, getPreferredEditor, onAuthError, performMemoryRefresh, modelSwitchedFromQuotaError, setModelSwitchedFromQuotaError, onCancelSubmit, setShellInputFocused, terminalWidth, terminalHeight, isShellFocused, consumeUserHint) => {
97
- const [initError, setInitError] = useState(null);
98
- const [retryStatus, setRetryStatus] = useState(null);
99
- const isLowErrorVerbosity = settings.merged.ui?.errorVerbosity !== "full";
100
- const suppressedToolErrorCountRef = useRef(0);
101
- const suppressedToolErrorNoteShownRef = useRef(false);
102
- const lowVerbosityFailureNoteShownRef = useRef(false);
103
- const abortControllerRef = useRef(null);
104
- const turnCancelledRef = useRef(false);
105
- const activeQueryIdRef = useRef(null);
106
- const previousApprovalModeRef = useRef(config.getApprovalMode());
107
- const [isResponding, isRespondingRef, setIsResponding] = useStateAndRef(false);
108
- const [thought, thoughtRef, setThought] = useStateAndRef(null);
109
- const [pendingHistoryItem, pendingHistoryItemRef, setPendingHistoryItem] = useStateAndRef(null);
110
- const [lastGeminiActivityTime, setLastGeminiActivityTime] = useState(0);
111
- const [pushedToolCallIds, pushedToolCallIdsRef, setPushedToolCallIds] = useStateAndRef(new Set());
112
- const [_isFirstToolInGroup, isFirstToolInGroupRef, setIsFirstToolInGroup] = useStateAndRef(true);
113
- const processedMemoryToolsRef = useRef(new Set());
114
- const { startNewPrompt, getPromptCount } = useSessionStats();
115
- const logger = useLogger(config);
116
- const gitService = useMemo(() => {
117
- if (!config.getProjectRoot()) {
118
- return;
119
- }
120
- return new GitService(config.getProjectRoot(), config.storage);
121
- }, [config]);
122
- useEffect(() => {
123
- const handleRetryAttempt = (payload) => {
124
- if (turnCancelledRef.current || !isRespondingRef.current) {
125
- return;
126
- }
127
- setRetryStatus(payload);
128
- };
129
- coreEvents.on(CoreEvent.RetryAttempt, handleRetryAttempt);
130
- return () => {
131
- coreEvents.off(CoreEvent.RetryAttempt, handleRetryAttempt);
132
- };
133
- }, [isRespondingRef]);
134
- const [toolCalls, scheduleToolCalls, markToolsAsSubmitted, setToolCallsForDisplay, cancelAllToolCalls, lastToolOutputTime,] = useToolScheduler(async (completedToolCallsFromScheduler) => {
135
- // This onComplete is called when ALL scheduled tools for a given batch are done.
136
- if (completedToolCallsFromScheduler.length > 0) {
137
- // Add only the tools that haven't been pushed to history yet.
138
- const toolsToPush = completedToolCallsFromScheduler.filter((tc) => !pushedToolCallIdsRef.current.has(tc.request.callId));
139
- if (toolsToPush.length > 0) {
140
- const isCompactModeEnabled = settings.merged.ui?.compactToolOutput === true;
141
- const firstToolToPush = toolsToPush[0];
142
- const tcIndex = toolCalls.indexOf(firstToolToPush);
143
- const prevTool = tcIndex > 0 ? toolCalls[tcIndex - 1] : null;
144
- let borderTop = isFirstToolInGroupRef.current;
145
- if (!borderTop && prevTool) {
146
- // If the first tool in this push is non-compact but follows a compact tool,
147
- // we must start a new border group.
148
- const currentIsCompact = isCompactTool(mapTrackedToolCallsToDisplay(firstToolToPush).tools[0], isCompactModeEnabled);
149
- const prevWasCompact = isCompactTool(mapTrackedToolCallsToDisplay(prevTool).tools[0], isCompactModeEnabled);
150
- if (!currentIsCompact && prevWasCompact) {
151
- borderTop = true;
152
- }
153
- }
154
- addItem(mapTrackedToolCallsToDisplay(toolsToPush, {
155
- borderTop,
156
- borderBottom: true,
157
- borderColor: theme.border.default,
158
- borderDimColor: false,
159
- }));
160
- }
161
- // Clear the live-updating display now that the final state is in history.
162
- setToolCallsForDisplay([]);
163
- // Record tool calls with full metadata before sending responses.
164
- try {
165
- const currentModel = config.getGeminiClient().getCurrentSequenceModel() ??
166
- config.getModel();
167
- config
168
- .getGeminiClient()
169
- .getChat()
170
- .recordCompletedToolCalls(currentModel, completedToolCallsFromScheduler);
171
- await recordToolCallInteractions(config, completedToolCallsFromScheduler);
172
- }
173
- catch (error) {
174
- debugLogger.warn(`Error recording completed tool call information: ${error}`);
175
- }
176
- // Handle tool response submission immediately when tools complete
177
- await handleCompletedTools(completedToolCallsFromScheduler);
178
- }
179
- }, config, getPreferredEditor);
180
- const activeBackgroundExecutionId = useMemo(() => {
181
- const executingBackgroundableTool = toolCalls.find(isBackgroundableExecutingToolCall);
182
- return executingBackgroundableTool?.pid;
183
- }, [toolCalls]);
184
- const onExec = useCallback(async (done) => {
185
- setIsResponding(true);
186
- await done;
187
- setIsResponding(false);
188
- }, [setIsResponding]);
189
- const { handleShellCommand, activeShellPtyId, lastShellOutputTime, backgroundTaskCount, isBackgroundTaskVisible, toggleBackgroundTasks, backgroundCurrentExecution, registerBackgroundTask, dismissBackgroundTask, backgroundTasks, } = useExecutionLifecycle(addItem, setPendingHistoryItem, onExec, onDebugMessage, config, geminiClient, setShellInputFocused, terminalWidth, terminalHeight, activeBackgroundExecutionId);
190
- const streamingState = useMemo(() => calculateStreamingState(isResponding, toolCalls), [isResponding, toolCalls]);
191
- // Reset tracking when a new batch of tools starts
192
- useEffect(() => {
193
- if (toolCalls.length > 0) {
194
- const isNewBatch = !toolCalls.some((tc) => pushedToolCallIdsRef.current.has(tc.request.callId));
195
- if (isNewBatch) {
196
- setPushedToolCallIds(new Set());
197
- setIsFirstToolInGroup(true);
198
- }
199
- }
200
- else if (streamingState === StreamingState.Idle) {
201
- // Clear when idle to be ready for next turn
202
- setPushedToolCallIds(new Set());
203
- setIsFirstToolInGroup(true);
204
- }
205
- }, [
206
- toolCalls,
207
- pushedToolCallIdsRef,
208
- setPushedToolCallIds,
209
- setIsFirstToolInGroup,
210
- streamingState,
211
- ]);
212
- // Push completed tools to history as they finish
213
- useEffect(() => {
214
- const toolsToPush = [];
215
- for (let i = 0; i < toolCalls.length; i++) {
216
- const tc = toolCalls[i];
217
- if (pushedToolCallIdsRef.current.has(tc.request.callId))
218
- continue;
219
- if (tc.status === "success" ||
220
- tc.status === "error" ||
221
- tc.status === "cancelled") {
222
- // TODO(#22883): This lookahead logic is a tactical UI fix to prevent parallel agents
223
- // from tearing visually when they finish at slightly different times.
224
- // Architecturally, `useGeminiStream` should not be responsible for stitching
225
- // together semantic batches using timing/refs. `packages/core` should be
226
- // refactored to emit structured `ToolBatch` or `Turn` objects, and this layer
227
- // should simply render those semantic boundaries.
228
- // If this is an agent tool, look ahead to ensure all subsequent
229
- // contiguous agents in the same batch are also finished before pushing.
230
- const isAgent = tc.tool?.kind === Kind.Agent;
231
- if (isAgent) {
232
- let contigAgentsComplete = true;
233
- for (let j = i + 1; j < toolCalls.length; j++) {
234
- const nextTc = toolCalls[j];
235
- if (nextTc.tool?.kind === Kind.Agent) {
236
- if (nextTc.status !== "success" &&
237
- nextTc.status !== "error" &&
238
- nextTc.status !== "cancelled") {
239
- contigAgentsComplete = false;
240
- break;
241
- }
242
- }
243
- else {
244
- // End of the contiguous agent block
245
- break;
246
- }
247
- }
248
- if (!contigAgentsComplete) {
249
- // Wait for the entire contiguous block of agents to finish
250
- break;
251
- }
252
- }
253
- toolsToPush.push(tc);
254
- }
255
- else {
256
- // Stop at first non-terminal tool to preserve order
257
- break;
258
- }
259
- }
260
- if (toolsToPush.length > 0) {
261
- const newPushed = new Set(pushedToolCallIdsRef.current);
262
- const isFirstInThisPush = isFirstToolInGroupRef.current;
263
- const isCompactModeEnabled = settings.merged.ui?.compactToolOutput === true;
264
- const groups = [];
265
- let currentGroup = [];
266
- for (const tc of toolsToPush) {
267
- newPushed.add(tc.request.callId);
268
- if (tc.tool?.kind === Kind.Agent) {
269
- currentGroup.push(tc);
270
- }
271
- else {
272
- if (currentGroup.length > 0) {
273
- groups.push(currentGroup);
274
- currentGroup = [];
275
- }
276
- groups.push([tc]);
277
- }
278
- }
279
- if (currentGroup.length > 0) {
280
- groups.push(currentGroup);
281
- }
282
- for (let i = 0; i < groups.length; i++) {
283
- const group = groups[i];
284
- const isFirstInBatch = i === 0 && isFirstInThisPush;
285
- const lastTcInGroup = group[group.length - 1];
286
- const tcIndexInBatch = toolCalls.indexOf(lastTcInGroup);
287
- const isLastInBatch = tcIndexInBatch === toolCalls.length - 1;
288
- const nextTcInBatch = tcIndexInBatch < toolCalls.length - 1
289
- ? toolCalls[tcIndexInBatch + 1]
290
- : null;
291
- const prevTcInBatch = toolCalls.indexOf(group[0]) > 0
292
- ? toolCalls[toolCalls.indexOf(group[0]) - 1]
293
- : null;
294
- const historyItem = mapTrackedToolCallsToDisplay(group, {
295
- ...getToolGroupBorderAppearance({ type: "tool_group", tools: toolCalls }, activeShellPtyId, !!isShellFocused, [], backgroundTasks),
296
- });
297
- // Determine if this group starts with a compact tool
298
- const currentIsCompact = historyItem.tools.length === 1 &&
299
- isCompactTool(historyItem.tools[0], isCompactModeEnabled);
300
- let nextIsCompact = false;
301
- if (nextTcInBatch) {
302
- const nextHistoryItem = mapTrackedToolCallsToDisplay(nextTcInBatch);
303
- nextIsCompact =
304
- nextHistoryItem.tools.length === 1 &&
305
- isCompactTool(nextHistoryItem.tools[0], isCompactModeEnabled);
306
- }
307
- let prevWasCompact = false;
308
- if (prevTcInBatch) {
309
- const prevHistoryItem = mapTrackedToolCallsToDisplay(prevTcInBatch);
310
- prevWasCompact =
311
- prevHistoryItem.tools.length === 1 &&
312
- isCompactTool(prevHistoryItem.tools[0], isCompactModeEnabled);
313
- }
314
- historyItem.borderTop =
315
- isFirstInBatch || (!currentIsCompact && prevWasCompact);
316
- historyItem.borderBottom = currentIsCompact
317
- ? isLastInBatch && !nextIsCompact
318
- : isLastInBatch || nextIsCompact;
319
- addItem(historyItem);
320
- }
321
- setPushedToolCallIds(newPushed);
322
- // If this batch ONLY contains topics, and we were the first in the group,
323
- // the NEXT batch is still effectively the first VISIBLE bordered tool in the group.
324
- if (isFirstToolInGroupRef.current &&
325
- toolsToPush.every((tc) => isTopicTool(tc.request.name))) {
326
- // Keep it true!
327
- }
328
- else {
329
- setIsFirstToolInGroup(false);
330
- }
331
- }
332
- }, [
333
- toolCalls,
334
- pushedToolCallIdsRef,
335
- isFirstToolInGroupRef,
336
- setPushedToolCallIds,
337
- setIsFirstToolInGroup,
338
- addItem,
339
- activeShellPtyId,
340
- isShellFocused,
341
- backgroundTasks,
342
- settings.merged.ui?.compactToolOutput,
343
- ]);
344
- const pendingToolGroupItems = useMemo(() => {
345
- const remainingTools = toolCalls.filter((tc) => !pushedToolCallIds.has(tc.request.callId));
346
- const items = [];
347
- const appearance = getToolGroupBorderAppearance({ type: "tool_group", tools: toolCalls }, activeShellPtyId, !!isShellFocused, [], backgroundTasks);
348
- if (remainingTools.length > 0) {
349
- // Should we draw a top border? Yes if NO previous tools were drawn,
350
- // OR if ALL previously drawn tools were topics (which don't draw top borders).
351
- let needsTopBorder = pushedToolCallIds.size === 0;
352
- if (!needsTopBorder) {
353
- const allPushedWereTopics = toolCalls
354
- .filter((tc) => pushedToolCallIds.has(tc.request.callId))
355
- .every((tc) => isTopicTool(tc.request.name));
356
- if (allPushedWereTopics) {
357
- needsTopBorder = true;
358
- }
359
- }
360
- items.push(mapTrackedToolCallsToDisplay(remainingTools, {
361
- borderTop: needsTopBorder,
362
- borderBottom: false, // Stay open to connect with the slice below
363
- ...appearance,
364
- }));
365
- }
366
- // Always show a bottom border slice if we have ANY tools in the batch
367
- // and we haven't finished pushing the whole batch to history yet.
368
- // Once all tools are terminal and pushed, the last history item handles the closing border.
369
- const allTerminal = toolCalls.length > 0 &&
370
- toolCalls.every((tc) => tc.status === "success" ||
371
- tc.status === "error" ||
372
- tc.status === "cancelled");
373
- const allPushed = toolCalls.length > 0 &&
374
- toolCalls.every((tc) => pushedToolCallIds.has(tc.request.callId));
375
- const isToolVisible = (tc) => {
376
- // AskUser tools and Plan Mode write/edit are handled by this logic
377
- if (!isRenderedInHistory(buildToolVisibilityContext(tc))) {
378
- return false;
379
- }
380
- // ToolGroupMessage explicitly hides Confirming tools because they are
381
- // rendered in the interactive ToolConfirmationQueue instead.
382
- const displayStatus = mapCoreStatusToDisplayStatus(tc.status);
383
- if (displayStatus === ToolCallStatus.Confirming) {
384
- return false;
385
- }
386
- // ToolGroupMessage now shows all non-canceled tools, so they are visible
387
- // in pending and we need to draw the closing border for them.
388
- return true;
389
- };
390
- let lastVisibleIsCompact = false;
391
- const isCompactModeEnabled = settings.merged.ui?.compactToolOutput === true;
392
- for (let i = toolCalls.length - 1; i >= 0; i--) {
393
- if (isToolVisible(toolCalls[i])) {
394
- const mapped = mapTrackedToolCallsToDisplay(toolCalls[i]);
395
- lastVisibleIsCompact = mapped.tools[0]
396
- ? isCompactTool(mapped.tools[0], isCompactModeEnabled)
397
- : false;
398
- break;
399
- }
400
- }
401
- if (toolCalls.length > 0 &&
402
- !(allTerminal && allPushed) &&
403
- toolCalls.some(isToolVisible) &&
404
- !lastVisibleIsCompact) {
405
- items.push({
406
- type: "tool_group",
407
- tools: [],
408
- borderTop: false,
409
- borderBottom: true,
410
- ...appearance,
411
- });
412
- }
413
- return items;
414
- }, [
415
- toolCalls,
416
- pushedToolCallIds,
417
- activeShellPtyId,
418
- isShellFocused,
419
- backgroundTasks,
420
- settings.merged.ui?.compactToolOutput,
421
- ]);
422
- const lastQueryRef = useRef(null);
423
- const lastPromptIdRef = useRef(null);
424
- const loopDetectedRef = useRef(false);
425
- const [loopDetectionConfirmationRequest, setLoopDetectionConfirmationRequest,] = useState(null);
426
- const activePtyId = activeShellPtyId ?? activeBackgroundExecutionId ?? undefined;
427
- const prevActiveShellPtyIdRef = useRef(null);
428
- useEffect(() => {
429
- if (turnCancelledRef.current &&
430
- prevActiveShellPtyIdRef.current !== null &&
431
- activeShellPtyId === null) {
432
- addItem({ type: MessageType.INFO, text: "Request cancelled." });
433
- setIsResponding(false);
434
- }
435
- prevActiveShellPtyIdRef.current = activeShellPtyId;
436
- }, [activeShellPtyId, addItem, setIsResponding]);
437
- useEffect(() => {
438
- if (config.getApprovalMode() === ApprovalMode.YOLO &&
439
- streamingState === StreamingState.Idle) {
440
- const lastUserMessageIndex = history.findLastIndex((item) => item.type === MessageType.USER);
441
- const turnCount = lastUserMessageIndex === -1 ? 0 : history.length - lastUserMessageIndex;
442
- if (turnCount > 0) {
443
- logConversationFinishedEvent(config, new ConversationFinishedEvent(config.getApprovalMode(), turnCount));
444
- }
445
- }
446
- }, [streamingState, config, history]);
447
- useEffect(() => {
448
- if (!isResponding) {
449
- setRetryStatus(null);
450
- }
451
- }, [isResponding]);
452
- const maybeAddSuppressedToolErrorNote = useCallback((userMessageTimestamp) => {
453
- if (!isLowErrorVerbosity) {
454
- return;
455
- }
456
- if (suppressedToolErrorCountRef.current === 0) {
457
- return;
458
- }
459
- if (suppressedToolErrorNoteShownRef.current) {
460
- return;
461
- }
462
- addItem({
463
- type: MessageType.INFO,
464
- text: SUPPRESSED_TOOL_ERRORS_NOTE,
465
- }, userMessageTimestamp);
466
- suppressedToolErrorNoteShownRef.current = true;
467
- }, [addItem, isLowErrorVerbosity]);
468
- const maybeAddLowVerbosityFailureNote = useCallback((userMessageTimestamp) => {
469
- if (!isLowErrorVerbosity || config.getDebugMode()) {
470
- return;
471
- }
472
- if (lowVerbosityFailureNoteShownRef.current ||
473
- suppressedToolErrorNoteShownRef.current) {
474
- return;
475
- }
476
- addItem({
477
- type: MessageType.INFO,
478
- text: LOW_VERBOSITY_FAILURE_NOTE,
479
- }, userMessageTimestamp);
480
- lowVerbosityFailureNoteShownRef.current = true;
481
- }, [addItem, config, isLowErrorVerbosity]);
482
- const cancelOngoingRequest = useCallback(() => {
483
- if (streamingState !== StreamingState.Responding &&
484
- streamingState !== StreamingState.WaitingForConfirmation) {
485
- return;
486
- }
487
- if (turnCancelledRef.current) {
488
- return;
489
- }
490
- turnCancelledRef.current = true;
491
- setRetryStatus(null);
492
- // A full cancellation means no tools have produced a final result yet.
493
- // This determines if we show a generic "Request cancelled" message.
494
- const isFullCancellation = !toolCalls.some((tc) => tc.status === "success" || tc.status === "error");
495
- // Ensure we have an abort controller, creating one if it doesn't exist.
496
- if (!abortControllerRef.current) {
497
- abortControllerRef.current = new AbortController();
498
- }
499
- // The order is important here.
500
- // 1. Fire the signal to interrupt any active async operations.
501
- abortControllerRef.current.abort();
502
- // 2. Call the imperative cancel to clear the queue of pending tools.
503
- cancelAllToolCalls(abortControllerRef.current.signal);
504
- if (pendingHistoryItemRef.current) {
505
- const isShellCommand = pendingHistoryItemRef.current.type === "tool_group" &&
506
- pendingHistoryItemRef.current.tools.some((t) => t.name === SHELL_COMMAND_NAME);
507
- // If it is a shell command, we update the status to Canceled and clear the output
508
- // to avoid artifacts, then add it to history immediately.
509
- if (isShellCommand) {
510
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
511
- const toolGroup = pendingHistoryItemRef.current;
512
- const updatedTools = toolGroup.tools.map((tool) => {
513
- if (tool.name === SHELL_COMMAND_NAME) {
514
- return {
515
- ...tool,
516
- status: CoreToolCallStatus.Cancelled,
517
- resultDisplay: tool.resultDisplay,
518
- };
519
- }
520
- return tool;
521
- });
522
- addItem({ ...toolGroup, tools: updatedTools });
523
- }
524
- else {
525
- addItem(pendingHistoryItemRef.current);
526
- }
527
- }
528
- setPendingHistoryItem(null);
529
- // If it was a full cancellation, add the info message now.
530
- // Otherwise, we let handleCompletedTools figure out the next step,
531
- // which might involve sending partial results back to the model.
532
- if (isFullCancellation) {
533
- // If shell is active, we delay this message to ensure correct ordering
534
- // (Shell item first, then Info message).
535
- if (!activeShellPtyId) {
536
- addItem({
537
- type: MessageType.INFO,
538
- text: "Request cancelled.",
539
- });
540
- setIsResponding(false);
541
- }
542
- }
543
- onCancelSubmit(false);
544
- setShellInputFocused(false);
545
- }, [
546
- streamingState,
547
- addItem,
548
- setPendingHistoryItem,
549
- onCancelSubmit,
550
- pendingHistoryItemRef,
551
- setShellInputFocused,
552
- cancelAllToolCalls,
553
- toolCalls,
554
- activeShellPtyId,
555
- setIsResponding,
556
- ]);
557
- useKeypress((key) => {
558
- if (key.name === "escape" && !isShellFocused) {
559
- cancelOngoingRequest();
560
- }
561
- }, {
562
- isActive: streamingState === StreamingState.Responding ||
563
- streamingState === StreamingState.WaitingForConfirmation,
564
- });
565
- const prepareQueryForGemini = useCallback(async (query, userMessageTimestamp, abortSignal, prompt_id) => {
566
- if (turnCancelledRef.current) {
567
- return { queryToSend: null, shouldProceed: false };
568
- }
569
- if (typeof query === "string" && query.trim().length === 0) {
570
- return { queryToSend: null, shouldProceed: false };
571
- }
572
- let localQueryToSendToGemini = null;
573
- if (typeof query === "string") {
574
- const trimmedQuery = query.trim();
575
- await logger?.logMessage(MessageSenderType.USER, trimmedQuery);
576
- if (!shellModeActive) {
577
- // Handle UI-only commands first
578
- const slashCommandResult = isSlashCommand(trimmedQuery)
579
- ? await handleSlashCommand(trimmedQuery)
580
- : false;
581
- if (slashCommandResult) {
582
- switch (slashCommandResult.type) {
583
- case "schedule_tool": {
584
- const { toolName, toolArgs, postSubmitPrompt } = slashCommandResult;
585
- const toolCallRequest = {
586
- callId: `${toolName}-${Date.now()}-${Math.random().toString(16).slice(2)}`,
587
- name: toolName,
588
- args: toolArgs,
589
- isClientInitiated: true,
590
- prompt_id,
591
- };
592
- await scheduleToolCalls([toolCallRequest], abortSignal);
593
- if (postSubmitPrompt) {
594
- localQueryToSendToGemini = postSubmitPrompt;
595
- return {
596
- queryToSend: localQueryToSendToGemini,
597
- shouldProceed: true,
598
- };
599
- }
600
- return { queryToSend: null, shouldProceed: false };
601
- }
602
- case "submit_prompt": {
603
- localQueryToSendToGemini = slashCommandResult.content;
604
- return {
605
- queryToSend: localQueryToSendToGemini,
606
- shouldProceed: true,
607
- };
608
- }
609
- case "handled": {
610
- return { queryToSend: null, shouldProceed: false };
611
- }
612
- default: {
613
- const unreachable = slashCommandResult;
614
- throw new Error(`Unhandled slash command result type: ${unreachable}`);
615
- }
616
- }
617
- }
618
- }
619
- if (shellModeActive && handleShellCommand(trimmedQuery, abortSignal)) {
620
- return { queryToSend: null, shouldProceed: false };
621
- }
622
- // Handle @-commands (which might involve tool calls)
623
- if (isAtCommand(trimmedQuery)) {
624
- // Add user's turn before @ command processing for correct UI ordering.
625
- addItem({ type: MessageType.USER, text: trimmedQuery }, userMessageTimestamp);
626
- const atCommandResult = await handleAtCommand({
627
- query: trimmedQuery,
628
- config,
629
- addItem,
630
- onDebugMessage,
631
- messageId: userMessageTimestamp,
632
- signal: abortSignal,
633
- escapePastedAtSymbols: settings.merged.ui?.escapePastedAtSymbols,
634
- });
635
- if (atCommandResult.error) {
636
- onDebugMessage(atCommandResult.error);
637
- return { queryToSend: null, shouldProceed: false };
638
- }
639
- localQueryToSendToGemini = atCommandResult.processedQuery;
640
- }
641
- else {
642
- // Normal query for Gemini
643
- addItem({ type: MessageType.USER, text: trimmedQuery }, userMessageTimestamp);
644
- localQueryToSendToGemini = trimmedQuery;
645
- }
646
- }
647
- else {
648
- // It's a function response (PartListUnion that isn't a string)
649
- localQueryToSendToGemini = query;
650
- }
651
- if (localQueryToSendToGemini === null) {
652
- onDebugMessage("Query processing resulted in null, not sending to Gemini.");
653
- return { queryToSend: null, shouldProceed: false };
654
- }
655
- return { queryToSend: localQueryToSendToGemini, shouldProceed: true };
656
- }, [
657
- config,
658
- addItem,
659
- onDebugMessage,
660
- handleShellCommand,
661
- handleSlashCommand,
662
- logger,
663
- shellModeActive,
664
- scheduleToolCalls,
665
- settings,
666
- ]);
667
- // --- Stream Event Handlers ---
668
- const handleContentEvent = useCallback((eventValue, currentGeminiMessageBuffer, userMessageTimestamp) => {
669
- setRetryStatus(null);
670
- if (turnCancelledRef.current) {
671
- // Prevents additional output after a user initiated cancel.
672
- return "";
673
- }
674
- let newGeminiMessageBuffer = currentGeminiMessageBuffer + eventValue;
675
- if (pendingHistoryItemRef.current?.type !== "gemini" &&
676
- pendingHistoryItemRef.current?.type !== "gemini_content") {
677
- // Flush any pending item before starting gemini content
678
- if (pendingHistoryItemRef.current) {
679
- addItem(pendingHistoryItemRef.current, userMessageTimestamp);
680
- }
681
- setPendingHistoryItem({ type: "gemini", text: "" });
682
- newGeminiMessageBuffer = eventValue;
683
- }
684
- // Split large messages for better rendering performance. Ideally,
685
- // we should maximize the amount of output sent to <Static />.
686
- const splitPoint = findLastSafeSplitPoint(newGeminiMessageBuffer);
687
- if (splitPoint === newGeminiMessageBuffer.length) {
688
- // Update the existing message with accumulated content
689
- setPendingHistoryItem((item) => ({
690
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
691
- type: item?.type,
692
- text: newGeminiMessageBuffer,
693
- }));
694
- }
695
- else {
696
- // This indicates that we need to split up this Gemini Message.
697
- // Splitting a message is primarily a performance consideration. There is a
698
- // <Static> component at the root of App.tsx which takes care of rendering
699
- // content statically or dynamically. Everything but the last message is
700
- // treated as static in order to prevent re-rendering an entire message history
701
- // multiple times per-second (as streaming occurs). Prior to this change you'd
702
- // see heavy flickering of the terminal. This ensures that larger messages get
703
- // broken up so that there are more "statically" rendered.
704
- const beforeText = newGeminiMessageBuffer.substring(0, splitPoint);
705
- const afterText = newGeminiMessageBuffer.substring(splitPoint);
706
- if (beforeText.length > 0) {
707
- addItem({
708
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
709
- type: pendingHistoryItemRef.current?.type,
710
- text: beforeText,
711
- }, userMessageTimestamp);
712
- }
713
- setPendingHistoryItem({ type: "gemini_content", text: afterText });
714
- newGeminiMessageBuffer = afterText;
715
- }
716
- return newGeminiMessageBuffer;
717
- }, [addItem, pendingHistoryItemRef, setPendingHistoryItem]);
718
- const handleThoughtEvent = useCallback((eventValue, _userMessageTimestamp) => {
719
- setThought(eventValue);
720
- if (getInlineThinkingMode(settings) === "full") {
721
- addItem({
722
- type: "thinking",
723
- thought: eventValue,
724
- });
725
- }
726
- }, [addItem, settings, setThought]);
727
- const handleUserCancelledEvent = useCallback((userMessageTimestamp) => {
728
- if (turnCancelledRef.current) {
729
- return;
730
- }
731
- if (pendingHistoryItemRef.current) {
732
- if (pendingHistoryItemRef.current.type === "tool_group") {
733
- const updatedTools = pendingHistoryItemRef.current.tools.map((tool) => tool.status === CoreToolCallStatus.Validating ||
734
- tool.status === CoreToolCallStatus.Scheduled ||
735
- tool.status === CoreToolCallStatus.AwaitingApproval ||
736
- tool.status === CoreToolCallStatus.Executing
737
- ? { ...tool, status: CoreToolCallStatus.Cancelled }
738
- : tool);
739
- const pendingItem = {
740
- ...pendingHistoryItemRef.current,
741
- tools: updatedTools,
742
- };
743
- addItem(pendingItem, userMessageTimestamp);
744
- }
745
- else {
746
- addItem(pendingHistoryItemRef.current, userMessageTimestamp);
747
- }
748
- setPendingHistoryItem(null);
749
- }
750
- addItem({ type: MessageType.INFO, text: "User cancelled the request." }, userMessageTimestamp);
751
- setIsResponding(false);
752
- setThought(null); // Reset thought when user cancels
753
- }, [
754
- addItem,
755
- pendingHistoryItemRef,
756
- setPendingHistoryItem,
757
- setThought,
758
- setIsResponding,
759
- ]);
760
- const handleErrorEvent = useCallback((eventValue, userMessageTimestamp) => {
761
- if (pendingHistoryItemRef.current) {
762
- addItem(pendingHistoryItemRef.current, userMessageTimestamp);
763
- setPendingHistoryItem(null);
764
- }
765
- maybeAddSuppressedToolErrorNote(userMessageTimestamp);
766
- addItem({
767
- type: MessageType.ERROR,
768
- text: parseAndFormatApiError(eventValue.error, config.getContentGeneratorConfig()?.authType, undefined, config.getModel(), DEFAULT_GEMINI_FLASH_MODEL),
769
- }, userMessageTimestamp);
770
- maybeAddLowVerbosityFailureNote(userMessageTimestamp);
771
- setThought(null); // Reset thought when there's an error
772
- }, [
773
- addItem,
774
- pendingHistoryItemRef,
775
- setPendingHistoryItem,
776
- config,
777
- setThought,
778
- maybeAddSuppressedToolErrorNote,
779
- maybeAddLowVerbosityFailureNote,
780
- ]);
781
- const handleCitationEvent = useCallback((text, userMessageTimestamp) => {
782
- if (!showCitations(settings)) {
783
- return;
784
- }
785
- if (pendingHistoryItemRef.current) {
786
- addItem(pendingHistoryItemRef.current, userMessageTimestamp);
787
- setPendingHistoryItem(null);
788
- }
789
- addItem({ type: MessageType.INFO, text }, userMessageTimestamp);
790
- }, [addItem, pendingHistoryItemRef, setPendingHistoryItem, settings]);
791
- const handleFinishedEvent = useCallback((event, userMessageTimestamp) => {
792
- const finishReason = event.value.reason;
793
- if (!finishReason) {
794
- return;
795
- }
796
- const finishReasonMessages = {
797
- [FinishReason.FINISH_REASON_UNSPECIFIED]: undefined,
798
- [FinishReason.STOP]: undefined,
799
- [FinishReason.MAX_TOKENS]: "Response truncated due to token limits.",
800
- [FinishReason.SAFETY]: "Response stopped due to safety reasons.",
801
- [FinishReason.RECITATION]: "Response stopped due to recitation policy.",
802
- [FinishReason.LANGUAGE]: "Response stopped due to unsupported language.",
803
- [FinishReason.BLOCKLIST]: "Response stopped due to forbidden terms.",
804
- [FinishReason.PROHIBITED_CONTENT]: "Response stopped due to prohibited content.",
805
- [FinishReason.SPII]: "Response stopped due to sensitive personally identifiable information.",
806
- [FinishReason.OTHER]: "Response stopped for other reasons.",
807
- [FinishReason.MALFORMED_FUNCTION_CALL]: "Response stopped due to malformed function call.",
808
- [FinishReason.IMAGE_SAFETY]: "Response stopped due to image safety violations.",
809
- [FinishReason.UNEXPECTED_TOOL_CALL]: "Response stopped due to unexpected tool call.",
810
- [FinishReason.IMAGE_PROHIBITED_CONTENT]: "Response stopped due to prohibited image content.",
811
- [FinishReason.NO_IMAGE]: "Response stopped because no image was generated.",
812
- };
813
- const message = finishReasonMessages[finishReason];
814
- if (message) {
815
- addItem({
816
- type: "info",
817
- text: `⚠️ ${message}`,
818
- }, userMessageTimestamp);
819
- }
820
- }, [addItem]);
821
- const handleChatCompressionEvent = useCallback((eventValue, userMessageTimestamp) => {
822
- if (pendingHistoryItemRef.current) {
823
- addItem(pendingHistoryItemRef.current, userMessageTimestamp);
824
- setPendingHistoryItem(null);
825
- }
826
- const limit = tokenLimit(config.getModel());
827
- const originalPercentage = Math.round(((eventValue?.originalTokenCount ?? 0) / limit) * 100);
828
- const newPercentage = Math.round(((eventValue?.newTokenCount ?? 0) / limit) * 100);
829
- addItem({
830
- type: MessageType.INFO,
831
- text: `Context compressed from ${originalPercentage}% to ${newPercentage}%.`,
832
- secondaryText: `Change threshold in /settings.`,
833
- color: theme.status.warning,
834
- marginBottom: 1,
835
- }, userMessageTimestamp);
836
- }, [addItem, pendingHistoryItemRef, setPendingHistoryItem, config]);
837
- const handleMaxSessionTurnsEvent = useCallback(() => addItem({
838
- type: "info",
839
- text: `The session has reached the maximum number of turns: ${config.getMaxSessionTurns()}. ` +
840
- `Please update this limit in your setting.json file.`,
841
- }), [addItem, config]);
842
- const handleContextWindowWillOverflowEvent = useCallback((estimatedRequestTokenCount, remainingTokenCount) => {
843
- onCancelSubmit(true);
844
- const limit = tokenLimit(config.getModel());
845
- const isMoreThan25PercentUsed = limit > 0 && remainingTokenCount < limit * 0.75;
846
- let text = `Sending this message (${estimatedRequestTokenCount} tokens) might exceed the context window limit (${remainingTokenCount.toLocaleString()} tokens left).`;
847
- if (isMoreThan25PercentUsed) {
848
- text +=
849
- " Please try reducing the size of your message or use the `/compress` command to compress the chat history.";
850
- }
851
- addItem({
852
- type: "info",
853
- text,
854
- });
855
- }, [addItem, onCancelSubmit, config]);
856
- const handleChatModelEvent = useCallback((eventValue, userMessageTimestamp) => {
857
- if (!settings.merged.ui.showModelInfoInChat) {
858
- return;
859
- }
860
- if (pendingHistoryItemRef.current) {
861
- addItem(pendingHistoryItemRef.current, userMessageTimestamp);
862
- setPendingHistoryItem(null);
863
- }
864
- addItem({
865
- type: "model",
866
- model: eventValue,
867
- }, userMessageTimestamp);
868
- }, [addItem, pendingHistoryItemRef, setPendingHistoryItem, settings]);
869
- const handleAgentExecutionStoppedEvent = useCallback((reason, userMessageTimestamp, systemMessage, contextCleared) => {
870
- if (pendingHistoryItemRef.current) {
871
- addItem(pendingHistoryItemRef.current, userMessageTimestamp);
872
- setPendingHistoryItem(null);
873
- }
874
- addItem({
875
- type: MessageType.INFO,
876
- text: `Agent execution stopped: ${systemMessage?.trim() || reason}`,
877
- }, userMessageTimestamp);
878
- maybeAddLowVerbosityFailureNote(userMessageTimestamp);
879
- if (contextCleared) {
880
- addItem({
881
- type: MessageType.INFO,
882
- text: "Conversation context has been cleared.",
883
- }, userMessageTimestamp);
884
- }
885
- setIsResponding(false);
886
- }, [
887
- addItem,
888
- pendingHistoryItemRef,
889
- setPendingHistoryItem,
890
- setIsResponding,
891
- maybeAddLowVerbosityFailureNote,
892
- ]);
893
- const handleAgentExecutionBlockedEvent = useCallback((reason, userMessageTimestamp, systemMessage, contextCleared) => {
894
- if (pendingHistoryItemRef.current) {
895
- addItem(pendingHistoryItemRef.current, userMessageTimestamp);
896
- setPendingHistoryItem(null);
897
- }
898
- addItem({
899
- type: MessageType.WARNING,
900
- text: `Agent execution blocked: ${systemMessage?.trim() || reason}`,
901
- }, userMessageTimestamp);
902
- maybeAddLowVerbosityFailureNote(userMessageTimestamp);
903
- if (contextCleared) {
904
- addItem({
905
- type: MessageType.INFO,
906
- text: "Conversation context has been cleared.",
907
- }, userMessageTimestamp);
908
- }
909
- }, [
910
- addItem,
911
- pendingHistoryItemRef,
912
- setPendingHistoryItem,
913
- maybeAddLowVerbosityFailureNote,
914
- ]);
915
- const processGeminiStreamEvents = useCallback(async (stream, userMessageTimestamp, signal) => {
916
- let geminiMessageBuffer = "";
917
- const toolCallRequests = [];
918
- for await (const event of stream) {
919
- if (event.type !== ServerGeminiEventType.Thought &&
920
- thoughtRef.current !== null) {
921
- setThought(null);
922
- }
923
- switch (event.type) {
924
- case ServerGeminiEventType.Thought:
925
- setLastGeminiActivityTime(Date.now());
926
- handleThoughtEvent(event.value, userMessageTimestamp);
927
- break;
928
- case ServerGeminiEventType.Content:
929
- setLastGeminiActivityTime(Date.now());
930
- geminiMessageBuffer = handleContentEvent(event.value, geminiMessageBuffer, userMessageTimestamp);
931
- break;
932
- case ServerGeminiEventType.ToolCallRequest:
933
- toolCallRequests.push(event.value);
934
- break;
935
- case ServerGeminiEventType.UserCancelled:
936
- handleUserCancelledEvent(userMessageTimestamp);
937
- break;
938
- case ServerGeminiEventType.Error:
939
- handleErrorEvent(event.value, userMessageTimestamp);
940
- break;
941
- case ServerGeminiEventType.AgentExecutionStopped:
942
- handleAgentExecutionStoppedEvent(event.value.reason, userMessageTimestamp, event.value.systemMessage, event.value.contextCleared);
943
- break;
944
- case ServerGeminiEventType.AgentExecutionBlocked:
945
- handleAgentExecutionBlockedEvent(event.value.reason, userMessageTimestamp, event.value.systemMessage, event.value.contextCleared);
946
- break;
947
- case ServerGeminiEventType.ChatCompressed:
948
- handleChatCompressionEvent(event.value, userMessageTimestamp);
949
- break;
950
- case ServerGeminiEventType.ToolCallConfirmation:
951
- case ServerGeminiEventType.ToolCallResponse:
952
- // do nothing
953
- break;
954
- case ServerGeminiEventType.MaxSessionTurns:
955
- handleMaxSessionTurnsEvent();
956
- break;
957
- case ServerGeminiEventType.ContextWindowWillOverflow:
958
- handleContextWindowWillOverflowEvent(event.value.estimatedRequestTokenCount, event.value.remainingTokenCount);
959
- break;
960
- case ServerGeminiEventType.Finished:
961
- handleFinishedEvent(event, userMessageTimestamp);
962
- break;
963
- case ServerGeminiEventType.Citation:
964
- handleCitationEvent(event.value, userMessageTimestamp);
965
- break;
966
- case ServerGeminiEventType.ModelInfo:
967
- handleChatModelEvent(event.value, userMessageTimestamp);
968
- break;
969
- case ServerGeminiEventType.LoopDetected:
970
- // handle later because we want to move pending history to history
971
- // before we add loop detected message to history
972
- loopDetectedRef.current = true;
973
- break;
974
- case ServerGeminiEventType.Retry:
975
- case ServerGeminiEventType.InvalidStream:
976
- // Will add the missing logic later
977
- break;
978
- default: {
979
- // enforces exhaustive switch-case
980
- const unreachable = event;
981
- return unreachable;
982
- }
983
- }
984
- }
985
- if (toolCallRequests.length > 0) {
986
- if (pendingHistoryItemRef.current) {
987
- addItem(pendingHistoryItemRef.current, userMessageTimestamp);
988
- setPendingHistoryItem(null);
989
- }
990
- await scheduleToolCalls(toolCallRequests, signal);
991
- }
992
- return StreamProcessingStatus.Completed;
993
- }, [
994
- handleContentEvent,
995
- handleThoughtEvent,
996
- thoughtRef,
997
- handleUserCancelledEvent,
998
- handleErrorEvent,
999
- scheduleToolCalls,
1000
- handleChatCompressionEvent,
1001
- handleFinishedEvent,
1002
- handleMaxSessionTurnsEvent,
1003
- handleContextWindowWillOverflowEvent,
1004
- handleCitationEvent,
1005
- handleChatModelEvent,
1006
- handleAgentExecutionStoppedEvent,
1007
- handleAgentExecutionBlockedEvent,
1008
- addItem,
1009
- pendingHistoryItemRef,
1010
- setPendingHistoryItem,
1011
- setThought,
1012
- ]);
1013
- const submitQuery = useCallback(async (query, options, prompt_id) => runInDevTraceSpan({
1014
- operation: options?.isContinuation
1015
- ? GeminiCliOperation.SystemPrompt
1016
- : GeminiCliOperation.UserPrompt,
1017
- sessionId: config.getSessionId(),
1018
- }, async ({ metadata: spanMetadata }) => {
1019
- spanMetadata.input = query;
1020
- if ((isRespondingRef.current ||
1021
- streamingState === StreamingState.Responding ||
1022
- streamingState === StreamingState.WaitingForConfirmation) &&
1023
- !options?.isContinuation)
1024
- return;
1025
- const queryId = `${Date.now()}-${Math.random()}`;
1026
- activeQueryIdRef.current = queryId;
1027
- const userMessageTimestamp = Date.now();
1028
- // Reset quota error flag when starting a new query (not a continuation)
1029
- if (!options?.isContinuation) {
1030
- setModelSwitchedFromQuotaError(false);
1031
- config.setQuotaErrorOccurred(false);
1032
- config.resetBillingTurnState(settings.merged.billing?.overageStrategy);
1033
- suppressedToolErrorCountRef.current = 0;
1034
- suppressedToolErrorNoteShownRef.current = false;
1035
- lowVerbosityFailureNoteShownRef.current = false;
1036
- }
1037
- abortControllerRef.current = new AbortController();
1038
- const abortSignal = abortControllerRef.current.signal;
1039
- turnCancelledRef.current = false;
1040
- if (!prompt_id) {
1041
- prompt_id = `${config.getSessionId()}########${getPromptCount()}`;
1042
- }
1043
- return promptIdContext.run(prompt_id, async () => {
1044
- const { queryToSend, shouldProceed } = await prepareQueryForGemini(query, userMessageTimestamp, abortSignal, prompt_id);
1045
- if (!shouldProceed || queryToSend === null) {
1046
- return;
1047
- }
1048
- if (!options?.isContinuation) {
1049
- if (typeof queryToSend === "string") {
1050
- // logging the text prompts only for now
1051
- const promptText = queryToSend;
1052
- logUserPrompt(config, new UserPromptEvent(promptText.length, prompt_id, config.getContentGeneratorConfig()?.authType, promptText));
1053
- }
1054
- startNewPrompt();
1055
- setThought(null); // Reset thought when starting a new prompt
1056
- }
1057
- setIsResponding(true);
1058
- setInitError(null);
1059
- // Store query and prompt_id for potential retry on loop detection
1060
- lastQueryRef.current = queryToSend;
1061
- lastPromptIdRef.current = prompt_id;
1062
- try {
1063
- const stream = geminiClient.sendMessageStream(queryToSend, abortSignal, prompt_id, undefined, false, query);
1064
- const processingStatus = await processGeminiStreamEvents(stream, userMessageTimestamp, abortSignal);
1065
- if (processingStatus === StreamProcessingStatus.UserCancelled) {
1066
- return;
1067
- }
1068
- if (pendingHistoryItemRef.current) {
1069
- addItem(pendingHistoryItemRef.current, userMessageTimestamp);
1070
- setPendingHistoryItem(null);
1071
- }
1072
- if (loopDetectedRef.current) {
1073
- loopDetectedRef.current = false;
1074
- // Show the confirmation dialog to choose whether to disable loop detection
1075
- setLoopDetectionConfirmationRequest({
1076
- onComplete: async (result) => {
1077
- setLoopDetectionConfirmationRequest(null);
1078
- if (result.userSelection === "disable") {
1079
- config
1080
- .getGeminiClient()
1081
- .getLoopDetectionService()
1082
- .disableForSession();
1083
- addItem({
1084
- type: "info",
1085
- text: `Loop detection has been disabled for this session. Retrying request...`,
1086
- });
1087
- if (lastQueryRef.current && lastPromptIdRef.current) {
1088
- await submitQuery(lastQueryRef.current, { isContinuation: true }, lastPromptIdRef.current);
1089
- }
1090
- }
1091
- else {
1092
- addItem({
1093
- type: "info",
1094
- text: `A potential loop was detected. This can happen due to repetitive tool calls or other model behavior. The request has been halted.`,
1095
- });
1096
- }
1097
- },
1098
- });
1099
- }
1100
- }
1101
- catch (error) {
1102
- spanMetadata.error = error;
1103
- if (error instanceof UnauthorizedError) {
1104
- onAuthError("Session expired or is unauthorized.");
1105
- }
1106
- else if (
1107
- // Suppress ValidationRequiredError if it was marked as handled (e.g. user clicked change_auth or cancelled)
1108
- error instanceof ValidationRequiredError &&
1109
- error.userHandled) {
1110
- // Error was handled by validation dialog, don't display again
1111
- }
1112
- else if (!isNodeError(error) || error.name !== "AbortError") {
1113
- maybeAddSuppressedToolErrorNote(userMessageTimestamp);
1114
- addItem({
1115
- type: MessageType.ERROR,
1116
- text: parseAndFormatApiError(getErrorMessage(error) || "Unknown error", config.getContentGeneratorConfig()?.authType, undefined, config.getModel(), DEFAULT_GEMINI_FLASH_MODEL),
1117
- }, userMessageTimestamp);
1118
- maybeAddLowVerbosityFailureNote(userMessageTimestamp);
1119
- }
1120
- }
1121
- finally {
1122
- if (activeQueryIdRef.current === queryId) {
1123
- setIsResponding(false);
1124
- }
1125
- }
1126
- });
1127
- }), [
1128
- streamingState,
1129
- setModelSwitchedFromQuotaError,
1130
- prepareQueryForGemini,
1131
- processGeminiStreamEvents,
1132
- pendingHistoryItemRef,
1133
- addItem,
1134
- setPendingHistoryItem,
1135
- geminiClient,
1136
- onAuthError,
1137
- config,
1138
- startNewPrompt,
1139
- getPromptCount,
1140
- setThought,
1141
- maybeAddSuppressedToolErrorNote,
1142
- maybeAddLowVerbosityFailureNote,
1143
- isRespondingRef,
1144
- settings.merged.billing?.overageStrategy,
1145
- setIsResponding,
1146
- ]);
1147
- const handleApprovalModeChange = useCallback(async (newApprovalMode) => {
1148
- if (previousApprovalModeRef.current === ApprovalMode.PLAN &&
1149
- newApprovalMode !== ApprovalMode.PLAN &&
1150
- streamingState === StreamingState.Idle) {
1151
- if (geminiClient) {
1152
- try {
1153
- await geminiClient.addHistory({
1154
- role: "user",
1155
- parts: [
1156
- {
1157
- text: getPlanModeExitMessage(newApprovalMode, true),
1158
- },
1159
- ],
1160
- });
1161
- }
1162
- catch (error) {
1163
- onDebugMessage(`Failed to notify model of Plan Mode exit: ${getErrorMessage(error)}`);
1164
- addItem({
1165
- type: MessageType.ERROR,
1166
- text: "Failed to update the model about exiting Plan Mode. The model might be out of sync. Please consider restarting the session if you see unexpected behavior.",
1167
- });
1168
- }
1169
- }
1170
- }
1171
- previousApprovalModeRef.current = newApprovalMode;
1172
- // Auto-approve pending tool calls when switching to auto-approval modes
1173
- if (newApprovalMode === ApprovalMode.YOLO ||
1174
- newApprovalMode === ApprovalMode.AUTO_EDIT) {
1175
- let awaitingApprovalCalls = toolCalls.filter((call) => call.status === "awaiting_approval" && !call.request.forcedAsk);
1176
- // For AUTO_EDIT mode, only approve edit tools (replace, write_file)
1177
- if (newApprovalMode === ApprovalMode.AUTO_EDIT) {
1178
- awaitingApprovalCalls = awaitingApprovalCalls.filter((call) => EDIT_TOOL_NAMES.has(call.request.name));
1179
- }
1180
- // Process pending tool calls sequentially to reduce UI chaos
1181
- for (const call of awaitingApprovalCalls) {
1182
- if (call.correlationId) {
1183
- try {
1184
- await config.getMessageBus().publish({
1185
- type: MessageBusType.TOOL_CONFIRMATION_RESPONSE,
1186
- correlationId: call.correlationId,
1187
- confirmed: true,
1188
- requiresUserConfirmation: false,
1189
- outcome: ToolConfirmationOutcome.ProceedOnce,
1190
- });
1191
- }
1192
- catch (error) {
1193
- debugLogger.warn(`Failed to auto-approve tool call ${call.request.callId}:`, error);
1194
- }
1195
- }
1196
- }
1197
- }
1198
- }, [config, toolCalls, geminiClient, streamingState, addItem, onDebugMessage]);
1199
- const handleCompletedTools = useCallback(async (completedToolCallsFromScheduler) => {
1200
- const completedAndReadyToSubmitTools = completedToolCallsFromScheduler.filter((tc) => {
1201
- const isTerminalState = tc.status === "success" ||
1202
- tc.status === "error" ||
1203
- tc.status === "cancelled";
1204
- if (isTerminalState) {
1205
- const completedOrCancelledCall = tc;
1206
- return (completedOrCancelledCall.response?.responseParts !== undefined);
1207
- }
1208
- return false;
1209
- });
1210
- // Finalize any client-initiated tools as soon as they are done.
1211
- const clientTools = completedAndReadyToSubmitTools.filter((t) => t.request.isClientInitiated);
1212
- if (clientTools.length > 0) {
1213
- markToolsAsSubmitted(clientTools.map((t) => t.request.callId));
1214
- if (geminiClient) {
1215
- for (const tool of clientTools) {
1216
- // Only manually record skill activations in the chat history.
1217
- // Other client-initiated tools (like save_memory) update the system
1218
- // prompt/context and don't strictly need to be in the history.
1219
- if (tool.request.name !== ACTIVATE_SKILL_TOOL_NAME) {
1220
- continue;
1221
- }
1222
- // Add both the call (model turn) and the result (user turn) to history.
1223
- // Client-initiated calls are essentially "synthetic" turns that let
1224
- // subsequent model calls understand what just happened in the UI.
1225
- await geminiClient.addHistory({
1226
- role: "model",
1227
- parts: [
1228
- {
1229
- functionCall: {
1230
- name: tool.request.name,
1231
- args: tool.request.args,
1232
- },
1233
- },
1234
- ],
1235
- });
1236
- await geminiClient.addHistory({
1237
- role: "user",
1238
- parts: tool.response.responseParts,
1239
- });
1240
- }
1241
- }
1242
- }
1243
- // Identify new, successful save_memory calls that we haven't processed yet.
1244
- const newSuccessfulMemorySaves = completedAndReadyToSubmitTools.filter((t) => t.request.name === "save_memory" &&
1245
- t.status === "success" &&
1246
- !processedMemoryToolsRef.current.has(t.request.callId));
1247
- for (const toolCall of completedAndReadyToSubmitTools) {
1248
- const backgroundedTool = getBackgroundedToolInfo(toolCall);
1249
- if (backgroundedTool) {
1250
- registerBackgroundTask(backgroundedTool.pid, backgroundedTool.command, backgroundedTool.initialOutput);
1251
- }
1252
- }
1253
- if (newSuccessfulMemorySaves.length > 0) {
1254
- // Perform the refresh only if there are new ones.
1255
- void performMemoryRefresh();
1256
- // Mark them as processed so we don't do this again on the next render.
1257
- newSuccessfulMemorySaves.forEach((t) => processedMemoryToolsRef.current.add(t.request.callId));
1258
- }
1259
- const geminiTools = completedAndReadyToSubmitTools.filter((t) => !t.request.isClientInitiated);
1260
- if (isLowErrorVerbosity) {
1261
- // Low-mode suppression applies only to model-initiated tool failures.
1262
- suppressedToolErrorCountRef.current += geminiTools.filter((tc) => tc.status === CoreToolCallStatus.Error).length;
1263
- }
1264
- if (geminiTools.length === 0) {
1265
- return;
1266
- }
1267
- // Check if any tool requested to stop execution immediately
1268
- const stopExecutionTool = geminiTools.find((tc) => tc.response.errorType === ToolErrorType.STOP_EXECUTION);
1269
- if (stopExecutionTool?.response.error) {
1270
- maybeAddSuppressedToolErrorNote();
1271
- addItem({
1272
- type: MessageType.INFO,
1273
- text: `Agent execution stopped: ${stopExecutionTool.response.error.message}`,
1274
- });
1275
- maybeAddLowVerbosityFailureNote();
1276
- setIsResponding(false);
1277
- const callIdsToMarkAsSubmitted = geminiTools.map((toolCall) => toolCall.request.callId);
1278
- markToolsAsSubmitted(callIdsToMarkAsSubmitted);
1279
- return;
1280
- }
1281
- // If all the tools were cancelled, don't submit a response to Gemini.
1282
- // Note: we ignore the topic tool because the user doesn't have a chance to decline it.
1283
- const declinableTools = geminiTools.filter((tc) => !isTopicTool(tc.request.name));
1284
- const allDeclinableToolsCancelled = declinableTools.length > 0 &&
1285
- declinableTools.every((tc) => tc.status === CoreToolCallStatus.Cancelled);
1286
- const allToolsCancelled = geminiTools.length > 0 &&
1287
- geminiTools.every((tc) => tc.status === CoreToolCallStatus.Cancelled);
1288
- if (allDeclinableToolsCancelled || allToolsCancelled) {
1289
- // If the turn was cancelled via the imperative escape key flow,
1290
- // the cancellation message is added there. We check the ref to avoid duplication.
1291
- if (!turnCancelledRef.current) {
1292
- addItem({
1293
- type: MessageType.INFO,
1294
- text: "Request cancelled.",
1295
- });
1296
- }
1297
- setIsResponding(false);
1298
- if (geminiClient) {
1299
- // We need to manually add the function responses to the history
1300
- // so the model knows the tools were cancelled.
1301
- const combinedParts = geminiTools.flatMap((toolCall) => toolCall.response.responseParts);
1302
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
1303
- geminiClient.addHistory({
1304
- role: "user",
1305
- parts: combinedParts,
1306
- });
1307
- }
1308
- const callIdsToMarkAsSubmitted = geminiTools.map((toolCall) => toolCall.request.callId);
1309
- markToolsAsSubmitted(callIdsToMarkAsSubmitted);
1310
- return;
1311
- }
1312
- const responsesToSend = geminiTools.flatMap((toolCall) => toolCall.response.responseParts);
1313
- if (consumeUserHint) {
1314
- const userHint = consumeUserHint();
1315
- if (userHint && userHint.trim().length > 0) {
1316
- const hintText = userHint.trim();
1317
- responsesToSend.unshift({
1318
- text: buildUserSteeringHintPrompt(hintText),
1319
- });
1320
- }
1321
- }
1322
- const callIdsToMarkAsSubmitted = geminiTools.map((toolCall) => toolCall.request.callId);
1323
- const prompt_ids = geminiTools.map((toolCall) => toolCall.request.prompt_id);
1324
- markToolsAsSubmitted(callIdsToMarkAsSubmitted);
1325
- // Don't continue if model was switched due to quota error
1326
- if (modelSwitchedFromQuotaError) {
1327
- return;
1328
- }
1329
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
1330
- submitQuery(responsesToSend, {
1331
- isContinuation: true,
1332
- }, prompt_ids[0]);
1333
- }, [
1334
- submitQuery,
1335
- markToolsAsSubmitted,
1336
- geminiClient,
1337
- performMemoryRefresh,
1338
- modelSwitchedFromQuotaError,
1339
- addItem,
1340
- registerBackgroundTask,
1341
- consumeUserHint,
1342
- isLowErrorVerbosity,
1343
- maybeAddSuppressedToolErrorNote,
1344
- maybeAddLowVerbosityFailureNote,
1345
- setIsResponding,
1346
- ]);
1347
- const pendingHistoryItems = useMemo(() => [pendingHistoryItem, ...pendingToolGroupItems].filter((i) => i !== undefined && i !== null), [pendingHistoryItem, pendingToolGroupItems]);
1348
- useEffect(() => {
1349
- const saveRestorableToolCalls = async () => {
1350
- if (!config.getCheckpointingEnabled()) {
1351
- return;
1352
- }
1353
- const restorableToolCalls = toolCalls.filter((toolCall) => EDIT_TOOL_NAMES.has(toolCall.request.name) &&
1354
- toolCall.status === CoreToolCallStatus.AwaitingApproval);
1355
- if (restorableToolCalls.length > 0) {
1356
- if (!gitService) {
1357
- onDebugMessage("Checkpointing is enabled but Git service is not available. Failed to create snapshot. Ensure Git is installed and working properly.");
1358
- return;
1359
- }
1360
- const { checkpointsToWrite, errors } = await processRestorableToolCalls(restorableToolCalls.map((call) => call.request), gitService, geminiClient, history);
1361
- if (errors.length > 0) {
1362
- errors.forEach(onDebugMessage);
1363
- }
1364
- if (checkpointsToWrite.size > 0) {
1365
- const checkpointDir = config.storage.getProjectTempCheckpointsDir();
1366
- try {
1367
- await fs.mkdir(checkpointDir, { recursive: true });
1368
- for (const [fileName, content] of checkpointsToWrite) {
1369
- const filePath = path.join(checkpointDir, fileName);
1370
- await fs.writeFile(filePath, content);
1371
- }
1372
- }
1373
- catch (error) {
1374
- onDebugMessage(`Failed to write checkpoint file: ${getErrorMessage(error)}`);
1375
- }
1376
- }
1377
- }
1378
- };
1379
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
1380
- saveRestorableToolCalls();
1381
- }, [toolCalls, config, onDebugMessage, gitService, history, geminiClient]);
1382
- const lastOutputTime = Math.max(lastToolOutputTime, lastShellOutputTime, lastGeminiActivityTime);
1383
- return {
1384
- streamingState,
1385
- submitQuery,
1386
- initError,
1387
- pendingHistoryItems,
1388
- thought,
1389
- cancelOngoingRequest,
1390
- pendingToolCalls: toolCalls,
1391
- handleApprovalModeChange,
1392
- activePtyId,
1393
- loopDetectionConfirmationRequest,
1394
- lastOutputTime,
1395
- backgroundTaskCount,
1396
- isBackgroundTaskVisible,
1397
- toggleBackgroundTasks,
1398
- backgroundCurrentExecution,
1399
- backgroundTasks,
1400
- dismissBackgroundTask,
1401
- retryStatus,
1402
- };
1403
- };
1404
- //# sourceMappingURL=useGeminiStream.js.map