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 session_id: Option<String>,
36 reasoning_effort: Option<String>,
37 base_url: Option<String>,
38 working_directory: Option<PathBuf>,
39 envs: Vec<(String, String)>,
40}
41
42impl MuseExecBuilder {
43 pub fn new(prompt: impl Into<String>) -> Self {
44 Self {
45 binary: "muse".to_string(),
46 prompt: prompt.into(),
47 provider: None,
48 preset: None,
49 model: None,
50 session_id: None,
51 reasoning_effort: None,
52 base_url: None,
53 working_directory: None,
54 envs: Vec::new(),
55 }
56 }
57
58 pub fn binary(mut self, path: impl Into<String>) -> Self {
60 self.binary = path.into();
61 self
62 }
63
64 pub fn provider(mut self, provider: Provider) -> Self {
65 self.provider = Some(provider);
66 self
67 }
68
69 pub fn preset(mut self, preset: impl Into<String>) -> Self {
71 self.preset = Some(preset.into());
72 self
73 }
74
75 pub fn model(mut self, model: impl Into<String>) -> Self {
76 self.model = Some(model.into());
77 self
78 }
79
80 pub fn session_id(mut self, session_id: impl Into<String>) -> Self {
93 self.session_id = Some(session_id.into());
94 self
95 }
96
97 pub fn reasoning_effort(mut self, effort: impl Into<String>) -> Self {
100 self.reasoning_effort = Some(effort.into());
101 self
102 }
103
104 pub fn base_url(mut self, url: impl Into<String>) -> Self {
105 self.base_url = Some(url.into());
106 self
107 }
108
109 pub fn working_directory(mut self, dir: impl Into<PathBuf>) -> Self {
110 self.working_directory = Some(dir.into());
111 self
112 }
113
114 pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
115 self.envs.push((key.into(), value.into()));
116 self
117 }
118
119 pub fn build_command(&self) -> Result<tokio::process::Command> {
121 let program = which::which(&self.binary).map_err(|_| Error::BinaryNotFound {
122 name: self.binary.clone(),
123 })?;
124 let mut cmd = tokio::process::Command::new(program);
125 cmd.arg("exec").arg("--json");
126 if let Some(p) = self.provider {
127 cmd.args(["--provider", p.as_str()]);
128 }
129 if let Some(p) = &self.preset {
130 cmd.args(["--preset", p]);
131 }
132 if let Some(m) = &self.model {
133 cmd.args(["--model", m]);
134 }
135 if let Some(s) = &self.session_id {
136 cmd.args(["--session-id", s]);
137 }
138 if let Some(e) = &self.reasoning_effort {
139 cmd.args(["--reasoning-effort", e]);
140 }
141 if let Some(u) = &self.base_url {
142 cmd.args(["--base-url", u]);
143 }
144 cmd.arg(&self.prompt)
145 .stdin(Stdio::null())
146 .stdout(Stdio::piped())
147 .stderr(Stdio::piped())
148 .kill_on_drop(true);
149 if let Some(dir) = &self.working_directory {
150 cmd.current_dir(dir);
151 }
152 for (k, v) in &self.envs {
153 cmd.env(k, v);
154 }
155 Ok(cmd)
156 }
157
158 pub async fn spawn(&self) -> Result<tokio::process::Child> {
160 Ok(self.build_command()?.spawn()?)
161 }
162}