tool card UI redesign: semantic icons, inline diff persistence, tool detail page, MCP-friendly titles
Nightly Build / build (push) Successful in 6m38s
Nightly Build / build (push) Successful in 6m38s
This commit is contained in:
@@ -26,6 +26,8 @@ pub struct ExecuteCmd;
|
||||
|
||||
impl Tool for ExecuteCmd {
|
||||
fn name(&self) -> &str { crate::tools::tool_names::EXECUTE_CMD }
|
||||
fn display_name(&self) -> &str { "Run Command" }
|
||||
fn icon(&self) -> &str { "shell" }
|
||||
fn category(&self) -> crate::tools::ToolCategory { crate::tools::ToolCategory::Shell }
|
||||
|
||||
fn description(&self) -> &str {
|
||||
|
||||
@@ -114,6 +114,8 @@ impl EditFile {
|
||||
|
||||
impl Tool for EditFile {
|
||||
fn name(&self) -> &str { "edit_file" }
|
||||
fn display_name(&self) -> &str { "Edit File" }
|
||||
fn icon(&self) -> &str { "edit" }
|
||||
fn category(&self) -> crate::tools::ToolCategory { crate::tools::ToolCategory::Filesystem }
|
||||
|
||||
fn description(&self) -> &str {
|
||||
|
||||
@@ -16,6 +16,8 @@ impl GrepFiles {
|
||||
|
||||
impl Tool for GrepFiles {
|
||||
fn name(&self) -> &str { "grep_files" }
|
||||
fn display_name(&self) -> &str { "Search" }
|
||||
fn icon(&self) -> &str { "search" }
|
||||
fn category(&self) -> crate::tools::ToolCategory { crate::tools::ToolCategory::Filesystem }
|
||||
|
||||
fn description(&self) -> &str {
|
||||
|
||||
@@ -48,6 +48,8 @@ fn apply_insert(text: &str, args: &Value, display: &str) -> Result<(String, Stri
|
||||
|
||||
impl Tool for InsertAtLine {
|
||||
fn name(&self) -> &str { "insert_at_line" }
|
||||
fn display_name(&self) -> &str { "Edit File" }
|
||||
fn icon(&self) -> &str { "edit" }
|
||||
fn category(&self) -> crate::tools::ToolCategory { crate::tools::ToolCategory::Filesystem }
|
||||
|
||||
fn description(&self) -> &str {
|
||||
|
||||
@@ -25,6 +25,8 @@ impl ListFiles {
|
||||
|
||||
impl Tool for ListFiles {
|
||||
fn name(&self) -> &str { "list_files" }
|
||||
fn display_name(&self) -> &str { "List Files" }
|
||||
fn icon(&self) -> &str { "list" }
|
||||
fn category(&self) -> crate::tools::ToolCategory { crate::tools::ToolCategory::Filesystem }
|
||||
|
||||
fn description(&self) -> &str {
|
||||
|
||||
@@ -46,6 +46,8 @@ fn render_hits(store: &str, hits: &[MemoryHit], out: &mut String) {
|
||||
|
||||
impl Tool for MemorySearch {
|
||||
fn name(&self) -> &str { "memory_search" }
|
||||
fn display_name(&self) -> &str { "Search Memory" }
|
||||
fn icon(&self) -> &str { "search" }
|
||||
fn category(&self) -> crate::tools::ToolCategory { crate::tools::ToolCategory::Introspection }
|
||||
|
||||
fn description(&self) -> &str {
|
||||
|
||||
@@ -49,6 +49,8 @@ fn number_lines(content: &str, start: usize, end_line: Option<usize>, limit: Opt
|
||||
|
||||
impl Tool for ReadFile {
|
||||
fn name(&self) -> &str { "read_file" }
|
||||
fn display_name(&self) -> &str { "Read File" }
|
||||
fn icon(&self) -> &str { "read" }
|
||||
fn category(&self) -> crate::tools::ToolCategory { crate::tools::ToolCategory::Filesystem }
|
||||
|
||||
fn description(&self) -> &str {
|
||||
|
||||
@@ -55,6 +55,8 @@ fn apply_replace(content: &str, args: &Value, display: &str) -> Result<(String,
|
||||
|
||||
impl Tool for ReplaceLines {
|
||||
fn name(&self) -> &str { "replace_lines" }
|
||||
fn display_name(&self) -> &str { "Edit File" }
|
||||
fn icon(&self) -> &str { "edit" }
|
||||
fn category(&self) -> crate::tools::ToolCategory { crate::tools::ToolCategory::Filesystem }
|
||||
|
||||
fn description(&self) -> &str {
|
||||
|
||||
@@ -66,6 +66,8 @@ fn render_search(text: &str, args: &Value, display: &str) -> Result<String> {
|
||||
|
||||
impl Tool for SearchFile {
|
||||
fn name(&self) -> &str { "search_file" }
|
||||
fn display_name(&self) -> &str { "Search" }
|
||||
fn icon(&self) -> &str { "search" }
|
||||
fn category(&self) -> crate::tools::ToolCategory { crate::tools::ToolCategory::Filesystem }
|
||||
|
||||
fn description(&self) -> &str {
|
||||
|
||||
@@ -21,6 +21,8 @@ impl WriteFile {
|
||||
|
||||
impl Tool for WriteFile {
|
||||
fn name(&self) -> &str { "write_file" }
|
||||
fn display_name(&self) -> &str { "Edit File" }
|
||||
fn icon(&self) -> &str { "edit" }
|
||||
fn category(&self) -> crate::tools::ToolCategory { crate::tools::ToolCategory::Filesystem }
|
||||
|
||||
fn description(&self) -> &str {
|
||||
|
||||
@@ -47,6 +47,8 @@ pub struct ImageGenerateTool {
|
||||
|
||||
impl Tool for ImageGenerateTool {
|
||||
fn name(&self) -> &str { "image_generate" }
|
||||
fn display_name(&self) -> &str { "Generate Image" }
|
||||
fn icon(&self) -> &str { "image" }
|
||||
fn category(&self) -> ToolCategory { ToolCategory::Config }
|
||||
|
||||
fn description(&self) -> &str {
|
||||
|
||||
@@ -62,6 +62,33 @@ pub use core_api::tool::{
|
||||
pub const MAX_LABEL_SHORT: usize = 60;
|
||||
pub const MAX_LABEL_FULL: usize = 120;
|
||||
|
||||
/// UI metadata for one tool call — the friendly card title plus a **semantic** icon
|
||||
/// key (never a glyph; the frontend maps the key to an icon + accent color). Computed
|
||||
/// by [`ToolRegistry::display_meta`], the single seam shared by the live WS event and
|
||||
/// the history projection so the two can't drift.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ToolUiMeta {
|
||||
pub display_name: String,
|
||||
pub icon: String,
|
||||
}
|
||||
|
||||
/// Turns a raw tool id (`snake_case` / `kebab-case`) into a Title-Cased phrase for a
|
||||
/// UI label when no friendly name is declared — `list_recent_files` → "List Recent
|
||||
/// Files". The last-resort fallback for MCP and unknown tools.
|
||||
pub fn prettify_tool_name(name: &str) -> String {
|
||||
name.split(|c| c == '_' || c == '-')
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|w| {
|
||||
let mut chars = w.chars();
|
||||
match chars.next() {
|
||||
Some(f) => f.to_uppercase().collect::<String>() + chars.as_str(),
|
||||
None => String::new(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
/// Registry of all available tools.
|
||||
pub struct ToolRegistry {
|
||||
tools: HashMap<String, Arc<dyn Tool>>,
|
||||
@@ -173,6 +200,44 @@ impl ToolRegistry {
|
||||
name.to_string()
|
||||
}
|
||||
|
||||
/// Friendly card metadata (display name + semantic icon key) for any tool call,
|
||||
/// including non-registry tools. Registry tools delegate to
|
||||
/// [`Tool::display_name`]/[`Tool::icon`]; sub-agent and interface tools are
|
||||
/// handled inline; an `mcp__server__tool` name gets `icon = "mcp"` and a
|
||||
/// prettified tool name — the caller (which holds the [`McpProvider`]) may then
|
||||
/// override `display_name` with the connector's resolved friendly name.
|
||||
///
|
||||
/// [`McpProvider`]: crate::mcp::provider::McpProvider
|
||||
pub fn display_meta(&self, name: &str, args: &Value) -> ToolUiMeta {
|
||||
if let Some(tool) = self.tools.get(name) {
|
||||
return ToolUiMeta {
|
||||
display_name: tool.display_name().to_string(),
|
||||
icon: tool.icon().to_string(),
|
||||
};
|
||||
}
|
||||
// Sub-agent delegation tools (InterfaceTools, not in the registry).
|
||||
if name == tool_names::EXECUTE_TASK
|
||||
|| name == tool_names::EXECUTE_SUBTASK
|
||||
|| name == "run_subtask"
|
||||
{
|
||||
let dn = match args["agent_id"].as_str().map(str::trim).filter(|s| !s.is_empty()) {
|
||||
Some(a) => format!("Sub-agent: {a}"),
|
||||
None => "Sub-agent".to_string(),
|
||||
};
|
||||
return ToolUiMeta { display_name: dn, icon: "subagent".to_string() };
|
||||
}
|
||||
if name == tool_names::SHOW_FILE_TO_USER {
|
||||
return ToolUiMeta { display_name: "Show File".to_string(), icon: "read".to_string() };
|
||||
}
|
||||
// MCP tool `mcp__server__tool`: default to a prettified tool name; the caller
|
||||
// overrides with the connector's manifest/`title` friendly name when known.
|
||||
if let Some(rest) = name.strip_prefix("mcp__") {
|
||||
let tool = rest.split_once("__").map(|(_, t)| t).unwrap_or(rest);
|
||||
return ToolUiMeta { display_name: prettify_tool_name(tool), icon: "mcp".to_string() };
|
||||
}
|
||||
ToolUiMeta { display_name: prettify_tool_name(name), icon: "tool".to_string() }
|
||||
}
|
||||
|
||||
/// Returns the category of a registered tool, or `None` for unknown tools
|
||||
/// (MCP tools, interface tools, call_agent, etc.).
|
||||
pub fn category_of(&self, name: &str) -> Option<ToolCategory> {
|
||||
|
||||
Reference in New Issue
Block a user