smol_workflow_engine/
environment.rs1mod local;
8mod sandbox;
9
10use anyhow::anyhow;
11use std::collections::BTreeMap;
12use std::path::Path;
13
14pub use local::LocalExecutionEnvironment;
15pub use sandbox::SandboxExecutionEnvironment;
16
17#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19#[serde(transparent)]
20pub struct EnvironmentPath(pub String);
21
22impl EnvironmentPath {
23 pub fn as_str(&self) -> &str {
24 &self.0
25 }
26}
27
28impl From<String> for EnvironmentPath {
29 fn from(value: String) -> Self {
30 Self(value)
31 }
32}
33
34impl From<&str> for EnvironmentPath {
35 fn from(value: &str) -> Self {
36 Self(value.to_string())
37 }
38}
39
40#[derive(Debug, Clone, Default)]
42pub struct ExecRequest {
43 pub argv: Vec<String>,
45 pub cwd: Option<EnvironmentPath>,
47 pub env: BTreeMap<String, String>,
49 pub stdin: Option<Vec<u8>>,
51}
52
53#[derive(Debug, Clone, Default, PartialEq, Eq)]
55pub struct ExecOutput {
56 pub exit_code: i32,
57 pub stdout: Vec<u8>,
58 pub stderr: Vec<u8>,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
63pub enum ExecEvent {
64 Started { process_id: Option<String> },
65 Stdout { chunk: Vec<u8> },
66 Stderr { chunk: Vec<u8> },
67 Exited { exit_code: i32 },
68}
69
70#[async_trait::async_trait]
71pub trait ExecEventSink: Send {
72 async fn event(&mut self, event: ExecEvent) -> anyhow::Result<()>;
73}
74
75#[derive(Debug, Clone, Default, PartialEq, Eq)]
77pub struct SpawnOutput {
78 pub process_id: Option<String>,
79}
80
81#[async_trait::async_trait]
82pub trait AgentExecutionEnvironment: Send + Sync {
83 fn cwd(&self) -> Option<&EnvironmentPath>;
84
85 async fn create_dir_all(&self, path: &EnvironmentPath) -> anyhow::Result<()>;
86
87 async fn write_file(&self, path: &EnvironmentPath, content: &[u8]) -> anyhow::Result<()>;
88
89 async fn read_file(&self, path: &EnvironmentPath) -> anyhow::Result<Vec<u8>>;
90
91 async fn remove(&self, path: &EnvironmentPath) -> anyhow::Result<()>;
92
93 async fn create_temp_dir(&self, prefix: &str) -> anyhow::Result<EnvironmentPath>;
94
95 async fn exec(
96 &self,
97 request: ExecRequest,
98 sink: &mut dyn ExecEventSink,
99 ) -> anyhow::Result<ExecOutput>;
100
101 async fn spawn(
102 &self,
103 request: ExecRequest,
104 sink: Option<Box<dyn ExecEventSink>>,
105 ) -> anyhow::Result<SpawnOutput>;
106}
107
108#[derive(Debug, Default)]
110pub struct NullExecEventSink;
111
112#[async_trait::async_trait]
113impl ExecEventSink for NullExecEventSink {
114 async fn event(&mut self, _event: ExecEvent) -> anyhow::Result<()> {
115 Ok(())
116 }
117}
118
119pub(crate) fn path_to_environment_path(path: impl AsRef<Path>) -> anyhow::Result<EnvironmentPath> {
120 let path = path.as_ref();
121 let value = path
122 .to_str()
123 .ok_or_else(|| anyhow!("environment paths must be valid UTF-8: {path:?}"))?;
124 Ok(EnvironmentPath(value.to_string()))
125}