feat: Added agent variables support for graph agents and improved script executor to use the same environment variables as normal agent tool calling for further flexibility

This commit is contained in:
2026-05-21 13:27:33 -06:00
parent 0bb312a85c
commit 558b764db8
6 changed files with 64 additions and 4 deletions
+1
View File
@@ -678,6 +678,7 @@ impl AgentConfig {
global_tools: graph.global_tools.clone(), global_tools: graph.global_tools.clone(),
mcp_servers: graph.mcp_servers.clone(), mcp_servers: graph.mcp_servers.clone(),
conversation_starters: graph.conversation_starters.clone(), conversation_starters: graph.conversation_starters.clone(),
variables: graph.variables.clone(),
can_spawn_agents: graph.has_agent_node(), can_spawn_agents: graph.has_agent_node(),
max_concurrent_agents: default_max_concurrent_agents(), max_concurrent_agents: default_max_concurrent_agents(),
max_agent_depth: default_max_agent_depth(), max_agent_depth: default_max_agent_depth(),
+3 -1
View File
@@ -14,7 +14,9 @@ pub(crate) mod todo;
mod tool_scope; mod tool_scope;
mod update; 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)] #[allow(unused_imports)]
pub use self::app_config::AppConfig; pub use self::app_config::AppConfig;
#[allow(unused_imports)] #[allow(unused_imports)]
+6 -1
View File
@@ -76,7 +76,12 @@ impl GraphExecutor {
} }
let mut state = StateManager::new(graph.initial_state.clone()); 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 max_iterations = graph.settings.max_loop_iterations;
let graph_timeout = graph.settings.timeout.map(Duration::from_secs); let graph_timeout = graph.settings.timeout.map(Duration::from_secs);
let max_concurrency = graph.settings.max_concurrency; let max_concurrency = graph.settings.max_concurrency;
+49 -2
View File
@@ -1,26 +1,43 @@
use super::state::{StateManager, StateRepresentation}; use super::state::{StateManager, StateRepresentation};
use super::types::ScriptNode; use super::types::ScriptNode;
use crate::config::paths;
use crate::function::Language; use crate::function::Language;
use anyhow::{Context, Result, anyhow, bail}; use anyhow::{Context, Result, anyhow, bail};
use serde_json::Value; use serde_json::Value;
use std::collections::HashMap;
use std::env;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Stdio; use std::process::Stdio;
use std::time::Duration; use std::time::Duration;
use tokio::process::Command; use tokio::process::Command;
use tokio::time::timeout; use tokio::time::timeout;
#[cfg(windows)]
const PATH_SEP: &str = ";";
#[cfg(not(windows))]
const PATH_SEP: &str = ":";
#[derive(Clone)] #[derive(Clone)]
pub struct ScriptExecutor { pub struct ScriptExecutor {
base_dir: PathBuf, base_dir: PathBuf,
extra_envs: HashMap<String, String>,
} }
impl ScriptExecutor { impl ScriptExecutor {
pub fn new(base_dir: impl Into<PathBuf>) -> Self { pub fn new(base_dir: impl Into<PathBuf>) -> Self {
let base_dir = base_dir.into();
let extra_envs = build_default_envs(&base_dir);
Self { Self {
base_dir: base_dir.into(), base_dir,
extra_envs,
} }
} }
pub fn with_envs(mut self, envs: HashMap<String, String>) -> Self {
self.extra_envs.extend(envs);
self
}
pub async fn execute( pub async fn execute(
&self, &self,
node: &ScriptNode, node: &ScriptNode,
@@ -35,9 +52,9 @@ impl ScriptExecutor {
let state_repr = state_manager.serialize_state()?; let state_repr = state_manager.serialize_state()?;
let mut cmd = build_command(language, &script_path)?; let mut cmd = build_command(language, &script_path)?;
cmd.current_dir(&self.base_dir);
cmd.stdout(Stdio::piped()); cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped()); cmd.stderr(Stdio::piped());
cmd.envs(&self.extra_envs);
match &state_repr { match &state_repr {
StateRepresentation::Inline(json) => { StateRepresentation::Inline(json) => {
cmd.env("GRAPH_STATE", 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<String, String> {
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<Language> { fn detect_language(script_path: &Path) -> Result<Language> {
let ext = script_path let ext = script_path
.extension() .extension()
+4
View File
@@ -1,3 +1,4 @@
use crate::config::AgentVariable;
use anyhow::Result; use anyhow::Result;
use indexmap::IndexMap; use indexmap::IndexMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -33,6 +34,9 @@ pub struct Graph {
#[serde(default)] #[serde(default)]
pub conversation_starters: Vec<String>, pub conversation_starters: Vec<String>,
#[serde(default)]
pub variables: Vec<AgentVariable>,
#[serde(default)] #[serde(default)]
pub settings: GraphSettings, pub settings: GraphSettings,
+1
View File
@@ -848,6 +848,7 @@ mod tests {
global_tools: Vec::new(), global_tools: Vec::new(),
mcp_servers: Vec::new(), mcp_servers: Vec::new(),
conversation_starters: Vec::new(), conversation_starters: Vec::new(),
variables: Vec::new(),
settings: GraphSettings::default(), settings: GraphSettings::default(),
initial_state: HashMap::new(), initial_state: HashMap::new(),
reducers: HashMap::new(), reducers: HashMap::new(),