use super::caller::{Caller, resolve};
use super::{Passage, Verdict, gate};
use crate::prompt::SystemClock;
use crate::prompt::tool::builtin::dispatch::EnvLookup;
use crate::prompt::tool::inject::ToolInjection;
use crate::prompt::tool::{SpawnTool, ToolCall, ToolExecutor, envelope};
use serde::Deserialize;
use serde_json::Value;
use std::path::Path;
use thiserror::Error;
const NO_CHILD: i32 = 1;
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Block {
id: String,
name: String,
#[serde(default = "empty_input")]
input: Value,
}
fn empty_input() -> Value {
Value::Object(serde_json::Map::new())
}
#[derive(Debug, Error)]
pub(crate) enum Error {
#[error("read the tool_use block from stdin: {0}")]
Stdin(std::io::Error),
#[error(
"malformed tool_use block: {0}. Expected \
{{\"id\": \"<id>\", \"name\": \"<tool>\", \"input\": {{...}}}}"
)]
Malformed(serde_json::Error),
#[error(transparent)]
Caller(#[from] super::caller::Error),
#[error(transparent)]
Window(#[from] crate::prompt::Error),
#[error(transparent)]
Exec(#[from] crate::prompt::tool::ExecError),
#[error("write the result envelope: {0}")]
Stdout(std::io::Error),
}
#[allow(clippy::too_many_arguments)] pub(crate) fn run(
env: &dyn EnvLookup,
stdin: &mut dyn std::io::Read,
stdout: &mut dyn std::io::Write,
driver_target: &Path,
adapter_target: Option<&Path>,
stop: &std::sync::atomic::AtomicBool,
injection: Option<&dyn ToolInjection>,
) -> Result<i32, Error> {
let mut buf = Vec::new();
stdin.read_to_end(&mut buf).map_err(Error::Stdin)?;
let block: Block = serde_json::from_slice(&buf).map_err(Error::Malformed)?;
let caller: Caller = resolve(env, driver_target, adapter_target, stop, injection)?;
let executor =
SpawnTool::new(&caller.data_root, &SystemClock, driver_target).with_injection(injection);
let passage = Passage {
id: &block.id,
name: &block.name,
input: &block.input,
role: &caller.role,
grant: &caller.grant,
injected: &caller.injected,
tool_control: caller.tool_control.as_ref(),
conv_repo: &caller.workspace,
conv_id: &caller.agent,
stop,
};
let (bytes, code) = match gate(&passage)? {
None => (STOPPED.as_bytes().to_vec(), NO_CHILD),
Some(Verdict::Declined(text)) => (text.into_bytes(), NO_CHILD),
Some(Verdict::Proceed) => {
let call = ToolCall {
id: &block.id,
name: &block.name,
input: &block.input,
};
let outcome = executor.execute(call, &caller.step_dir, stop, None)?;
let code = envelope::stated_exit_code(&outcome.content).unwrap_or(NO_CHILD);
(outcome.content, code)
}
};
stdout.write_all(&bytes).map_err(Error::Stdout)?;
Ok(code)
}
const STOPPED: &str = "the harness is stopping (ARCH §2.9): this invocation did not run.";