Skip to main content

call_coding_clis/invoke/
request.rs

1#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
2pub enum RunnerKind {
3    OpenCode,
4    Claude,
5    Codex,
6    Kimi,
7    Cursor,
8    Gemini,
9    RooCode,
10    Crush,
11}
12
13impl RunnerKind {
14    pub(crate) fn from_cli_token(token: &str) -> Option<Self> {
15        match token {
16            "oc" | "opencode" => Some(RunnerKind::OpenCode),
17            "cc" | "claude" => Some(RunnerKind::Claude),
18            "c" | "cx" | "codex" => Some(RunnerKind::Codex),
19            "k" | "kimi" => Some(RunnerKind::Kimi),
20            "cu" | "cursor" => Some(RunnerKind::Cursor),
21            "g" | "gemini" => Some(RunnerKind::Gemini),
22            "rc" | "roocode" => Some(RunnerKind::RooCode),
23            "cr" | "crush" => Some(RunnerKind::Crush),
24            _ => None,
25        }
26    }
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum OutputMode {
31    Text,
32    StreamText,
33    Json,
34    StreamJson,
35    Formatted,
36    StreamFormatted,
37}
38
39#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct Request {
41    prompt: String,
42    prompt_supplied: bool,
43    runner: Option<RunnerKind>,
44    agent: Option<String>,
45    thinking: Option<i32>,
46    show_thinking: Option<bool>,
47    sanitize_osc: Option<bool>,
48    permission_mode: Option<String>,
49    save_session: bool,
50    cleanup_session: bool,
51    provider: Option<String>,
52    model: Option<String>,
53    output_mode: Option<OutputMode>,
54}
55
56impl Request {
57    pub fn new(prompt: impl Into<String>) -> Self {
58        Self {
59            prompt: prompt.into(),
60            prompt_supplied: true,
61            runner: None,
62            agent: None,
63            thinking: None,
64            show_thinking: None,
65            sanitize_osc: None,
66            permission_mode: None,
67            save_session: false,
68            cleanup_session: false,
69            provider: None,
70            model: None,
71            output_mode: None,
72        }
73    }
74
75    pub fn with_runner(mut self, runner: RunnerKind) -> Self {
76        self.runner = Some(runner);
77        self
78    }
79
80    pub fn with_provider(mut self, provider: impl Into<String>) -> Self {
81        self.provider = Some(provider.into());
82        self
83    }
84
85    pub fn with_agent(mut self, agent: impl Into<String>) -> Self {
86        self.agent = Some(agent.into());
87        self
88    }
89
90    pub fn with_thinking(mut self, thinking: i32) -> Self {
91        self.thinking = Some(thinking);
92        self
93    }
94
95    pub fn with_show_thinking(mut self, enabled: bool) -> Self {
96        self.show_thinking = Some(enabled);
97        self
98    }
99
100    pub fn with_sanitize_osc(mut self, enabled: bool) -> Self {
101        self.sanitize_osc = Some(enabled);
102        self
103    }
104
105    pub fn with_permission_mode(mut self, mode: impl Into<String>) -> Self {
106        self.permission_mode = Some(mode.into());
107        self
108    }
109
110    pub fn with_save_session(mut self, enabled: bool) -> Self {
111        self.save_session = enabled;
112        self
113    }
114
115    pub fn with_cleanup_session(mut self, enabled: bool) -> Self {
116        self.cleanup_session = enabled;
117        self
118    }
119
120    pub fn with_model(mut self, model: impl Into<String>) -> Self {
121        self.model = Some(model.into());
122        self
123    }
124
125    pub fn with_output_mode(mut self, output_mode: OutputMode) -> Self {
126        self.output_mode = Some(output_mode);
127        self
128    }
129
130    pub fn prompt(&self) -> &str {
131        &self.prompt
132    }
133
134    pub fn runner(&self) -> Option<RunnerKind> {
135        self.runner
136    }
137
138    pub fn provider(&self) -> Option<&str> {
139        self.provider.as_deref()
140    }
141
142    pub fn model(&self) -> Option<&str> {
143        self.model.as_deref()
144    }
145
146    pub fn output_mode(&self) -> Option<OutputMode> {
147        self.output_mode
148    }
149
150    pub(crate) fn prompt_text(&self) -> &str {
151        &self.prompt
152    }
153
154    pub(crate) fn runner_kind(&self) -> Option<RunnerKind> {
155        self.runner
156    }
157
158    pub(crate) fn provider_text(&self) -> Option<&str> {
159        self.provider.as_deref()
160    }
161
162    pub(crate) fn model_text(&self) -> Option<&str> {
163        self.model.as_deref()
164    }
165
166    pub(crate) fn output_mode_kind(&self) -> Option<OutputMode> {
167        self.output_mode
168    }
169
170    pub(crate) fn from_parsed_args(parsed: &crate::parser::ParsedArgs) -> Result<Self, String> {
171        let runner = parsed
172            .runner
173            .as_deref()
174            .and_then(RunnerKind::from_cli_token);
175        if parsed.runner.is_some() && runner.is_none() {
176            return Err("unknown runner selector".to_string());
177        }
178
179        let output_mode = match parsed.output_mode.as_deref() {
180            Some("") => {
181                return Err(
182                    "output mode requires one of: text, stream-text, json, stream-json, formatted, stream-formatted"
183                        .to_string(),
184                )
185            }
186            Some(value) => Some(OutputMode::from_cli_value(value).ok_or_else(|| {
187                "output mode must be one of: text, stream-text, json, stream-json, formatted, stream-formatted"
188                    .to_string()
189            })?),
190            None => None,
191        };
192
193        Ok(Self {
194            prompt: parsed.prompt.clone(),
195            prompt_supplied: parsed.prompt_supplied,
196            runner,
197            agent: parsed.alias.clone(),
198            thinking: parsed.thinking,
199            show_thinking: parsed.show_thinking,
200            sanitize_osc: parsed.sanitize_osc,
201            permission_mode: parsed.permission_mode.clone(),
202            save_session: parsed.save_session,
203            cleanup_session: parsed.cleanup_session,
204            provider: parsed.provider.clone(),
205            model: parsed.model.clone(),
206            output_mode,
207        })
208    }
209
210    pub(crate) fn to_cli_tokens(&self) -> Vec<String> {
211        let mut tokens = Vec::new();
212        if let Some(runner) = self.runner_kind() {
213            tokens.push(runner.as_cli_token().to_string());
214        }
215        if let Some(thinking) = self.thinking {
216            tokens.push(format!("+{thinking}"));
217        }
218        if let Some(show_thinking) = self.show_thinking {
219            tokens.push(if show_thinking {
220                "--show-thinking".to_string()
221            } else {
222                "--no-show-thinking".to_string()
223            });
224        }
225        if let Some(sanitize_osc) = self.sanitize_osc {
226            tokens.push(if sanitize_osc {
227                "--sanitize-osc".to_string()
228            } else {
229                "--no-sanitize-osc".to_string()
230            });
231        }
232        if let Some(permission_mode) = self.permission_mode.as_deref() {
233            tokens.push("--permission-mode".to_string());
234            tokens.push(permission_mode.to_string());
235        }
236        if self.save_session {
237            tokens.push("--save-session".to_string());
238        }
239        if self.cleanup_session {
240            tokens.push("--cleanup-session".to_string());
241        }
242        if let Some(agent) = self.agent.as_deref() {
243            tokens.push(format!("@{agent}"));
244        }
245        if let Some(provider) = self.provider_text() {
246            if let Some(model) = self.model_text() {
247                tokens.push(format!(":{provider}:{model}"));
248            }
249        } else if let Some(model) = self.model_text() {
250            tokens.push(format!(":{model}"));
251        }
252        if let Some(output_mode) = self.output_mode_kind() {
253            tokens.push("--output-mode".to_string());
254            tokens.push(output_mode.as_cli_value().to_string());
255        }
256        if self.prompt_supplied {
257            tokens.push(self.prompt_text().to_string());
258        }
259        tokens
260    }
261}
262
263impl RunnerKind {
264    pub(crate) fn as_cli_token(self) -> &'static str {
265        match self {
266            RunnerKind::OpenCode => "oc",
267            RunnerKind::Claude => "cc",
268            RunnerKind::Codex => "c",
269            RunnerKind::Kimi => "k",
270            RunnerKind::Cursor => "cu",
271            RunnerKind::Gemini => "g",
272            RunnerKind::RooCode => "rc",
273            RunnerKind::Crush => "cr",
274        }
275    }
276}
277
278impl OutputMode {
279    pub(crate) fn as_cli_value(self) -> &'static str {
280        match self {
281            OutputMode::Text => "text",
282            OutputMode::StreamText => "stream-text",
283            OutputMode::Json => "json",
284            OutputMode::StreamJson => "stream-json",
285            OutputMode::Formatted => "formatted",
286            OutputMode::StreamFormatted => "stream-formatted",
287        }
288    }
289
290    pub(crate) fn from_cli_value(value: &str) -> Option<Self> {
291        match value {
292            "text" => Some(OutputMode::Text),
293            "stream-text" => Some(OutputMode::StreamText),
294            "json" => Some(OutputMode::Json),
295            "stream-json" => Some(OutputMode::StreamJson),
296            "formatted" => Some(OutputMode::Formatted),
297            "stream-formatted" => Some(OutputMode::StreamFormatted),
298            _ => None,
299        }
300    }
301}