优化文件编辑权限请求的差异样式
This commit is contained in:
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user