use std::collections::BTreeMap;
use serde_json::{Map, Value};
use crate::config::schema::SandboxBackend;
pub const WORKSPACE_PATH: &str = "/workspace";
pub const STATE_PATH: &str = "/state";
pub const AGENT_HOME: &str = "/state/home";
pub const AGENT_BIN: &str = "/state/home/bin";
pub const AGENT_SESSIONS: &str = "/state/sessions";
pub const SYSTEM_LABEL: &str = "errand.system";
pub const SESSION_LABEL: &str = "errand.session";
pub const SANDBOX_NAME_PREFIX: &str = "errand-";
#[derive(Debug, Clone, Default)]
pub struct SandboxLaunch {
pub session_id: String,
pub project_path: String,
pub state_dir: String,
pub env: BTreeMap<String, String>,
pub system_prompt_path: Option<String>,
pub provider: String,
pub model: Option<String>,
pub providers: Map<String, Value>,
pub resume: bool,
}
#[derive(Debug, Clone)]
pub struct CapabilityReport {
pub backend: SandboxBackend,
pub gaps: Vec<String>,
pub notes: Vec<String>,
}
#[derive(Debug, thiserror::Error)]
#[error("the {backend} sandbox backend is unavailable:\n{}", reasons.iter().map(|reason| format!(" - {reason}")).collect::<Vec<_>>().join("\n"))]
pub struct SandboxUnavailableError {
pub backend: SandboxBackend,
pub reasons: Vec<String>,
}
#[derive(Debug, thiserror::Error)]
#[error("{0}")]
pub struct SandboxLaunchError(pub String);
pub fn sandbox_name(session_id: &str) -> String {
format!("{SANDBOX_NAME_PREFIX}{session_id}")
}
pub fn agent_command(launch: &AgentCommand) -> Vec<String> {
let mut command = vec![
"pi".to_owned(),
"--mode".to_owned(),
"rpc".to_owned(),
"--session-dir".to_owned(),
launch.session_dir.clone(),
];
command.push("--provider".to_owned());
command.push(launch.provider.clone());
if let Some(system_prompt_path) = &launch.system_prompt_path {
command.push("--append-system-prompt".to_owned());
command.push(system_prompt_path.clone());
}
if launch.model.as_ref().is_some_and(|model| !model.is_empty()) {
command.push("--model".to_owned());
command.push(launch.model.clone().expect("checked above"));
}
if launch.resume {
command.push("--continue".to_owned());
}
command
}
#[derive(Debug, Clone, Default)]
pub struct AgentCommand {
pub session_dir: String,
pub provider: String,
pub model: Option<String>,
pub system_prompt_path: Option<String>,
pub resume: bool,
}
pub fn placed_prompt_path(system_prompt_path: Option<&String>) -> Option<String> {
let path = system_prompt_path?;
let filename = std::path::Path::new(path)
.file_name()
.map_or_else(|| path.clone(), |name| name.to_string_lossy().into_owned());
Some(format!("{STATE_PATH}/{filename}"))
}
#[cfg(test)]
mod tests;