refactor: Agents that depend on global tools now have all binaries compiled and stored in the agent's bin directory so multiple agents can run at once
This commit is contained in:
@@ -389,6 +389,7 @@ impl Agent {
|
||||
self.name().to_string(),
|
||||
vec!["_instructions".into(), "{}".into()],
|
||||
self.variable_envs(),
|
||||
Some(self.name().to_string()),
|
||||
)?;
|
||||
match value {
|
||||
Some(v) => Ok(v),
|
||||
|
||||
@@ -74,6 +74,8 @@ const FUNCTIONS_BIN_DIR_NAME: &str = "bin";
|
||||
const AGENTS_DIR_NAME: &str = "agents";
|
||||
const GLOBAL_TOOLS_DIR_NAME: &str = "tools";
|
||||
const GLOBAL_TOOLS_FILE_NAME: &str = "tools.txt";
|
||||
const GLOBAL_TOOLS_UTILS_DIR_NAME: &str = "utils";
|
||||
const BASH_PROMPT_UTILS_FILE_NAME: &str = "prompt-utils.sh";
|
||||
const MCP_FILE_NAME: &str = "mcp.json";
|
||||
|
||||
const CLIENTS_FIELD: &str = "clients";
|
||||
@@ -492,6 +494,14 @@ impl Config {
|
||||
Self::functions_dir().join(GLOBAL_TOOLS_DIR_NAME)
|
||||
}
|
||||
|
||||
pub fn global_utils_dir() -> PathBuf {
|
||||
Self::functions_dir().join(GLOBAL_TOOLS_UTILS_DIR_NAME)
|
||||
}
|
||||
|
||||
pub fn bash_prompt_utils_file() -> PathBuf {
|
||||
Self::global_utils_dir().join(BASH_PROMPT_UTILS_FILE_NAME)
|
||||
}
|
||||
|
||||
pub fn session_file(&self, name: &str) -> PathBuf {
|
||||
match name.split_once("/") {
|
||||
Some((dir, name)) => self.sessions_dir().join(dir).join(format!("{name}.yaml")),
|
||||
|
||||
@@ -32,8 +32,8 @@ const PATH_SEP: &str = ";";
|
||||
const PATH_SEP: &str = ":";
|
||||
|
||||
#[derive(AsRefStr)]
|
||||
enum BinaryType {
|
||||
Tool,
|
||||
enum BinaryType<'a> {
|
||||
Tool(Option<&'a str>),
|
||||
Agent,
|
||||
}
|
||||
|
||||
@@ -169,6 +169,7 @@ impl Functions {
|
||||
|
||||
pub fn init() -> Result<Self> {
|
||||
Self::install_global_tools()?;
|
||||
Self::clear_global_functions_bin_dir()?;
|
||||
info!(
|
||||
"Initializing global functions from {}",
|
||||
Config::global_tools_file().display()
|
||||
@@ -191,6 +192,8 @@ impl Functions {
|
||||
|
||||
pub fn init_agent(name: &str, global_tools: &[String]) -> Result<Self> {
|
||||
Self::install_global_tools()?;
|
||||
Self::clear_agent_bin_dir(name)?;
|
||||
|
||||
let global_tools_declarations = if !global_tools.is_empty() {
|
||||
let enabled_tools = global_tools.join("\n");
|
||||
info!("Loading global tools for agent: {name}: {enabled_tools}");
|
||||
@@ -200,7 +203,7 @@ impl Functions {
|
||||
"Building global function binaries required by agent: {name} in {}",
|
||||
Config::functions_bin_dir().display()
|
||||
);
|
||||
Self::build_global_function_binaries(&enabled_tools)?;
|
||||
Self::build_global_function_binaries(&enabled_tools, Some(name))?;
|
||||
tools_declarations
|
||||
} else {
|
||||
debug!("No global tools found for agent: {}", name);
|
||||
@@ -381,17 +384,7 @@ impl Functions {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_global_function_binaries(enabled_tools: &str) -> Result<()> {
|
||||
let bin_dir = Config::functions_bin_dir();
|
||||
if !bin_dir.exists() {
|
||||
fs::create_dir_all(&bin_dir)?;
|
||||
}
|
||||
info!(
|
||||
"Clearing existing function binaries in {}",
|
||||
bin_dir.display()
|
||||
);
|
||||
clear_dir(&bin_dir)?;
|
||||
|
||||
fn build_global_function_binaries(enabled_tools: &str, agent_name: Option<&str>) -> Result<()> {
|
||||
for line in enabled_tools.lines() {
|
||||
if line.starts_with('#') {
|
||||
continue;
|
||||
@@ -417,7 +410,7 @@ impl Functions {
|
||||
bail!("Unsupported tool file extension: {}", language.as_ref());
|
||||
}
|
||||
|
||||
Self::build_binaries(binary_name, language, BinaryType::Tool)?;
|
||||
Self::build_binaries(binary_name, language, BinaryType::Tool(agent_name))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -427,10 +420,10 @@ impl Functions {
|
||||
let enabled_tools = fs::read_to_string(&tools_txt_path)
|
||||
.with_context(|| format!("failed to load functions at {}", tools_txt_path.display()))?;
|
||||
|
||||
Self::build_global_function_binaries(&enabled_tools)
|
||||
Self::build_global_function_binaries(&enabled_tools, None)
|
||||
}
|
||||
|
||||
fn build_agent_tool_binaries(name: &str) -> Result<()> {
|
||||
fn clear_agent_bin_dir(name: &str) -> Result<()> {
|
||||
let agent_bin_directory = Config::agent_bin_dir(name);
|
||||
if !agent_bin_directory.exists() {
|
||||
debug!(
|
||||
@@ -446,6 +439,25 @@ impl Functions {
|
||||
clear_dir(&agent_bin_directory)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear_global_functions_bin_dir() -> Result<()> {
|
||||
let bin_dir = Config::functions_bin_dir();
|
||||
if !bin_dir.exists() {
|
||||
fs::create_dir_all(&bin_dir)?;
|
||||
}
|
||||
|
||||
info!(
|
||||
"Clearing existing function binaries in {}",
|
||||
bin_dir.display()
|
||||
);
|
||||
clear_dir(&bin_dir)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_agent_tool_binaries(name: &str) -> Result<()> {
|
||||
let language = Language::from(
|
||||
&Config::agent_functions_file(name)?
|
||||
.extension()
|
||||
@@ -471,11 +483,16 @@ impl Functions {
|
||||
) -> Result<()> {
|
||||
use native::runtime;
|
||||
let (binary_file, binary_script_file) = match binary_type {
|
||||
BinaryType::Tool => (
|
||||
BinaryType::Tool(None) => (
|
||||
Config::functions_bin_dir().join(format!("{binary_name}.cmd")),
|
||||
Config::functions_bin_dir()
|
||||
.join(format!("run-{binary_name}.{}", language.to_extension())),
|
||||
),
|
||||
BinaryType::Tool(Some(agent_name)) => (
|
||||
Config::agent_bin_dir(agent_name).join(format!("{binary_name}.cmd")),
|
||||
Config::agent_bin_dir(agent_name)
|
||||
.join(format!("run-{binary_name}.{}", language.to_extension())),
|
||||
),
|
||||
BinaryType::Agent => (
|
||||
Config::agent_bin_dir(binary_name).join(format!("{binary_name}.cmd")),
|
||||
Config::agent_bin_dir(binary_name)
|
||||
@@ -501,10 +518,36 @@ impl Functions {
|
||||
})?;
|
||||
let content_template = unsafe { std::str::from_utf8_unchecked(&embedded_file.data) };
|
||||
let content = match binary_type {
|
||||
BinaryType::Tool => content_template.replace("{function_name}", binary_name),
|
||||
BinaryType::Agent => content_template.replace("{agent_name}", binary_name),
|
||||
BinaryType::Tool(None) => {
|
||||
let root_dir = Config::functions_dir();
|
||||
let tool_path = format!(
|
||||
"{}/{binary_name}",
|
||||
&Config::global_tools_dir().to_string_lossy()
|
||||
);
|
||||
content_template
|
||||
.replace("{function_name}", binary_name)
|
||||
.replace("{root_dir}", &root_dir.to_string_lossy())
|
||||
.replace("{tool_path}", &tool_path)
|
||||
}
|
||||
BinaryType::Tool(Some(agent_name)) => {
|
||||
let root_dir = Config::agent_data_dir(agent_name);
|
||||
let tool_path = format!(
|
||||
"{}/{binary_name}",
|
||||
&Config::global_tools_dir().to_string_lossy()
|
||||
);
|
||||
content_template
|
||||
.replace("{function_name}", binary_name)
|
||||
.replace("{root_dir}", &root_dir.to_string_lossy())
|
||||
.replace("{tool_path}", &tool_path)
|
||||
}
|
||||
BinaryType::Agent => content_template
|
||||
.replace("{agent_name}", binary_name)
|
||||
.replace("{config_dir}", &Config::config_dir().to_string_lossy()),
|
||||
}
|
||||
.replace("{config_dir}", &Config::config_dir().to_string_lossy());
|
||||
.replace(
|
||||
"{prompt_utils_file}",
|
||||
&Config::bash_prompt_utils_file().to_string_lossy(),
|
||||
);
|
||||
if binary_script_file.exists() {
|
||||
fs::remove_file(&binary_script_file)?;
|
||||
}
|
||||
@@ -578,7 +621,10 @@ impl Functions {
|
||||
use std::os::unix::prelude::PermissionsExt;
|
||||
|
||||
let binary_file = match binary_type {
|
||||
BinaryType::Tool => Config::functions_bin_dir().join(binary_name),
|
||||
BinaryType::Tool(None) => Config::functions_bin_dir().join(binary_name),
|
||||
BinaryType::Tool(Some(agent_name)) => {
|
||||
Config::agent_bin_dir(agent_name).join(binary_name)
|
||||
}
|
||||
BinaryType::Agent => Config::agent_bin_dir(binary_name).join(binary_name),
|
||||
};
|
||||
info!(
|
||||
@@ -600,10 +646,36 @@ impl Functions {
|
||||
})?;
|
||||
let content_template = unsafe { std::str::from_utf8_unchecked(&embedded_file.data) };
|
||||
let content = match binary_type {
|
||||
BinaryType::Tool => content_template.replace("{function_name}", binary_name),
|
||||
BinaryType::Agent => content_template.replace("{agent_name}", binary_name),
|
||||
BinaryType::Tool(None) => {
|
||||
let root_dir = Config::functions_dir();
|
||||
let tool_path = format!(
|
||||
"{}/{binary_name}",
|
||||
&Config::global_tools_dir().to_string_lossy()
|
||||
);
|
||||
content_template
|
||||
.replace("{function_name}", binary_name)
|
||||
.replace("{root_dir}", &root_dir.to_string_lossy())
|
||||
.replace("{tool_path}", &tool_path)
|
||||
}
|
||||
BinaryType::Tool(Some(agent_name)) => {
|
||||
let root_dir = Config::agent_data_dir(agent_name);
|
||||
let tool_path = format!(
|
||||
"{}/{binary_name}",
|
||||
&Config::global_tools_dir().to_string_lossy()
|
||||
);
|
||||
content_template
|
||||
.replace("{function_name}", binary_name)
|
||||
.replace("{root_dir}", &root_dir.to_string_lossy())
|
||||
.replace("{tool_path}", &tool_path)
|
||||
}
|
||||
BinaryType::Agent => content_template
|
||||
.replace("{agent_name}", binary_name)
|
||||
.replace("{config_dir}", &Config::config_dir().to_string_lossy()),
|
||||
}
|
||||
.replace("{config_dir}", &Config::config_dir().to_string_lossy());
|
||||
.replace(
|
||||
"{prompt_utils_file}",
|
||||
&Config::bash_prompt_utils_file().to_string_lossy(),
|
||||
);
|
||||
if binary_file.exists() {
|
||||
fs::remove_file(&binary_file)?;
|
||||
}
|
||||
@@ -696,6 +768,11 @@ impl ToolCall {
|
||||
Some(agent) => self.extract_call_config_from_agent(config, agent)?,
|
||||
None => self.extract_call_config_from_config(config)?,
|
||||
};
|
||||
let agent_name = config
|
||||
.read()
|
||||
.agent
|
||||
.as_ref()
|
||||
.map(|agent| agent.name().to_owned());
|
||||
|
||||
let json_data = if self.arguments.is_object() {
|
||||
self.arguments.clone()
|
||||
@@ -754,7 +831,7 @@ impl ToolCall {
|
||||
let result = registry_arc.invoke(server, tool, arguments).await?;
|
||||
serde_json::to_value(result)?
|
||||
}
|
||||
_ => match run_llm_function(cmd_name, cmd_args, envs)? {
|
||||
_ => match run_llm_function(cmd_name, cmd_args, envs, agent_name)? {
|
||||
Some(contents) => serde_json::from_str(&contents)
|
||||
.ok()
|
||||
.unwrap_or_else(|| json!({"output": contents})),
|
||||
@@ -812,15 +889,17 @@ pub fn run_llm_function(
|
||||
cmd_name: String,
|
||||
cmd_args: Vec<String>,
|
||||
mut envs: HashMap<String, String>,
|
||||
agent_name: Option<String>,
|
||||
) -> Result<Option<String>> {
|
||||
let mut bin_dirs: Vec<PathBuf> = vec![];
|
||||
if cmd_args.len() > 1 {
|
||||
let dir = Config::agent_bin_dir(&cmd_name);
|
||||
if let Some(agent_name) = agent_name {
|
||||
let dir = Config::agent_bin_dir(&agent_name);
|
||||
if dir.exists() {
|
||||
bin_dirs.push(dir);
|
||||
}
|
||||
} else {
|
||||
bin_dirs.push(Config::functions_bin_dir());
|
||||
}
|
||||
bin_dirs.push(Config::functions_bin_dir());
|
||||
let current_path = env::var("PATH").context("No PATH environment variable")?;
|
||||
let prepend_path = bin_dirs
|
||||
.iter()
|
||||
Reference in New Issue
Block a user