diff --git a/src/core/Cline.ts b/src/core/Cline.ts index f7c7b40..42358ec 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -61,7 +61,7 @@ import { OpenRouterHandler } from "../api/providers/openrouter" import { McpHub } from "../services/mcp/McpHub" import crypto from "crypto" import { insertGroups } from "./diff/insert-groups" -import { EXPERIMENT_IDS } from "../shared/experiments" +import { EXPERIMENT_IDS, experiments as Experiments } from "../shared/experiments" const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution @@ -117,7 +117,7 @@ export class Cline { task?: string | undefined, images?: string[] | undefined, historyItem?: HistoryItem | undefined, - experimentalDiffStrategy: boolean = false, + experiments?: Record, ) { if (!task && !images && !historyItem) { throw new Error("Either historyItem or task/images must be provided") @@ -139,7 +139,7 @@ export class Cline { } // Initialize diffStrategy based on current state - this.updateDiffStrategy(experimentalDiffStrategy) + this.updateDiffStrategy(Experiments.isEnabled(experiments ?? {}, EXPERIMENT_IDS.DIFF_STRATEGY)) if (task || images) { this.startTask(task, images) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 6a1cf1a..09f4e77 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -45,6 +45,7 @@ import { experimentConfigs, experiments as Experiments, experimentDefault, + ExperimentId, } from "../../shared/experiments" import { CustomSupportPrompts, supportPrompt } from "../../shared/support-prompt" @@ -360,7 +361,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { task, images, undefined, - Experiments.isEnabled(experiments, EXPERIMENT_IDS.DIFF_STRATEGY), + experiments, ) } @@ -388,7 +389,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { undefined, undefined, historyItem, - Experiments.isEnabled(experiments, EXPERIMENT_IDS.DIFF_STRATEGY), + experiments, ) } @@ -1222,7 +1223,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { const updatedExperiments = { ...((await this.getGlobalState("experiments")) ?? experimentDefault), ...message.values, - } + } as Record await this.updateGlobalState("experiments", updatedExperiments) @@ -2132,7 +2133,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getGlobalState("enhancementApiConfigId") as Promise, this.getGlobalState("autoApprovalEnabled") as Promise, this.customModesManager.getCustomModes(), - this.getGlobalState("experiments") as Promise | undefined>, + this.getGlobalState("experiments") as Promise | undefined>, ]) let apiProvider: ApiProvider diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index bab54b0..0fb7012 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -639,7 +639,7 @@ describe("ClineProvider", () => { "Test task", undefined, undefined, - false, + experimentDefault, ) }) test("handles mode-specific custom instructions updates", async () => { diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 75b2183..4cd6370 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -6,6 +6,7 @@ import { McpServer } from "./mcp" import { GitCommit } from "../utils/git" import { Mode, CustomModePrompts, ModeConfig } from "./modes" import { CustomSupportPrompts } from "./support-prompt" +import { ExperimentId } from "./experiments" export interface LanguageModelChatSelector { vendor?: string @@ -108,7 +109,7 @@ export interface ExtensionState { mode: Mode modeApiConfigs?: Record enhancementApiConfigId?: string - experiments: Record // Map of experiment IDs to their enabled state + experiments: Record // Map of experiment IDs to their enabled state autoApprovalEnabled?: boolean customModes: ModeConfig[] toolRequirements?: Record // Map of tool names to their requirements (e.g. {"apply_diff": true} if diffEnabled) diff --git a/src/shared/experiments.ts b/src/shared/experiments.ts index 0c36ed6..90805a2 100644 --- a/src/shared/experiments.ts +++ b/src/shared/experiments.ts @@ -1,19 +1,22 @@ -export interface ExperimentConfig { - id: string - name: string - description: string - enabled: boolean -} - export const EXPERIMENT_IDS = { DIFF_STRATEGY: "experimentalDiffStrategy", SEARCH_AND_REPLACE: "search_and_replace", INSERT_BLOCK: "insert_code_block", } as const -export type ExperimentId = keyof typeof EXPERIMENT_IDS +export type ExperimentKey = keyof typeof EXPERIMENT_IDS +export type ExperimentId = valueof -export const experimentConfigsMap: Record = { +export interface ExperimentConfig { + id: ExperimentId + name: string + description: string + enabled: boolean +} + +type valueof = X[keyof X] + +export const experimentConfigsMap: Record = { DIFF_STRATEGY: { id: EXPERIMENT_IDS.DIFF_STRATEGY, name: "Use experimental unified diff strategy", @@ -42,13 +45,13 @@ export const experimentConfigsMap: Record = { export const experimentConfigs = Object.values(experimentConfigsMap) export const experimentDefault = Object.fromEntries( Object.entries(experimentConfigsMap).map(([_, config]) => [config.id, config.enabled]), -) +) as Record export const experiments = { - get: (id: ExperimentId): ExperimentConfig | undefined => { + get: (id: ExperimentKey): ExperimentConfig | undefined => { return experimentConfigsMap[id] }, - isEnabled: (experimentsConfig: Record, id: string): boolean => { + isEnabled: (experimentsConfig: Record, id: ExperimentId): boolean => { return experimentsConfig[id] ?? experimentDefault[id] }, } as const diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 76c5ad9..9738edf 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -16,7 +16,7 @@ import { McpServer } from "../../../src/shared/mcp" import { checkExistKey } from "../../../src/shared/checkExistApiConfig" import { Mode, CustomModePrompts, defaultModeSlug, defaultPrompts, ModeConfig } from "../../../src/shared/modes" import { CustomSupportPrompts } from "../../../src/shared/support-prompt" -import { experimentDefault } from "../../../src/shared/experiments" +import { experimentDefault, ExperimentId } from "../../../src/shared/experiments" export interface ExtensionStateContextType extends ExtensionState { didHydrateState: boolean @@ -64,8 +64,7 @@ export interface ExtensionStateContextType extends ExtensionState { setCustomSupportPrompts: (value: CustomSupportPrompts) => void enhancementApiConfigId?: string setEnhancementApiConfigId: (value: string) => void - experiments: Record - setExperimentEnabled: (id: string, enabled: boolean) => void + setExperimentEnabled: (id: ExperimentId, enabled: boolean) => void setAutoApprovalEnabled: (value: boolean) => void handleInputChange: (field: keyof ApiConfiguration) => (event: any) => void customModes: ModeConfig[]