use std::path::PathBuf;
use std::time::Duration;
use serde_json::Value;
use super::sandbox::SandboxPolicy;
#[derive(Debug, Clone)]
pub struct PluginToolSpec {
pub plugin_name: String,
pub plugin_dir: PathBuf,
pub name: String,
pub description: String,
pub command: String,
pub timeout_ms: u64,
pub input_schema: Value,
pub policy: SandboxPolicy,
}
pub fn invoke(spec: &PluginToolSpec, args_json: &str) -> Result<String, String> {
let output = super::executor::run_subprocess(
&spec.command,
&spec.plugin_dir,
&[("LEAN_CTX_TOOL", spec.name.as_str())],
args_json,
Duration::from_millis(spec.timeout_ms),
&spec.policy,
)?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(if stderr.trim().is_empty() {
format!("tool '{}' exited with {}", spec.name, output.status)
} else {
format!("tool '{}': {}", spec.name, stderr.trim())
})
}
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
fn spec(command: &str) -> PluginToolSpec {
PluginToolSpec {
plugin_name: "demo".into(),
plugin_dir: PathBuf::from("/tmp"),
name: "demo_tool".into(),
description: "demo".into(),
command: command.into(),
timeout_ms: 2000,
input_schema: Value::Null,
policy: SandboxPolicy::strict(),
}
}
#[test]
fn invoke_returns_stdout() {
let out = invoke(&spec("cat"), "{\"q\":1}").unwrap();
assert_eq!(out, "{\"q\":1}");
}
#[test]
fn invoke_reports_failure() {
let err = invoke(&spec("false"), "{}").unwrap_err();
assert!(err.contains("demo_tool"));
}
}