1use serde::{Deserialize, Serialize};
2
3#[cfg(feature = "local")]
4use crate::local::LocalBackend;
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub enum Capability {
9 ReadFile,
11 WriteFile,
13 GitReadOnly,
15 ShellCommand { pattern: String },
17 Network,
19 Custom(String),
21}
22
23#[derive(Debug, Clone, Default, Serialize, Deserialize)]
28pub struct Sandbox {
29 pub allowed: Vec<Capability>,
31 pub denied: Vec<Capability>,
33}
34
35#[derive(Debug, Clone, Default)]
37pub struct ProviderConfig {
38 #[cfg(feature = "local")]
40 pub backend: Option<LocalBackend>,
41 pub model: Option<String>,
43 pub budget: Option<f64>,
45 pub sandbox: Option<Sandbox>,
47 pub debug: bool,
49}
50
51pub(crate) const GIT_READONLY_COMMANDS: &[&str] = &[
53 "diff",
54 "log",
55 "show",
56 "status",
57 "ls-files",
58 "rev-parse",
59 "branch",
60 "cat-file",
61 "rev-list",
62 "shortlog",
63 "blame",
64];
65
66pub(crate) trait SandboxTranslator {
68 fn translate_allowed(&self, capabilities: &[Capability]) -> Vec<String>;
70
71 fn translate_sandbox(&self, sandbox: &Sandbox) -> Vec<String> {
73 let allowed: Vec<_> = sandbox
74 .allowed
75 .iter()
76 .filter(|cap| !sandbox.denied.contains(cap))
77 .cloned()
78 .collect();
79 self.translate_allowed(&allowed)
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn sandbox_default_is_empty() {
89 let sandbox = Sandbox::default();
90 assert!(sandbox.allowed.is_empty());
91 assert!(sandbox.denied.is_empty());
92 }
93
94 #[test]
95 fn provider_config_default() {
96 let config = ProviderConfig::default();
97 assert!(config.model.is_none());
98 assert!(config.budget.is_none());
99 assert!(config.sandbox.is_none());
100 assert!(!config.debug);
101 }
102
103 #[test]
104 fn capability_equality() {
105 assert_eq!(Capability::ReadFile, Capability::ReadFile);
106 assert_ne!(Capability::ReadFile, Capability::WriteFile);
107 assert_eq!(
108 Capability::ShellCommand {
109 pattern: "ls".into()
110 },
111 Capability::ShellCommand {
112 pattern: "ls".into()
113 }
114 );
115 assert_ne!(
116 Capability::ShellCommand {
117 pattern: "ls".into()
118 },
119 Capability::ShellCommand {
120 pattern: "cat".into()
121 }
122 );
123 }
124}