Build the `detail` JSON for a tool-derived node.
(
kind: NodeKind,
tool_name: &str,
tool_input: Option<&serde_json::Value>,
tool_output: Option<&str>,
)
| 93 | |
| 94 | /// Build the `detail` JSON for a tool-derived node. |
| 95 | pub(crate) fn build_tool_detail( |
| 96 | kind: NodeKind, |
| 97 | tool_name: &str, |
| 98 | tool_input: Option<&serde_json::Value>, |
| 99 | tool_output: Option<&str>, |
| 100 | ) -> Option<serde_json::Value> { |
| 101 | match kind { |
| 102 | NodeKind::Exploration => { |
| 103 | let path = tool_input |
| 104 | .and_then(|v| { |
| 105 | v.get("path") |
| 106 | .or_else(|| v.get("file")) |
| 107 | .or_else(|| v.get("glob")) |
| 108 | .or_else(|| v.get("regex")) |
| 109 | }) |
| 110 | .and_then(|v| v.as_str()); |
| 111 | path.map(|p| serde_json::json!({"tool": tool_name, "target": p})) |
| 112 | } |
| 113 | |
| 114 | NodeKind::Commitment => { |
| 115 | // Extract file path from tool_input — OpenCode uses "filePath" (camelCase) |
| 116 | // while other agents may use "path", "file", or "file_path" (snake_case). |
| 117 | // Also check the top-level "file_path" field added by the enriched plugin. |
| 118 | let path = tool_input |
| 119 | .and_then(|v| { |
| 120 | v.get("filePath") |
| 121 | .or_else(|| v.get("path")) |
| 122 | .or_else(|| v.get("file")) |
| 123 | .or_else(|| v.get("file_path")) |
| 124 | }) |
| 125 | .and_then(|v| v.as_str()); |
| 126 | |
| 127 | let mut detail = serde_json::json!({"tool": tool_name}); |
| 128 | |
| 129 | if let Some(p) = path { |
| 130 | // Store both the full path and a shortened display path |
| 131 | detail["file_path"] = serde_json::Value::String(p.to_string()); |
| 132 | // Shorten: take last 2-3 path components for display |
| 133 | let short = shorten_path(p); |
| 134 | detail["file"] = serde_json::Value::String(short); |
| 135 | } |
| 136 | |
| 137 | // Determine operation: create vs edit |
| 138 | // "write" tool = create (new file), "edit"/"multiedit"/"patch" = edit |
| 139 | let operation = match tool_name.to_lowercase().as_str() { |
| 140 | "write" | "write_file" | "create" | "create_file" => "create", |
| 141 | "delete_file" | "remove_file" => "delete", |
| 142 | _ => "edit", |
| 143 | }; |
| 144 | detail["operation"] = serde_json::Value::String(operation.to_string()); |
| 145 | |
| 146 | // Pull in filediff from the enriched after-tool payload if present. |
| 147 | // The plugin sends: { filediff: { file, before, after, additions, deletions } } |
| 148 | if let Some(filediff) = tool_input.and_then(|v| v.get("filediff")).cloned() { |
| 149 | detail["filediff"] = filediff; |
| 150 | } |
| 151 | |
| 152 | // Pull in unified diff string |
no test coverage detected