webviewMessageHandler.ts 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432
  1. import * as path from "path"
  2. import fs from "fs/promises"
  3. import pWaitFor from "p-wait-for"
  4. import * as vscode from "vscode"
  5. import { type Language, type ProviderSettings, type GlobalState, TelemetryEventName } from "@roo-code/types"
  6. import { CloudService } from "@roo-code/cloud"
  7. import { TelemetryService } from "@roo-code/telemetry"
  8. import { ClineProvider } from "./ClineProvider"
  9. import { changeLanguage, t } from "../../i18n"
  10. import { Package } from "../../shared/package"
  11. import { RouterName, toRouterName, ModelRecord } from "../../shared/api"
  12. import { supportPrompt } from "../../shared/support-prompt"
  13. import { checkoutDiffPayloadSchema, checkoutRestorePayloadSchema, WebviewMessage } from "../../shared/WebviewMessage"
  14. import { checkExistKey } from "../../shared/checkExistApiConfig"
  15. import { experimentDefault } from "../../shared/experiments"
  16. import { Terminal } from "../../integrations/terminal/Terminal"
  17. import { openFile, openImage } from "../../integrations/misc/open-file"
  18. import { selectImages } from "../../integrations/misc/process-images"
  19. import { getTheme } from "../../integrations/theme/getTheme"
  20. import { discoverChromeHostUrl, tryChromeHostUrl } from "../../services/browser/browserDiscovery"
  21. import { searchWorkspaceFiles } from "../../services/search/file-search"
  22. import { fileExistsAtPath } from "../../utils/fs"
  23. import { playTts, setTtsEnabled, setTtsSpeed, stopTts } from "../../utils/tts"
  24. import { singleCompletionHandler } from "../../utils/single-completion-handler"
  25. import { searchCommits } from "../../utils/git"
  26. import { exportSettings, importSettings } from "../config/importExport"
  27. import { getOpenAiModels } from "../../api/providers/openai"
  28. import { getOllamaModels } from "../../api/providers/ollama"
  29. import { getVsCodeLmModels } from "../../api/providers/vscode-lm"
  30. import { getLmStudioModels } from "../../api/providers/lmstudio"
  31. import { openMention } from "../mentions"
  32. import { TelemetrySetting } from "../../shared/TelemetrySetting"
  33. import { getWorkspacePath } from "../../utils/path"
  34. import { Mode, defaultModeSlug } from "../../shared/modes"
  35. import { getModels, flushModels } from "../../api/providers/fetchers/modelCache"
  36. import { GetModelsOptions } from "../../shared/api"
  37. import { generateSystemPrompt } from "./generateSystemPrompt"
  38. import { getCommand } from "../../utils/commands"
  39. const ALLOWED_VSCODE_SETTINGS = new Set(["terminal.integrated.inheritEnv"])
  40. export const webviewMessageHandler = async (provider: ClineProvider, message: WebviewMessage) => {
  41. // Utility functions provided for concise get/update of global state via contextProxy API.
  42. const getGlobalState = <K extends keyof GlobalState>(key: K) => provider.contextProxy.getValue(key)
  43. const updateGlobalState = async <K extends keyof GlobalState>(key: K, value: GlobalState[K]) =>
  44. await provider.contextProxy.setValue(key, value)
  45. switch (message.type) {
  46. case "webviewDidLaunch":
  47. // Load custom modes first
  48. const customModes = await provider.customModesManager.getCustomModes()
  49. await updateGlobalState("customModes", customModes)
  50. provider.postStateToWebview()
  51. provider.workspaceTracker?.initializeFilePaths() // Don't await.
  52. getTheme().then((theme) => provider.postMessageToWebview({ type: "theme", text: JSON.stringify(theme) }))
  53. // If MCP Hub is already initialized, update the webview with
  54. // current server list.
  55. const mcpHub = provider.getMcpHub()
  56. if (mcpHub) {
  57. provider.postMessageToWebview({ type: "mcpServers", mcpServers: mcpHub.getAllServers() })
  58. }
  59. provider.providerSettingsManager
  60. .listConfig()
  61. .then(async (listApiConfig) => {
  62. if (!listApiConfig) {
  63. return
  64. }
  65. if (listApiConfig.length === 1) {
  66. // Check if first time init then sync with exist config.
  67. if (!checkExistKey(listApiConfig[0])) {
  68. const { apiConfiguration } = await provider.getState()
  69. await provider.providerSettingsManager.saveConfig(
  70. listApiConfig[0].name ?? "default",
  71. apiConfiguration,
  72. )
  73. listApiConfig[0].apiProvider = apiConfiguration.apiProvider
  74. }
  75. }
  76. const currentConfigName = getGlobalState("currentApiConfigName")
  77. if (currentConfigName) {
  78. if (!(await provider.providerSettingsManager.hasConfig(currentConfigName))) {
  79. // Current config name not valid, get first config in list.
  80. const name = listApiConfig[0]?.name
  81. await updateGlobalState("currentApiConfigName", name)
  82. if (name) {
  83. await provider.activateProviderProfile({ name })
  84. return
  85. }
  86. }
  87. }
  88. await Promise.all([
  89. await updateGlobalState("listApiConfigMeta", listApiConfig),
  90. await provider.postMessageToWebview({ type: "listApiConfig", listApiConfig }),
  91. ])
  92. })
  93. .catch((error) =>
  94. provider.log(
  95. `Error list api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  96. ),
  97. )
  98. // If user already opted in to telemetry, enable telemetry service
  99. provider.getStateToPostToWebview().then((state) => {
  100. const { telemetrySetting } = state
  101. const isOptedIn = telemetrySetting === "enabled"
  102. TelemetryService.instance.updateTelemetryState(isOptedIn)
  103. })
  104. provider.isViewLaunched = true
  105. break
  106. case "newTask":
  107. // Initializing new instance of Cline will make sure that any
  108. // agentically running promises in old instance don't affect our new
  109. // task. This essentially creates a fresh slate for the new task.
  110. await provider.initClineWithTask(message.text, message.images)
  111. break
  112. case "customInstructions":
  113. await provider.updateCustomInstructions(message.text)
  114. break
  115. case "alwaysAllowReadOnly":
  116. await updateGlobalState("alwaysAllowReadOnly", message.bool ?? undefined)
  117. await provider.postStateToWebview()
  118. break
  119. case "alwaysAllowReadOnlyOutsideWorkspace":
  120. await updateGlobalState("alwaysAllowReadOnlyOutsideWorkspace", message.bool ?? undefined)
  121. await provider.postStateToWebview()
  122. break
  123. case "alwaysAllowWrite":
  124. await updateGlobalState("alwaysAllowWrite", message.bool ?? undefined)
  125. await provider.postStateToWebview()
  126. break
  127. case "alwaysAllowWriteOutsideWorkspace":
  128. await updateGlobalState("alwaysAllowWriteOutsideWorkspace", message.bool ?? undefined)
  129. await provider.postStateToWebview()
  130. break
  131. case "alwaysAllowExecute":
  132. await updateGlobalState("alwaysAllowExecute", message.bool ?? undefined)
  133. await provider.postStateToWebview()
  134. break
  135. case "alwaysAllowBrowser":
  136. await updateGlobalState("alwaysAllowBrowser", message.bool ?? undefined)
  137. await provider.postStateToWebview()
  138. break
  139. case "alwaysAllowMcp":
  140. await updateGlobalState("alwaysAllowMcp", message.bool)
  141. await provider.postStateToWebview()
  142. break
  143. case "alwaysAllowModeSwitch":
  144. await updateGlobalState("alwaysAllowModeSwitch", message.bool)
  145. await provider.postStateToWebview()
  146. break
  147. case "allowedMaxRequests":
  148. await updateGlobalState("allowedMaxRequests", message.value)
  149. await provider.postStateToWebview()
  150. break
  151. case "alwaysAllowSubtasks":
  152. await updateGlobalState("alwaysAllowSubtasks", message.bool)
  153. await provider.postStateToWebview()
  154. break
  155. case "askResponse":
  156. provider.getCurrentCline()?.handleWebviewAskResponse(message.askResponse!, message.text, message.images)
  157. break
  158. case "autoCondenseContext":
  159. await updateGlobalState("autoCondenseContext", message.bool)
  160. await provider.postStateToWebview()
  161. break
  162. case "autoCondenseContextPercent":
  163. await updateGlobalState("autoCondenseContextPercent", message.value)
  164. await provider.postStateToWebview()
  165. break
  166. case "terminalOperation":
  167. if (message.terminalOperation) {
  168. provider.getCurrentCline()?.handleTerminalOperation(message.terminalOperation)
  169. }
  170. break
  171. case "clearTask":
  172. // clear task resets the current session and allows for a new task to be started, if this session is a subtask - it allows the parent task to be resumed
  173. await provider.finishSubTask(t("common:tasks.canceled"))
  174. await provider.postStateToWebview()
  175. break
  176. case "didShowAnnouncement":
  177. await updateGlobalState("lastShownAnnouncementId", provider.latestAnnouncementId)
  178. await provider.postStateToWebview()
  179. break
  180. case "selectImages":
  181. const images = await selectImages()
  182. await provider.postMessageToWebview({ type: "selectedImages", images })
  183. break
  184. case "exportCurrentTask":
  185. const currentTaskId = provider.getCurrentCline()?.taskId
  186. if (currentTaskId) {
  187. provider.exportTaskWithId(currentTaskId)
  188. }
  189. break
  190. case "showTaskWithId":
  191. provider.showTaskWithId(message.text!)
  192. break
  193. case "condenseTaskContextRequest":
  194. provider.condenseTaskContext(message.text!)
  195. break
  196. case "deleteTaskWithId":
  197. provider.deleteTaskWithId(message.text!)
  198. break
  199. case "deleteMultipleTasksWithIds": {
  200. const ids = message.ids
  201. if (Array.isArray(ids)) {
  202. // Process in batches of 20 (or another reasonable number)
  203. const batchSize = 20
  204. const results = []
  205. // Only log start and end of the operation
  206. console.log(`Batch deletion started: ${ids.length} tasks total`)
  207. for (let i = 0; i < ids.length; i += batchSize) {
  208. const batch = ids.slice(i, i + batchSize)
  209. const batchPromises = batch.map(async (id) => {
  210. try {
  211. await provider.deleteTaskWithId(id)
  212. return { id, success: true }
  213. } catch (error) {
  214. // Keep error logging for debugging purposes
  215. console.log(
  216. `Failed to delete task ${id}: ${error instanceof Error ? error.message : String(error)}`,
  217. )
  218. return { id, success: false }
  219. }
  220. })
  221. // Process each batch in parallel but wait for completion before starting the next batch
  222. const batchResults = await Promise.all(batchPromises)
  223. results.push(...batchResults)
  224. // Update the UI after each batch to show progress
  225. await provider.postStateToWebview()
  226. }
  227. // Log final results
  228. const successCount = results.filter((r) => r.success).length
  229. const failCount = results.length - successCount
  230. console.log(
  231. `Batch deletion completed: ${successCount}/${ids.length} tasks successful, ${failCount} tasks failed`,
  232. )
  233. }
  234. break
  235. }
  236. case "exportTaskWithId":
  237. provider.exportTaskWithId(message.text!)
  238. break
  239. case "importSettings": {
  240. const result = await importSettings({
  241. providerSettingsManager: provider.providerSettingsManager,
  242. contextProxy: provider.contextProxy,
  243. customModesManager: provider.customModesManager,
  244. })
  245. if (result.success) {
  246. provider.settingsImportedAt = Date.now()
  247. await provider.postStateToWebview()
  248. await vscode.window.showInformationMessage(t("common:info.settings_imported"))
  249. } else if (result.error) {
  250. await vscode.window.showErrorMessage(t("common:errors.settings_import_failed", { error: result.error }))
  251. }
  252. break
  253. }
  254. case "exportSettings":
  255. await exportSettings({
  256. providerSettingsManager: provider.providerSettingsManager,
  257. contextProxy: provider.contextProxy,
  258. })
  259. break
  260. case "resetState":
  261. await provider.resetState()
  262. break
  263. case "flushRouterModels":
  264. const routerNameFlush: RouterName = toRouterName(message.text)
  265. await flushModels(routerNameFlush)
  266. break
  267. case "requestRouterModels":
  268. const { apiConfiguration } = await provider.getState()
  269. const routerModels: Partial<Record<RouterName, ModelRecord>> = {
  270. openrouter: {},
  271. requesty: {},
  272. glama: {},
  273. unbound: {},
  274. litellm: {},
  275. }
  276. const safeGetModels = async (options: GetModelsOptions): Promise<ModelRecord> => {
  277. try {
  278. return await getModels(options)
  279. } catch (error) {
  280. console.error(
  281. `Failed to fetch models in webviewMessageHandler requestRouterModels for ${options.provider}:`,
  282. error,
  283. )
  284. throw error // Re-throw to be caught by Promise.allSettled
  285. }
  286. }
  287. const modelFetchPromises: Array<{ key: RouterName; options: GetModelsOptions }> = [
  288. { key: "openrouter", options: { provider: "openrouter" } },
  289. { key: "requesty", options: { provider: "requesty", apiKey: apiConfiguration.requestyApiKey } },
  290. { key: "glama", options: { provider: "glama" } },
  291. { key: "unbound", options: { provider: "unbound", apiKey: apiConfiguration.unboundApiKey } },
  292. ]
  293. const litellmApiKey = apiConfiguration.litellmApiKey || message?.values?.litellmApiKey
  294. const litellmBaseUrl = apiConfiguration.litellmBaseUrl || message?.values?.litellmBaseUrl
  295. if (litellmApiKey && litellmBaseUrl) {
  296. modelFetchPromises.push({
  297. key: "litellm",
  298. options: { provider: "litellm", apiKey: litellmApiKey, baseUrl: litellmBaseUrl },
  299. })
  300. }
  301. const results = await Promise.allSettled(
  302. modelFetchPromises.map(async ({ key, options }) => {
  303. const models = await safeGetModels(options)
  304. return { key, models } // key is RouterName here
  305. }),
  306. )
  307. const fetchedRouterModels: Partial<Record<RouterName, ModelRecord>> = { ...routerModels }
  308. results.forEach((result, index) => {
  309. const routerName = modelFetchPromises[index].key // Get RouterName using index
  310. if (result.status === "fulfilled") {
  311. fetchedRouterModels[routerName] = result.value.models
  312. } else {
  313. // Handle rejection: Post a specific error message for this provider
  314. const errorMessage = result.reason instanceof Error ? result.reason.message : String(result.reason)
  315. console.error(`Error fetching models for ${routerName}:`, result.reason)
  316. fetchedRouterModels[routerName] = {} // Ensure it's an empty object in the main routerModels message
  317. provider.postMessageToWebview({
  318. type: "singleRouterModelFetchResponse",
  319. success: false,
  320. error: errorMessage,
  321. values: { provider: routerName },
  322. })
  323. }
  324. })
  325. provider.postMessageToWebview({
  326. type: "routerModels",
  327. routerModels: fetchedRouterModels as Record<RouterName, ModelRecord>,
  328. })
  329. break
  330. case "requestOpenAiModels":
  331. if (message?.values?.baseUrl && message?.values?.apiKey) {
  332. const openAiModels = await getOpenAiModels(
  333. message?.values?.baseUrl,
  334. message?.values?.apiKey,
  335. message?.values?.openAiHeaders,
  336. )
  337. provider.postMessageToWebview({ type: "openAiModels", openAiModels })
  338. }
  339. break
  340. case "requestOllamaModels":
  341. const ollamaModels = await getOllamaModels(message.text)
  342. // TODO: Cache like we do for OpenRouter, etc?
  343. provider.postMessageToWebview({ type: "ollamaModels", ollamaModels })
  344. break
  345. case "requestLmStudioModels":
  346. const lmStudioModels = await getLmStudioModels(message.text)
  347. // TODO: Cache like we do for OpenRouter, etc?
  348. provider.postMessageToWebview({ type: "lmStudioModels", lmStudioModels })
  349. break
  350. case "requestVsCodeLmModels":
  351. const vsCodeLmModels = await getVsCodeLmModels()
  352. // TODO: Cache like we do for OpenRouter, etc?
  353. provider.postMessageToWebview({ type: "vsCodeLmModels", vsCodeLmModels })
  354. break
  355. case "openImage":
  356. openImage(message.text!)
  357. break
  358. case "openFile":
  359. openFile(message.text!, message.values as { create?: boolean; content?: string; line?: number })
  360. break
  361. case "openMention":
  362. openMention(message.text)
  363. break
  364. case "checkpointDiff":
  365. const result = checkoutDiffPayloadSchema.safeParse(message.payload)
  366. if (result.success) {
  367. await provider.getCurrentCline()?.checkpointDiff(result.data)
  368. }
  369. break
  370. case "checkpointRestore": {
  371. const result = checkoutRestorePayloadSchema.safeParse(message.payload)
  372. if (result.success) {
  373. await provider.cancelTask()
  374. try {
  375. await pWaitFor(() => provider.getCurrentCline()?.isInitialized === true, { timeout: 3_000 })
  376. } catch (error) {
  377. vscode.window.showErrorMessage(t("common:errors.checkpoint_timeout"))
  378. }
  379. try {
  380. await provider.getCurrentCline()?.checkpointRestore(result.data)
  381. } catch (error) {
  382. vscode.window.showErrorMessage(t("common:errors.checkpoint_failed"))
  383. }
  384. }
  385. break
  386. }
  387. case "cancelTask":
  388. await provider.cancelTask()
  389. break
  390. case "allowedCommands":
  391. await provider.context.globalState.update("allowedCommands", message.commands)
  392. // Also update workspace settings.
  393. await vscode.workspace
  394. .getConfiguration(Package.name)
  395. .update("allowedCommands", message.commands, vscode.ConfigurationTarget.Global)
  396. break
  397. case "openCustomModesSettings": {
  398. const customModesFilePath = await provider.customModesManager.getCustomModesFilePath()
  399. if (customModesFilePath) {
  400. openFile(customModesFilePath)
  401. }
  402. break
  403. }
  404. case "openMcpSettings": {
  405. const mcpSettingsFilePath = await provider.getMcpHub()?.getMcpSettingsFilePath()
  406. if (mcpSettingsFilePath) {
  407. openFile(mcpSettingsFilePath)
  408. }
  409. break
  410. }
  411. case "openProjectMcpSettings": {
  412. if (!vscode.workspace.workspaceFolders?.length) {
  413. vscode.window.showErrorMessage(t("common:errors.no_workspace"))
  414. return
  415. }
  416. const workspaceFolder = vscode.workspace.workspaceFolders[0]
  417. const rooDir = path.join(workspaceFolder.uri.fsPath, ".roo")
  418. const mcpPath = path.join(rooDir, "mcp.json")
  419. try {
  420. await fs.mkdir(rooDir, { recursive: true })
  421. const exists = await fileExistsAtPath(mcpPath)
  422. if (!exists) {
  423. await fs.writeFile(mcpPath, JSON.stringify({ mcpServers: {} }, null, 2))
  424. }
  425. await openFile(mcpPath)
  426. } catch (error) {
  427. vscode.window.showErrorMessage(t("common:errors.create_mcp_json", { error: `${error}` }))
  428. }
  429. break
  430. }
  431. case "deleteMcpServer": {
  432. if (!message.serverName) {
  433. break
  434. }
  435. try {
  436. provider.log(`Attempting to delete MCP server: ${message.serverName}`)
  437. await provider.getMcpHub()?.deleteServer(message.serverName, message.source as "global" | "project")
  438. provider.log(`Successfully deleted MCP server: ${message.serverName}`)
  439. } catch (error) {
  440. const errorMessage = error instanceof Error ? error.message : String(error)
  441. provider.log(`Failed to delete MCP server: ${errorMessage}`)
  442. // Error messages are already handled by McpHub.deleteServer
  443. }
  444. break
  445. }
  446. case "restartMcpServer": {
  447. try {
  448. await provider.getMcpHub()?.restartConnection(message.text!, message.source as "global" | "project")
  449. } catch (error) {
  450. provider.log(
  451. `Failed to retry connection for ${message.text}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  452. )
  453. }
  454. break
  455. }
  456. case "toggleToolAlwaysAllow": {
  457. try {
  458. await provider
  459. .getMcpHub()
  460. ?.toggleToolAlwaysAllow(
  461. message.serverName!,
  462. message.source as "global" | "project",
  463. message.toolName!,
  464. Boolean(message.alwaysAllow),
  465. )
  466. } catch (error) {
  467. provider.log(
  468. `Failed to toggle auto-approve for tool ${message.toolName}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  469. )
  470. }
  471. break
  472. }
  473. case "toggleMcpServer": {
  474. try {
  475. await provider
  476. .getMcpHub()
  477. ?.toggleServerDisabled(
  478. message.serverName!,
  479. message.disabled!,
  480. message.source as "global" | "project",
  481. )
  482. } catch (error) {
  483. provider.log(
  484. `Failed to toggle MCP server ${message.serverName}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  485. )
  486. }
  487. break
  488. }
  489. case "mcpEnabled":
  490. const mcpEnabled = message.bool ?? true
  491. await updateGlobalState("mcpEnabled", mcpEnabled)
  492. await provider.postStateToWebview()
  493. break
  494. case "enableMcpServerCreation":
  495. await updateGlobalState("enableMcpServerCreation", message.bool ?? true)
  496. await provider.postStateToWebview()
  497. break
  498. // playSound handler removed - now handled directly in the webview
  499. case "soundEnabled":
  500. const soundEnabled = message.bool ?? true
  501. await updateGlobalState("soundEnabled", soundEnabled)
  502. await provider.postStateToWebview()
  503. break
  504. case "soundVolume":
  505. const soundVolume = message.value ?? 0.5
  506. await updateGlobalState("soundVolume", soundVolume)
  507. await provider.postStateToWebview()
  508. break
  509. case "ttsEnabled":
  510. const ttsEnabled = message.bool ?? true
  511. await updateGlobalState("ttsEnabled", ttsEnabled)
  512. setTtsEnabled(ttsEnabled) // Add this line to update the tts utility
  513. await provider.postStateToWebview()
  514. break
  515. case "ttsSpeed":
  516. const ttsSpeed = message.value ?? 1.0
  517. await updateGlobalState("ttsSpeed", ttsSpeed)
  518. setTtsSpeed(ttsSpeed)
  519. await provider.postStateToWebview()
  520. break
  521. case "playTts":
  522. if (message.text) {
  523. playTts(message.text, {
  524. onStart: () => provider.postMessageToWebview({ type: "ttsStart", text: message.text }),
  525. onStop: () => provider.postMessageToWebview({ type: "ttsStop", text: message.text }),
  526. })
  527. }
  528. break
  529. case "stopTts":
  530. stopTts()
  531. break
  532. case "diffEnabled":
  533. const diffEnabled = message.bool ?? true
  534. await updateGlobalState("diffEnabled", diffEnabled)
  535. await provider.postStateToWebview()
  536. break
  537. case "enableCheckpoints":
  538. const enableCheckpoints = message.bool ?? true
  539. await updateGlobalState("enableCheckpoints", enableCheckpoints)
  540. await provider.postStateToWebview()
  541. break
  542. case "browserViewportSize":
  543. const browserViewportSize = message.text ?? "900x600"
  544. await updateGlobalState("browserViewportSize", browserViewportSize)
  545. await provider.postStateToWebview()
  546. break
  547. case "remoteBrowserHost":
  548. await updateGlobalState("remoteBrowserHost", message.text)
  549. await provider.postStateToWebview()
  550. break
  551. case "remoteBrowserEnabled":
  552. // Store the preference in global state
  553. // remoteBrowserEnabled now means "enable remote browser connection"
  554. await updateGlobalState("remoteBrowserEnabled", message.bool ?? false)
  555. // If disabling remote browser connection, clear the remoteBrowserHost
  556. if (!message.bool) {
  557. await updateGlobalState("remoteBrowserHost", undefined)
  558. }
  559. await provider.postStateToWebview()
  560. break
  561. case "testBrowserConnection":
  562. // If no text is provided, try auto-discovery
  563. if (!message.text) {
  564. // Use testBrowserConnection for auto-discovery
  565. const chromeHostUrl = await discoverChromeHostUrl()
  566. if (chromeHostUrl) {
  567. // Send the result back to the webview
  568. await provider.postMessageToWebview({
  569. type: "browserConnectionResult",
  570. success: !!chromeHostUrl,
  571. text: `Auto-discovered and tested connection to Chrome: ${chromeHostUrl}`,
  572. values: { endpoint: chromeHostUrl },
  573. })
  574. } else {
  575. await provider.postMessageToWebview({
  576. type: "browserConnectionResult",
  577. success: false,
  578. text: "No Chrome instances found on the network. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).",
  579. })
  580. }
  581. } else {
  582. // Test the provided URL
  583. const customHostUrl = message.text
  584. const hostIsValid = await tryChromeHostUrl(message.text)
  585. // Send the result back to the webview
  586. await provider.postMessageToWebview({
  587. type: "browserConnectionResult",
  588. success: hostIsValid,
  589. text: hostIsValid
  590. ? `Successfully connected to Chrome: ${customHostUrl}`
  591. : "Failed to connect to Chrome",
  592. })
  593. }
  594. break
  595. case "fuzzyMatchThreshold":
  596. await updateGlobalState("fuzzyMatchThreshold", message.value)
  597. await provider.postStateToWebview()
  598. break
  599. case "updateVSCodeSetting": {
  600. const { setting, value } = message
  601. if (setting !== undefined && value !== undefined) {
  602. if (ALLOWED_VSCODE_SETTINGS.has(setting)) {
  603. await vscode.workspace.getConfiguration().update(setting, value, true)
  604. } else {
  605. vscode.window.showErrorMessage(`Cannot update restricted VSCode setting: ${setting}`)
  606. }
  607. }
  608. break
  609. }
  610. case "getVSCodeSetting":
  611. const { setting } = message
  612. if (setting) {
  613. try {
  614. await provider.postMessageToWebview({
  615. type: "vsCodeSetting",
  616. setting,
  617. value: vscode.workspace.getConfiguration().get(setting),
  618. })
  619. } catch (error) {
  620. console.error(`Failed to get VSCode setting ${message.setting}:`, error)
  621. await provider.postMessageToWebview({
  622. type: "vsCodeSetting",
  623. setting,
  624. error: `Failed to get setting: ${error.message}`,
  625. value: undefined,
  626. })
  627. }
  628. }
  629. break
  630. case "alwaysApproveResubmit":
  631. await updateGlobalState("alwaysApproveResubmit", message.bool ?? false)
  632. await provider.postStateToWebview()
  633. break
  634. case "requestDelaySeconds":
  635. await updateGlobalState("requestDelaySeconds", message.value ?? 5)
  636. await provider.postStateToWebview()
  637. break
  638. case "writeDelayMs":
  639. await updateGlobalState("writeDelayMs", message.value)
  640. await provider.postStateToWebview()
  641. break
  642. case "terminalOutputLineLimit":
  643. await updateGlobalState("terminalOutputLineLimit", message.value)
  644. await provider.postStateToWebview()
  645. break
  646. case "terminalShellIntegrationTimeout":
  647. await updateGlobalState("terminalShellIntegrationTimeout", message.value)
  648. await provider.postStateToWebview()
  649. if (message.value !== undefined) {
  650. Terminal.setShellIntegrationTimeout(message.value)
  651. }
  652. break
  653. case "terminalShellIntegrationDisabled":
  654. await updateGlobalState("terminalShellIntegrationDisabled", message.bool)
  655. await provider.postStateToWebview()
  656. if (message.bool !== undefined) {
  657. Terminal.setShellIntegrationDisabled(message.bool)
  658. }
  659. break
  660. case "terminalCommandDelay":
  661. await updateGlobalState("terminalCommandDelay", message.value)
  662. await provider.postStateToWebview()
  663. if (message.value !== undefined) {
  664. Terminal.setCommandDelay(message.value)
  665. }
  666. break
  667. case "terminalPowershellCounter":
  668. await updateGlobalState("terminalPowershellCounter", message.bool)
  669. await provider.postStateToWebview()
  670. if (message.bool !== undefined) {
  671. Terminal.setPowershellCounter(message.bool)
  672. }
  673. break
  674. case "terminalZshClearEolMark":
  675. await updateGlobalState("terminalZshClearEolMark", message.bool)
  676. await provider.postStateToWebview()
  677. if (message.bool !== undefined) {
  678. Terminal.setTerminalZshClearEolMark(message.bool)
  679. }
  680. break
  681. case "terminalZshOhMy":
  682. await updateGlobalState("terminalZshOhMy", message.bool)
  683. await provider.postStateToWebview()
  684. if (message.bool !== undefined) {
  685. Terminal.setTerminalZshOhMy(message.bool)
  686. }
  687. break
  688. case "terminalZshP10k":
  689. await updateGlobalState("terminalZshP10k", message.bool)
  690. await provider.postStateToWebview()
  691. if (message.bool !== undefined) {
  692. Terminal.setTerminalZshP10k(message.bool)
  693. }
  694. break
  695. case "terminalZdotdir":
  696. await updateGlobalState("terminalZdotdir", message.bool)
  697. await provider.postStateToWebview()
  698. if (message.bool !== undefined) {
  699. Terminal.setTerminalZdotdir(message.bool)
  700. }
  701. break
  702. case "terminalCompressProgressBar":
  703. await updateGlobalState("terminalCompressProgressBar", message.bool)
  704. await provider.postStateToWebview()
  705. if (message.bool !== undefined) {
  706. Terminal.setCompressProgressBar(message.bool)
  707. }
  708. break
  709. case "mode":
  710. await provider.handleModeSwitch(message.text as Mode)
  711. break
  712. case "updateSupportPrompt":
  713. try {
  714. if (Object.keys(message?.values ?? {}).length === 0) {
  715. return
  716. }
  717. const existingPrompts = getGlobalState("customSupportPrompts") ?? {}
  718. const updatedPrompts = { ...existingPrompts, ...message.values }
  719. await updateGlobalState("customSupportPrompts", updatedPrompts)
  720. await provider.postStateToWebview()
  721. } catch (error) {
  722. provider.log(
  723. `Error update support prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  724. )
  725. vscode.window.showErrorMessage(t("common:errors.update_support_prompt"))
  726. }
  727. break
  728. case "resetSupportPrompt":
  729. try {
  730. if (!message?.text) {
  731. return
  732. }
  733. const existingPrompts = getGlobalState("customSupportPrompts") ?? {}
  734. const updatedPrompts = { ...existingPrompts }
  735. updatedPrompts[message.text] = undefined
  736. await updateGlobalState("customSupportPrompts", updatedPrompts)
  737. await provider.postStateToWebview()
  738. } catch (error) {
  739. provider.log(
  740. `Error reset support prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  741. )
  742. vscode.window.showErrorMessage(t("common:errors.reset_support_prompt"))
  743. }
  744. break
  745. case "updatePrompt":
  746. if (message.promptMode && message.customPrompt !== undefined) {
  747. const existingPrompts = getGlobalState("customModePrompts") ?? {}
  748. const updatedPrompts = { ...existingPrompts, [message.promptMode]: message.customPrompt }
  749. await updateGlobalState("customModePrompts", updatedPrompts)
  750. const currentState = await provider.getStateToPostToWebview()
  751. const stateWithPrompts = { ...currentState, customModePrompts: updatedPrompts }
  752. provider.postMessageToWebview({ type: "state", state: stateWithPrompts })
  753. }
  754. break
  755. case "deleteMessage": {
  756. const answer = await vscode.window.showInformationMessage(
  757. t("common:confirmation.delete_message"),
  758. { modal: true },
  759. t("common:confirmation.just_this_message"),
  760. t("common:confirmation.this_and_subsequent"),
  761. )
  762. if (
  763. (answer === t("common:confirmation.just_this_message") ||
  764. answer === t("common:confirmation.this_and_subsequent")) &&
  765. provider.getCurrentCline() &&
  766. typeof message.value === "number" &&
  767. message.value
  768. ) {
  769. const timeCutoff = message.value - 1000 // 1 second buffer before the message to delete
  770. const messageIndex = provider
  771. .getCurrentCline()!
  772. .clineMessages.findIndex((msg) => msg.ts && msg.ts >= timeCutoff)
  773. const apiConversationHistoryIndex = provider
  774. .getCurrentCline()
  775. ?.apiConversationHistory.findIndex((msg) => msg.ts && msg.ts >= timeCutoff)
  776. if (messageIndex !== -1) {
  777. const { historyItem } = await provider.getTaskWithId(provider.getCurrentCline()!.taskId)
  778. if (answer === t("common:confirmation.just_this_message")) {
  779. // Find the next user message first
  780. const nextUserMessage = provider
  781. .getCurrentCline()!
  782. .clineMessages.slice(messageIndex + 1)
  783. .find((msg) => msg.type === "say" && msg.say === "user_feedback")
  784. // Handle UI messages
  785. if (nextUserMessage) {
  786. // Find absolute index of next user message
  787. const nextUserMessageIndex = provider
  788. .getCurrentCline()!
  789. .clineMessages.findIndex((msg) => msg === nextUserMessage)
  790. // Keep messages before current message and after next user message
  791. await provider
  792. .getCurrentCline()!
  793. .overwriteClineMessages([
  794. ...provider.getCurrentCline()!.clineMessages.slice(0, messageIndex),
  795. ...provider.getCurrentCline()!.clineMessages.slice(nextUserMessageIndex),
  796. ])
  797. } else {
  798. // If no next user message, keep only messages before current message
  799. await provider
  800. .getCurrentCline()!
  801. .overwriteClineMessages(
  802. provider.getCurrentCline()!.clineMessages.slice(0, messageIndex),
  803. )
  804. }
  805. // Handle API messages
  806. if (apiConversationHistoryIndex !== -1) {
  807. if (nextUserMessage && nextUserMessage.ts) {
  808. // Keep messages before current API message and after next user message
  809. await provider
  810. .getCurrentCline()!
  811. .overwriteApiConversationHistory([
  812. ...provider
  813. .getCurrentCline()!
  814. .apiConversationHistory.slice(0, apiConversationHistoryIndex),
  815. ...provider
  816. .getCurrentCline()!
  817. .apiConversationHistory.filter(
  818. (msg) => msg.ts && msg.ts >= nextUserMessage.ts,
  819. ),
  820. ])
  821. } else {
  822. // If no next user message, keep only messages before current API message
  823. await provider
  824. .getCurrentCline()!
  825. .overwriteApiConversationHistory(
  826. provider
  827. .getCurrentCline()!
  828. .apiConversationHistory.slice(0, apiConversationHistoryIndex),
  829. )
  830. }
  831. }
  832. } else if (answer === t("common:confirmation.this_and_subsequent")) {
  833. // Delete this message and all that follow
  834. await provider
  835. .getCurrentCline()!
  836. .overwriteClineMessages(provider.getCurrentCline()!.clineMessages.slice(0, messageIndex))
  837. if (apiConversationHistoryIndex !== -1) {
  838. await provider
  839. .getCurrentCline()!
  840. .overwriteApiConversationHistory(
  841. provider
  842. .getCurrentCline()!
  843. .apiConversationHistory.slice(0, apiConversationHistoryIndex),
  844. )
  845. }
  846. }
  847. await provider.initClineWithHistoryItem(historyItem)
  848. }
  849. }
  850. break
  851. }
  852. case "screenshotQuality":
  853. await updateGlobalState("screenshotQuality", message.value)
  854. await provider.postStateToWebview()
  855. break
  856. case "maxOpenTabsContext":
  857. const tabCount = Math.min(Math.max(0, message.value ?? 20), 500)
  858. await updateGlobalState("maxOpenTabsContext", tabCount)
  859. await provider.postStateToWebview()
  860. break
  861. case "maxWorkspaceFiles":
  862. const fileCount = Math.min(Math.max(0, message.value ?? 200), 500)
  863. await updateGlobalState("maxWorkspaceFiles", fileCount)
  864. await provider.postStateToWebview()
  865. break
  866. case "browserToolEnabled":
  867. await updateGlobalState("browserToolEnabled", message.bool ?? true)
  868. await provider.postStateToWebview()
  869. break
  870. case "language":
  871. changeLanguage(message.text ?? "en")
  872. await updateGlobalState("language", message.text as Language)
  873. await provider.postStateToWebview()
  874. break
  875. case "showRooIgnoredFiles":
  876. await updateGlobalState("showRooIgnoredFiles", message.bool ?? true)
  877. await provider.postStateToWebview()
  878. break
  879. case "maxReadFileLine":
  880. await updateGlobalState("maxReadFileLine", message.value)
  881. await provider.postStateToWebview()
  882. break
  883. case "setHistoryPreviewCollapsed": // Add the new case handler
  884. await updateGlobalState("historyPreviewCollapsed", message.bool ?? false)
  885. // No need to call postStateToWebview here as the UI already updated optimistically
  886. break
  887. case "toggleApiConfigPin":
  888. if (message.text) {
  889. const currentPinned = getGlobalState("pinnedApiConfigs") ?? {}
  890. const updatedPinned: Record<string, boolean> = { ...currentPinned }
  891. if (currentPinned[message.text]) {
  892. delete updatedPinned[message.text]
  893. } else {
  894. updatedPinned[message.text] = true
  895. }
  896. await updateGlobalState("pinnedApiConfigs", updatedPinned)
  897. await provider.postStateToWebview()
  898. }
  899. break
  900. case "enhancementApiConfigId":
  901. await updateGlobalState("enhancementApiConfigId", message.text)
  902. await provider.postStateToWebview()
  903. break
  904. case "condensingApiConfigId":
  905. await updateGlobalState("condensingApiConfigId", message.text)
  906. await provider.postStateToWebview()
  907. break
  908. case "updateCondensingPrompt":
  909. await updateGlobalState("customCondensingPrompt", message.text)
  910. await provider.postStateToWebview()
  911. break
  912. case "autoApprovalEnabled":
  913. await updateGlobalState("autoApprovalEnabled", message.bool ?? false)
  914. await provider.postStateToWebview()
  915. break
  916. case "enhancePrompt":
  917. if (message.text) {
  918. try {
  919. const { apiConfiguration, customSupportPrompts, listApiConfigMeta, enhancementApiConfigId } =
  920. await provider.getState()
  921. // Try to get enhancement config first, fall back to current config.
  922. let configToUse: ProviderSettings = apiConfiguration
  923. if (enhancementApiConfigId && !!listApiConfigMeta.find(({ id }) => id === enhancementApiConfigId)) {
  924. const { name: _, ...providerSettings } = await provider.providerSettingsManager.getProfile({
  925. id: enhancementApiConfigId,
  926. })
  927. if (providerSettings.apiProvider) {
  928. configToUse = providerSettings
  929. }
  930. }
  931. const enhancedPrompt = await singleCompletionHandler(
  932. configToUse,
  933. supportPrompt.create("ENHANCE", { userInput: message.text }, customSupportPrompts),
  934. )
  935. // Capture telemetry for prompt enhancement.
  936. const currentCline = provider.getCurrentCline()
  937. TelemetryService.instance.capturePromptEnhanced(currentCline?.taskId)
  938. await provider.postMessageToWebview({ type: "enhancedPrompt", text: enhancedPrompt })
  939. } catch (error) {
  940. provider.log(
  941. `Error enhancing prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  942. )
  943. vscode.window.showErrorMessage(t("common:errors.enhance_prompt"))
  944. await provider.postMessageToWebview({ type: "enhancedPrompt" })
  945. }
  946. }
  947. break
  948. case "getSystemPrompt":
  949. try {
  950. const systemPrompt = await generateSystemPrompt(provider, message)
  951. await provider.postMessageToWebview({
  952. type: "systemPrompt",
  953. text: systemPrompt,
  954. mode: message.mode,
  955. })
  956. } catch (error) {
  957. provider.log(
  958. `Error getting system prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  959. )
  960. vscode.window.showErrorMessage(t("common:errors.get_system_prompt"))
  961. }
  962. break
  963. case "copySystemPrompt":
  964. try {
  965. const systemPrompt = await generateSystemPrompt(provider, message)
  966. await vscode.env.clipboard.writeText(systemPrompt)
  967. await vscode.window.showInformationMessage(t("common:info.clipboard_copy"))
  968. } catch (error) {
  969. provider.log(
  970. `Error getting system prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  971. )
  972. vscode.window.showErrorMessage(t("common:errors.get_system_prompt"))
  973. }
  974. break
  975. case "searchCommits": {
  976. const cwd = provider.cwd
  977. if (cwd) {
  978. try {
  979. const commits = await searchCommits(message.query || "", cwd)
  980. await provider.postMessageToWebview({
  981. type: "commitSearchResults",
  982. commits,
  983. })
  984. } catch (error) {
  985. provider.log(
  986. `Error searching commits: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  987. )
  988. vscode.window.showErrorMessage(t("common:errors.search_commits"))
  989. }
  990. }
  991. break
  992. }
  993. case "searchFiles": {
  994. const workspacePath = getWorkspacePath()
  995. if (!workspacePath) {
  996. // Handle case where workspace path is not available
  997. await provider.postMessageToWebview({
  998. type: "fileSearchResults",
  999. results: [],
  1000. requestId: message.requestId,
  1001. error: "No workspace path available",
  1002. })
  1003. break
  1004. }
  1005. try {
  1006. // Call file search service with query from message
  1007. const results = await searchWorkspaceFiles(
  1008. message.query || "",
  1009. workspacePath,
  1010. 20, // Use default limit, as filtering is now done in the backend
  1011. )
  1012. // Send results back to webview
  1013. await provider.postMessageToWebview({
  1014. type: "fileSearchResults",
  1015. results,
  1016. requestId: message.requestId,
  1017. })
  1018. } catch (error) {
  1019. const errorMessage = error instanceof Error ? error.message : String(error)
  1020. // Send error response to webview
  1021. await provider.postMessageToWebview({
  1022. type: "fileSearchResults",
  1023. results: [],
  1024. error: errorMessage,
  1025. requestId: message.requestId,
  1026. })
  1027. }
  1028. break
  1029. }
  1030. case "saveApiConfiguration":
  1031. if (message.text && message.apiConfiguration) {
  1032. try {
  1033. await provider.providerSettingsManager.saveConfig(message.text, message.apiConfiguration)
  1034. const listApiConfig = await provider.providerSettingsManager.listConfig()
  1035. await updateGlobalState("listApiConfigMeta", listApiConfig)
  1036. } catch (error) {
  1037. provider.log(
  1038. `Error save api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1039. )
  1040. vscode.window.showErrorMessage(t("common:errors.save_api_config"))
  1041. }
  1042. }
  1043. break
  1044. case "upsertApiConfiguration":
  1045. if (message.text && message.apiConfiguration) {
  1046. await provider.upsertProviderProfile(message.text, message.apiConfiguration)
  1047. }
  1048. break
  1049. case "renameApiConfiguration":
  1050. if (message.values && message.apiConfiguration) {
  1051. try {
  1052. const { oldName, newName } = message.values
  1053. if (oldName === newName) {
  1054. break
  1055. }
  1056. // Load the old configuration to get its ID.
  1057. const { id } = await provider.providerSettingsManager.getProfile({ name: oldName })
  1058. // Create a new configuration with the new name and old ID.
  1059. await provider.providerSettingsManager.saveConfig(newName, { ...message.apiConfiguration, id })
  1060. // Delete the old configuration.
  1061. await provider.providerSettingsManager.deleteConfig(oldName)
  1062. // Re-activate to update the global settings related to the
  1063. // currently activated provider profile.
  1064. await provider.activateProviderProfile({ name: newName })
  1065. } catch (error) {
  1066. provider.log(
  1067. `Error rename api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1068. )
  1069. vscode.window.showErrorMessage(t("common:errors.rename_api_config"))
  1070. }
  1071. }
  1072. break
  1073. case "loadApiConfiguration":
  1074. if (message.text) {
  1075. try {
  1076. await provider.activateProviderProfile({ name: message.text })
  1077. } catch (error) {
  1078. provider.log(
  1079. `Error load api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1080. )
  1081. vscode.window.showErrorMessage(t("common:errors.load_api_config"))
  1082. }
  1083. }
  1084. break
  1085. case "loadApiConfigurationById":
  1086. if (message.text) {
  1087. try {
  1088. await provider.activateProviderProfile({ id: message.text })
  1089. } catch (error) {
  1090. provider.log(
  1091. `Error load api configuration by ID: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1092. )
  1093. vscode.window.showErrorMessage(t("common:errors.load_api_config"))
  1094. }
  1095. }
  1096. break
  1097. case "deleteApiConfiguration":
  1098. if (message.text) {
  1099. const answer = await vscode.window.showInformationMessage(
  1100. t("common:confirmation.delete_config_profile"),
  1101. { modal: true },
  1102. t("common:answers.yes"),
  1103. )
  1104. if (answer !== t("common:answers.yes")) {
  1105. break
  1106. }
  1107. const oldName = message.text
  1108. const newName = (await provider.providerSettingsManager.listConfig()).filter(
  1109. (c) => c.name !== oldName,
  1110. )[0]?.name
  1111. if (!newName) {
  1112. vscode.window.showErrorMessage(t("common:errors.delete_api_config"))
  1113. return
  1114. }
  1115. try {
  1116. await provider.providerSettingsManager.deleteConfig(oldName)
  1117. await provider.activateProviderProfile({ name: newName })
  1118. } catch (error) {
  1119. provider.log(
  1120. `Error delete api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1121. )
  1122. vscode.window.showErrorMessage(t("common:errors.delete_api_config"))
  1123. }
  1124. }
  1125. break
  1126. case "getListApiConfiguration":
  1127. try {
  1128. const listApiConfig = await provider.providerSettingsManager.listConfig()
  1129. await updateGlobalState("listApiConfigMeta", listApiConfig)
  1130. provider.postMessageToWebview({ type: "listApiConfig", listApiConfig })
  1131. } catch (error) {
  1132. provider.log(
  1133. `Error get list api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1134. )
  1135. vscode.window.showErrorMessage(t("common:errors.list_api_config"))
  1136. }
  1137. break
  1138. case "updateExperimental": {
  1139. if (!message.values) {
  1140. break
  1141. }
  1142. const updatedExperiments = {
  1143. ...(getGlobalState("experiments") ?? experimentDefault),
  1144. ...message.values,
  1145. }
  1146. await updateGlobalState("experiments", updatedExperiments)
  1147. await provider.postStateToWebview()
  1148. break
  1149. }
  1150. case "updateMcpTimeout":
  1151. if (message.serverName && typeof message.timeout === "number") {
  1152. try {
  1153. await provider
  1154. .getMcpHub()
  1155. ?.updateServerTimeout(
  1156. message.serverName,
  1157. message.timeout,
  1158. message.source as "global" | "project",
  1159. )
  1160. } catch (error) {
  1161. provider.log(
  1162. `Failed to update timeout for ${message.serverName}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1163. )
  1164. vscode.window.showErrorMessage(t("common:errors.update_server_timeout"))
  1165. }
  1166. }
  1167. break
  1168. case "updateCustomMode":
  1169. if (message.modeConfig) {
  1170. await provider.customModesManager.updateCustomMode(message.modeConfig.slug, message.modeConfig)
  1171. // Update state after saving the mode
  1172. const customModes = await provider.customModesManager.getCustomModes()
  1173. await updateGlobalState("customModes", customModes)
  1174. await updateGlobalState("mode", message.modeConfig.slug)
  1175. await provider.postStateToWebview()
  1176. }
  1177. break
  1178. case "deleteCustomMode":
  1179. if (message.slug) {
  1180. const answer = await vscode.window.showInformationMessage(
  1181. t("common:confirmation.delete_custom_mode"),
  1182. { modal: true },
  1183. t("common:answers.yes"),
  1184. )
  1185. if (answer !== t("common:answers.yes")) {
  1186. break
  1187. }
  1188. await provider.customModesManager.deleteCustomMode(message.slug)
  1189. // Switch back to default mode after deletion
  1190. await updateGlobalState("mode", defaultModeSlug)
  1191. await provider.postStateToWebview()
  1192. }
  1193. break
  1194. case "humanRelayResponse":
  1195. if (message.requestId && message.text) {
  1196. vscode.commands.executeCommand(getCommand("handleHumanRelayResponse"), {
  1197. requestId: message.requestId,
  1198. text: message.text,
  1199. cancelled: false,
  1200. })
  1201. }
  1202. break
  1203. case "humanRelayCancel":
  1204. if (message.requestId) {
  1205. vscode.commands.executeCommand(getCommand("handleHumanRelayResponse"), {
  1206. requestId: message.requestId,
  1207. cancelled: true,
  1208. })
  1209. }
  1210. break
  1211. case "telemetrySetting": {
  1212. const telemetrySetting = message.text as TelemetrySetting
  1213. await updateGlobalState("telemetrySetting", telemetrySetting)
  1214. const isOptedIn = telemetrySetting === "enabled"
  1215. TelemetryService.instance.updateTelemetryState(isOptedIn)
  1216. await provider.postStateToWebview()
  1217. break
  1218. }
  1219. case "accountButtonClicked": {
  1220. // Navigate to the account tab.
  1221. provider.postMessageToWebview({ type: "action", action: "accountButtonClicked" })
  1222. break
  1223. }
  1224. case "rooCloudSignIn": {
  1225. try {
  1226. TelemetryService.instance.captureEvent(TelemetryEventName.AUTHENTICATION_INITIATED)
  1227. await CloudService.instance.login()
  1228. } catch (error) {
  1229. provider.log(`AuthService#login failed: ${error}`)
  1230. vscode.window.showErrorMessage("Sign in failed.")
  1231. }
  1232. break
  1233. }
  1234. case "rooCloudSignOut": {
  1235. try {
  1236. await CloudService.instance.logout()
  1237. await provider.postStateToWebview()
  1238. provider.postMessageToWebview({ type: "authenticatedUser", userInfo: undefined })
  1239. } catch (error) {
  1240. provider.log(`AuthService#logout failed: ${error}`)
  1241. vscode.window.showErrorMessage("Sign out failed.")
  1242. }
  1243. break
  1244. }
  1245. case "codebaseIndexConfig": {
  1246. const codebaseIndexConfig = message.values ?? {
  1247. codebaseIndexEnabled: false,
  1248. codebaseIndexQdrantUrl: "http://localhost:6333",
  1249. codebaseIndexEmbedderProvider: "openai",
  1250. codebaseIndexEmbedderBaseUrl: "",
  1251. codebaseIndexEmbedderModelId: "",
  1252. }
  1253. await updateGlobalState("codebaseIndexConfig", codebaseIndexConfig)
  1254. try {
  1255. if (provider.codeIndexManager) {
  1256. await provider.codeIndexManager.handleExternalSettingsChange()
  1257. // If now configured and enabled, start indexing automatically
  1258. if (provider.codeIndexManager.isFeatureEnabled && provider.codeIndexManager.isFeatureConfigured) {
  1259. if (!provider.codeIndexManager.isInitialized) {
  1260. await provider.codeIndexManager.initialize(provider.contextProxy)
  1261. }
  1262. // Start indexing in background (no await)
  1263. provider.codeIndexManager.startIndexing()
  1264. }
  1265. }
  1266. } catch (error) {
  1267. provider.log(
  1268. `[CodeIndexManager] Error during background CodeIndexManager configuration/indexing: ${error.message || error}`,
  1269. )
  1270. }
  1271. await provider.postStateToWebview()
  1272. break
  1273. }
  1274. case "requestIndexingStatus": {
  1275. const status = provider.codeIndexManager!.getCurrentStatus()
  1276. provider.postMessageToWebview({
  1277. type: "indexingStatusUpdate",
  1278. values: status,
  1279. })
  1280. break
  1281. }
  1282. case "startIndexing": {
  1283. try {
  1284. const manager = provider.codeIndexManager!
  1285. if (manager.isFeatureEnabled && manager.isFeatureConfigured) {
  1286. if (!manager.isInitialized) {
  1287. await manager.initialize(provider.contextProxy)
  1288. }
  1289. manager.startIndexing()
  1290. }
  1291. } catch (error) {
  1292. provider.log(`Error starting indexing: ${error instanceof Error ? error.message : String(error)}`)
  1293. }
  1294. break
  1295. }
  1296. case "clearIndexData": {
  1297. try {
  1298. const manager = provider.codeIndexManager!
  1299. await manager.clearIndexData()
  1300. provider.postMessageToWebview({ type: "indexCleared", values: { success: true } })
  1301. } catch (error) {
  1302. provider.log(`Error clearing index data: ${error instanceof Error ? error.message : String(error)}`)
  1303. provider.postMessageToWebview({
  1304. type: "indexCleared",
  1305. values: {
  1306. success: false,
  1307. error: error instanceof Error ? error.message : String(error),
  1308. },
  1309. })
  1310. }
  1311. break
  1312. }
  1313. }
  1314. }