| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- import { buildApiHandler } from "@core/api"
- import { Empty } from "@shared/proto/cline/common"
- import {
- PlanActMode,
- OpenaiReasoningEffort as ProtoOpenaiReasoningEffort,
- UpdateSettingsRequestCli,
- } from "@shared/proto/cline/state"
- import { convertProtoToApiProvider } from "@shared/proto-conversions/models/api-configuration-conversion"
- import { Settings } from "@shared/storage/state-keys"
- import { TelemetrySetting } from "@shared/TelemetrySetting"
- import { ClineEnv } from "@/config"
- import { HostProvider } from "@/hosts/host-provider"
- import { ShowMessageType } from "@/shared/proto/host/window"
- import { Mode, OpenaiReasoningEffort } from "@/shared/storage/types"
- import { telemetryService } from "../../../services/telemetry"
- import { Controller } from ".."
- import { accountLogoutClicked } from "../account/accountLogoutClicked"
- /**
- * Updates multiple extension settings in a single request
- * @param controller The controller instance
- * @param request The request containing the settings to update
- * @returns An empty response
- */
- export async function updateSettingsCli(controller: Controller, request: UpdateSettingsRequestCli): Promise<Empty> {
- const convertOpenaiReasoningEffort = (effort: ProtoOpenaiReasoningEffort): OpenaiReasoningEffort => {
- switch (effort) {
- case ProtoOpenaiReasoningEffort.LOW:
- return "low"
- case ProtoOpenaiReasoningEffort.MEDIUM:
- return "medium"
- case ProtoOpenaiReasoningEffort.HIGH:
- return "high"
- case ProtoOpenaiReasoningEffort.MINIMAL:
- return "minimal"
- default:
- return "medium"
- }
- }
- const convertPlanActMode = (mode: PlanActMode): Mode => {
- return mode === PlanActMode.PLAN ? "plan" : "act"
- }
- try {
- if (request.environment !== undefined) {
- ClineEnv.setEnvironment(request.environment)
- await accountLogoutClicked(controller, Empty.create())
- }
- if (request.settings) {
- // Extract all special case fields that need dedicated handlers
- // These should NOT be included in the batch update
- const {
- // Fields requiring conversion
- autoApprovalSettings,
- openaiReasoningEffort,
- mode,
- customPrompt,
- planModeApiProvider,
- actModeApiProvider,
- // Fields requiring special logic (telemetry, merging, etc.)
- telemetrySetting,
- yoloModeToggled,
- useAutoCondense,
- focusChainSettings,
- browserSettings,
- defaultTerminalProfile,
- ...simpleSettings
- } = request.settings
- // Batch update for simple pass-through fields
- const filteredSettings: Partial<Settings> = Object.fromEntries(
- Object.entries(simpleSettings).filter(([_, value]) => value !== undefined),
- )
- controller.stateManager.setGlobalStateBatch(filteredSettings)
- console.log("autoApprovalSettings", controller.stateManager.getGlobalSettingsKey("autoApprovalSettings"))
- // Handle fields requiring type conversion from generated protobuf types to application types
- if (autoApprovalSettings) {
- // Merge with current settings to preserve unspecified fields
- const currentAutoApprovalSettings = controller.stateManager.getGlobalSettingsKey("autoApprovalSettings")
- const mergedSettings = {
- ...currentAutoApprovalSettings,
- ...(autoApprovalSettings.version !== undefined && { version: autoApprovalSettings.version }),
- ...(autoApprovalSettings.enableNotifications !== undefined && {
- enableNotifications: autoApprovalSettings.enableNotifications,
- }),
- actions: {
- ...currentAutoApprovalSettings.actions,
- ...(autoApprovalSettings.actions
- ? Object.fromEntries(Object.entries(autoApprovalSettings.actions).filter(([_, v]) => v !== undefined))
- : {}),
- },
- }
- controller.stateManager.setGlobalState("autoApprovalSettings", mergedSettings)
- }
- if (openaiReasoningEffort !== undefined) {
- const converted = convertOpenaiReasoningEffort(openaiReasoningEffort)
- controller.stateManager.setGlobalState("openaiReasoningEffort", converted)
- }
- if (mode !== undefined) {
- const converted = convertPlanActMode(mode)
- controller.stateManager.setGlobalState("mode", converted)
- }
- if (customPrompt === "compact") {
- controller.stateManager.setGlobalState("customPrompt", "compact")
- }
- if (planModeApiProvider !== undefined) {
- const converted = convertProtoToApiProvider(planModeApiProvider)
- controller.stateManager.setGlobalState("planModeApiProvider", converted)
- }
- if (actModeApiProvider !== undefined) {
- const converted = convertProtoToApiProvider(actModeApiProvider)
- controller.stateManager.setGlobalState("actModeApiProvider", converted)
- }
- if (controller.task) {
- const currentMode = controller.stateManager.getGlobalSettingsKey("mode")
- const apiConfigForHandler = {
- ...controller.stateManager.getApiConfiguration(),
- ulid: controller.task.ulid,
- }
- controller.task.api = buildApiHandler(apiConfigForHandler, currentMode)
- }
- // Update telemetry setting
- if (telemetrySetting) {
- await controller.updateTelemetrySetting(telemetrySetting as TelemetrySetting)
- }
- // Update yolo mode setting (requires telemetry)
- if (yoloModeToggled !== undefined) {
- if (controller.task) {
- telemetryService.captureYoloModeToggle(controller.task.ulid, yoloModeToggled)
- }
- controller.stateManager.setGlobalState("yoloModeToggled", yoloModeToggled)
- }
- // Update auto-condense setting (requires telemetry)
- if (useAutoCondense !== undefined) {
- if (controller.task) {
- telemetryService.captureAutoCondenseToggle(
- controller.task.ulid,
- useAutoCondense,
- controller.task.api.getModel().id,
- )
- }
- controller.stateManager.setGlobalState("useAutoCondense", useAutoCondense)
- }
- // Update focus chain settings (requires telemetry on state change)
- if (focusChainSettings !== undefined) {
- const currentSettings = controller.stateManager.getGlobalSettingsKey("focusChainSettings")
- const wasEnabled = currentSettings?.enabled ?? false
- const isEnabled = focusChainSettings.enabled
- const newFocusChainSettings = {
- enabled: isEnabled,
- remindClineInterval: focusChainSettings.remindClineInterval,
- }
- controller.stateManager.setGlobalState("focusChainSettings", newFocusChainSettings)
- // Capture telemetry when setting changes
- if (wasEnabled !== isEnabled) {
- telemetryService.captureFocusChainToggle(isEnabled)
- }
- }
- // Update browser settings (requires careful merging to avoid protobuf defaults)
- if (browserSettings !== undefined) {
- const currentSettings = controller.stateManager.getGlobalSettingsKey("browserSettings")
- const newBrowserSettings = {
- ...currentSettings,
- viewport: {
- width: browserSettings.viewport?.width || currentSettings.viewport.width,
- height: browserSettings.viewport?.height || currentSettings.viewport.height,
- },
- ...(browserSettings.remoteBrowserEnabled !== undefined && {
- remoteBrowserEnabled: browserSettings.remoteBrowserEnabled,
- }),
- ...(browserSettings.remoteBrowserHost !== undefined && {
- remoteBrowserHost: browserSettings.remoteBrowserHost,
- }),
- ...(browserSettings.chromeExecutablePath !== undefined && {
- chromeExecutablePath: browserSettings.chromeExecutablePath,
- }),
- ...(browserSettings.disableToolUse !== undefined && {
- disableToolUse: browserSettings.disableToolUse,
- }),
- ...(browserSettings.customArgs !== undefined && {
- customArgs: browserSettings.customArgs,
- }),
- }
- controller.stateManager.setGlobalState("browserSettings", newBrowserSettings)
- }
- // Update default terminal profile (requires terminal manager updates and notifications)
- if (defaultTerminalProfile !== undefined && defaultTerminalProfile !== "") {
- const profileId = defaultTerminalProfile
- // Update the terminal profile in the state
- controller.stateManager.setGlobalState("defaultTerminalProfile", profileId)
- let closedCount = 0
- let busyTerminalsCount = 0
- // Update the terminal manager of the current task if it exists
- if (controller.task) {
- // Terminal manager must exist when task is active
- if (!controller.task.terminalManager) {
- throw new Error("Cannot update terminal profile: Terminal manager missing from active task")
- }
- // Call the updated setDefaultTerminalProfile method that returns closed terminal info
- // Use `as any` to handle type incompatibility between VSCode's TerminalInfo and standalone TerminalInfo
- const result = controller.task.terminalManager.setDefaultTerminalProfile(profileId) as any
- closedCount = result.closedCount
- busyTerminalsCount = result.busyTerminals?.length ?? 0
- // Show information message if terminals were closed
- if (closedCount > 0) {
- const message = `Closed ${closedCount} ${closedCount === 1 ? "terminal" : "terminals"} with different profile.`
- HostProvider.window.showMessage({
- type: ShowMessageType.INFORMATION,
- message,
- })
- }
- // Show warning if there are busy terminals that couldn't be closed
- if (busyTerminalsCount > 0) {
- const message =
- `${busyTerminalsCount} busy ${busyTerminalsCount === 1 ? "terminal has" : "terminals have"} a different profile. ` +
- `Close ${busyTerminalsCount === 1 ? "it" : "them"} to use the new profile for all commands.`
- HostProvider.window.showMessage({
- type: ShowMessageType.WARNING,
- message,
- })
- }
- }
- }
- }
- // Handle secrets updates
- if (request.secrets) {
- const filteredSecrets = Object.fromEntries(
- Object.entries(request.secrets).filter(([_, value]) => value !== undefined),
- )
- controller.stateManager.setSecretsBatch(filteredSecrets)
- }
- // Post updated state to webview
- await controller.postStateToWebview()
- return Empty.create()
- } catch (error) {
- console.error("Failed to update settings:", error)
- throw error
- }
- }
|