Skip to main content

zag_agent/providers/
common.rs

1use crate::output::AgentOutput;
2use crate::sandbox::SandboxConfig;
3use anyhow::Context;
4use std::path::Path;
5use std::process::Stdio;
6use tokio::process::Command;
7
8/// Shared configuration state for CLI-based agent providers.
9///
10/// Embed this struct in each provider to avoid duplicating field
11/// declarations and trivial setter implementations.
12pub struct CommonAgentState {
13    pub system_prompt: String,
14    pub model: String,
15    pub root: Option<String>,
16    pub skip_permissions: bool,
17    pub output_format: Option<String>,
18    pub add_dirs: Vec<String>,
19    pub capture_output: bool,
20    pub sandbox: Option<SandboxConfig>,
21    pub max_turns: Option<u32>,
22    pub env_vars: Vec<(String, String)>,
23}
24
25impl CommonAgentState {
26    pub fn new(default_model: &str) -> Self {
27        Self {
28            system_prompt: String::new(),
29            model: default_model.to_string(),
30            root: None,
31            skip_permissions: false,
32            output_format: None,
33            add_dirs: Vec::new(),
34            capture_output: false,
35            sandbox: None,
36            max_turns: None,
37            env_vars: Vec::new(),
38        }
39    }
40
41    /// Get the effective base path (root directory or ".").
42    pub fn get_base_path(&self) -> &Path {
43        self.root.as_ref().map(Path::new).unwrap_or(Path::new("."))
44    }
45
46    /// Create a `Command` either directly or wrapped in sandbox.
47    ///
48    /// Standard pattern used by Claude, Copilot, and Gemini. Sets
49    /// `current_dir`, args, and env vars. Providers with custom sandbox
50    /// behavior (Codex, Ollama) keep their own `make_command()`.
51    pub fn make_command(&self, binary_name: &str, agent_args: Vec<String>) -> Command {
52        if let Some(ref sb) = self.sandbox {
53            let std_cmd = crate::sandbox::build_sandbox_command(sb, agent_args);
54            Command::from(std_cmd)
55        } else {
56            let mut cmd = Command::new(binary_name);
57            if let Some(ref root) = self.root {
58                cmd.current_dir(root);
59            }
60            cmd.args(&agent_args);
61            for (key, value) in &self.env_vars {
62                cmd.env(key, value);
63            }
64            cmd
65        }
66    }
67
68    /// Execute a command interactively (inheriting stdin/stdout/stderr).
69    ///
70    /// Returns `ProcessError` on non-zero exit.
71    pub async fn run_interactive_command(
72        cmd: &mut Command,
73        agent_display_name: &str,
74    ) -> anyhow::Result<()> {
75        cmd.stdin(Stdio::inherit())
76            .stdout(Stdio::inherit())
77            .stderr(Stdio::inherit());
78        let status = cmd.status().await.with_context(|| {
79            format!(
80                "Failed to execute '{}' CLI. Is it installed and in PATH?",
81                agent_display_name.to_lowercase()
82            )
83        })?;
84        if !status.success() {
85            return Err(crate::process::ProcessError {
86                exit_code: status.code(),
87                stderr: String::new(),
88                agent_name: agent_display_name.to_string(),
89            }
90            .into());
91        }
92        Ok(())
93    }
94
95    /// Execute a non-interactive command with simple capture-or-passthrough.
96    ///
97    /// If `capture_output` is set, captures stdout and returns `Some(AgentOutput)`.
98    /// Otherwise streams stdout to the terminal and returns `None`.
99    ///
100    /// Used by Copilot, Gemini, Ollama. Providers with custom output parsing
101    /// (Claude, Codex) keep their own non-interactive logic.
102    pub async fn run_non_interactive_simple(
103        &self,
104        cmd: &mut Command,
105        agent_display_name: &str,
106    ) -> anyhow::Result<Option<AgentOutput>> {
107        if self.capture_output {
108            let text = crate::process::run_captured(cmd, agent_display_name).await?;
109            log::debug!(
110                "{} raw response ({} bytes): {}",
111                agent_display_name,
112                text.len(),
113                text
114            );
115            Ok(Some(AgentOutput::from_text(
116                &agent_display_name.to_lowercase(),
117                &text,
118            )))
119        } else {
120            cmd.stdin(Stdio::inherit()).stdout(Stdio::inherit());
121            crate::process::run_with_captured_stderr(cmd).await?;
122            Ok(None)
123        }
124    }
125}
126
127/// Delegate common Agent trait setter methods to `self.common`.
128///
129/// Generates the 12 trivial setters that are identical across all CLI-based
130/// providers. Excludes `set_skip_permissions` since Ollama overrides it.
131macro_rules! impl_common_agent_setters {
132    () => {
133        fn system_prompt(&self) -> &str {
134            &self.common.system_prompt
135        }
136
137        fn set_system_prompt(&mut self, prompt: String) {
138            self.common.system_prompt = prompt;
139        }
140
141        fn get_model(&self) -> &str {
142            &self.common.model
143        }
144
145        fn set_model(&mut self, model: String) {
146            self.common.model = model;
147        }
148
149        fn set_root(&mut self, root: String) {
150            self.common.root = Some(root);
151        }
152
153        fn set_output_format(&mut self, format: Option<String>) {
154            self.common.output_format = format;
155        }
156
157        fn set_add_dirs(&mut self, dirs: Vec<String>) {
158            self.common.add_dirs = dirs;
159        }
160
161        fn set_env_vars(&mut self, vars: Vec<(String, String)>) {
162            self.common.env_vars = vars;
163        }
164
165        fn set_capture_output(&mut self, capture: bool) {
166            self.common.capture_output = capture;
167        }
168
169        fn set_sandbox(&mut self, config: crate::sandbox::SandboxConfig) {
170            self.common.sandbox = Some(config);
171        }
172
173        fn set_max_turns(&mut self, turns: u32) {
174            self.common.max_turns = Some(turns);
175        }
176    };
177}
178pub(crate) use impl_common_agent_setters;
179
180/// Implement `as_any_ref` and `as_any_mut` for a concrete agent type.
181macro_rules! impl_as_any {
182    () => {
183        fn as_any_ref(&self) -> &dyn std::any::Any {
184            self
185        }
186
187        fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
188            self
189        }
190    };
191}
192pub(crate) use impl_as_any;