use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Stdio;
use aion_integrations::contract::AgentHarness;
use aion_integrations::{AgentRunSpec, HarnessError};
use async_trait::async_trait;
use tokio::process::{ChildStdout, Command};
use crate::demux::EventIdentity;
use crate::session::CliSession;
#[derive(Clone, Debug)]
pub struct CliHarness {
program: PathBuf,
args: Vec<OsString>,
env: Vec<(String, String)>,
pass_input_as_arg: bool,
}
impl CliHarness {
#[must_use]
pub fn new(program: impl Into<PathBuf>) -> Self {
Self {
program: program.into(),
args: Vec::new(),
env: Vec::new(),
pass_input_as_arg: false,
}
}
#[must_use]
pub fn with_arg(mut self, arg: impl Into<OsString>) -> Self {
self.args.push(arg.into());
self
}
#[must_use]
pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.env.push((key.into(), value.into()));
self
}
#[must_use]
pub fn passing_input_as_arg(mut self) -> Self {
self.pass_input_as_arg = true;
self
}
fn spawn(&self, spec: &AgentRunSpec) -> Result<tokio::process::Child, HarnessError> {
let mut command = Command::new(&self.program);
command
.args(&self.args)
.envs(self.env.iter().map(|(key, value)| (key, value)))
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.kill_on_drop(true);
if self.pass_input_as_arg {
command.arg(input_arg(spec)?);
}
command.spawn().map_err(|source| {
HarnessError::transport(format!(
"failed to spawn `{}`: {source}",
self.program.display()
))
})
}
}
fn input_arg(spec: &AgentRunSpec) -> Result<String, HarnessError> {
std::str::from_utf8(spec.input.bytes())
.map(str::to_owned)
.map_err(|source| HarnessError::protocol(format!("run input is not valid UTF-8: {source}")))
}
fn take_stdout(child: &mut tokio::process::Child) -> Result<ChildStdout, HarnessError> {
child
.stdout
.take()
.ok_or_else(|| HarnessError::transport("spawned cli child has no piped stdout"))
}
#[async_trait]
impl AgentHarness for CliHarness {
type Session = CliSession;
async fn start(&self, spec: AgentRunSpec) -> Result<Self::Session, HarnessError> {
let mut child = self.spawn(&spec)?;
let stdout = take_stdout(&mut child)?;
let identity = EventIdentity {
workflow_id: spec.workflow_id,
activity_id: spec.activity_id,
attempt: spec.attempt,
};
Ok(CliSession::start(stdout, identity, Some(child)))
}
}