优化文件编辑权限请求的差异样式

This commit is contained in:
xintaofei
2026-03-28 18:47:00 +08:00
parent a049db51e2
commit ab8a936767
2 changed files with 76 additions and 25 deletions

View File

@@ -886,32 +886,65 @@ async fn handle_permission_request(
let mut tool_call_value = serde_json::to_value(&req.tool_call).unwrap_or_default(); let mut tool_call_value = serde_json::to_value(&req.tool_call).unwrap_or_default();
// Resolve line numbers in rawInput for edit tool permission requests // Inject _start_line into rawInput object for edit tool permission requests
if let Some(obj) = tool_call_value.as_object_mut() { if let Some(obj) = tool_call_value.as_object_mut() {
let raw_input_key = if obj.contains_key("rawInput") { let key = ["rawInput", "raw_input"]
Some("rawInput") .into_iter()
} else if obj.contains_key("raw_input") { .find(|k| obj.contains_key(*k));
Some("raw_input") if let Some(key) = key {
} else { // If rawInput is an object with file_path + old_string, inject _start_line directly
None if let Some(input_obj) = obj.get_mut(key).and_then(|v| v.as_object_mut()) {
}; let file_path = input_obj
if let Some(key) = raw_input_key { .get("file_path")
if let Some(input_val) = obj.get(key).cloned() { .or_else(|| input_obj.get("path"))
let input_text = match &input_val { .and_then(|v| v.as_str())
serde_json::Value::String(s) => Some(s.clone()), .map(|s| s.to_string());
v if !v.is_null() => Some(v.to_string()), let old_string = input_obj
_ => None, .get("old_string")
}; .and_then(|v| v.as_str())
if let Some(text) = input_text { .map(|s| s.to_string());
let resolved = resolve_live_tool_input(&text, Some(cwd)); if let (Some(fp), Some(old_str)) = (file_path, old_string) {
if resolved != text { if let Some(sl) = find_string_start_line(&fp, &old_str, Some(cwd)) {
obj.insert( input_obj.insert(
key.to_string(), "_start_line".to_string(),
serde_json::Value::String(resolved), serde_json::json!(sl),
); );
} }
} }
} }
// If rawInput is a string, parse it and try to inject _start_line or resolve @@
else if let Some(serde_json::Value::String(text)) = obj.get(key).cloned() {
// Try canonical edit JSON: parse, inject _start_line, write back as object
if let Ok(mut parsed) = serde_json::from_str::<serde_json::Value>(&text) {
if let Some(input_obj) = parsed.as_object_mut() {
let fp = input_obj
.get("file_path")
.or_else(|| input_obj.get("path"))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let old_str = input_obj
.get("old_string")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
if let (Some(fp), Some(old_str)) = (fp, old_str) {
if let Some(sl) = find_string_start_line(&fp, &old_str, Some(cwd)) {
input_obj.insert(
"_start_line".to_string(),
serde_json::json!(sl),
);
// Write back as object so frontend asObject() works directly
obj.insert(key.to_string(), parsed);
}
}
}
}
// Apply_patch format: resolve bare @@
else if text.contains("@@\n") || text.contains("@@\r\n") {
if let Some(resolved) = crate::parsers::resolve_patch_text(&text, Some(cwd)) {
obj.insert(key.to_string(), serde_json::Value::String(resolved));
}
}
}
} }
} }

View File

@@ -12,6 +12,7 @@ export interface PermissionFileChange {
oldText: string oldText: string
newText: string newText: string
unifiedDiff?: string unifiedDiff?: string
startLine?: number
} }
export interface PermissionPlanEntry { export interface PermissionPlanEntry {
@@ -111,7 +112,8 @@ function buildCompactDiffFromTexts(
path: string, path: string,
oldText: string, oldText: string,
newText: string, newText: string,
contextLines: number = 2 contextLines: number = 2,
startLine: number = 1
): string | null { ): string | null {
const oldLines = splitNormalizedLines(oldText) const oldLines = splitNormalizedLines(oldText)
const newLines = splitNormalizedLines(newText) const newLines = splitNormalizedLines(newText)
@@ -145,7 +147,7 @@ function buildCompactDiffFromTexts(
Math.min(oldLines.length, oldLines.length - suffix + contextLines) Math.min(oldLines.length, oldLines.length - suffix + contextLines)
) )
const oldStart = Math.max(1, prefix - before.length + 1) const oldStart = Math.max(1, startLine + prefix - before.length)
const oldCount = before.length + removed.length + after.length const oldCount = before.length + removed.length + after.length
const newCount = before.length + added.length + after.length const newCount = before.length + added.length + after.length
@@ -197,7 +199,13 @@ function buildDiffPreviewFromChanges(
typeof change.unifiedDiff === "string" && typeof change.unifiedDiff === "string" &&
change.unifiedDiff.trim().length > 0 change.unifiedDiff.trim().length > 0
? change.unifiedDiff.trim() ? change.unifiedDiff.trim()
: buildCompactDiffFromTexts(change.path, change.oldText, change.newText) : buildCompactDiffFromTexts(
change.path,
change.oldText,
change.newText,
2,
change.startLine ?? 1
)
if (!block) continue if (!block) continue
for (const line of block.split("\n")) { for (const line of block.split("\n")) {
@@ -358,11 +366,18 @@ function parseChangeRecord(
pickString(record, ["unifiedDiff", "unified_diff", "diff", "patch"]) ?? pickString(record, ["unifiedDiff", "unified_diff", "diff", "patch"]) ??
undefined undefined
const rawStartLine = record._start_line ?? record.start_line
const startLine =
typeof rawStartLine === "number" && rawStartLine > 0
? rawStartLine
: undefined
return { return {
path: normalizedPath, path: normalizedPath,
oldText, oldText,
newText, newText,
unifiedDiff, unifiedDiff,
startLine,
} }
} }
@@ -410,11 +425,13 @@ function extractRawInputFileChanges(
]) ?? "" ]) ?? ""
if (oldText || newText || changes.length === 0) { if (oldText || newText || changes.length === 0) {
const rawSl = rawInputObj._start_line ?? rawInputObj.start_line
changes.push({ changes.push({
path: directPath, path: directPath,
oldText, oldText,
newText, newText,
unifiedDiff: undefined, unifiedDiff: undefined,
startLine: typeof rawSl === "number" && rawSl > 0 ? rawSl : undefined,
}) })
} }
} }
@@ -503,7 +520,8 @@ function mergeFileChanges(
const oldText = prev.oldText || change.oldText const oldText = prev.oldText || change.oldText
const newText = prev.newText || change.newText const newText = prev.newText || change.newText
const unifiedDiff = prev.unifiedDiff || change.unifiedDiff const unifiedDiff = prev.unifiedDiff || change.unifiedDiff
merged.set(path, { path, oldText, newText, unifiedDiff }) const startLine = prev.startLine ?? change.startLine
merged.set(path, { path, oldText, newText, unifiedDiff, startLine })
} }
return Array.from(merged.values()) return Array.from(merged.values())
} }