feat: introduce experimental diff strategy toggle and enhance diff handling

- Added support for an experimental diff strategy in the Cline class, allowing users to opt for a new unified diff approach.
- Updated the getDiffStrategy function to accommodate the experimental strategy, adjusting the fuzzy match threshold accordingly.
- Integrated experimentalDiffStrategy into the global state management, enabling persistence across sessions.
- Enhanced the ClineProvider and related components to handle the new experimental strategy, including UI updates for user settings.
- Improved task history management to include the experimentalDiffStrategy setting, ensuring consistency in task execution.
- Updated relevant interfaces and types to reflect the new experimentalDiffStrategy property.
This commit is contained in:
Daniel Riccio
2025-01-14 17:57:09 -05:00
parent a211927097
commit f6e85fa133
8 changed files with 89 additions and 27 deletions

View File

@@ -51,6 +51,7 @@ import { detectCodeOmission } from "../integrations/editor/detect-omission"
import { BrowserSession } from "../services/browser/BrowserSession"
import { OpenRouterHandler } from "../api/providers/openrouter"
import { McpHub } from "../services/mcp/McpHub"
import crypto from "crypto"
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
@@ -105,26 +106,30 @@ export class Cline {
task?: string | undefined,
images?: string[] | undefined,
historyItem?: HistoryItem | undefined,
experimentalDiffStrategy?: boolean,
) {
this.providerRef = new WeakRef(provider)
this.taskId = crypto.randomUUID()
this.api = buildApiHandler(apiConfiguration)
this.terminalManager = new TerminalManager()
this.urlContentFetcher = new UrlContentFetcher(provider.context)
this.browserSession = new BrowserSession(provider.context)
this.diffViewProvider = new DiffViewProvider(cwd)
this.customInstructions = customInstructions
this.diffEnabled = enableDiff ?? false
if (this.diffEnabled && this.api.getModel().id) {
this.diffStrategy = getDiffStrategy(this.api.getModel().id, fuzzyMatchThreshold ?? 1.0)
}
// Prioritize experimentalDiffStrategy from history item if available
const effectiveExperimentalDiffStrategy = historyItem?.experimentalDiffStrategy ?? experimentalDiffStrategy
this.diffStrategy = getDiffStrategy(this.api.getModel().id, fuzzyMatchThreshold, effectiveExperimentalDiffStrategy)
this.diffViewProvider = new DiffViewProvider(cwd)
this.providerRef = new WeakRef(provider)
if (historyItem) {
this.taskId = historyItem.id
this.resumeTaskFromHistory()
} else if (task || images) {
this.taskId = Date.now().toString()
}
if (task || images) {
this.startTask(task, images)
} else {
throw new Error("Either historyItem or task/images must be provided")
} else if (historyItem) {
this.resumeTaskFromHistory()
}
}

View File

@@ -7,10 +7,14 @@ import { NewUnifiedDiffStrategy } from './strategies/new-unified'
* @param model The name of the model being used (e.g., 'gpt-4', 'claude-3-opus')
* @returns The appropriate diff strategy for the model
*/
export function getDiffStrategy(model: string, fuzzyMatchThreshold?: number): DiffStrategy {
// For now, return SearchReplaceDiffStrategy for all models
// This architecture allows for future optimizations based on model capabilities
return new NewUnifiedDiffStrategy()
export function getDiffStrategy(model: string, fuzzyMatchThreshold?: number, experimentalDiffStrategy?: boolean): DiffStrategy {
if (experimentalDiffStrategy) {
// Use the fuzzyMatchThreshold with a minimum of 0.8 (80%)
const threshold = Math.max(fuzzyMatchThreshold ?? 1.0, 0.8)
return new NewUnifiedDiffStrategy(threshold)
}
// Default to the stable SearchReplaceDiffStrategy
return new SearchReplaceDiffStrategy()
}
export type { DiffStrategy }

View File

@@ -85,6 +85,7 @@ type GlobalStateKey =
| "mcpEnabled"
| "alwaysApproveResubmit"
| "requestDelaySeconds"
| "experimentalDiffStrategy"
export const GlobalFileNames = {
apiConversationHistory: "api_conversation_history.json",
uiMessages: "ui_messages.json",
@@ -233,7 +234,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
apiConfiguration,
customInstructions,
diffEnabled,
fuzzyMatchThreshold
fuzzyMatchThreshold,
experimentalDiffStrategy
} = await this.getState()
this.cline = new Cline(
@@ -243,7 +245,9 @@ export class ClineProvider implements vscode.WebviewViewProvider {
diffEnabled,
fuzzyMatchThreshold,
task,
images
images,
undefined,
experimentalDiffStrategy
)
}
@@ -253,7 +257,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
apiConfiguration,
customInstructions,
diffEnabled,
fuzzyMatchThreshold
fuzzyMatchThreshold,
experimentalDiffStrategy
} = await this.getState()
this.cline = new Cline(
@@ -264,7 +269,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
fuzzyMatchThreshold,
undefined,
undefined,
historyItem
historyItem,
experimentalDiffStrategy
)
}
@@ -805,6 +811,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
}
break
}
case "experimentalDiffStrategy":
await this.updateGlobalState("experimentalDiffStrategy", message.bool ?? false)
await this.postStateToWebview()
break
}
},
null,
@@ -1155,7 +1165,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
uiMessagesFilePath: string
apiConversationHistory: Anthropic.MessageParam[]
}> {
const history = ((await this.getGlobalState("taskHistory")) as HistoryItem[] | undefined) || []
const history = (await this.getGlobalState("taskHistory") as HistoryItem[] | undefined) || []
const historyItem = history.find((item) => item.id === id)
if (historyItem) {
const taskDirPath = path.join(this.context.globalStorageUri.fsPath, "tasks", id)
@@ -1220,7 +1230,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
async deleteTaskFromState(id: string) {
// Remove the task from history
const taskHistory = ((await this.getGlobalState("taskHistory")) as HistoryItem[]) || []
const taskHistory = (await this.getGlobalState("taskHistory") as HistoryItem[]) || []
const updatedTaskHistory = taskHistory.filter((task) => task.id !== id)
await this.updateGlobalState("taskHistory", updatedTaskHistory)
@@ -1256,6 +1266,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
mcpEnabled,
alwaysApproveResubmit,
requestDelaySeconds,
experimentalDiffStrategy,
} = await this.getState()
const allowedCommands = vscode.workspace
@@ -1290,6 +1301,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
mcpEnabled: mcpEnabled ?? true,
alwaysApproveResubmit: alwaysApproveResubmit ?? false,
requestDelaySeconds: requestDelaySeconds ?? 5,
experimentalDiffStrategy: experimentalDiffStrategy ?? false,
}
}
@@ -1397,6 +1409,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
mcpEnabled,
alwaysApproveResubmit,
requestDelaySeconds,
experimentalDiffStrategy,
] = await Promise.all([
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
this.getGlobalState("apiModelId") as Promise<string | undefined>,
@@ -1449,6 +1462,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
this.getGlobalState("mcpEnabled") as Promise<boolean | undefined>,
this.getGlobalState("alwaysApproveResubmit") as Promise<boolean | undefined>,
this.getGlobalState("requestDelaySeconds") as Promise<number | undefined>,
this.getGlobalState("experimentalDiffStrategy") as Promise<boolean | undefined>,
])
let apiProvider: ApiProvider
@@ -1545,16 +1559,25 @@ export class ClineProvider implements vscode.WebviewViewProvider {
mcpEnabled: mcpEnabled ?? true,
alwaysApproveResubmit: alwaysApproveResubmit ?? false,
requestDelaySeconds: requestDelaySeconds ?? 5,
experimentalDiffStrategy: experimentalDiffStrategy ?? false,
}
}
async updateTaskHistory(item: HistoryItem): Promise<HistoryItem[]> {
const history = ((await this.getGlobalState("taskHistory")) as HistoryItem[]) || []
const history = (await this.getGlobalState("taskHistory") as HistoryItem[] | undefined) || []
const existingItemIndex = history.findIndex((h) => h.id === item.id)
// Ensure experimentalDiffStrategy is included from current settings if not already set
const { experimentalDiffStrategy } = await this.getState() ?? {}
const updatedItem = {
...item,
experimentalDiffStrategy: item.experimentalDiffStrategy ?? experimentalDiffStrategy
}
if (existingItemIndex !== -1) {
history[existingItemIndex] = item
history[existingItemIndex] = updatedItem
} else {
history.push(item)
history.push(updatedItem)
}
await this.updateGlobalState("taskHistory", history)
return history

View File

@@ -70,6 +70,7 @@ export interface ExtensionState {
writeDelayMs: number
terminalOutputLineLimit?: number
mcpEnabled: boolean
experimentalDiffStrategy?: boolean
}
export interface ClineMessage {

View File

@@ -7,4 +7,5 @@ export type HistoryItem = {
cacheWrites?: number
cacheReads?: number
totalCost: number
experimentalDiffStrategy?: boolean
}

View File

@@ -54,6 +54,7 @@ export interface WebviewMessage {
| "searchCommits"
| "alwaysApproveResubmit"
| "requestDelaySeconds"
| "experimentalDiffStrategy"
text?: string
disabled?: boolean
askResponse?: ClineAskResponse