(tool_name: &str, tool_input: Option<&serde_json::Value>)
| 410 | // ============================================================================= |
| 411 | |
| 412 | fn summarize_exploration(tool_name: &str, tool_input: Option<&serde_json::Value>) -> String { |
| 413 | // Try to extract the file/path being explored. |
| 414 | // OpenCode uses "filePath" (camelCase); other agents use "path", "file", etc. |
| 415 | // For bash-based reads (ls, cat, find), the path is embedded in the command string. |
| 416 | let path = tool_input |
| 417 | .and_then(|v| { |
| 418 | v.get("filePath") |
| 419 | .or_else(|| v.get("path")) |
| 420 | .or_else(|| v.get("file")) |
| 421 | .or_else(|| v.get("file_path")) |
| 422 | .or_else(|| v.get("glob")) |
| 423 | .or_else(|| v.get("regex")) |
| 424 | .or_else(|| v.get("pattern")) |
| 425 | }) |
| 426 | .and_then(|v| v.as_str()); |
| 427 | |
| 428 | // For bash tools, try to extract a meaningful target from the command string |
| 429 | let bash_target = if path.is_none() { |
| 430 | tool_input |
| 431 | .and_then(|v| v.get("command").or_else(|| v.get("cmd"))) |
| 432 | .and_then(|v| v.as_str()) |
| 433 | .map(|cmd| { |
| 434 | // Extract the last meaningful argument from common read commands |
| 435 | // "ls -la /some/path" → "/some/path" |
| 436 | // "cat src/index.ts" → "src/index.ts" |
| 437 | // "find . -name '*.ts'" → "*.ts files" |
| 438 | let cmd = cmd.trim(); |
| 439 | if let Some(rest) = cmd |
| 440 | .strip_prefix("cat ") |
| 441 | .or_else(|| cmd.strip_prefix("head ")) |
| 442 | .or_else(|| cmd.strip_prefix("tail ")) |
| 443 | { |
| 444 | let target = rest.split_whitespace().last().unwrap_or(rest); |
| 445 | return shorten_explore_path(target); |
| 446 | } |
| 447 | if let Some(rest) = cmd.strip_prefix("ls ") { |
| 448 | let target = rest |
| 449 | .split_whitespace() |
| 450 | .rfind(|s| !s.starts_with('-')) |
| 451 | .unwrap_or("."); |
| 452 | return format!("directory {}", shorten_explore_path(target)); |
| 453 | } |
| 454 | // For other commands, use the description if available |
| 455 | String::new() |
| 456 | }) |
| 457 | .filter(|s| !s.is_empty()) |
| 458 | } else { |
| 459 | None |
| 460 | }; |
| 461 | |
| 462 | // Also check for a human-readable description (from enriched after-tool payload) |
| 463 | let description = tool_input |
| 464 | .and_then(|v| v.get("description")) |
| 465 | .and_then(|v| v.as_str()); |
| 466 | |
| 467 | // Build the summary with the best available information |
| 468 | match (path, bash_target.as_deref(), description) { |
| 469 | (Some(p), _, _) => { |
no test coverage detected