| use std::collections::HashSet; |
|
|
| use convert_case::{Case, Casing}; |
| use forge_api::{ToolName, ToolsOverview}; |
|
|
| use crate::info::Info; |
|
|
| |
| |
| pub fn format_tools(agent_tools: &[ToolName], overview: &ToolsOverview) -> Info { |
| let mut info = Info::new(); |
| let agent_tools = agent_tools.iter().collect::<HashSet<_>>(); |
| let checkbox = |tool_name: &ToolName| -> &str { |
| if agent_tools.contains(tool_name) { |
| "[β]" |
| } else { |
| "[ ]" |
| } |
| }; |
|
|
| |
| info = info.add_title("SYSTEM"); |
| for tool in &overview.system { |
| info = info.add_value(format!("{} {}", checkbox(&tool.name), tool.name)); |
| } |
|
|
| |
| info = info.add_title("AGENTS"); |
| for tool in &overview.agents { |
| info = info.add_value(format!("{} {}", checkbox(&tool.name), tool.name)); |
| } |
|
|
| |
| if !overview.mcp.get_servers().is_empty() { |
| for (server_name, tools) in overview.mcp.get_servers().iter() { |
| let title = (*server_name).to_case(Case::UpperSnake); |
| info = info.add_title(title); |
|
|
| for tool in tools { |
| info = info.add_value(format!("{} {}", checkbox(&tool.name), tool.name)); |
| } |
| } |
| } |
|
|
| |
| if !overview.mcp.get_failures().is_empty() { |
| info = info.add_title("FAILED MCP SERVERS"); |
| for (server_name, error) in overview.mcp.get_failures().iter() { |
| |
| |
| let truncated_error = truncate_error(error); |
| info = info.add_value(format!("[β] {server_name} - {truncated_error}")); |
| } |
| } |
|
|
| info |
| } |
|
|
| |
| |
| |
| |
| |
| fn truncate_error(error: &str) -> String { |
| if error.chars().count() > 80 { |
| let truncated: String = error.chars().take(77).collect(); |
| format!("{truncated}...") |
| } else { |
| error.to_string() |
| } |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use pretty_assertions::assert_eq; |
|
|
| use super::*; |
|
|
| #[test] |
| fn test_truncate_error_short_message() { |
| let fixture = "Connection refused"; |
| let actual = truncate_error(fixture); |
| let expected = "Connection refused"; |
| assert_eq!(actual, expected); |
| } |
|
|
| #[test] |
| fn test_truncate_error_long_ascii_message() { |
| let fixture = "A".repeat(100); |
| let actual = truncate_error(&fixture); |
| assert_eq!(actual.chars().count(), 80); |
| assert!(actual.ends_with("...")); |
| } |
|
|
| #[test] |
| fn test_truncate_error_multibyte_chars_no_panic() { |
| |
| let fixture = "Error: struct β prioritizes struct definitions, trait β prioritizes traits, impl β prioritizes impls, and more details follow here"; |
| let actual = truncate_error(fixture); |
| |
| assert!(actual.chars().count() <= 80); |
| assert!(actual.ends_with("...")); |
| } |
|
|
| #[test] |
| fn test_truncate_error_emoji_no_panic() { |
| |
| let fixture = "π".repeat(90); |
| let actual = truncate_error(&fixture); |
| assert_eq!(actual.chars().count(), 80); |
| assert!(actual.ends_with("...")); |
| } |
|
|
| #[test] |
| fn test_truncate_error_exactly_80_chars() { |
| let fixture = "A".repeat(80); |
| let actual = truncate_error(&fixture); |
| let expected = "A".repeat(80); |
| assert_eq!(actual, expected); |
| } |
| } |
|
|