Refactor reasoning effort option and checkpoint handling (#3454)

• Replace "o3MiniReasoningEffort" with "reasoningEffort" in API providers
• Remove deprecated configuration properties from package.json
• Guard checkpoint tracker initialization and saving using the enableCheckpoints flag

Co-authored-by: Cline Evaluation <cline@example.com>
This commit is contained in:
Ara 2025-05-15 20:54:16 +04:00 committed by GitHub
parent d2979631d8
commit cd1ff2ad25
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 355 additions and 96 deletions

View File

@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Finishing the migration of Vscode Advanced settings to Settings Webview

View File

@ -236,43 +236,7 @@
},
"configuration": {
"title": "Cline",
"properties": {
"cline.enableCheckpoints": {
"type": "boolean",
"default": true,
"description": "Enables extension to save checkpoints of workspace throughout the task. Uses git under the hood which may not work well with large workspaces."
},
"cline.preferredLanguage": {
"type": "string",
"enum": [
"English",
"Arabic - العربية",
"Portuguese - Português (Brasil)",
"Czech - Čeština",
"French - Français",
"German - Deutsch",
"Hindi - हिन्दी",
"Hungarian - Magyar",
"Italian - Italiano",
"Japanese - 日本語",
"Korean - 한국어",
"Polish - Polski",
"Portuguese - Português (Portugal)",
"Russian - Русский",
"Simplified Chinese - 简体中文",
"Spanish - Español",
"Traditional Chinese - 繁體中文",
"Turkish - Türkçe"
],
"default": "English",
"description": "The language that Cline should use for communication."
},
"cline.mcpMarketplace.enabled": {
"type": "boolean",
"default": true,
"description": "Controls whether the MCP Marketplace is enabled."
}
}
"properties": {}
}
},
"scripts": {

View File

@ -33,7 +33,7 @@ export class ClineHandler implements ApiHandler {
systemPrompt,
messages,
this.getModel(),
this.options.o3MiniReasoningEffort,
this.options.reasoningEffort,
this.options.thinkingBudgetTokens,
this.options.openRouterProviderSorting,
)

View File

@ -66,8 +66,9 @@ export class OpenAiNativeHandler implements ApiHandler {
messages: [{ role: "developer", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
stream: true,
stream_options: { include_usage: true },
reasoning_effort: (this.options.o3MiniReasoningEffort as ChatCompletionReasoningEffort) || "medium",
reasoning_effort: (this.options.reasoningEffort as ChatCompletionReasoningEffort) || "medium",
})
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta
if (delta?.content) {

View File

@ -65,7 +65,7 @@ export class OpenAiHandler implements ApiHandler {
if (isReasoningModelFamily) {
openAiMessages = [{ role: "developer", content: systemPrompt }, ...convertToOpenAiMessages(messages)]
temperature = undefined // does not support temperature
reasoningEffort = (this.options.o3MiniReasoningEffort as ChatCompletionReasoningEffort) || "medium"
reasoningEffort = (this.options.reasoningEffort as ChatCompletionReasoningEffort) || "medium"
}
const stream = await this.client.chat.completions.create({

View File

@ -35,7 +35,7 @@ export class OpenRouterHandler implements ApiHandler {
systemPrompt,
messages,
this.getModel(),
this.options.o3MiniReasoningEffort,
this.options.reasoningEffort,
this.options.thinkingBudgetTokens,
this.options.openRouterProviderSorting,
)

View File

@ -32,7 +32,7 @@ export class RequestyHandler implements ApiHandler {
...convertToOpenAiMessages(messages),
]
const reasoningEffort = this.options.o3MiniReasoningEffort || "medium"
const reasoningEffort = this.options.reasoningEffort || "medium"
const reasoning = { reasoning_effort: reasoningEffort }
const reasoningArgs = model.id.startsWith("openai/o") ? reasoning : {}

View File

@ -9,7 +9,7 @@ export async function createOpenRouterStream(
systemPrompt: string,
messages: Anthropic.Messages.MessageParam[],
model: { id: string; info: ModelInfo },
o3MiniReasoningEffort?: string,
reasoningEffort?: string,
thinkingBudgetTokens?: number,
openRouterProviderSorting?: string,
) {
@ -144,7 +144,7 @@ export async function createOpenRouterStream(
stream_options: { include_usage: true },
transforms: shouldApplyMiddleOutTransform ? ["middle-out"] : undefined,
include_reasoning: true,
...(model.id.startsWith("openai/o") ? { reasoning_effort: o3MiniReasoningEffort || "medium" } : {}),
...(model.id.startsWith("openai/o") ? { reasoning_effort: reasoningEffort || "medium" } : {}),
...(reasoning ? { reasoning } : {}),
...(openRouterProviderSorting ? { provider: { sort: openRouterProviderSorting } } : {}),
})

View File

@ -145,6 +145,7 @@ export class Controller {
browserSettings,
chatSettings,
shellIntegrationTimeout,
enableCheckpointsSetting,
} = await getAllExtensionState(this.context)
if (autoApprovalSettings) {
@ -168,6 +169,7 @@ export class Controller {
browserSettings,
chatSettings,
shellIntegrationTimeout,
enableCheckpointsSetting ?? true,
customInstructions,
task,
images,
@ -563,6 +565,22 @@ export class Controller {
// plan act setting
await updateGlobalState(this.context, "planActSeparateModelsSetting", message.planActSeparateModelsSetting)
if (typeof message.enableCheckpointsSetting === "boolean") {
await updateGlobalState(this.context, "enableCheckpointsSetting", message.enableCheckpointsSetting)
}
if (typeof message.mcpMarketplaceEnabled === "boolean") {
await updateGlobalState(this.context, "mcpMarketplaceEnabled", message.mcpMarketplaceEnabled)
}
// chat settings (including preferredLanguage and openAIReasoningEffort)
if (message.chatSettings) {
await updateGlobalState(this.context, "chatSettings", message.chatSettings)
if (this.task) {
this.task.chatSettings = message.chatSettings
}
}
// after settings are updated, post state to webview
await this.postStateToWebview()
@ -1332,6 +1350,7 @@ export class Controller {
mcpMarketplaceEnabled,
telemetrySetting,
planActSeparateModelsSetting,
enableCheckpointsSetting,
globalClineRulesToggles,
shellIntegrationTimeout,
} = await getAllExtensionState(this.context)
@ -1366,6 +1385,7 @@ export class Controller {
mcpMarketplaceEnabled,
telemetrySetting,
planActSeparateModelsSetting,
enableCheckpointsSetting: enableCheckpointsSetting ?? true,
vscMachineId: vscode.env.machineId,
globalClineRulesToggles: globalClineRulesToggles || {},
localClineRulesToggles: localClineRulesToggles || {},

View File

@ -83,6 +83,8 @@ export type GlobalStateKey =
| "thinkingBudgetTokens"
| "reasoningEffort"
| "planActSeparateModelsSetting"
| "enableCheckpointsSetting"
| "mcpMarketplaceEnabled"
| "favoritedModelIds"
| "requestTimeoutMs"
| "shellIntegrationTimeout"

View File

@ -1,5 +1,5 @@
import * as vscode from "vscode"
import { DEFAULT_CHAT_SETTINGS } from "@shared/ChatSettings"
import { DEFAULT_CHAT_SETTINGS, OpenAIReasoningEffort } from "@shared/ChatSettings"
import { DEFAULT_BROWSER_SETTINGS } from "@shared/BrowserSettings"
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "@shared/AutoApprovalSettings"
import { GlobalStateKey, SecretKey } from "./state-keys"
@ -51,6 +51,29 @@ export async function getWorkspaceState(context: vscode.ExtensionContext, key: s
return await context.workspaceState.get(key)
}
async function migrateMcpMarketplaceEnableSetting(mcpMarketplaceEnabledRaw: boolean | undefined): Promise<boolean> {
const config = vscode.workspace.getConfiguration("cline")
const mcpMarketplaceEnabled = config.get<boolean>("mcpMarketplace.enabled")
if (mcpMarketplaceEnabled !== undefined) {
// Remove from VSCode configuration
await config.update("mcpMarketplace.enabled", undefined, true)
return !mcpMarketplaceEnabled
}
return mcpMarketplaceEnabledRaw ?? true
}
async function migrateEnableCheckpointsSetting(enableCheckpointsSettingRaw: boolean | undefined): Promise<boolean> {
const config = vscode.workspace.getConfiguration("cline")
const enableCheckpoints = config.get<boolean>("enableCheckpoints")
if (enableCheckpoints !== undefined) {
// Remove from VSCode configuration
await config.update("enableCheckpoints", undefined, true)
return enableCheckpoints
}
return enableCheckpointsSettingRaw ?? true
}
export async function getAllExtensionState(context: vscode.ExtensionContext) {
const [
storedApiProvider,
@ -136,6 +159,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
globalClineRulesToggles,
requestTimeoutMs,
shellIntegrationTimeout,
enableCheckpointsSettingRaw,
mcpMarketplaceEnabledRaw,
] = await Promise.all([
getGlobalState(context, "apiProvider") as Promise<ApiProvider | undefined>,
getGlobalState(context, "apiModelId") as Promise<string | undefined>,
@ -220,6 +245,9 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
getGlobalState(context, "globalClineRulesToggles") as Promise<ClineRulesToggles | undefined>,
getGlobalState(context, "requestTimeoutMs") as Promise<number | undefined>,
getGlobalState(context, "shellIntegrationTimeout") as Promise<number | undefined>,
getGlobalState(context, "enableCheckpointsSetting") as Promise<boolean | undefined>,
getGlobalState(context, "mcpMarketplaceEnabled") as Promise<boolean | undefined>,
fetch,
])
let apiProvider: ApiProvider
@ -238,9 +266,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
const localClineRulesToggles = (await getWorkspaceState(context, "localClineRulesToggles")) as ClineRulesToggles
const o3MiniReasoningEffort = vscode.workspace.getConfiguration("cline.modelSettings.o3Mini").get("reasoningEffort", "medium")
const mcpMarketplaceEnabled = vscode.workspace.getConfiguration("cline").get<boolean>("mcpMarketplace.enabled", true)
const mcpMarketplaceEnabled = await migrateMcpMarketplaceEnableSetting(mcpMarketplaceEnabledRaw)
const enableCheckpointsSetting = await migrateEnableCheckpointsSetting(enableCheckpointsSettingRaw)
// Plan/Act separate models setting is a boolean indicating whether the user wants to use different models for plan and act. Existing users expect this to be enabled, while we want new users to opt in to this being disabled by default.
// On win11 state sometimes initializes as empty string instead of undefined
@ -309,7 +336,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
openRouterModelInfo,
openRouterProviderSorting,
vsCodeLmModelSelector,
o3MiniReasoningEffort,
thinkingBudgetTokens,
reasoningEffort,
liteLlmBaseUrl,
@ -335,7 +361,10 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
globalClineRulesToggles: globalClineRulesToggles || {},
localClineRulesToggles: localClineRulesToggles || {},
browserSettings: { ...DEFAULT_BROWSER_SETTINGS, ...browserSettings }, // this will ensure that older versions of browserSettings (e.g. before remoteBrowserEnabled was added) are merged with the default values (false for remoteBrowserEnabled)
chatSettings: chatSettings || DEFAULT_CHAT_SETTINGS,
chatSettings: {
...DEFAULT_CHAT_SETTINGS, // Apply defaults first
...(chatSettings || {}), // Spread fetched chatSettings, which includes preferredLanguage, and openAIReasoningEffort
},
userInfo,
previousModeApiProvider,
previousModeModelId,
@ -345,9 +374,10 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
previousModeReasoningEffort,
previousModeAwsBedrockCustomSelected,
previousModeAwsBedrockCustomModelBaseId,
mcpMarketplaceEnabled,
mcpMarketplaceEnabled: mcpMarketplaceEnabled,
telemetrySetting: telemetrySetting || "unset",
planActSeparateModelsSetting,
enableCheckpointsSetting: enableCheckpointsSetting,
shellIntegrationTimeout: shellIntegrationTimeout || 4000,
}
}

View File

@ -167,6 +167,7 @@ export class Task {
private didAlreadyUseTool = false
private didCompleteReadingStream = false
private didAutomaticallyRetryFailedApiRequest = false
private enableCheckpoints: boolean
constructor(
context: vscode.ExtensionContext,
@ -182,6 +183,7 @@ export class Task {
browserSettings: BrowserSettings,
chatSettings: ChatSettings,
shellIntegrationTimeout: number,
enableCheckpointsSetting: boolean,
customInstructions?: string,
task?: string,
images?: string[],
@ -207,6 +209,7 @@ export class Task {
this.autoApprovalSettings = autoApprovalSettings
this.browserSettings = browserSettings
this.chatSettings = chatSettings
this.enableCheckpoints = enableCheckpointsSetting
// Initialize taskId first
if (historyItem) {
@ -222,11 +225,19 @@ export class Task {
// Initialize file context tracker
this.fileContextTracker = new FileContextTracker(context, this.taskId)
this.modelContextTracker = new ModelContextTracker(context, this.taskId)
// Now that taskId is initialized, we can build the API handler
this.api = buildApiHandler({
// Prepare effective API configuration
let effectiveApiConfiguration: ApiConfiguration = {
...apiConfiguration,
taskId: this.taskId,
})
}
if (apiConfiguration.apiProvider === "openai" || apiConfiguration.apiProvider === "openai-native") {
effectiveApiConfiguration.reasoningEffort = chatSettings.openAIReasoningEffort
}
// Now that taskId is initialized, we can build the API handler
this.api = buildApiHandler(effectiveApiConfiguration)
// Set taskId on browserSession for telemetry tracking
this.browserSession.setTaskId(this.taskId)
@ -342,9 +353,19 @@ export class Task {
break
case "taskAndWorkspace":
case "workspace":
if (!this.enableCheckpoints) {
vscode.window.showErrorMessage("Checkpoints are disabled in settings.")
didWorkspaceRestoreFail = true
break
}
if (!this.checkpointTracker && !this.checkpointTrackerErrorMessage) {
try {
this.checkpointTracker = await CheckpointTracker.create(this.taskId, this.context.globalStorageUri.fsPath)
this.checkpointTracker = await CheckpointTracker.create(
this.taskId,
this.context.globalStorageUri.fsPath,
this.enableCheckpoints,
)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error"
console.error("Failed to initialize checkpoint tracker:", errorMessage)
@ -451,6 +472,11 @@ export class Task {
const relinquishButton = () => {
this.postMessageToWebview({ type: "relinquishControl" })
}
if (!this.enableCheckpoints) {
vscode.window.showInformationMessage("Checkpoints are disabled in settings. Cannot show diff.")
relinquishButton()
return
}
console.log("presentMultifileDiff", messageTs)
const messageIndex = this.clineMessages.findIndex((m) => m.ts === messageTs)
@ -468,9 +494,13 @@ export class Task {
}
// TODO: handle if this is called from outside original workspace, in which case we need to show user error message we can't show diff outside of workspace?
if (!this.checkpointTracker && !this.checkpointTrackerErrorMessage) {
if (!this.checkpointTracker && this.enableCheckpoints && !this.checkpointTrackerErrorMessage) {
try {
this.checkpointTracker = await CheckpointTracker.create(this.taskId, this.context.globalStorageUri.fsPath)
this.checkpointTracker = await CheckpointTracker.create(
this.taskId,
this.context.globalStorageUri.fsPath,
this.enableCheckpoints,
)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error"
console.error("Failed to initialize checkpoint tracker:", errorMessage)
@ -568,6 +598,8 @@ export class Task {
}
async doesLatestTaskCompletionHaveNewChanges() {
if (!this.enableCheckpoints) return false
const messageIndex = findLastIndex(this.clineMessages, (m) => m.say === "completion_result")
const message = this.clineMessages[messageIndex]
if (!message) {
@ -580,9 +612,13 @@ export class Task {
return false
}
if (!this.checkpointTracker && !this.checkpointTrackerErrorMessage) {
if (this.enableCheckpoints && !this.checkpointTracker && !this.checkpointTrackerErrorMessage) {
try {
this.checkpointTracker = await CheckpointTracker.create(this.taskId, this.context.globalStorageUri.fsPath)
this.checkpointTracker = await CheckpointTracker.create(
this.taskId,
this.context.globalStorageUri.fsPath,
this.enableCheckpoints,
)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error"
console.error("Failed to initialize checkpoint tracker:", errorMessage)
@ -1081,6 +1117,10 @@ export class Task {
// Checkpoints
async saveCheckpoint(isAttemptCompletionMessage: boolean = false) {
if (!this.enableCheckpoints) {
// If checkpoints are disabled, do nothing.
return
}
// Set isCheckpointCheckedOut to false for all checkpoint_created messages
this.clineMessages.forEach((message) => {
if (message.say === "checkpoint_created") {
@ -1448,7 +1488,7 @@ export class Task {
*/
private async migrateDisableBrowserToolSetting(): Promise<void> {
const config = vscode.workspace.getConfiguration("cline")
const disableBrowserTool = vscode.workspace.getConfiguration("cline").get<boolean>("disableBrowserTool")
const disableBrowserTool = config.get<boolean>("disableBrowserTool")
if (disableBrowserTool !== undefined) {
this.browserSettings.disableToolUse = disableBrowserTool
@ -1457,6 +1497,16 @@ export class Task {
}
}
private async migratePreferredLanguageToolSetting(): Promise<void> {
const config = vscode.workspace.getConfiguration("cline")
const preferredLanguage = config.get<LanguageDisplay>("preferredLanguage")
if (preferredLanguage !== undefined) {
this.chatSettings.preferredLanguage = preferredLanguage
// Remove from VSCode configuration
await config.update("preferredLanguage", undefined, true)
}
}
async *attemptApiRequest(previousApiReqIndex: number): ApiStream {
// Wait for MCP servers to be connected before generating system prompt
await pWaitFor(() => this.mcpHub.isConnecting !== true, { timeout: 10_000 }).catch(() => {
@ -1473,9 +1523,8 @@ export class Task {
let systemPrompt = await SYSTEM_PROMPT(cwd, supportsBrowserUse, this.mcpHub, this.browserSettings)
let settingsCustomInstructions = this.customInstructions?.trim()
const preferredLanguage = getLanguageKey(
vscode.workspace.getConfiguration("cline").get<LanguageDisplay>("preferredLanguage"),
)
await this.migratePreferredLanguageToolSetting()
const preferredLanguage = getLanguageKey(this.chatSettings.preferredLanguage as LanguageDisplay)
const preferredLanguageInstructions =
preferredLanguage && preferredLanguage !== DEFAULT_LANGUAGE_SETTINGS
? `# Preferred Language\n\nSpeak in ${preferredLanguage}.`
@ -3670,17 +3719,11 @@ export class Task {
}),
)
if (isFirstRequest) {
await this.say("checkpoint_created") // no hash since we need to wait for CheckpointTracker to be initialized
}
// use this opportunity to initialize the checkpoint tracker (can be expensive to initialize in the constructor)
// FIXME: right now we're letting users init checkpoints for old tasks, but this could be a problem if opening a task in the wrong workspace
// isNewTask &&
if (!this.checkpointTracker && !this.checkpointTrackerErrorMessage) {
// Initialize checkpoint tracker first if enabled and it's the first request
if (isFirstRequest && this.enableCheckpoints && !this.checkpointTracker && !this.checkpointTrackerErrorMessage) {
try {
this.checkpointTracker = await pTimeout(
CheckpointTracker.create(this.taskId, this.context.globalStorageUri.fsPath),
CheckpointTracker.create(this.taskId, this.context.globalStorageUri.fsPath, this.enableCheckpoints),
{
milliseconds: 15_000,
message:
@ -3694,14 +3737,22 @@ export class Task {
}
}
// Now that checkpoint tracker is initialized, update the dummy checkpoint_created message with the commit hash. (This is necessary since we use the API request loading as an opportunity to initialize the checkpoint tracker, which can take some time)
if (isFirstRequest) {
const commitHash = await this.checkpointTracker?.commit()
// Now, if it's the first request AND checkpoints are enabled AND tracker was successfully initialized,
// then say "checkpoint_created" and perform the commit.
if (isFirstRequest && this.enableCheckpoints && this.checkpointTracker) {
await this.say("checkpoint_created") // Now this is conditional
const commitHash = await this.checkpointTracker.commit() // Actual commit
const lastCheckpointMessage = findLast(this.clineMessages, (m) => m.say === "checkpoint_created")
if (lastCheckpointMessage) {
lastCheckpointMessage.lastCheckpointHash = commitHash
await this.saveClineMessagesAndUpdateHistory()
// saveClineMessagesAndUpdateHistory will be called later after API response,
// so no need to call it here unless this is the only modification to this message.
// For now, assuming it's handled later.
}
} else if (isFirstRequest && this.enableCheckpoints && !this.checkpointTracker && this.checkpointTrackerErrorMessage) {
// Checkpoints are enabled, but tracker failed to initialize.
// checkpointTrackerErrorMessage is already set and will be part of the state.
// No explicit UI message here, error message will be in ExtensionState.
}
const [parsedUserContent, environmentDetails, clinerulesError] = await this.loadContext(userContent, includeFileDetails)

View File

@ -21,15 +21,17 @@ class CheckpointTracker {
this.cwd = cwd
}
public static async create(taskId: string, provider?: ClineProvider): Promise<CheckpointTracker | undefined> {
public static async create(
taskId: string,
enableCheckpointsSetting: boolean,
provider?: ClineProvider,
): Promise<CheckpointTracker | undefined> {
try {
if (!provider) {
throw new Error("Provider is required to create a checkpoint tracker")
}
// Check if checkpoints are disabled in VS Code settings
const enableCheckpoints = vscode.workspace.getConfiguration("cline").get<boolean>("enableCheckpoints") ?? true
if (!enableCheckpoints) {
if (!enableCheckpointsSetting) {
return undefined // Don't create tracker when disabled
}

View File

@ -90,7 +90,11 @@ class CheckpointTracker {
* Configuration:
* - Respects 'cline.enableCheckpoints' VS Code setting
*/
public static async create(taskId: string, globalStoragePath: string | undefined): Promise<CheckpointTracker | undefined> {
public static async create(
taskId: string,
globalStoragePath: string | undefined,
enableCheckpointsSetting: boolean,
): Promise<CheckpointTracker | undefined> {
if (!globalStoragePath) {
throw new Error("Global storage path is required to create a checkpoint tracker")
}
@ -98,9 +102,9 @@ class CheckpointTracker {
console.info(`Creating new CheckpointTracker for task ${taskId}`)
const startTime = performance.now()
// Check if checkpoints are disabled in VS Code settings
const enableCheckpoints = vscode.workspace.getConfiguration("cline").get<boolean>("enableCheckpoints") ?? true
if (!enableCheckpoints) {
// Check if checkpoints are disabled by setting
if (!enableCheckpointsSetting) {
console.info(`Checkpoints disabled by setting for task ${taskId}`)
return undefined // Don't create tracker when disabled
}

View File

@ -1,7 +1,15 @@
export type OpenAIReasoningEffort = "low" | "medium" | "high"
export interface ChatSettings {
mode: "plan" | "act"
preferredLanguage?: string
openAIReasoningEffort?: OpenAIReasoningEffort
}
export type PartialChatSettings = Partial<ChatSettings>
export const DEFAULT_CHAT_SETTINGS: ChatSettings = {
mode: "act",
preferredLanguage: "English",
openAIReasoningEffort: "medium",
}

View File

@ -126,6 +126,7 @@ export interface ExtensionState {
customInstructions?: string
mcpMarketplaceEnabled?: boolean
planActSeparateModelsSetting: boolean
enableCheckpointsSetting?: boolean
platform: Platform
shouldShowAnnouncement: boolean
taskHistory: HistoryItem[]

View File

@ -90,6 +90,8 @@ export interface WebviewMessage {
// For openInBrowser
url?: string
planActSeparateModelsSetting?: boolean
enableCheckpointsSetting?: boolean
mcpMarketplaceEnabled?: boolean
telemetrySetting?: TelemetrySetting
customInstructionsSetting?: string
// For task feedback

View File

@ -80,7 +80,6 @@ export interface ApiHandlerOptions {
mistralApiKey?: string
azureApiVersion?: string
vsCodeLmModelSelector?: LanguageModelChatSelector
o3MiniReasoningEffort?: string
qwenApiLine?: string
asksageApiUrl?: string
asksageApiKey?: string

View File

@ -0,0 +1,75 @@
import { VSCodeCheckbox, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { memo } from "react"
import { OpenAIReasoningEffort } from "@shared/ChatSettings"
const FeatureSettingsSection = () => {
const {
enableCheckpointsSetting,
setEnableCheckpointsSetting,
mcpMarketplaceEnabled,
setMcpMarketplaceEnabled,
chatSettings,
setChatSettings,
} = useExtensionState()
return (
<div style={{ marginBottom: 20, borderTop: "1px solid var(--vscode-panel-border)", paddingTop: 15 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 10px 0", fontSize: "14px" }}>Feature Settings</h3>
<div>
<VSCodeCheckbox
checked={enableCheckpointsSetting}
onChange={(e: any) => {
const checked = e.target.checked === true
setEnableCheckpointsSetting(checked)
}}>
Enable Checkpoints
</VSCodeCheckbox>
<p className="text-xs text-[var(--vscode-descriptionForeground)]">
Enables extension to save checkpoints of workspace throughout the task. Uses git under the hood which may not
work well with large workspaces.
</p>
</div>
<div style={{ marginTop: 10 }}>
<VSCodeCheckbox
checked={mcpMarketplaceEnabled}
onChange={(e: any) => {
const checked = e.target.checked === true
setMcpMarketplaceEnabled(checked)
}}>
Enable MCP Marketplace
</VSCodeCheckbox>
<p className="text-xs text-[var(--vscode-descriptionForeground)]">
Enables the MCP Marketplace tab for discovering and installing MCP servers.
</p>
</div>
<div style={{ marginTop: 10 }}>
<label
htmlFor="openai-reasoning-effort-dropdown"
className="block text-sm font-medium text-[var(--vscode-foreground)] mb-1">
OpenAI Reasoning Effort
</label>
<VSCodeDropdown
id="openai-reasoning-effort-dropdown"
currentValue={chatSettings.openAIReasoningEffort || "medium"}
onChange={(e: any) => {
const newValue = e.target.currentValue as OpenAIReasoningEffort
setChatSettings({
...chatSettings,
openAIReasoningEffort: newValue,
})
}}
className="w-full">
<VSCodeOption value="low">Low</VSCodeOption>
<VSCodeOption value="medium">Medium</VSCodeOption>
<VSCodeOption value="high">High</VSCodeOption>
</VSCodeDropdown>
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
Reasoning effort for the OpenAI family of models(applies to all OpenAI model providers)
</p>
</div>
</div>
)
}
export default memo(FeatureSettingsSection)

View File

@ -0,0 +1,53 @@
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
import React from "react"
import { ChatSettings } from "@shared/ChatSettings"
interface PreferredLanguageSettingProps {
chatSettings: ChatSettings
setChatSettings: (settings: ChatSettings) => void
}
const PreferredLanguageSetting: React.FC<PreferredLanguageSettingProps> = ({ chatSettings, setChatSettings }) => {
return (
<div style={{ marginTop: 10, marginBottom: 10 }}>
<label htmlFor="preferred-language-dropdown" className="block mb-1 text-sm font-medium">
Preferred Language
</label>
<VSCodeDropdown
id="preferred-language-dropdown"
currentValue={chatSettings.preferredLanguage || "English"}
onChange={(e: any) => {
const newLanguage = e.target.value
setChatSettings({
...chatSettings,
preferredLanguage: newLanguage,
}) // This constructs a full ChatSettings object
}}
style={{ width: "100%" }}>
<VSCodeOption value="English">English</VSCodeOption>
<VSCodeOption value="Arabic - العربية">Arabic - العربية</VSCodeOption>
<VSCodeOption value="Portuguese - Português (Brasil)">Portuguese - Português (Brasil)</VSCodeOption>
<VSCodeOption value="Czech - Čeština">Czech - Čeština</VSCodeOption>
<VSCodeOption value="French - Français">French - Français</VSCodeOption>
<VSCodeOption value="German - Deutsch">German - Deutsch</VSCodeOption>
<VSCodeOption value="Hindi - हिन्दी">Hindi - ि</VSCodeOption>
<VSCodeOption value="Hungarian - Magyar">Hungarian - Magyar</VSCodeOption>
<VSCodeOption value="Italian - Italiano">Italian - Italiano</VSCodeOption>
<VSCodeOption value="Japanese - 日本語">Japanese - </VSCodeOption>
<VSCodeOption value="Korean - 한국어">Korean - </VSCodeOption>
<VSCodeOption value="Polish - Polski">Polish - Polski</VSCodeOption>
<VSCodeOption value="Portuguese - Português (Portugal)">Portuguese - Português (Portugal)</VSCodeOption>
<VSCodeOption value="Russian - Русский">Russian - Русский</VSCodeOption>
<VSCodeOption value="Simplified Chinese - 简体中文">Simplified Chinese - </VSCodeOption>
<VSCodeOption value="Spanish - Español">Spanish - Español</VSCodeOption>
<VSCodeOption value="Traditional Chinese - 繁體中文">Traditional Chinese - </VSCodeOption>
<VSCodeOption value="Turkish - Türkçe">Turkish - Türkçe</VSCodeOption>
</VSCodeDropdown>
<p className="text-xs text-[var(--vscode-descriptionForeground)] mt-1">
The language that Cline should use for communication.
</p>
</div>
)
}
export default React.memo(PreferredLanguageSetting)

View File

@ -1,5 +1,14 @@
import { VSCodeButton, VSCodeCheckbox, VSCodeLink, VSCodeTextArea } from "@vscode/webview-ui-toolkit/react"
import {
VSCodeButton,
VSCodeCheckbox,
VSCodeDropdown,
VSCodeLink,
VSCodeOption,
VSCodeTextArea,
} from "@vscode/webview-ui-toolkit/react"
import { memo, useCallback, useEffect, useState } from "react"
import PreferredLanguageSetting from "./PreferredLanguageSetting" // Added import
import { OpenAIReasoningEffort } from "@shared/ChatSettings"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { validateApiConfiguration, validateModelId } from "@/utils/validate"
import { vscode } from "@/utils/vscode"
@ -8,6 +17,7 @@ import ApiOptions from "./ApiOptions"
import { TabButton } from "../mcp/configuration/McpConfigurationView"
import { useEvent } from "react-use"
import { ExtensionMessage } from "@shared/ExtensionMessage"
import FeatureSettingsSection from "./FeatureSettingsSection"
import BrowserSettingsSection from "./BrowserSettingsSection"
import TerminalSettingsSection from "./TerminalSettingsSection"
import { FEATURE_FLAGS } from "@shared/services/feature-flags/feature-flags"
@ -27,8 +37,11 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
telemetrySetting,
setTelemetrySetting,
chatSettings,
setChatSettings,
planActSeparateModelsSetting,
setPlanActSeparateModelsSetting,
enableCheckpointsSetting,
mcpMarketplaceEnabled,
} = useExtensionState()
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
const [modelIdErrorMessage, setModelIdErrorMessage] = useState<string | undefined>(undefined)
@ -67,6 +80,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
planActSeparateModelsSetting,
customInstructionsSetting: customInstructions,
telemetrySetting,
enableCheckpointsSetting,
mcpMarketplaceEnabled,
apiConfiguration: apiConfigurationToSubmit,
})
@ -198,6 +213,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
</p>
</div>
{chatSettings && <PreferredLanguageSetting chatSettings={chatSettings} setChatSettings={setChatSettings} />}
<div className="mb-[5px]">
<VSCodeCheckbox
className="mb-[5px]"
@ -238,21 +255,15 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
</p>
</div>
{/* Feature Settings Section */}
<FeatureSettingsSection />
{/* Browser Settings Section */}
<BrowserSettingsSection />
{/* Terminal Settings Section */}
<TerminalSettingsSection />
<div className="mt-auto pr-2 flex justify-center">
<SettingsButton
onClick={() => vscode.postMessage({ type: "openExtensionSettings" })}
className="mt-0 mr-0 mb-4 ml-0">
<i className="codicon codicon-settings-gear" />
Advanced Settings
</SettingsButton>
</div>
{IS_DEV && (
<>
<div className="mt-[10px] mb-1">Debug</div>

View File

@ -17,7 +17,7 @@ import { McpMarketplaceCatalog, McpServer, McpViewTab } from "../../../src/share
import { convertTextMateToHljs } from "../utils/textMateToHljs"
import { vscode } from "../utils/vscode"
import { DEFAULT_BROWSER_SETTINGS } from "@shared/BrowserSettings"
import { DEFAULT_CHAT_SETTINGS } from "@shared/ChatSettings"
import { ChatSettings, DEFAULT_CHAT_SETTINGS } from "@shared/ChatSettings"
import { TelemetrySetting } from "@shared/TelemetrySetting"
interface ExtensionStateContextType extends ExtensionState {
@ -41,7 +41,10 @@ interface ExtensionStateContextType extends ExtensionState {
setTelemetrySetting: (value: TelemetrySetting) => void
setShowAnnouncement: (value: boolean) => void
setPlanActSeparateModelsSetting: (value: boolean) => void
setEnableCheckpointsSetting: (value: boolean) => void
setMcpMarketplaceEnabled: (value: boolean) => void
setShellIntegrationTimeout: (value: number) => void
setChatSettings: (value: ChatSettings) => void
setMcpServers: (value: McpServer[]) => void
// Navigation
@ -70,6 +73,7 @@ export const ExtensionStateContextProvider: React.FC<{
telemetrySetting: "unset",
vscMachineId: "",
planActSeparateModelsSetting: true,
enableCheckpointsSetting: true,
globalClineRulesToggles: {},
localClineRulesToggles: {},
localCursorRulesToggles: {},
@ -268,6 +272,7 @@ export const ExtensionStateContextProvider: React.FC<{
localClineRulesToggles: state.localClineRulesToggles || {},
localCursorRulesToggles: state.localCursorRulesToggles || {},
localWindsurfRulesToggles: state.localWindsurfRulesToggles || {},
enableCheckpointsSetting: state.enableCheckpointsSetting,
setApiConfiguration: (value) =>
setState((prevState) => ({
...prevState,
@ -288,6 +293,16 @@ export const ExtensionStateContextProvider: React.FC<{
...prevState,
planActSeparateModelsSetting: value,
})),
setEnableCheckpointsSetting: (value) =>
setState((prevState) => ({
...prevState,
enableCheckpointsSetting: value,
})),
setMcpMarketplaceEnabled: (value) =>
setState((prevState) => ({
...prevState,
mcpMarketplaceEnabled: value,
})),
setShowAnnouncement: (value) =>
setState((prevState) => ({
...prevState,
@ -300,6 +315,22 @@ export const ExtensionStateContextProvider: React.FC<{
})),
setMcpServers: (mcpServers: McpServer[]) => setMcpServers(mcpServers),
setShowMcp,
setChatSettings: (value) => {
setState((prevState) => ({
...prevState,
chatSettings: value,
}))
vscode.postMessage({
type: "updateSettings",
chatSettings: value,
apiConfiguration: state.apiConfiguration,
customInstructionsSetting: state.customInstructions,
telemetrySetting: state.telemetrySetting,
planActSeparateModelsSetting: state.planActSeparateModelsSetting,
enableCheckpointsSetting: state.enableCheckpointsSetting,
mcpMarketplaceEnabled: state.mcpMarketplaceEnabled,
})
},
setMcpTab,
}