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