Skip to content

Commit 42a1e99

Browse files
committed
refactor(experiments): improve type safety for experiment configuration
Change ExperimentId type to be value-based rather than key-based Make experiment record types more strict with proper typing Pass full experiment config object instead of single boolean flag Update type definitions and usages across codebase
1 parent 33206cc commit 42a1e99

File tree

6 files changed

+28
-24
lines changed

6 files changed

+28
-24
lines changed

src/core/Cline.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ import { OpenRouterHandler } from "../api/providers/openrouter"
6161
import { McpHub } from "../services/mcp/McpHub"
6262
import crypto from "crypto"
6363
import { insertGroups } from "./diff/insert-groups"
64-
import { EXPERIMENT_IDS } from "../shared/experiments"
64+
import { EXPERIMENT_IDS, experiments as Experiments } from "../shared/experiments"
6565

6666
const cwd =
6767
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 {
117117
task?: string | undefined,
118118
images?: string[] | undefined,
119119
historyItem?: HistoryItem | undefined,
120-
experimentalDiffStrategy: boolean = false,
120+
experiments?: Record<string, boolean>,
121121
) {
122122
if (!task && !images && !historyItem) {
123123
throw new Error("Either historyItem or task/images must be provided")
@@ -139,7 +139,7 @@ export class Cline {
139139
}
140140

141141
// Initialize diffStrategy based on current state
142-
this.updateDiffStrategy(experimentalDiffStrategy)
142+
this.updateDiffStrategy(Experiments.isEnabled(experiments ?? {}, EXPERIMENT_IDS.DIFF_STRATEGY))
143143

144144
if (task || images) {
145145
this.startTask(task, images)

src/core/webview/ClineProvider.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
experimentConfigs,
4646
experiments as Experiments,
4747
experimentDefault,
48+
ExperimentId,
4849
} from "../../shared/experiments"
4950
import { CustomSupportPrompts, supportPrompt } from "../../shared/support-prompt"
5051

@@ -326,7 +327,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
326327
task,
327328
images,
328329
undefined,
329-
Experiments.isEnabled(experiments, EXPERIMENT_IDS.DIFF_STRATEGY),
330+
experiments,
330331
)
331332
}
332333

@@ -354,7 +355,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
354355
undefined,
355356
undefined,
356357
historyItem,
357-
Experiments.isEnabled(experiments, EXPERIMENT_IDS.DIFF_STRATEGY),
358+
experiments,
358359
)
359360
}
360361

@@ -1184,7 +1185,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
11841185
const updatedExperiments = {
11851186
...((await this.getGlobalState("experiments")) ?? experimentDefault),
11861187
...message.values,
1187-
}
1188+
} as Record<ExperimentId, boolean>
11881189

11891190
await this.updateGlobalState("experiments", updatedExperiments)
11901191

@@ -2090,7 +2091,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
20902091
this.getGlobalState("enhancementApiConfigId") as Promise<string | undefined>,
20912092
this.getGlobalState("autoApprovalEnabled") as Promise<boolean | undefined>,
20922093
this.customModesManager.getCustomModes(),
2093-
this.getGlobalState("experiments") as Promise<Record<string, boolean> | undefined>,
2094+
this.getGlobalState("experiments") as Promise<Record<ExperimentId, boolean> | undefined>,
20942095
])
20952096

20962097
let apiProvider: ApiProvider

src/core/webview/__tests__/ClineProvider.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ describe("ClineProvider", () => {
639639
"Test task",
640640
undefined,
641641
undefined,
642-
false,
642+
experimentDefault,
643643
)
644644
})
645645
test("handles mode-specific custom instructions updates", async () => {

src/shared/ExtensionMessage.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { McpServer } from "./mcp"
66
import { GitCommit } from "../utils/git"
77
import { Mode, CustomModePrompts, ModeConfig } from "./modes"
88
import { CustomSupportPrompts } from "./support-prompt"
9+
import { ExperimentId } from "./experiments"
910

1011
export interface LanguageModelChatSelector {
1112
vendor?: string
@@ -107,7 +108,7 @@ export interface ExtensionState {
107108
mode: Mode
108109
modeApiConfigs?: Record<Mode, string>
109110
enhancementApiConfigId?: string
110-
experiments: Record<string, boolean> // Map of experiment IDs to their enabled state
111+
experiments: Record<ExperimentId, boolean> // Map of experiment IDs to their enabled state
111112
autoApprovalEnabled?: boolean
112113
customModes: ModeConfig[]
113114
toolRequirements?: Record<string, boolean> // Map of tool names to their requirements (e.g. {"apply_diff": true} if diffEnabled)

src/shared/experiments.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
1-
export interface ExperimentConfig {
2-
id: string
3-
name: string
4-
description: string
5-
enabled: boolean
6-
}
7-
81
export const EXPERIMENT_IDS = {
92
DIFF_STRATEGY: "experimentalDiffStrategy",
103
SEARCH_AND_REPLACE: "search_and_replace",
114
INSERT_BLOCK: "insert_code_block",
125
} as const
136

14-
export type ExperimentId = keyof typeof EXPERIMENT_IDS
7+
export type ExperimentKey = keyof typeof EXPERIMENT_IDS
8+
export type ExperimentId = valueof<typeof EXPERIMENT_IDS>
9+
10+
export interface ExperimentConfig {
11+
id: ExperimentId
12+
name: string
13+
description: string
14+
enabled: boolean
15+
}
16+
17+
type valueof<X> = X[keyof X]
1518

16-
export const experimentConfigsMap: Record<ExperimentId, ExperimentConfig> = {
19+
export const experimentConfigsMap: Record<ExperimentKey, ExperimentConfig> = {
1720
DIFF_STRATEGY: {
1821
id: EXPERIMENT_IDS.DIFF_STRATEGY,
1922
name: "Use experimental unified diff strategy",
@@ -42,13 +45,13 @@ export const experimentConfigsMap: Record<ExperimentId, ExperimentConfig> = {
4245
export const experimentConfigs = Object.values(experimentConfigsMap)
4346
export const experimentDefault = Object.fromEntries(
4447
Object.entries(experimentConfigsMap).map(([_, config]) => [config.id, config.enabled]),
45-
)
48+
) as Record<ExperimentId, boolean>
4649

4750
export const experiments = {
48-
get: (id: ExperimentId): ExperimentConfig | undefined => {
51+
get: (id: ExperimentKey): ExperimentConfig | undefined => {
4952
return experimentConfigsMap[id]
5053
},
51-
isEnabled: (experimentsConfig: Record<string, boolean>, id: string): boolean => {
54+
isEnabled: (experimentsConfig: Record<ExperimentId, boolean>, id: ExperimentId): boolean => {
5255
return experimentsConfig[id] ?? experimentDefault[id]
5356
},
5457
} as const

webview-ui/src/context/ExtensionStateContext.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { McpServer } from "../../../src/shared/mcp"
1616
import { checkExistKey } from "../../../src/shared/checkExistApiConfig"
1717
import { Mode, CustomModePrompts, defaultModeSlug, defaultPrompts, ModeConfig } from "../../../src/shared/modes"
1818
import { CustomSupportPrompts } from "../../../src/shared/support-prompt"
19-
import { experimentDefault } from "../../../src/shared/experiments"
19+
import { experimentDefault, ExperimentId } from "../../../src/shared/experiments"
2020

2121
export interface ExtensionStateContextType extends ExtensionState {
2222
didHydrateState: boolean
@@ -63,8 +63,7 @@ export interface ExtensionStateContextType extends ExtensionState {
6363
setCustomSupportPrompts: (value: CustomSupportPrompts) => void
6464
enhancementApiConfigId?: string
6565
setEnhancementApiConfigId: (value: string) => void
66-
experiments: Record<string, boolean>
67-
setExperimentEnabled: (id: string, enabled: boolean) => void
66+
setExperimentEnabled: (id: ExperimentId, enabled: boolean) => void
6867
setAutoApprovalEnabled: (value: boolean) => void
6968
handleInputChange: (field: keyof ApiConfiguration) => (event: any) => void
7069
customModes: ModeConfig[]

0 commit comments

Comments
 (0)