style: Cleaned up all graph agent code
This commit is contained in:
+34
-23
@@ -1,11 +1,3 @@
|
||||
//! Script execution for `script`-type graph nodes.
|
||||
//!
|
||||
//! Scripts receive graph state via either `GRAPH_STATE` (inline JSON env var)
|
||||
//! or `GRAPH_STATE_FILE` (path to a file containing the JSON) when state
|
||||
//! exceeds [`super::MAX_STATE_SIZE_BYTES`]. Scripts MUST print a single JSON
|
||||
//! object on stdout. The `_next` key (if present) is consumed for routing
|
||||
//! and removed before the remaining keys are merged into state.
|
||||
|
||||
use super::state::{StateManager, StateRepresentation};
|
||||
use super::types::ScriptNode;
|
||||
use crate::function::Language;
|
||||
@@ -18,9 +10,6 @@ use std::time::Duration;
|
||||
use tokio::process::Command;
|
||||
use tokio::time::timeout;
|
||||
|
||||
/// Executor for script nodes. `base_dir` is the directory script paths are
|
||||
/// resolved against (typically the owning agent's data directory) and is
|
||||
/// also used as the child process's working directory.
|
||||
pub struct ScriptExecutor {
|
||||
base_dir: PathBuf,
|
||||
}
|
||||
@@ -32,10 +21,6 @@ impl ScriptExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the script, merge its JSON output into state (extracting `_next`
|
||||
/// for routing), and then apply any `state_updates` templates. Returns
|
||||
/// the routing decision from `_next`, or `None` if the script did not
|
||||
/// emit one (in which case the executor falls back to `Node.next`).
|
||||
pub async fn execute(
|
||||
&self,
|
||||
node: &ScriptNode,
|
||||
@@ -153,6 +138,7 @@ fn detect_language(script_path: &Path) -> Result<Language> {
|
||||
.and_then(|e| e.to_str())
|
||||
.ok_or_else(|| anyhow!("Script has no file extension: '{}'", script_path.display()))?
|
||||
.to_string();
|
||||
|
||||
match Language::from(&ext) {
|
||||
Language::Unsupported => bail!(
|
||||
"Unsupported script extension '.{}' for '{}'",
|
||||
@@ -171,9 +157,11 @@ fn build_command(language: Language, script_path: &Path) -> Result<Command> {
|
||||
)
|
||||
})?;
|
||||
let mut cmd = Command::new(program);
|
||||
|
||||
for arg in prefix_args {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
|
||||
cmd.arg(script_path);
|
||||
Ok(cmd)
|
||||
}
|
||||
@@ -183,8 +171,10 @@ mod tests {
|
||||
use super::super::MAX_STATE_SIZE_BYTES;
|
||||
use super::*;
|
||||
use crate::utils::temp_file;
|
||||
use indoc::formatdoc;
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use std::env::temp_dir;
|
||||
use std::fs;
|
||||
|
||||
fn cmd_available(name: &str) -> bool {
|
||||
@@ -226,6 +216,7 @@ echo '{"quality": 0.85, "issues": 3, "_next": "approve"}'
|
||||
);
|
||||
let mut state = StateManager::new(HashMap::new());
|
||||
let executor = ScriptExecutor::new(&dir);
|
||||
|
||||
let next = executor
|
||||
.execute(
|
||||
&node_for(path.file_name().unwrap().to_str().unwrap(), 5),
|
||||
@@ -233,6 +224,7 @@ echo '{"quality": 0.85, "issues": 3, "_next": "approve"}'
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(next.as_deref(), Some("approve"));
|
||||
assert_eq!(state.state().get("quality"), Some(&json!(0.85)));
|
||||
assert_eq!(state.state().get("issues"), Some(&json!(3)));
|
||||
@@ -257,6 +249,7 @@ printf '{"greeting": "hello %s"}' "$NAME"
|
||||
initial.insert("name".into(), json!("alice"));
|
||||
let mut state = StateManager::new(initial);
|
||||
let executor = ScriptExecutor::new(&dir);
|
||||
|
||||
let _ = executor
|
||||
.execute(
|
||||
&node_for(path.file_name().unwrap().to_str().unwrap(), 5),
|
||||
@@ -264,6 +257,7 @@ printf '{"greeting": "hello %s"}' "$NAME"
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(state.state().get("greeting"), Some(&json!("hello alice")));
|
||||
cleanup(&dir);
|
||||
}
|
||||
@@ -281,6 +275,7 @@ echo '{"ok": true}'
|
||||
);
|
||||
let mut state = StateManager::new(HashMap::new());
|
||||
let executor = ScriptExecutor::new(&dir);
|
||||
|
||||
let next = executor
|
||||
.execute(
|
||||
&node_for(path.file_name().unwrap().to_str().unwrap(), 5),
|
||||
@@ -288,6 +283,7 @@ echo '{"ok": true}'
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(next.is_none());
|
||||
assert_eq!(state.state().get("ok"), Some(&json!(true)));
|
||||
cleanup(&dir);
|
||||
@@ -321,12 +317,14 @@ echo '{"raw": "hello"}'
|
||||
#[tokio::test]
|
||||
async fn missing_script_file_errors_before_spawning() {
|
||||
let mut state = StateManager::new(HashMap::new());
|
||||
let executor = ScriptExecutor::new(std::env::temp_dir());
|
||||
let executor = ScriptExecutor::new(temp_dir());
|
||||
|
||||
let err = executor
|
||||
.execute(&node_for("__does_not_exist__.sh", 5), &mut state)
|
||||
.await
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
|
||||
assert!(err.contains("Script file not found"), "got: {err}");
|
||||
}
|
||||
|
||||
@@ -338,6 +336,7 @@ echo '{"raw": "hello"}'
|
||||
let (dir, path) = write_script("#!/bin/bash\n", "sh");
|
||||
let mut state = StateManager::new(HashMap::new());
|
||||
let executor = ScriptExecutor::new(&dir);
|
||||
|
||||
let err = executor
|
||||
.execute(
|
||||
&node_for(path.file_name().unwrap().to_str().unwrap(), 5),
|
||||
@@ -346,6 +345,7 @@ echo '{"raw": "hello"}'
|
||||
.await
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
|
||||
assert!(err.contains("produced no output"), "got: {err}");
|
||||
cleanup(&dir);
|
||||
}
|
||||
@@ -356,13 +356,15 @@ echo '{"raw": "hello"}'
|
||||
return;
|
||||
}
|
||||
let (dir, path) = write_script(
|
||||
r#"#!/bin/bash
|
||||
echo "not json at all"
|
||||
"#,
|
||||
&formatdoc! {r#"
|
||||
#!/bin/bash
|
||||
echo "not json at all"
|
||||
"#},
|
||||
"sh",
|
||||
);
|
||||
let mut state = StateManager::new(HashMap::new());
|
||||
let executor = ScriptExecutor::new(&dir);
|
||||
|
||||
let err = executor
|
||||
.execute(
|
||||
&node_for(path.file_name().unwrap().to_str().unwrap(), 5),
|
||||
@@ -371,6 +373,7 @@ echo "not json at all"
|
||||
.await
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
|
||||
assert!(err.contains("merge output"), "got: {err}");
|
||||
cleanup(&dir);
|
||||
}
|
||||
@@ -381,14 +384,16 @@ echo "not json at all"
|
||||
return;
|
||||
}
|
||||
let (dir, path) = write_script(
|
||||
r#"#!/bin/bash
|
||||
echo "bad happened" >&2
|
||||
exit 7
|
||||
"#,
|
||||
&formatdoc! {r#"
|
||||
#!/bin/bash
|
||||
echo "bad happened" >&2
|
||||
exit 7
|
||||
"#},
|
||||
"sh",
|
||||
);
|
||||
let mut state = StateManager::new(HashMap::new());
|
||||
let executor = ScriptExecutor::new(&dir);
|
||||
|
||||
let err = executor
|
||||
.execute(
|
||||
&node_for(path.file_name().unwrap().to_str().unwrap(), 5),
|
||||
@@ -397,6 +402,7 @@ exit 7
|
||||
.await
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
|
||||
assert!(err.contains("exit code"), "got: {err}");
|
||||
assert!(err.contains("bad happened"), "got: {err}");
|
||||
cleanup(&dir);
|
||||
@@ -416,6 +422,7 @@ echo '{"ok":true}'
|
||||
);
|
||||
let mut state = StateManager::new(HashMap::new());
|
||||
let executor = ScriptExecutor::new(&dir);
|
||||
|
||||
let err = executor
|
||||
.execute(
|
||||
&node_for(path.file_name().unwrap().to_str().unwrap(), 1),
|
||||
@@ -424,6 +431,7 @@ echo '{"ok":true}'
|
||||
.await
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
|
||||
assert!(err.contains("timed out"), "got: {err}");
|
||||
cleanup(&dir);
|
||||
}
|
||||
@@ -493,6 +501,7 @@ print(json.dumps({
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(next.as_deref(), Some("next_node"));
|
||||
assert_eq!(state.state().get("doubled"), Some(&json!(42)));
|
||||
cleanup(&dir);
|
||||
@@ -503,6 +512,7 @@ print(json.dumps({
|
||||
let (dir, path) = write_script("echo hi", "xyz");
|
||||
let mut state = StateManager::new(HashMap::new());
|
||||
let executor = ScriptExecutor::new(&dir);
|
||||
|
||||
let err = executor
|
||||
.execute(
|
||||
&node_for(path.file_name().unwrap().to_str().unwrap(), 5),
|
||||
@@ -511,6 +521,7 @@ print(json.dumps({
|
||||
.await
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
|
||||
assert!(
|
||||
err.contains("Unsupported script extension '.xyz'"),
|
||||
"got: {err}"
|
||||
|
||||
Reference in New Issue
Block a user