use anyhow::Result;
use serde_json::{json, Value};
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::core::messaging::MessageBus;
use crate::core::skill::SkillManager;
use crate::core::store::{MessageFilter, Store};
use crate::core::tool::ToolRegistry;
use crate::core::tool_provider::{CompositeToolProvider, FrameworkToolProvider};
use crate::domain::tool::{MatchType, ToolCallContext, ToolProvider};
use crate::domain::{Group, Message, MessageTarget, Organization};
use crate::infrastructure::tool::ToolResult;
#[derive(Clone)]
pub struct ToolEnvironment {
pub message_bus: Arc<MessageBus>,
pub organization: Arc<RwLock<Organization>>,
pub tool_registry: Arc<ToolRegistry>,
pub tool_provider: Arc<CompositeToolProvider>,
pub message_store: Arc<dyn Store>,
pub skill_manager: Arc<SkillManager>,
}
impl ToolEnvironment {
pub fn new(
message_bus: Arc<MessageBus>,
organization: Arc<RwLock<Organization>>,
tool_registry: Arc<ToolRegistry>,
message_store: Arc<dyn Store>,
skill_manager: Arc<SkillManager>,
) -> Self {
let tool_provider = CompositeToolProvider::new()
.add_provider(Box::new(FrameworkToolProvider::new()))
.with_registry(tool_registry.clone());
Self {
message_bus,
organization,
tool_registry,
tool_provider: Arc::new(tool_provider),
message_store,
skill_manager,
}
}
}
pub struct FrameworkToolExecutor {
env: ToolEnvironment,
}
impl FrameworkToolExecutor {
pub fn new(env: ToolEnvironment) -> Self {
Self { env }
}
pub fn supported_tool_ids() -> Vec<&'static str> {
vec![
"tool.search",
"tool.list_categories",
"tool.get_category_tools",
"message.send_direct",
"message.send_group",
"message.send_to_guilty_line",
"message.reply",
"group.list",
"time.now",
"org.get_structure",
"org.get_department",
"org.get_leader",
"org.find_agents",
"org.get_sub_departments",
"org.get_subordinates",
"file.read",
"file.write",
"file.delete",
"file.list",
"shell.exec",
"http.fetch",
]
}
pub async fn execute(
&self,
tool_id: &str,
params: Value,
context: &ToolCallContext,
) -> Result<ToolResult> {
match tool_id {
"tool.search" => self.execute_tool_search(params).await,
"tool.list_categories" => self.execute_tool_list_categories(params).await,
"tool.get_category_tools" => self.execute_tool_get_category_tools(params).await,
"message.send_direct" => self.execute_message_send_direct(params, context).await,
"message.send_group" => self.execute_message_send_group(params, context).await,
"message.send_to_guilty_line" => {
self.execute_message_send_to_guilty_line(params, context)
.await
}
"message.reply" => self.execute_message_reply(params, context).await,
"group.list" => self.execute_group_list(params, context).await,
"time.now" => self.execute_time_now().await,
"org.get_structure" => self.execute_org_get_structure().await,
"org.get_department" => self.execute_org_get_department(params).await,
"org.get_leader" => self.execute_org_get_leader(params).await,
"org.find_agents" => self.execute_org_find_agents(params).await,
"org.get_sub_departments" => self.execute_org_get_sub_departments(params).await,
"org.get_subordinates" => self.execute_org_get_subordinates(params).await,
"file.read" => self.execute_file_read(params).await,
"file.write" => self.execute_file_write(params).await,
"file.delete" => self.execute_file_delete(params).await,
"file.list" => self.execute_file_list(params).await,
"shell.exec" => self.execute_shell_exec(params).await,
"http.fetch" => self.execute_http_fetch(params).await,
_ => Ok(ToolResult::error(format!("Unknown tool: {}", tool_id))),
}
}
async fn execute_tool_search(&self, params: Value) -> Result<ToolResult> {
let query = params["query"].as_str().unwrap_or("");
if query.is_empty() {
return Ok(ToolResult::error("Query parameter is required"));
}
let match_type = match params["match_type"].as_str() {
Some("exact") => MatchType::Exact,
_ => MatchType::Fuzzy,
};
let category_filter = params["category_filter"].as_str();
let mut results = self.env.tool_provider.search_tools(query, match_type);
if let Some(category) = category_filter {
results.retain(|tool| tool.category.to_path_string().starts_with(category));
}
let tools_json: Vec<Value> = results
.iter()
.map(|tool| {
json!({
"id": tool.id,
"name": tool.name,
"description": tool.description,
"category": tool.category.to_path_string(),
})
})
.collect();
Ok(ToolResult::success(json!({
"query": query,
"match_type": if match_type == MatchType::Exact { "exact" } else { "fuzzy" },
"count": tools_json.len(),
"tools": tools_json,
})))
}
async fn execute_tool_list_categories(&self, params: Value) -> Result<ToolResult> {
let parent_category = params["parent_category"].as_str().unwrap_or("");
let tree = self.env.tool_provider.get_category_tree();
let target_node = if parent_category.is_empty() {
tree
} else {
find_category_node(&tree, parent_category)
.unwrap_or_else(|| crate::domain::tool::CategoryNodeInfo::new("empty", ""))
};
Ok(ToolResult::success(json!({
"parent": parent_category,
"categories": target_node.children.iter().map(|c| {
json!({
"name": c.name,
"path": c.path,
"tool_count": c.tool_count,
})
}).collect::<Vec<_>>(),
})))
}
async fn execute_tool_get_category_tools(&self, params: Value) -> Result<ToolResult> {
let category = params["category"].as_str().unwrap_or("");
if category.is_empty() {
return Ok(ToolResult::error("Category parameter is required"));
}
let _recursive = params["recursive"].as_bool().unwrap_or(true);
let tools = self.env.tool_provider.list_tools_by_category(category);
let tools_json: Vec<Value> = tools
.iter()
.map(|tool| {
json!({
"id": tool.id,
"name": tool.name,
"description": tool.description,
"category": tool.category.to_path_string(),
"parameters": tool.parameters,
})
})
.collect();
Ok(ToolResult::success(json!({
"category": category,
"count": tools_json.len(),
"tools": tools_json,
})))
}
async fn execute_message_send_direct(
&self,
params: Value,
context: &ToolCallContext,
) -> Result<ToolResult> {
let to_agent_id = params["to_agent_id"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("to_agent_id is required"))?;
let content = params["content"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("content is required"))?;
let reply_to = params["reply_to_message_id"].as_str();
let mut message = Message::private(&context.caller_id, to_agent_id, content);
if let Some(reply_id) = reply_to {
message = message.with_reply_to(reply_id);
}
self.env.message_bus.send(message).await?;
Ok(ToolResult::success(json!({ "sent": true })))
}
async fn execute_message_send_group(
&self,
params: Value,
context: &ToolCallContext,
) -> Result<ToolResult> {
let group_id = params["group_id"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("group_id is required"))?;
let content = params["content"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("content is required"))?;
let groups = self.env.message_store.load_groups().await?;
let target_group = groups.iter().find(|g| g.id == group_id);
match target_group {
Some(group) => {
if matches!(
group.visibility,
crate::domain::message::GroupVisibility::Hidden
) {
return Ok(ToolResult::error("Cannot send message to hidden group using regular message.send_group tool. Use message.send_to_guilty_line instead.".to_string()));
}
let is_member = group.members.contains(&context.caller_id);
if !is_member {
return Ok(ToolResult::error(
"Caller is not a member of the target group".to_string(),
));
}
}
None => {
return Ok(ToolResult::error("Target group not found".to_string()));
}
}
let mut message = Message::group(&context.caller_id, group_id, content);
if let Some(mentions) = params["mention_agent_ids"].as_array() {
for mention in mentions {
if let Some(id) = mention.as_str() {
message = message.with_mention(id);
}
}
}
if let Some(reply_id) = params["reply_to_message_id"].as_str() {
message = message.with_reply_to(reply_id);
}
self.env.message_bus.send(message).await?;
Ok(ToolResult::success(json!({ "sent": true })))
}
async fn execute_message_send_to_guilty_line(
&self,
params: Value,
context: &ToolCallContext,
) -> Result<ToolResult> {
let _required_skills = ["guilty_line_access".to_string()];
let _caller_has_required_skills = true;
let groups = self.env.message_store.load_groups().await?;
let guilty_line_group = groups.iter().find(|g| {
g.id == "guilty_line_group"
&& matches!(
g.visibility,
crate::domain::message::GroupVisibility::Hidden
)
});
match guilty_line_group {
Some(group) => {
let is_member = group.members.contains(&context.caller_id);
if !is_member {
return Ok(ToolResult::error(
"Caller is not a member of the Guilty Line group".to_string(),
));
}
let content = params["content"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("content is required"))?;
let mut message = Message::group(&context.caller_id, &group.id, content);
if let Some(mentions) = params["mention_agent_ids"].as_array() {
for mention in mentions {
if let Some(id) = mention.as_str() {
message = message.with_mention(id);
}
}
}
if let Some(reply_id) = params["reply_to_message_id"].as_str() {
message = message.with_reply_to(reply_id);
}
self.env.message_bus.send(message).await?;
Ok(ToolResult::success(json!({
"sent": true,
"group_id": &group.id,
"group_name": &group.name
})))
}
None => Ok(ToolResult::error(
"Guilty Line group not found or not hidden".to_string(),
)),
}
}
async fn execute_group_list(
&self,
_params: Value,
_context: &ToolCallContext,
) -> Result<ToolResult> {
let all_groups = self.env.message_store.load_groups().await?;
let visible_groups: Vec<&Group> = all_groups
.iter()
.filter(|g| {
matches!(
g.visibility,
crate::domain::message::GroupVisibility::Public
)
})
.collect();
let groups_json: Vec<Value> = visible_groups
.iter()
.map(|g| {
json!({
"id": g.id,
"name": g.name,
"creator_id": g.creator_id,
"member_count": g.members.len(),
"created_at": g.created_at,
})
})
.collect();
Ok(ToolResult::success(json!({
"count": groups_json.len(),
"groups": groups_json,
})))
}
async fn execute_message_reply(
&self,
params: Value,
context: &ToolCallContext,
) -> Result<ToolResult> {
let message_id = params["message_id"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("message_id is required"))?;
let content = params["content"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("content is required"))?;
let original_messages = self
.env
.message_store
.load_messages(MessageFilter::new().limit(1).to(message_id))
.await?;
let reply_message = if let Some(orig_msg) = original_messages.first() {
let reply_content = format!("[回复消息 {}] {}", message_id, content);
let mut message = match &orig_msg.to {
MessageTarget::Direct(sender_id) => {
if *sender_id == context.caller_id {
Message::private(&context.caller_id, &orig_msg.from, reply_content)
} else {
Message::private(&context.caller_id, sender_id, reply_content)
}
}
MessageTarget::Group(group_id) => {
Message::group(&context.caller_id, group_id, reply_content)
}
};
message = message.with_reply_to(message_id);
if let Some(mentions) = params["mention_agent_ids"].as_array() {
for mention in mentions {
if let Some(id) = mention.as_str() {
message = message.with_mention(id);
}
}
}
let message_id_clone = message.id.clone();
let target_clone = format!("{:?}", message.to);
self.env.message_bus.send(message).await?;
Ok(ToolResult::success(json!({
"sent": true,
"message_id": message_id_clone,
"reply_to": message_id,
"target": target_clone,
})))
} else {
Ok(ToolResult::error(format!(
"Original message not found: {}",
message_id
)))
};
reply_message
}
async fn execute_time_now(&self) -> Result<ToolResult> {
let now = chrono::Utc::now();
Ok(ToolResult::success(json!({
"timestamp": now.timestamp(),
"iso": now.to_rfc3339(),
"date": now.format("%Y-%m-%d").to_string(),
"time": now.format("%H:%M:%S").to_string(),
"timezone": "UTC",
})))
}
async fn execute_org_get_structure(&self) -> Result<ToolResult> {
let org = self.env.organization.read().await;
let tree = org.build_tree();
fn convert_node(node: &crate::domain::org::DepartmentNode) -> Value {
json!({
"id": node.department.id,
"name": node.department.name,
"leader_id": node.department.leader_id,
"members": node.members,
"children": node.children.iter().map(convert_node).collect::<Vec<_>>(),
})
}
let departments: Vec<Value> = tree.iter().map(convert_node).collect();
let agents: Vec<Value> = org
.agents
.iter()
.map(|a| {
json!({
"id": a.id,
"name": a.name,
"role": a.role.title,
"department_id": a.department_id,
})
})
.collect();
Ok(ToolResult::success(json!({
"departments": departments,
"agents": agents,
"total_departments": org.departments.len(),
"total_agents": org.agents.len(),
})))
}
async fn execute_org_get_department(&self, params: Value) -> Result<ToolResult> {
let dept_id = params["department_id"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("department_id is required"))?;
let org = self.env.organization.read().await;
let dept = org
.find_department(dept_id)
.ok_or_else(|| anyhow::anyhow!("Department not found: {}", dept_id))?;
let members: Vec<&crate::domain::Agent> = org.get_department_members(dept_id);
Ok(ToolResult::success(json!({
"id": dept.id,
"name": dept.name,
"parent_id": dept.parent_id,
"leader_id": dept.leader_id,
"members": members.iter().map(|m| {
json!({
"id": m.id,
"name": m.name,
"role": m.role.title,
})
}).collect::<Vec<_>>(),
"member_count": members.len(),
})))
}
async fn execute_org_get_leader(&self, params: Value) -> Result<ToolResult> {
let dept_id = params["department_id"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("department_id is required"))?;
let org = self.env.organization.read().await;
let leader = org
.get_department_leader(dept_id)
.ok_or_else(|| anyhow::anyhow!("No leader found for department: {}", dept_id))?;
Ok(ToolResult::success(json!({
"id": leader.id,
"name": leader.name,
"role": leader.role.title,
"department_id": leader.department_id,
})))
}
async fn execute_org_find_agents(&self, params: Value) -> Result<ToolResult> {
let query_type = params["query_type"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("query_type is required"))?;
let query_value = params["query_value"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("query_value is required"))?;
let fuzzy = params["fuzzy_match"].as_bool().unwrap_or(false);
let org = self.env.organization.read().await;
let query_lower = query_value.to_lowercase();
let results: Vec<&crate::domain::Agent> = org
.agents
.iter()
.filter(|agent| match query_type {
"id" => {
if fuzzy {
agent.id.to_lowercase().contains(&query_lower)
} else {
agent.id.to_lowercase() == query_lower
}
}
"name" => {
if fuzzy {
agent.name.to_lowercase().contains(&query_lower)
} else {
agent.name.to_lowercase() == query_lower
}
}
"role" | "position" => {
if fuzzy {
agent.role.title.to_lowercase().contains(&query_lower)
} else {
agent.role.title.to_lowercase() == query_lower
}
}
"department" => {
if let Some(d) = agent.department_id.as_ref() {
if fuzzy {
d.to_lowercase().contains(&query_lower)
} else {
d.to_lowercase() == query_lower
}
} else {
false
}
}
"description" => {
if fuzzy {
agent
.role
.system_prompt
.to_lowercase()
.contains(&query_lower)
} else {
agent.role.system_prompt.to_lowercase() == query_lower
}
}
_ => false,
})
.collect();
let agents_json: Vec<Value> = results
.iter()
.map(|a| {
json!({
"id": a.id,
"name": a.name,
"role": a.role.title,
"department_id": a.department_id,
"expertise": a.role.expertise,
})
})
.collect();
Ok(ToolResult::success(json!({
"query_type": query_type,
"query_value": query_value,
"fuzzy_match": fuzzy,
"count": agents_json.len(),
"agents": agents_json,
})))
}
async fn execute_org_get_sub_departments(&self, params: Value) -> Result<ToolResult> {
let dept_id = params["department_id"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("department_id is required"))?;
let org = self.env.organization.read().await;
let sub_depts = org.get_sub_departments(dept_id);
let depts_json: Vec<Value> = sub_depts
.iter()
.map(|d| {
json!({
"id": d.id,
"name": d.name,
"leader_id": d.leader_id,
})
})
.collect();
Ok(ToolResult::success(json!({
"parent_id": dept_id,
"count": depts_json.len(),
"departments": depts_json,
})))
}
async fn execute_org_get_subordinates(&self, params: Value) -> Result<ToolResult> {
let agent_id = params["agent_id"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("agent_id is required"))?;
let org = self.env.organization.read().await;
let agent = org
.find_agent(agent_id)
.ok_or_else(|| anyhow::anyhow!("Agent not found: {}", agent_id))?;
let mut subordinates = Vec::new();
if let Some(dept_id) = &agent.department_id {
if let Some(dept) = org.find_department(dept_id) {
if dept.leader_id.as_ref() == Some(&agent_id.to_string()) {
subordinates = org
.get_department_members(dept_id)
.into_iter()
.filter(|a| a.id != agent_id)
.cloned()
.collect::<Vec<_>>();
}
}
}
let subordinates_json: Vec<Value> = subordinates
.iter()
.map(|a| {
json!({
"id": a.id,
"name": a.name,
"role": a.role.title,
})
})
.collect();
Ok(ToolResult::success(json!({
"agent_id": agent_id,
"is_leader": !subordinates.is_empty(),
"count": subordinates_json.len(),
"subordinates": subordinates_json,
})))
}
async fn execute_file_read(&self, params: Value) -> Result<ToolResult> {
use tokio::fs;
let path = params["path"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("path is required"))?;
match fs::read_to_string(path).await {
Ok(content) => Ok(ToolResult::success(json!({
"success": true,
"content": content,
}))),
Err(e) => Ok(ToolResult::success(json!({
"success": false,
"error": format!("Failed to read file: {}", e),
}))),
}
}
async fn execute_file_write(&self, params: Value) -> Result<ToolResult> {
use tokio::fs;
use tokio::io::AsyncWriteExt;
let path = params["path"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("path is required"))?;
let content = params["content"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("content is required"))?;
let append = params["append"].as_bool().unwrap_or(false);
let result = if append {
match fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)
.await
{
Ok(mut file) => match file.write_all(content.as_bytes()).await {
Ok(_) => true,
Err(_) => {
let _ = fs::remove_file(path).await;
false
}
},
Err(_) => false,
}
} else {
fs::write(path, content).await.is_ok()
};
if result {
Ok(ToolResult::success(json!({
"success": true,
})))
} else {
Ok(ToolResult::success(json!({
"success": false,
"error": "Failed to write file",
})))
}
}
async fn execute_file_delete(&self, params: Value) -> Result<ToolResult> {
use tokio::fs;
let path = params["path"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("path is required"))?;
match fs::remove_file(path).await {
Ok(_) => Ok(ToolResult::success(json!({
"success": true,
}))),
Err(e) => Ok(ToolResult::success(json!({
"success": false,
"error": format!("Failed to delete file: {}", e),
}))),
}
}
async fn execute_file_list(&self, params: Value) -> Result<ToolResult> {
use tokio::fs;
let path = params["path"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("path is required"))?;
let pattern = params["pattern"].as_str();
match fs::read_dir(path).await {
Ok(mut dir) => {
let mut entries = Vec::new();
while let Ok(Some(entry)) = dir.next_entry().await {
if let Ok(name) = entry.file_name().into_string() {
let should_include = if let Some(pat) = pattern {
if pat.starts_with('*') && pat.ends_with('*') {
name.contains(&pat[1..pat.len() - 1])
} else if let Some(suffix) = pat.strip_prefix('*') {
name.ends_with(suffix)
} else if let Some(prefix) = pat.strip_suffix('*') {
name.starts_with(prefix)
} else {
name == pat
}
} else {
true
};
if should_include {
let entry_type = entry.file_type().await.ok();
entries.push(json!({
"name": name,
"is_dir": entry_type.map(|t| t.is_dir()).unwrap_or(false),
"is_file": entry_type.map(|t| t.is_file()).unwrap_or(true),
}));
}
}
}
Ok(ToolResult::success(json!({
"success": true,
"entries": entries,
})))
}
Err(e) => Ok(ToolResult::success(json!({
"success": false,
"error": format!("Failed to list directory: {}", e),
}))),
}
}
async fn execute_shell_exec(&self, params: Value) -> Result<ToolResult> {
use std::time::Duration;
use tokio::process::Command;
use tokio::time::timeout;
let command = params["command"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("command is required"))?;
let timeout_secs = params["timeout"].as_u64().unwrap_or(60);
let (shell, shell_arg) = if cfg!(windows) {
("cmd", "/C")
} else {
("sh", "-c")
};
match timeout(Duration::from_secs(timeout_secs), Command::new(shell).arg(shell_arg).arg(command).output()).await {
Ok(Ok(output)) => {
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
Ok(ToolResult::success(json!({
"success": output.status.success(),
"stdout": stdout,
"stderr": stderr,
"exit_code": output.status.code().unwrap_or(-1),
})))
}
Ok(Err(e)) => Ok(ToolResult::success(json!({
"success": false,
"stdout": "",
"stderr": "",
"exit_code": -1,
"error": format!("Failed to execute command: {}", e),
}))),
Err(_) => Ok(ToolResult::success(json!({
"success": false,
"stdout": "",
"stderr": "",
"exit_code": -1,
"error": format!("Command timed out after {} seconds", timeout_secs),
}))),
}
}
async fn execute_http_fetch(&self, params: Value) -> Result<ToolResult> {
use reqwest::{Client, header};
let url = match params["url"].as_str() {
Some(u) => u,
None => return Ok(ToolResult::success(json!({
"success": false,
"status": 0,
"body": "",
"headers": {},
"error": "url is required",
}))),
};
let method = params["method"].as_str().unwrap_or("GET");
let timeout_secs = params["timeout"].as_u64().unwrap_or(30);
let mut headers = header::HeaderMap::new();
headers.insert(
header::USER_AGENT,
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
.parse()
.unwrap(),
);
headers.insert(
header::ACCEPT,
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
.parse()
.unwrap(),
);
headers.insert(
header::ACCEPT_LANGUAGE,
"en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7".parse().unwrap(),
);
headers.insert(
header::ACCEPT_ENCODING,
"gzip, deflate, br".parse().unwrap(),
);
headers.insert(header::CONNECTION, "keep-alive".parse().unwrap());
headers.insert(
"Upgrade-Insecure-Requests",
"1".parse().unwrap(),
);
headers.insert(
"Sec-Ch-Ua",
"\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\"".parse().unwrap(),
);
headers.insert("Sec-Ch-Ua-Mobile", "?0".parse().unwrap());
headers.insert("Sec-Ch-Ua-Platform", "\"macOS\"".parse().unwrap());
let client = Client::builder()
.default_headers(headers)
.timeout(std::time::Duration::from_secs(timeout_secs))
.redirect(reqwest::redirect::Policy::limited(10))
.build();
let client = match client {
Ok(c) => c,
Err(e) => {
return Ok(ToolResult::success(json!({
"success": false,
"status": 0,
"body": "",
"headers": {},
"error": format!("Failed to create HTTP client: {}", e),
})));
}
};
let reqwest_method = match method.to_uppercase().as_str() {
"GET" => reqwest::Method::GET,
"POST" => reqwest::Method::POST,
"PUT" => reqwest::Method::PUT,
"DELETE" => reqwest::Method::DELETE,
"PATCH" => reqwest::Method::PATCH,
"HEAD" => reqwest::Method::HEAD,
"OPTIONS" => reqwest::Method::OPTIONS,
_ => {
return Ok(ToolResult::success(json!({
"success": false,
"status": 0,
"body": "",
"headers": {},
"error": format!("Unsupported HTTP method: {}", method),
})));
}
};
match client.request(reqwest_method, url).send().await {
Ok(response) => {
let status = response.status().as_u16();
let headers_map: serde_json::Map<String, Value> = response
.headers()
.iter()
.map(|(k, v)| {
(
k.as_str().to_string(),
Value::String(v.to_str().unwrap_or("").to_string()),
)
})
.collect();
let body = match response.text().await {
Ok(text) => text,
Err(e) => {
return Ok(ToolResult::success(json!({
"success": false,
"status": status,
"body": "",
"headers": headers_map,
"error": format!("Failed to read response body: {}", e),
})));
}
};
Ok(ToolResult::success(json!({
"success": true,
"status": status,
"body": body,
"headers": headers_map,
})))
}
Err(e) => Ok(ToolResult::success(json!({
"success": false,
"status": 0,
"body": "",
"headers": {},
"error": format!("Failed to fetch URL: {}", e),
}))),
}
}
}
fn find_category_node(
tree: &crate::domain::tool::CategoryNodeInfo,
path: &str,
) -> Option<crate::domain::tool::CategoryNodeInfo> {
if tree.path == path {
return Some(tree.clone());
}
for child in &tree.children {
if let Some(found) = find_category_node(child, path) {
return Some(found);
}
}
None
}
use crate::infrastructure::tool::ToolExecutor as ToolExecutorTrait;
use async_trait::async_trait;
#[async_trait]
impl ToolExecutorTrait for FrameworkToolExecutor {
async fn execute(
&self,
tool_id: &str,
params: Value,
context: &ToolCallContext,
) -> Result<Value> {
let result = Self::execute(self, tool_id, params, context).await?;
if result.success {
Ok(result.data)
} else {
Err(anyhow::anyhow!(result
.error
.unwrap_or_else(|| "Unknown error".to_string())))
}
}
fn can_execute(&self, tool_id: &str) -> bool {
Self::supported_tool_ids().contains(&tool_id)
}
fn can_execute_with_skills(&self, tool_id: &str, skills: &[String]) -> bool {
self.env.skill_manager.can_call_tool(tool_id, skills)
}
fn supported_tools(&self) -> Vec<String> {
Self::supported_tool_ids()
.iter()
.map(|s| s.to_string())
.collect()
}
}