use anyhow::{Context, Result};
use async_trait::async_trait;
use serde_json::Value;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use tracing::debug;
use crate::core::context::WorkflowContext;
use crate::core::tool_loader::ToolResource;
use crate::services::local_runtime::LocalRuntime;
use crate::services::prompt_loader::PromptRegistry;
use crate::services::tool_registry::ToolRegistry;
#[async_trait]
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn schema(&self) -> Option<Value> {
None
}
async fn execute(
&self,
params: &HashMap<String, String>,
context: &WorkflowContext,
) -> Result<Option<Value>>;
}
pub struct BuiltinRegistry {
tools: RwLock<HashMap<String, Arc<Box<dyn Tool>>>>,
executor: RwLock<Option<std::sync::Weak<crate::core::executor::WorkflowExecutor>>>,
_prompt_registry: Arc<PromptRegistry>,
}
impl BuiltinRegistry {
pub fn new(prompts: Arc<PromptRegistry>, runtime: Arc<LocalRuntime>) -> Arc<Self> {
let mut tool_map: HashMap<String, Arc<Box<dyn Tool>>> = HashMap::new();
macro_rules! reg {
($t:expr) => {
tool_map.insert($t.name().to_string(), Arc::new(Box::new($t)));
};
}
reg!(network::FetchUrl);
reg!(network::Fetch);
reg!(http_client::HttpRequest);
reg!(oauth::OAuthToken);
reg!(system::Timer);
reg!(system::Notify);
reg!(system::Print);
reg!(system::Reply::new());
reg!(system::SetContext);
reg!(system::FeishuWebhook);
reg!(system::FeishuSend);
reg!(system::Return);
reg!(http::HttpResponse);
reg!(devtools::ReadFile);
reg!(devtools::WriteFile);
reg!(devtools::EditFile);
reg!(devtools::GlobSearch);
reg!(devtools::GrepSearch);
reg!(devtools::Bash);
tool_map.insert("sh".to_string(), Arc::new(Box::new(devtools::Bash)));
reg!(ai::Prompt::new(prompts.clone()));
reg!(testing::Config);
#[cfg(feature = "device")]
{
reg!(device::KeyTap);
reg!(device::KeyCombo);
reg!(device::TypeText);
reg!(device::MouseMove);
reg!(device::MouseClick);
reg!(device::MouseScroll);
reg!(device::MousePosition);
reg!(device::MouseDrag);
reg!(device::ScreenSize);
reg!(device::Screenshot);
}
reg!(database::DbConnect);
reg!(database::DbDisconnect);
reg!(database::DbQuery);
reg!(database::DbExec);
reg!(database::DbFind);
reg!(database::DbFindOne);
reg!(database::DbCreate);
reg!(database::DbCreateMany);
reg!(database::DbUpsert);
reg!(database::DbUpdate);
reg!(database::DbDelete);
reg!(database::DbCount);
reg!(database::DbAggregate);
reg!(database::DbBegin);
reg!(database::DbCommit);
reg!(database::DbRollback);
reg!(database::DbCreateTable);
reg!(database::DbDropTable);
reg!(database::DbAlterTable);
reg!(database::DbTables);
reg!(database::DbColumns);
reg!(history::HistoryLoad);
reg!(history::HistoryAppend);
reg!(history::HistoryReplace);
reg!(history::HistoryTrim);
reg!(history::HistoryClear);
reg!(history::HistoryStats);
reg!(history::HistoryListChats);
let registry_arc = Arc::new(Self {
tools: RwLock::new(tool_map),
executor: RwLock::new(None),
_prompt_registry: prompts.clone(),
});
let mut chat_tool = ai::Chat::new(prompts, runtime);
chat_tool.set_registry(Arc::downgrade(®istry_arc));
let mut exec_wf_tool = ai::ExecuteWorkflow::new();
exec_wf_tool.set_registry(Arc::downgrade(®istry_arc));
let mut mock_tool = testing::Mock::new();
mock_tool.set_registry(Arc::downgrade(®istry_arc));
let mut call_tool = system::Call::new();
call_tool.set_registry(Arc::downgrade(®istry_arc));
let mut serve_tool = http::Serve::new();
serve_tool.set_registry(Arc::downgrade(®istry_arc));
#[cfg(feature = "device")]
let key_listen_tool = {
let mut t = device::KeyListen::new();
t.set_registry(Arc::downgrade(®istry_arc));
t
};
#[cfg(feature = "device")]
let mouse_listen_tool = {
let mut t = device::MouseListen::new();
t.set_registry(Arc::downgrade(®istry_arc));
t
};
{
let mut guard = registry_arc.tools.write().expect("Lock poisoned");
guard.insert("chat".to_string(), Arc::new(Box::new(chat_tool)));
guard.insert(
"execute_workflow".to_string(),
Arc::new(Box::new(exec_wf_tool)),
);
guard.insert("mock".to_string(), Arc::new(Box::new(mock_tool)));
guard.insert("call".to_string(), Arc::new(Box::new(call_tool)));
guard.insert("serve".to_string(), Arc::new(Box::new(serve_tool)));
#[cfg(feature = "device")]
{
guard.insert(
"key_listen".to_string(),
Arc::new(Box::new(key_listen_tool)),
);
guard.insert(
"mouse_listen".to_string(),
Arc::new(Box::new(mouse_listen_tool)),
);
}
}
registry_arc
}
pub fn get(&self, name: &str) -> Option<Arc<Box<dyn Tool>>> {
self.tools.read().ok()?.get(name).cloned()
}
pub fn list_schemas(&self) -> Vec<Value> {
let guard = self.tools.read().unwrap();
guard.values().filter_map(|tool| tool.schema()).collect()
}
pub fn register_devtools_to_registry(&self, tool_registry: &mut ToolRegistry) {
let schemas = self.list_schemas();
if !schemas.is_empty() {
let resource = ToolResource {
slug: "devtools".to_string(),
name: "Built-in Developer Tools".to_string(),
description: Some("Read, Write, Edit, Glob, Grep, Bash".to_string()),
tools: schemas,
};
tool_registry.register(resource);
}
}
pub fn set_executor(&self, executor: std::sync::Weak<crate::core::executor::WorkflowExecutor>) {
if let Ok(mut guard) = self.executor.write() {
*guard = Some(executor);
}
}
pub fn get_executor(&self) -> Option<Arc<crate::core::executor::WorkflowExecutor>> {
self.executor.read().ok()?.as_ref()?.upgrade()
}
pub async fn execute_nested_workflow(
&self,
workflow_path: &str,
base_dir: &std::path::Path,
context: &crate::core::context::WorkflowContext,
identifier: String,
) -> Result<Value> {
use crate::core::parser::GraphParser;
use std::fs;
use std::sync::Arc;
context.enter_execution(identifier.clone())?;
let abs_workflow_path = if std::path::Path::new(workflow_path).is_absolute() {
std::path::PathBuf::from(workflow_path)
} else {
base_dir.join(workflow_path)
};
let workflow_content = fs::read_to_string(&abs_workflow_path)
.with_context(|| format!("Failed to load nested workflow: {:?}", abs_workflow_path))?;
let workflow_graph = GraphParser::parse(&workflow_content)?;
let _workflow_base_dir = abs_workflow_path
.parent()
.unwrap_or(std::path::Path::new("."));
if !workflow_graph.prompt_patterns.is_empty() {
}
let executor_weak = {
let guard = self
.executor
.read()
.map_err(|_| anyhow::anyhow!("Failed to acquire executor lock"))?;
guard
.clone()
.ok_or_else(|| anyhow::anyhow!("WorkflowExecutor not initialized"))?
};
let executor = executor_weak
.upgrade()
.ok_or_else(|| anyhow::anyhow!("WorkflowExecutor has been dropped"))?;
debug!("│ ├─ Executing nested workflow: {}", workflow_path);
executor
.execute_graph(Arc::new(workflow_graph), context)
.await?;
context.exit_execution()?;
debug!("│ └─ Nested workflow completed");
let output = context
.resolve_path("reply.output")?
.unwrap_or(serde_json::json!(""));
Ok(output)
}
}
pub mod ai;
pub mod database;
#[cfg(feature = "device")]
pub mod device;
pub mod devtools;
pub mod history;
pub mod http;
pub mod http_client;
pub mod network;
pub mod oauth;
pub mod system;
pub mod testing;