feat(#2196): move Write/Edit diff body to backend, drop JS branch
- fmt_builtin_tool: Edit gets its own arm computing `-N +M` line counts
in _summary (was shared with Read/Write as bare file-path).
- is_rich_tool: drop Write (content is huge/one-sided; flat _summary row
is correct); Edit stays rich since it has an old/new diff.
- rich_tool_body: now returns Option<(String, &'static str)> where the
second field is the body type ('diff' or 'plain'). Edit arm builds the
'-'/'+ ' prefixed diff body; mcp__bash__run gets type 'plain'.
- enrich_tool_use_entry: stamps both _body and _body_type.
Frontend (app.js):
- Remove the Write/Edit branch from renderRichToolUse (~25 lines).
- Generic _body path now dispatches on _body_type: 'diff' ->
api.detailsDiff (colour-coded spans), default -> api.details.
No tool-specific JS remains for file diff rendering.
This commit is contained in:
parent
da056d0043
commit
0e4b69dd20
2 changed files with 64 additions and 43 deletions
|
|
@ -239,8 +239,9 @@ fn enrich_tool_use_entry(entry: &mut Value) {
|
|||
if rich {
|
||||
obj.insert("_category".to_owned(), json!("rich"));
|
||||
}
|
||||
if let Some(b) = body {
|
||||
if let Some((b, bt)) = body {
|
||||
obj.insert("_body".to_owned(), json!(b));
|
||||
obj.insert("_body_type".to_owned(), json!(bt));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -255,8 +256,7 @@ fn enrich_tool_use_entry(entry: &mut Value) {
|
|||
fn is_rich_tool(name: &str) -> bool {
|
||||
matches!(
|
||||
name,
|
||||
"Write"
|
||||
| "Edit"
|
||||
"Edit"
|
||||
| "mcp__bash__run"
|
||||
| "mcp__hyperhive__send"
|
||||
| "mcp__hyperhive__ask"
|
||||
|
|
@ -264,14 +264,42 @@ fn is_rich_tool(name: &str) -> bool {
|
|||
)
|
||||
}
|
||||
|
||||
/// Pre-compute the expandable body text for tools that have one.
|
||||
/// Pre-compute the expandable body for rich tool entries.
|
||||
///
|
||||
/// Returns `Some(body)` when the tool has a meaningful multi-line body that
|
||||
/// the frontend can display in a `<details>` block without tool-specific JS.
|
||||
/// `None` for tools whose body is computed client-side (diffs, markdown) or
|
||||
/// that have no body at all.
|
||||
fn rich_tool_body(name: &str, input: &Value) -> Option<String> {
|
||||
/// Returns `Some((body, body_type))` where `body_type` tells the frontend
|
||||
/// which renderer to use:
|
||||
/// - `"diff"` → `api.detailsDiff` (colour-coded `+`/`-` lines)
|
||||
/// - `"plain"` → `api.details` (plain `<pre>` block)
|
||||
///
|
||||
/// Returns `None` for tools whose body is inherently client-side (markdown
|
||||
/// rendering, DOM forms) or that have no body at all.
|
||||
fn rich_tool_body(name: &str, input: &Value) -> Option<(String, &'static str)> {
|
||||
match name {
|
||||
// Edit diff: old lines prefixed `- `, new lines prefixed `+ `.
|
||||
// The summary already carries `-N +M` counts (from `fmt_builtin_tool`),
|
||||
// so the body is the full colour-coded diff.
|
||||
"Edit" => {
|
||||
let old = input
|
||||
.get("old_string")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("");
|
||||
let new = input
|
||||
.get("new_string")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("");
|
||||
let mut body = String::new();
|
||||
for line in old.lines() {
|
||||
body.push_str("- ");
|
||||
body.push_str(line);
|
||||
body.push('\n');
|
||||
}
|
||||
for line in new.lines() {
|
||||
body.push_str("+ ");
|
||||
body.push_str(line);
|
||||
body.push('\n');
|
||||
}
|
||||
Some((body, "diff"))
|
||||
}
|
||||
// Full bash command — first line is already in `_summary`; the full
|
||||
// `cmd` (prefixed with `$ `) is the body so multi-line scripts are
|
||||
// readable on expand.
|
||||
|
|
@ -280,7 +308,7 @@ fn rich_tool_body(name: &str, input: &Value) -> Option<String> {
|
|||
if cmd.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(format!("$ {cmd}"))
|
||||
Some((format!("$ {cmd}"), "plain"))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
|
|
@ -365,7 +393,23 @@ fn fmt_tool_use(name: &str, input: &Value) -> String {
|
|||
/// unknown tool that doesn't carry a known MCP server prefix.
|
||||
fn fmt_builtin_tool(name: &str, short: &str, input: &Value) -> String {
|
||||
match name {
|
||||
"Read" | "Write" | "Edit" => format!("{short} {}", sv(input, "file_path")),
|
||||
"Read" | "Write" => format!("{short} {}", sv(input, "file_path")),
|
||||
"Edit" => {
|
||||
let path = sv(input, "file_path");
|
||||
let old_n = input
|
||||
.get("old_string")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.lines()
|
||||
.count();
|
||||
let new_n = input
|
||||
.get("new_string")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.lines()
|
||||
.count();
|
||||
format!("{short} {path} · -{old_n} +{new_n}")
|
||||
}
|
||||
"Glob" | "Grep" => format!("{short} {}", sv(input, "pattern")),
|
||||
"Bash" => {
|
||||
let bg = if input
|
||||
|
|
|
|||
Loading…
Reference in a new issue