use serde::{Deserialize, Serialize};
#[cfg(feature = "local")]
use crate::local::LocalBackend;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Capability {
ReadFile,
WriteFile,
GitReadOnly,
ShellCommand { pattern: String },
Network,
Custom(String),
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Sandbox {
pub allowed: Vec<Capability>,
pub denied: Vec<Capability>,
}
#[derive(Debug, Clone, Default)]
pub struct ProviderConfig {
#[cfg(feature = "local")]
pub backend: Option<LocalBackend>,
pub model: Option<String>,
pub budget: Option<f64>,
pub sandbox: Option<Sandbox>,
pub debug: bool,
}
pub(crate) const GIT_READONLY_COMMANDS: &[&str] = &[
"diff",
"log",
"show",
"status",
"ls-files",
"rev-parse",
"branch",
"cat-file",
"rev-list",
"shortlog",
"blame",
];
pub(crate) trait SandboxTranslator {
fn translate_allowed(&self, capabilities: &[Capability]) -> Vec<String>;
fn translate_sandbox(&self, sandbox: &Sandbox) -> Vec<String> {
let allowed: Vec<_> = sandbox
.allowed
.iter()
.filter(|cap| !sandbox.denied.contains(cap))
.cloned()
.collect();
self.translate_allowed(&allowed)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sandbox_default_is_empty() {
let sandbox = Sandbox::default();
assert!(sandbox.allowed.is_empty());
assert!(sandbox.denied.is_empty());
}
#[test]
fn provider_config_default() {
let config = ProviderConfig::default();
assert!(config.model.is_none());
assert!(config.budget.is_none());
assert!(config.sandbox.is_none());
assert!(!config.debug);
}
#[test]
fn capability_equality() {
assert_eq!(Capability::ReadFile, Capability::ReadFile);
assert_ne!(Capability::ReadFile, Capability::WriteFile);
assert_eq!(
Capability::ShellCommand {
pattern: "ls".into()
},
Capability::ShellCommand {
pattern: "ls".into()
}
);
assert_ne!(
Capability::ShellCommand {
pattern: "ls".into()
},
Capability::ShellCommand {
pattern: "cat".into()
}
);
}
}