1use crate::error::{Error, Result};
4use std::path::PathBuf;
5use std::process::Stdio;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Provider {
10 Meta,
13 Echo,
16}
17
18impl Provider {
19 fn as_str(self) -> &'static str {
20 match self {
21 Provider::Meta => "meta",
22 Provider::Echo => "echo",
23 }
24 }
25}
26
27#[derive(Debug, Clone)]
29pub struct MuseExecBuilder {
30 binary: String,
31 prompt: String,
32 provider: Option<Provider>,
33 preset: Option<String>,
34 model: Option<String>,
35 reasoning_effort: Option<String>,
36 base_url: Option<String>,
37 working_directory: Option<PathBuf>,
38 envs: Vec<(String, String)>,
39}
40
41impl MuseExecBuilder {
42 pub fn new(prompt: impl Into<String>) -> Self {
43 Self {
44 binary: "muse".to_string(),
45 prompt: prompt.into(),
46 provider: None,
47 preset: None,
48 model: None,
49 reasoning_effort: None,
50 base_url: None,
51 working_directory: None,
52 envs: Vec::new(),
53 }
54 }
55
56 pub fn binary(mut self, path: impl Into<String>) -> Self {
58 self.binary = path.into();
59 self
60 }
61
62 pub fn provider(mut self, provider: Provider) -> Self {
63 self.provider = Some(provider);
64 self
65 }
66
67 pub fn preset(mut self, preset: impl Into<String>) -> Self {
69 self.preset = Some(preset.into());
70 self
71 }
72
73 pub fn model(mut self, model: impl Into<String>) -> Self {
74 self.model = Some(model.into());
75 self
76 }
77
78 pub fn reasoning_effort(mut self, effort: impl Into<String>) -> Self {
81 self.reasoning_effort = Some(effort.into());
82 self
83 }
84
85 pub fn base_url(mut self, url: impl Into<String>) -> Self {
86 self.base_url = Some(url.into());
87 self
88 }
89
90 pub fn working_directory(mut self, dir: impl Into<PathBuf>) -> Self {
91 self.working_directory = Some(dir.into());
92 self
93 }
94
95 pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
96 self.envs.push((key.into(), value.into()));
97 self
98 }
99
100 pub fn build_command(&self) -> Result<tokio::process::Command> {
102 let program = which::which(&self.binary).map_err(|_| Error::BinaryNotFound {
103 name: self.binary.clone(),
104 })?;
105 let mut cmd = tokio::process::Command::new(program);
106 cmd.arg("exec").arg("--json");
107 if let Some(p) = self.provider {
108 cmd.args(["--provider", p.as_str()]);
109 }
110 if let Some(p) = &self.preset {
111 cmd.args(["--preset", p]);
112 }
113 if let Some(m) = &self.model {
114 cmd.args(["--model", m]);
115 }
116 if let Some(e) = &self.reasoning_effort {
117 cmd.args(["--reasoning-effort", e]);
118 }
119 if let Some(u) = &self.base_url {
120 cmd.args(["--base-url", u]);
121 }
122 cmd.arg(&self.prompt)
123 .stdin(Stdio::null())
124 .stdout(Stdio::piped())
125 .stderr(Stdio::piped())
126 .kill_on_drop(true);
127 if let Some(dir) = &self.working_directory {
128 cmd.current_dir(dir);
129 }
130 for (k, v) in &self.envs {
131 cmd.env(k, v);
132 }
133 Ok(cmd)
134 }
135
136 pub async fn spawn(&self) -> Result<tokio::process::Child> {
138 Ok(self.build_command()?.spawn()?)
139 }
140}