feat(core): replace expected_replacements with allow_multiple in replace tool (#20033)

This commit is contained in:
Sandy Tao
2026-02-23 11:53:58 -08:00
committed by GitHub
parent 70336e73b1
commit 0cc4f09595
7 changed files with 124 additions and 100 deletions
+26 -32
View File
@@ -185,12 +185,16 @@ export async function ensureCorrectEdit(
unescapeStringForGeminiBug(originalParams.new_string) !==
originalParams.new_string;
const expectedReplacements = originalParams.expected_replacements ?? 1;
const allowMultiple = originalParams.allow_multiple ?? false;
let finalOldString = originalParams.old_string;
let occurrences = countOccurrences(currentContent, finalOldString);
if (occurrences === expectedReplacements) {
const isOccurrencesMatch = allowMultiple
? occurrences > 0
: occurrences === 1;
if (isOccurrencesMatch) {
if (newStringPotentiallyEscaped && !disableLLMCorrection) {
finalNewString = await correctNewStringEscaping(
baseLlmClient,
@@ -199,30 +203,8 @@ export async function ensureCorrectEdit(
abortSignal,
);
}
} else if (occurrences > expectedReplacements) {
const expectedReplacements = originalParams.expected_replacements ?? 1;
// If user expects multiple replacements, return as-is
if (occurrences === expectedReplacements) {
const result: CorrectedEditResult = {
params: { ...originalParams },
occurrences,
};
editCorrectionCache.set(cacheKey, result);
return result;
}
// If user expects 1 but found multiple, try to correct (existing behavior)
if (expectedReplacements === 1) {
const result: CorrectedEditResult = {
params: { ...originalParams },
occurrences,
};
editCorrectionCache.set(cacheKey, result);
return result;
}
// If occurrences don't match expected, return as-is (will fail validation later)
} else if (occurrences > 1 && !allowMultiple) {
// If user doesn't allow multiple but found multiple, return as-is (will fail validation later)
const result: CorrectedEditResult = {
params: { ...originalParams },
occurrences,
@@ -236,7 +218,11 @@ export async function ensureCorrectEdit(
);
occurrences = countOccurrences(currentContent, unescapedOldStringAttempt);
if (occurrences === expectedReplacements) {
const isUnescapedOccurrencesMatch = allowMultiple
? occurrences > 0
: occurrences === 1;
if (isUnescapedOccurrencesMatch) {
finalOldString = unescapedOldStringAttempt;
if (newStringPotentiallyEscaped && !disableLLMCorrection) {
finalNewString = await correctNewString(
@@ -296,7 +282,11 @@ export async function ensureCorrectEdit(
llmCorrectedOldString,
);
if (llmOldOccurrences === expectedReplacements) {
const isLlmOccurrencesMatch = allowMultiple
? llmOldOccurrences > 0
: llmOldOccurrences === 1;
if (isLlmOccurrencesMatch) {
finalOldString = llmCorrectedOldString;
occurrences = llmOldOccurrences;
@@ -322,7 +312,7 @@ export async function ensureCorrectEdit(
return result;
}
} else {
// Unescaping old_string resulted in > 1 occurrence
// Unescaping old_string resulted in > 1 occurrence but not allowMultiple
const result: CorrectedEditResult = {
params: { ...originalParams },
occurrences, // This will be > 1
@@ -336,7 +326,7 @@ export async function ensureCorrectEdit(
finalOldString,
finalNewString,
currentContent,
expectedReplacements,
allowMultiple,
);
finalOldString = targetString;
finalNewString = pair;
@@ -705,7 +695,7 @@ function trimPairIfPossible(
target: string,
trimIfTargetTrims: string,
currentContent: string,
expectedReplacements: number,
allowMultiple: boolean,
) {
const trimmedTargetString = trimPreservingTrailingNewline(target);
if (target.length !== trimmedTargetString.length) {
@@ -714,7 +704,11 @@ function trimPairIfPossible(
trimmedTargetString,
);
if (trimmedTargetOccurrences === expectedReplacements) {
const isMatch = allowMultiple
? trimmedTargetOccurrences > 0
: trimmedTargetOccurrences === 1;
if (isMatch) {
const trimmedReactiveString =
trimPreservingTrailingNewline(trimIfTargetTrims);
return {