Remove the single diff strategy and make multi-diff the default (#2133)

This commit is contained in:
Matt Rubens 2025-03-30 23:07:31 -04:00 committed by GitHub
parent 520326648b
commit 9a13041186
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 71 additions and 1972 deletions

View file

@ -1,5 +1,4 @@
import type { DiffStrategy } from "./types"
import { SearchReplaceDiffStrategy } from "./strategies/search-replace"
import { NewUnifiedDiffStrategy } from "./strategies/new-unified"
import { MultiSearchReplaceDiffStrategy } from "./strategies/multi-search-replace"
import { EXPERIMENT_IDS, ExperimentId } from "../../shared/experiments"
@ -12,7 +11,7 @@ export type { DiffStrategy }
* @returns The appropriate diff strategy for the model
*/
export type DiffStrategyName = "unified" | "multi-search-and-replace" | "search-and-replace"
export type DiffStrategyName = "unified" | "multi-search-and-replace"
type GetDiffStrategyOptions = {
model: string
@ -23,6 +22,4 @@ type GetDiffStrategyOptions = {
export const getDiffStrategy = ({ fuzzyMatchThreshold, experiments }: GetDiffStrategyOptions): DiffStrategy =>
experiments[EXPERIMENT_IDS.DIFF_STRATEGY_UNIFIED]
? new NewUnifiedDiffStrategy(fuzzyMatchThreshold)
: experiments[EXPERIMENT_IDS.DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE]
? new MultiSearchReplaceDiffStrategy(fuzzyMatchThreshold)
: new SearchReplaceDiffStrategy(fuzzyMatchThreshold)
: new MultiSearchReplaceDiffStrategy(fuzzyMatchThreshold)

File diff suppressed because it is too large Load diff

View file

@ -1,306 +0,0 @@
import { DiffStrategy, DiffResult } from "../types"
import { addLineNumbers, everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text"
import { distance } from "fastest-levenshtein"
const BUFFER_LINES = 20 // Number of extra context lines to show before and after matches
function getSimilarity(original: string, search: string): number {
if (search === "") {
return 1
}
// Normalize strings by removing extra whitespace but preserve case
const normalizeStr = (str: string) => str.replace(/\s+/g, " ").trim()
const normalizedOriginal = normalizeStr(original)
const normalizedSearch = normalizeStr(search)
if (normalizedOriginal === normalizedSearch) {
return 1
}
// Calculate Levenshtein distance using fastest-levenshtein's distance function
const dist = distance(normalizedOriginal, normalizedSearch)
// Calculate similarity ratio (0 to 1, where 1 is an exact match)
const maxLength = Math.max(normalizedOriginal.length, normalizedSearch.length)
return 1 - dist / maxLength
}
export class SearchReplaceDiffStrategy implements DiffStrategy {
private fuzzyThreshold: number
private bufferLines: number
getName(): string {
return "SearchReplace"
}
constructor(fuzzyThreshold?: number, bufferLines?: number) {
// Use provided threshold or default to exact matching (1.0)
// Note: fuzzyThreshold is inverted in UI (0% = 1.0, 10% = 0.9)
// so we use it directly here
this.fuzzyThreshold = fuzzyThreshold ?? 1.0
this.bufferLines = bufferLines ?? BUFFER_LINES
}
getToolDescription(args: { cwd: string; toolOptions?: { [key: string]: string } }): string {
return `## apply_diff
Description: Request to replace existing code using a search and replace block.
This tool allows for precise, surgical replaces to files by specifying exactly what content to search for and what to replace it with.
The tool will maintain proper indentation and formatting while making changes.
Only a single operation is allowed per tool use.
The SEARCH section must exactly match existing content including whitespace and indentation.
If you're not confident in the exact content to search for, use the read_file tool first to get the exact content.
When applying the diffs, be extra careful to remember to change any closing brackets or other syntax that may be affected by the diff farther down in the file.
Parameters:
- path: (required) The path of the file to modify (relative to the current working directory ${args.cwd})
- diff: (required) The search/replace block defining the changes.
- start_line: (required) The line number where the search block starts.
- end_line: (required) The line number where the search block ends.
Diff format:
\`\`\`
<<<<<<< SEARCH
[exact content to find including whitespace]
=======
[new content to replace with]
>>>>>>> REPLACE
\`\`\`
Example:
Original file:
\`\`\`
1 | def calculate_total(items):
2 | total = 0
3 | for item in items:
4 | total += item
5 | return total
\`\`\`
Search/Replace content:
\`\`\`
<<<<<<< SEARCH
def calculate_total(items):
total = 0
for item in items:
total += item
return total
=======
def calculate_total(items):
"""Calculate total with 10% markup"""
return sum(item * 1.1 for item in items)
>>>>>>> REPLACE
\`\`\`
Usage:
<apply_diff>
<path>File path here</path>
<diff>
Your search/replace content here
</diff>
<start_line>1</start_line>
<end_line>5</end_line>
</apply_diff>`
}
async applyDiff(
originalContent: string,
diffContent: string,
startLine?: number,
endLine?: number,
): Promise<DiffResult> {
// Extract the search and replace blocks
const match = diffContent.match(/<<<<<<< SEARCH\n([\s\S]*?)\n?=======\n([\s\S]*?)\n?>>>>>>> REPLACE/)
if (!match) {
return {
success: false,
error: `Invalid diff format - missing required SEARCH/REPLACE sections\n\nDebug Info:\n- Expected Format: <<<<<<< SEARCH\\n[search content]\\n=======\\n[replace content]\\n>>>>>>> REPLACE\n- Tip: Make sure to include both SEARCH and REPLACE sections with correct markers`,
}
}
let [_, searchContent, replaceContent] = match
// Detect line ending from original content
const lineEnding = originalContent.includes("\r\n") ? "\r\n" : "\n"
// Strip line numbers from search and replace content if every line starts with a line number
if (everyLineHasLineNumbers(searchContent) && everyLineHasLineNumbers(replaceContent)) {
searchContent = stripLineNumbers(searchContent)
replaceContent = stripLineNumbers(replaceContent)
}
// Split content into lines, handling both \n and \r\n
const searchLines = searchContent === "" ? [] : searchContent.split(/\r?\n/)
const replaceLines = replaceContent === "" ? [] : replaceContent.split(/\r?\n/)
const originalLines = originalContent.split(/\r?\n/)
// Validate that empty search requires start line
if (searchLines.length === 0 && !startLine) {
return {
success: false,
error: `Empty search content requires start_line to be specified\n\nDebug Info:\n- Empty search content is only valid for insertions at a specific line\n- For insertions, specify the line number where content should be inserted`,
}
}
// Validate that empty search requires same start and end line
if (searchLines.length === 0 && startLine && endLine && startLine !== endLine) {
return {
success: false,
error: `Empty search content requires start_line and end_line to be the same (got ${startLine}-${endLine})\n\nDebug Info:\n- Empty search content is only valid for insertions at a specific line\n- For insertions, use the same line number for both start_line and end_line`,
}
}
// Initialize search variables
let matchIndex = -1
let bestMatchScore = 0
let bestMatchContent = ""
const searchChunk = searchLines.join("\n")
// Determine search bounds
let searchStartIndex = 0
let searchEndIndex = originalLines.length
// Validate and handle line range if provided
if (startLine && endLine) {
// Convert to 0-based index
const exactStartIndex = startLine - 1
const exactEndIndex = endLine - 1
if (exactStartIndex < 0 || exactEndIndex > originalLines.length || exactStartIndex > exactEndIndex) {
return {
success: false,
error: `Line range ${startLine}-${endLine} is invalid (file has ${originalLines.length} lines)\n\nDebug Info:\n- Requested Range: lines ${startLine}-${endLine}\n- File Bounds: lines 1-${originalLines.length}`,
}
}
// Try exact match first
const originalChunk = originalLines.slice(exactStartIndex, exactEndIndex + 1).join("\n")
const similarity = getSimilarity(originalChunk, searchChunk)
if (similarity >= this.fuzzyThreshold) {
matchIndex = exactStartIndex
bestMatchScore = similarity
bestMatchContent = originalChunk
} else {
// Set bounds for buffered search
searchStartIndex = Math.max(0, startLine - (this.bufferLines + 1))
searchEndIndex = Math.min(originalLines.length, endLine + this.bufferLines)
}
}
// If no match found yet, try middle-out search within bounds
if (matchIndex === -1) {
const midPoint = Math.floor((searchStartIndex + searchEndIndex) / 2)
let leftIndex = midPoint
let rightIndex = midPoint + 1
// Search outward from the middle within bounds
while (leftIndex >= searchStartIndex || rightIndex <= searchEndIndex - searchLines.length) {
// Check left side if still in range
if (leftIndex >= searchStartIndex) {
const originalChunk = originalLines.slice(leftIndex, leftIndex + searchLines.length).join("\n")
const similarity = getSimilarity(originalChunk, searchChunk)
if (similarity > bestMatchScore) {
bestMatchScore = similarity
matchIndex = leftIndex
bestMatchContent = originalChunk
}
leftIndex--
}
// Check right side if still in range
if (rightIndex <= searchEndIndex - searchLines.length) {
const originalChunk = originalLines.slice(rightIndex, rightIndex + searchLines.length).join("\n")
const similarity = getSimilarity(originalChunk, searchChunk)
if (similarity > bestMatchScore) {
bestMatchScore = similarity
matchIndex = rightIndex
bestMatchContent = originalChunk
}
rightIndex++
}
}
}
// Require similarity to meet threshold
if (matchIndex === -1 || bestMatchScore < this.fuzzyThreshold) {
const searchChunk = searchLines.join("\n")
const originalContentSection =
startLine !== undefined && endLine !== undefined
? `\n\nOriginal Content:\n${addLineNumbers(
originalLines
.slice(
Math.max(0, startLine - 1 - this.bufferLines),
Math.min(originalLines.length, endLine + this.bufferLines),
)
.join("\n"),
Math.max(1, startLine - this.bufferLines),
)}`
: `\n\nOriginal Content:\n${addLineNumbers(originalLines.join("\n"))}`
const bestMatchSection = bestMatchContent
? `\n\nBest Match Found:\n${addLineNumbers(bestMatchContent, matchIndex + 1)}`
: `\n\nBest Match Found:\n(no match)`
const lineRange =
startLine || endLine
? ` at ${startLine ? `start: ${startLine}` : "start"} to ${endLine ? `end: ${endLine}` : "end"}`
: ""
return {
success: false,
error: `No sufficiently similar match found${lineRange} (${Math.floor(bestMatchScore * 100)}% similar, needs ${Math.floor(this.fuzzyThreshold * 100)}%)\n\nDebug Info:\n- Similarity Score: ${Math.floor(bestMatchScore * 100)}%\n- Required Threshold: ${Math.floor(this.fuzzyThreshold * 100)}%\n- Search Range: ${startLine && endLine ? `lines ${startLine}-${endLine}` : "start to end"}\n- Tip: Use read_file to get the latest content of the file before attempting the diff again, as the file content may have changed\n\nSearch Content:\n${searchChunk}${bestMatchSection}${originalContentSection}`,
}
}
// Get the matched lines from the original content
const matchedLines = originalLines.slice(matchIndex, matchIndex + searchLines.length)
// Get the exact indentation (preserving tabs/spaces) of each line
const originalIndents = matchedLines.map((line) => {
const match = line.match(/^[\t ]*/)
return match ? match[0] : ""
})
// Get the exact indentation of each line in the search block
const searchIndents = searchLines.map((line) => {
const match = line.match(/^[\t ]*/)
return match ? match[0] : ""
})
// Apply the replacement while preserving exact indentation
const indentedReplaceLines = replaceLines.map((line, i) => {
// Get the matched line's exact indentation
const matchedIndent = originalIndents[0] || ""
// Get the current line's indentation relative to the search content
const currentIndentMatch = line.match(/^[\t ]*/)
const currentIndent = currentIndentMatch ? currentIndentMatch[0] : ""
const searchBaseIndent = searchIndents[0] || ""
// Calculate the relative indentation level
const searchBaseLevel = searchBaseIndent.length
const currentLevel = currentIndent.length
const relativeLevel = currentLevel - searchBaseLevel
// If relative level is negative, remove indentation from matched indent
// If positive, add to matched indent
const finalIndent =
relativeLevel < 0
? matchedIndent.slice(0, Math.max(0, matchedIndent.length + relativeLevel))
: matchedIndent + currentIndent.slice(searchBaseLevel)
return finalIndent + line.trim()
})
// Construct the final content
const beforeMatch = originalLines.slice(0, matchIndex)
const afterMatch = originalLines.slice(matchIndex + searchLines.length)
const finalContent = [...beforeMatch, ...indentedReplaceLines, ...afterMatch].join(lineEnding)
return {
success: true,
content: finalContent,
}
}
}

View file

@ -4031,22 +4031,26 @@ Only a single operation is allowed per tool use.
The SEARCH section must exactly match existing content including whitespace and indentation.
If you're not confident in the exact content to search for, use the read_file tool first to get the exact content.
When applying the diffs, be extra careful to remember to change any closing brackets or other syntax that may be affected by the diff farther down in the file.
ALWAYS make as many changes in a single 'apply_diff' request as possible using multiple SEARCH/REPLACE blocks
Parameters:
- path: (required) The path of the file to modify (relative to the current working directory /test/path)
- diff: (required) The search/replace block defining the changes.
- start_line: (required) The line number where the search block starts.
- end_line: (required) The line number where the search block ends.
Diff format:
\`\`\`
<<<<<<< SEARCH
:start_line: (required) The line number of original content where the search block starts.
:end_line: (required) The line number of original content where the search block ends.
-------
[exact content to find including whitespace]
=======
[new content to replace with]
>>>>>>> REPLACE
\`\`\`
Example:
Original file:
@ -4061,6 +4065,9 @@ Original file:
Search/Replace content:
\`\`\`
<<<<<<< SEARCH
:start_line:1
:end_line:5
-------
def calculate_total(items):
total = 0
for item in items:
@ -4071,16 +4078,43 @@ def calculate_total(items):
"""Calculate total with 10% markup"""
return sum(item * 1.1 for item in items)
>>>>>>> REPLACE
\`\`\`
Search/Replace content with multi edits:
\`\`\`
<<<<<<< SEARCH
:start_line:1
:end_line:2
-------
def calculate_sum(items):
sum = 0
=======
def calculate_sum(items):
sum = 0
>>>>>>> REPLACE
<<<<<<< SEARCH
:start_line:4
:end_line:5
-------
total += item
return total
=======
sum += item
return sum
>>>>>>> REPLACE
\`\`\`
Usage:
<apply_diff>
<path>File path here</path>
<diff>
Your search/replace content here
You can use multi search/replace block in one diff block, but make sure to include the line numbers for each block.
Only use a single line of '=======' between search and replacement content, because multiple '=======' will corrupt the file.
</diff>
<start_line>1</start_line>
<end_line>5</end_line>
</apply_diff>
## write_to_file

View file

@ -3,11 +3,11 @@ import * as vscode from "vscode"
import { SYSTEM_PROMPT } from "../system"
import { McpHub } from "../../../services/mcp/McpHub"
import { ClineProvider } from "../../../core/webview/ClineProvider"
import { SearchReplaceDiffStrategy } from "../../../core/diff/strategies/search-replace"
import { defaultModeSlug, modes, Mode, ModeConfig } from "../../../shared/modes"
import "../../../utils/path" // Import path utils to get access to toPosix string extension.
import { addCustomInstructions } from "../sections/custom-instructions"
import { EXPERIMENT_IDS } from "../../../shared/experiments"
import { MultiSearchReplaceDiffStrategy } from "../../diff/strategies/multi-search-replace"
// Mock the sections
jest.mock("../sections/modes", () => ({
@ -171,7 +171,7 @@ describe("SYSTEM_PROMPT", () => {
beforeEach(() => {
// Reset experiments before each test to ensure they're disabled by default
experiments = {
[EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: false,
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: false,
[EXPERIMENT_IDS.INSERT_BLOCK]: false,
}
})
@ -295,7 +295,7 @@ describe("SYSTEM_PROMPT", () => {
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
new SearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
new MultiSearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
@ -316,7 +316,7 @@ describe("SYSTEM_PROMPT", () => {
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
new SearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
new MultiSearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
@ -337,7 +337,7 @@ describe("SYSTEM_PROMPT", () => {
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
new SearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
new MultiSearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
@ -482,7 +482,7 @@ describe("SYSTEM_PROMPT", () => {
it("should disable experimental tools by default", async () => {
// Set experiments to explicitly disable experimental tools
const experimentsConfig = {
[EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: false,
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: false,
[EXPERIMENT_IDS.INSERT_BLOCK]: false,
}
@ -516,7 +516,7 @@ describe("SYSTEM_PROMPT", () => {
it("should enable experimental tools when explicitly enabled", async () => {
// Set experiments for testing experimental features
const experimentsEnabled = {
[EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: true,
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true,
[EXPERIMENT_IDS.INSERT_BLOCK]: true,
}
@ -552,7 +552,7 @@ describe("SYSTEM_PROMPT", () => {
it("should selectively enable experimental tools", async () => {
// Set experiments for testing selective enabling
const experimentsSelective = {
[EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: true,
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true,
[EXPERIMENT_IDS.INSERT_BLOCK]: false,
}
@ -587,7 +587,7 @@ describe("SYSTEM_PROMPT", () => {
it("should list all available editing tools in base instruction", async () => {
const experiments = {
[EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: true,
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true,
[EXPERIMENT_IDS.INSERT_BLOCK]: true,
}
@ -596,7 +596,7 @@ describe("SYSTEM_PROMPT", () => {
"/test/path",
false,
undefined,
new SearchReplaceDiffStrategy(),
new MultiSearchReplaceDiffStrategy(),
undefined,
defaultModeSlug,
undefined,
@ -615,7 +615,7 @@ describe("SYSTEM_PROMPT", () => {
})
it("should provide detailed instructions for each enabled tool", async () => {
const experiments = {
[EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: true,
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true,
[EXPERIMENT_IDS.INSERT_BLOCK]: true,
}
@ -624,7 +624,7 @@ describe("SYSTEM_PROMPT", () => {
"/test/path",
false,
undefined,
new SearchReplaceDiffStrategy(),
new MultiSearchReplaceDiffStrategy(),
undefined,
defaultModeSlug,
undefined,

View file

@ -254,7 +254,6 @@ type GlobalSettings = {
| {
search_and_replace: boolean
experimentalDiffStrategy: boolean
multi_search_and_replace: boolean
insert_content: boolean
powerSteering: boolean
}

View file

@ -257,7 +257,6 @@ type GlobalSettings = {
| {
search_and_replace: boolean
experimentalDiffStrategy: boolean
multi_search_and_replace: boolean
insert_content: boolean
powerSteering: boolean
}

View file

@ -277,7 +277,6 @@ export type CustomSupportPrompts = z.infer<typeof customSupportPromptsSchema>
export const experimentIds = [
"search_and_replace",
"experimentalDiffStrategy",
"multi_search_and_replace",
"insert_content",
"powerSteering",
] as const
@ -293,7 +292,6 @@ export type ExperimentId = z.infer<typeof experimentIdsSchema>
const experimentsSchema = z.object({
search_and_replace: z.boolean(),
experimentalDiffStrategy: z.boolean(),
multi_search_and_replace: z.boolean(),
insert_content: z.boolean(),
powerSteering: z.boolean(),
})

View file

@ -17,7 +17,6 @@ describe("experiments", () => {
experimentalDiffStrategy: false,
search_and_replace: false,
insert_content: false,
multi_search_and_replace: false,
}
expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false)
})
@ -28,7 +27,6 @@ describe("experiments", () => {
experimentalDiffStrategy: false,
search_and_replace: false,
insert_content: false,
multi_search_and_replace: false,
}
expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(true)
})
@ -39,7 +37,6 @@ describe("experiments", () => {
search_and_replace: false,
insert_content: false,
powerSteering: false,
multi_search_and_replace: false,
}
expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false)
})

View file

@ -4,10 +4,9 @@ import { AssertEqual, Equals, Keys, Values } from "../utils/type-fu"
export type { ExperimentId }
export const EXPERIMENT_IDS = {
DIFF_STRATEGY_SEARCH_AND_REPLACE: "search_and_replace",
DIFF_STRATEGY_UNIFIED: "experimentalDiffStrategy",
DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE: "multi_search_and_replace",
INSERT_BLOCK: "insert_content",
SEARCH_AND_REPLACE: "search_and_replace",
POWER_STEERING: "powerSteering",
} as const satisfies Record<string, ExperimentId>
@ -20,10 +19,9 @@ interface ExperimentConfig {
}
export const experimentConfigsMap: Record<ExperimentKey, ExperimentConfig> = {
DIFF_STRATEGY_SEARCH_AND_REPLACE: { enabled: false },
DIFF_STRATEGY_UNIFIED: { enabled: false },
DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE: { enabled: false },
INSERT_BLOCK: { enabled: false },
SEARCH_AND_REPLACE: { enabled: false },
POWER_STEERING: { enabled: false },
}

View file

@ -6,7 +6,7 @@ import { Cog } from "lucide-react"
import { EXPERIMENT_IDS, ExperimentId } from "../../../../src/shared/experiments"
import { cn } from "@/lib/utils"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Slider } from "@/components/ui"
import { Slider } from "@/components/ui"
import { SetCachedStateField, SetExperimentEnabled } from "./types"
import { SectionHeader } from "./SectionHeader"
@ -67,9 +67,8 @@ export const AdvancedSettings = ({
onChange={(e: any) => {
setCachedStateField("diffEnabled", e.target.checked)
if (!e.target.checked) {
// Reset both experimental strategies when diffs are disabled.
// Reset experimental strategies when diffs are disabled.
setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY_UNIFIED, false)
setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE, false)
}
}}>
<span className="font-medium">{t("settings:advanced.diff.label")}</span>
@ -81,63 +80,6 @@ export const AdvancedSettings = ({
{diffEnabled && (
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
<div>
<label className="block font-medium mb-1">
{t("settings:advanced.diff.strategy.label")}
</label>
<Select
value={
experiments[EXPERIMENT_IDS.DIFF_STRATEGY_UNIFIED]
? "unified"
: experiments[EXPERIMENT_IDS.DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE]
? "multiBlock"
: "standard"
}
onValueChange={(value) => {
if (value === "standard") {
setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY_UNIFIED, false)
setExperimentEnabled(
EXPERIMENT_IDS.DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE,
false,
)
} else if (value === "unified") {
setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY_UNIFIED, true)
setExperimentEnabled(
EXPERIMENT_IDS.DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE,
false,
)
} else if (value === "multiBlock") {
setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY_UNIFIED, false)
setExperimentEnabled(
EXPERIMENT_IDS.DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE,
true,
)
}
}}>
<SelectTrigger className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="standard">
{t("settings:advanced.diff.strategy.options.standard")}
</SelectItem>
<SelectItem value="multiBlock">
{t("settings:advanced.diff.strategy.options.multiBlock")}
</SelectItem>
<SelectItem value="unified">
{t("settings:advanced.diff.strategy.options.unified")}
</SelectItem>
</SelectContent>
</Select>
<div className="text-vscode-descriptionForeground text-sm mt-1">
{experiments[EXPERIMENT_IDS.DIFF_STRATEGY_UNIFIED]
? t("settings:advanced.diff.strategy.descriptions.unified")
: experiments[EXPERIMENT_IDS.DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE]
? t("settings:advanced.diff.strategy.descriptions.multiBlock")
: t("settings:advanced.diff.strategy.descriptions.standard")}
</div>
</div>
<div>
<label className="block font-medium mb-1">
{t("settings:advanced.diff.matchPrecision.label")}

View file

@ -222,7 +222,6 @@ describe("mergeExtensionState", () => {
apiConfiguration: { modelMaxThinkingTokens: 456, modelTemperature: 0.3 },
experiments: {
powerSteering: true,
multi_search_and_replace: true,
} as Record<ExperimentId, boolean>,
}
@ -238,7 +237,6 @@ describe("mergeExtensionState", () => {
search_and_replace: true,
insert_content: true,
powerSteering: true,
multi_search_and_replace: true,
})
})
})

View file

@ -321,7 +321,7 @@
},
"experimental": {
"warning": "⚠️",
"DIFF_STRATEGY": {
"DIFF_STRATEGY_UNIFIED": {
"name": "Utilitzar estratègia diff unificada experimental",
"description": "Activar l'estratègia diff unificada experimental. Aquesta estratègia podria reduir el nombre de reintents causats per errors del model, però pot causar comportaments inesperats o edicions incorrectes. Activeu-la només si enteneu els riscos i esteu disposats a revisar acuradament tots els canvis."
},

View file

@ -321,7 +321,7 @@
},
"experimental": {
"warning": "⚠️",
"DIFF_STRATEGY": {
"DIFF_STRATEGY_UNIFIED": {
"name": "Experimentelle einheitliche Diff-Strategie verwenden",
"description": "Aktiviert die experimentelle einheitliche Diff-Strategie. Diese Strategie könnte die Anzahl der durch Modellfehler verursachten Wiederholungen reduzieren, kann aber unerwartetes Verhalten oder falsche Bearbeitungen verursachen. Nur aktivieren, wenn du die Risiken verstehst und bereit bist, alle Änderungen sorgfältig zu überprüfen."
},

View file

@ -321,7 +321,7 @@
},
"experimental": {
"warning": "⚠️",
"DIFF_STRATEGY": {
"DIFF_STRATEGY_UNIFIED": {
"name": "Use experimental unified diff strategy",
"description": "Enable the experimental unified diff strategy. This strategy might reduce the number of retries caused by model errors but may cause unexpected behavior or incorrect edits. Only enable if you understand the risks and are willing to carefully review all changes."
},

View file

@ -321,7 +321,7 @@
},
"experimental": {
"warning": "⚠️",
"DIFF_STRATEGY": {
"DIFF_STRATEGY_UNIFIED": {
"name": "Usar estrategia de diff unificada experimental",
"description": "Habilitar la estrategia de diff unificada experimental. Esta estrategia podría reducir el número de reintentos causados por errores del modelo, pero puede causar comportamientos inesperados o ediciones incorrectas. Habilítela solo si comprende los riesgos y está dispuesto a revisar cuidadosamente todos los cambios."
},

View file

@ -321,7 +321,7 @@
},
"experimental": {
"warning": "⚠️",
"DIFF_STRATEGY": {
"DIFF_STRATEGY_UNIFIED": {
"name": "Utiliser la stratégie diff unifiée expérimentale",
"description": "Activer la stratégie diff unifiée expérimentale. Cette stratégie pourrait réduire le nombre de tentatives causées par des erreurs de modèle, mais peut provoquer des comportements inattendus ou des modifications incorrectes. Activez-la uniquement si vous comprenez les risques et êtes prêt à examiner attentivement tous les changements."
},

View file

@ -321,7 +321,7 @@
},
"experimental": {
"warning": "⚠️",
"DIFF_STRATEGY": {
"DIFF_STRATEGY_UNIFIED": {
"name": "प्रायोगिक एकीकृत diff रणनीति का उपयोग करें",
"description": "प्रायोगिक एकीकृत diff रणनीति सक्षम करें। यह रणनीति मॉडल त्रुटियों के कारण पुनः प्रयासों की संख्या को कम कर सकती है, लेकिन अप्रत्याशित व्यवहार या गलत संपादन का कारण बन सकती है। केवल तभी सक्षम करें जब आप जोखिमों को समझते हों और सभी परिवर्तनों की सावधानीपूर्वक समीक्षा करने के लिए तैयार हों।"
},

View file

@ -321,7 +321,7 @@
},
"experimental": {
"warning": "⚠️",
"DIFF_STRATEGY": {
"DIFF_STRATEGY_UNIFIED": {
"name": "Usa strategia diff unificata sperimentale",
"description": "Abilita la strategia diff unificata sperimentale. Questa strategia potrebbe ridurre il numero di tentativi causati da errori del modello, ma può causare comportamenti imprevisti o modifiche errate. Abilitala solo se comprendi i rischi e sei disposto a rivedere attentamente tutte le modifiche."
},

View file

@ -321,7 +321,7 @@
},
"experimental": {
"warning": "⚠️",
"DIFF_STRATEGY": {
"DIFF_STRATEGY_UNIFIED": {
"name": "実験的な統合diff戦略を使用する",
"description": "実験的な統合diff戦略を有効にします。この戦略はモデルエラーによる再試行の回数を減らす可能性がありますが、予期しない動作や不正確な編集を引き起こす可能性があります。リスクを理解し、すべての変更を注意深く確認する準備がある場合にのみ有効にしてください。"
},

View file

@ -321,7 +321,7 @@
},
"experimental": {
"warning": "⚠️",
"DIFF_STRATEGY": {
"DIFF_STRATEGY_UNIFIED": {
"name": "실험적 통합 diff 전략 사용",
"description": "실험적 통합 diff 전략을 활성화합니다. 이 전략은 모델 오류로 인한 재시도 횟수를 줄일 수 있지만 예기치 않은 동작이나 잘못된 편집을 일으킬 수 있습니다. 위험을 이해하고 모든 변경 사항을 신중하게 검토할 의향이 있는 경우에만 활성화하십시오."
},

View file

@ -321,7 +321,7 @@
},
"experimental": {
"warning": "⚠️",
"DIFF_STRATEGY": {
"DIFF_STRATEGY_UNIFIED": {
"name": "Użyj eksperymentalnej ujednoliconej strategii diff",
"description": "Włącz eksperymentalną ujednoliconą strategię diff. Ta strategia może zmniejszyć liczbę ponownych prób spowodowanych błędami modelu, ale może powodować nieoczekiwane zachowanie lub nieprawidłowe edycje. Włącz tylko jeśli rozumiesz ryzyko i jesteś gotów dokładnie przeglądać wszystkie zmiany."
},

View file

@ -321,7 +321,7 @@
},
"experimental": {
"warning": "⚠️",
"DIFF_STRATEGY": {
"DIFF_STRATEGY_UNIFIED": {
"name": "Usar estratégia diff unificada experimental",
"description": "Ativar a estratégia diff unificada experimental. Esta estratégia pode reduzir o número de novas tentativas causadas por erros do modelo, mas pode causar comportamento inesperado ou edições incorretas. Ative apenas se compreender os riscos e estiver disposto a revisar cuidadosamente todas as alterações."
},

View file

@ -321,7 +321,7 @@
},
"experimental": {
"warning": "⚠️",
"DIFF_STRATEGY": {
"DIFF_STRATEGY_UNIFIED": {
"name": "Deneysel birleştirilmiş diff stratejisini kullan",
"description": "Deneysel birleştirilmiş diff stratejisini etkinleştir. Bu strateji, model hatalarından kaynaklanan yeniden deneme sayısını azaltabilir, ancak beklenmeyen davranışlara veya hatalı düzenlemelere neden olabilir. Yalnızca riskleri anlıyorsanız ve tüm değişiklikleri dikkatlice incelemeye istekliyseniz etkinleştirin."
},

View file

@ -321,7 +321,7 @@
},
"experimental": {
"warning": "⚠️",
"DIFF_STRATEGY": {
"DIFF_STRATEGY_UNIFIED": {
"name": "Sử dụng chiến lược diff thống nhất thử nghiệm",
"description": "Bật chiến lược diff thống nhất thử nghiệm. Chiến lược này có thể giảm số lần thử lại do lỗi mô hình nhưng có thể gây ra hành vi không mong muốn hoặc chỉnh sửa không chính xác. Chỉ bật nếu bạn hiểu rõ các rủi ro và sẵn sàng xem xét cẩn thận tất cả các thay đổi."
},

View file

@ -321,7 +321,7 @@
},
"experimental": {
"warning": "⚠️",
"DIFF_STRATEGY": {
"DIFF_STRATEGY_UNIFIED": {
"name": "使用实验性统一差异策略",
"description": "启用实验性统一差异策略。此策略可能减少由模型错误引起的重试次数,但可能导致意外行为或不正确的编辑。仅在您了解风险并愿意仔细审查所有更改时才启用。"
},

View file

@ -321,7 +321,7 @@
},
"experimental": {
"warning": "⚠️",
"DIFF_STRATEGY": {
"DIFF_STRATEGY_UNIFIED": {
"name": "使用實驗性統一 diff 策略",
"description": "此實驗性策略可能減少模型錯誤導致的重試次數,但需謹慎使用"
},