webviewMessageHandler.ts 51 KB

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