diff --git a/src/config/agent.rs b/src/config/agent.rs index a17d733..c452c8c 100644 --- a/src/config/agent.rs +++ b/src/config/agent.rs @@ -678,6 +678,7 @@ impl AgentConfig { global_tools: graph.global_tools.clone(), mcp_servers: graph.mcp_servers.clone(), conversation_starters: graph.conversation_starters.clone(), + variables: graph.variables.clone(), can_spawn_agents: graph.has_agent_node(), max_concurrent_agents: default_max_concurrent_agents(), max_agent_depth: default_max_agent_depth(), diff --git a/src/config/mod.rs b/src/config/mod.rs index 08af88e..602d59d 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -14,7 +14,9 @@ pub(crate) mod todo; mod tool_scope; mod update; -pub use self::agent::{Agent, AgentVariables, complete_agent_variables, list_agents}; +pub use self::agent::{ + Agent, AgentVariable, AgentVariables, complete_agent_variables, list_agents, +}; #[allow(unused_imports)] pub use self::app_config::AppConfig; #[allow(unused_imports)] diff --git a/src/graph/executor.rs b/src/graph/executor.rs index 8670b4d..349e9d7 100644 --- a/src/graph/executor.rs +++ b/src/graph/executor.rs @@ -76,7 +76,12 @@ impl GraphExecutor { } let mut state = StateManager::new(graph.initial_state.clone()); - let script_executor = ScriptExecutor::new(&base_dir); + let agent_envs = ctx + .agent + .as_ref() + .map(|a| a.variable_envs()) + .unwrap_or_default(); + let script_executor = ScriptExecutor::new(&base_dir).with_envs(agent_envs); let max_iterations = graph.settings.max_loop_iterations; let graph_timeout = graph.settings.timeout.map(Duration::from_secs); let max_concurrency = graph.settings.max_concurrency; diff --git a/src/graph/script.rs b/src/graph/script.rs index cd53a53..c7541d1 100644 --- a/src/graph/script.rs +++ b/src/graph/script.rs @@ -1,26 +1,43 @@ use super::state::{StateManager, StateRepresentation}; use super::types::ScriptNode; +use crate::config::paths; use crate::function::Language; use anyhow::{Context, Result, anyhow, bail}; use serde_json::Value; +use std::collections::HashMap; +use std::env; use std::path::{Path, PathBuf}; use std::process::Stdio; use std::time::Duration; use tokio::process::Command; use tokio::time::timeout; +#[cfg(windows)] +const PATH_SEP: &str = ";"; +#[cfg(not(windows))] +const PATH_SEP: &str = ":"; + #[derive(Clone)] pub struct ScriptExecutor { base_dir: PathBuf, + extra_envs: HashMap, } impl ScriptExecutor { pub fn new(base_dir: impl Into) -> Self { + let base_dir = base_dir.into(); + let extra_envs = build_default_envs(&base_dir); Self { - base_dir: base_dir.into(), + base_dir, + extra_envs, } } + pub fn with_envs(mut self, envs: HashMap) -> Self { + self.extra_envs.extend(envs); + self + } + pub async fn execute( &self, node: &ScriptNode, @@ -35,9 +52,9 @@ impl ScriptExecutor { let state_repr = state_manager.serialize_state()?; let mut cmd = build_command(language, &script_path)?; - cmd.current_dir(&self.base_dir); cmd.stdout(Stdio::piped()); cmd.stderr(Stdio::piped()); + cmd.envs(&self.extra_envs); match &state_repr { StateRepresentation::Inline(json) => { cmd.env("GRAPH_STATE", json); @@ -111,6 +128,36 @@ fn apply_state_updates(node: &ScriptNode, state_manager: &mut StateManager) { } } +fn build_default_envs(agent_data_dir: &Path) -> HashMap { + let mut envs = HashMap::new(); + envs.insert( + "LLM_ROOT_DIR".to_string(), + paths::config_dir().to_string_lossy().into_owned(), + ); + envs.insert( + "LLM_PROMPT_UTILS_FILE".to_string(), + paths::bash_prompt_utils_file() + .to_string_lossy() + .into_owned(), + ); + envs.insert( + "LLM_AGENT_DATA_DIR".to_string(), + agent_data_dir.to_string_lossy().into_owned(), + ); + envs.insert("CLICOLOR_FORCE".to_string(), "1".to_string()); + envs.insert("FORCE_COLOR".to_string(), "1".to_string()); + + if let Ok(current_path) = env::var("PATH") { + let bin_dir = paths::functions_bin_dir(); + envs.insert( + "PATH".to_string(), + format!("{}{}{}", bin_dir.display(), PATH_SEP, current_path), + ); + } + + envs +} + fn detect_language(script_path: &Path) -> Result { let ext = script_path .extension() diff --git a/src/graph/types.rs b/src/graph/types.rs index 0341d82..aa0c72f 100644 --- a/src/graph/types.rs +++ b/src/graph/types.rs @@ -1,3 +1,4 @@ +use crate::config::AgentVariable; use anyhow::Result; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; @@ -33,6 +34,9 @@ pub struct Graph { #[serde(default)] pub conversation_starters: Vec, + #[serde(default)] + pub variables: Vec, + #[serde(default)] pub settings: GraphSettings, diff --git a/src/graph/validator.rs b/src/graph/validator.rs index 09b7b37..ca4e749 100644 --- a/src/graph/validator.rs +++ b/src/graph/validator.rs @@ -848,6 +848,7 @@ mod tests { global_tools: Vec::new(), mcp_servers: Vec::new(), conversation_starters: Vec::new(), + variables: Vec::new(), settings: GraphSettings::default(), initial_state: HashMap::new(), reducers: HashMap::new(),