1use crate::error::BobError;
10use crate::keychain::resolve_api_key;
11use cli_stream::{spawn_streaming, ProcessEvent, ProcessHandle};
12use serde::{Deserialize, Serialize};
13use std::path::PathBuf;
14
15#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
20#[serde(rename_all = "snake_case")]
21pub enum BobChatMode {
22 Plan,
23 Code,
24 Advanced,
25 Ask,
26}
27
28impl BobChatMode {
29 pub fn as_cli_value(self) -> &'static str {
30 match self {
31 Self::Plan => "plan",
32 Self::Code => "code",
33 Self::Advanced => "advanced",
34 Self::Ask => "ask",
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
44#[serde(rename_all = "snake_case")]
45pub enum BobApprovalMode {
46 Default,
47 AutoEdit,
48 Yolo,
49}
50
51impl BobApprovalMode {
52 pub fn as_cli_value(self) -> &'static str {
53 match self {
54 Self::Default => "default",
55 Self::AutoEdit => "auto_edit",
56 Self::Yolo => "yolo",
57 }
58 }
59}
60
61#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
64#[serde(rename_all = "camelCase")]
65pub struct RunBobOptions {
66 pub prompt: String,
67 #[serde(default = "default_chat_mode")]
68 pub chat_mode: BobChatMode,
69 #[serde(default = "default_approval_mode")]
70 pub approval_mode: BobApprovalMode,
71 #[serde(default = "default_max_coins")]
72 pub max_coins: u32,
73 pub cwd: Option<PathBuf>,
77 #[serde(default)]
81 pub bob_executable: Option<PathBuf>,
82}
83
84fn default_chat_mode() -> BobChatMode { BobChatMode::Ask }
85fn default_approval_mode() -> BobApprovalMode { BobApprovalMode::Default }
86fn default_max_coins() -> u32 { 30 }
87
88pub fn spawn_bob<F>(
97 opts: RunBobOptions,
98 run_id: String,
99 callback: F,
100) -> Result<ProcessHandle, BobError>
101where
102 F: FnMut(ProcessEvent) + Send + Sync + Clone + 'static,
103{
104 let args = build_args(&opts);
105 let api_key = resolve_api_key().map(|(value, _)| value).unwrap_or_default();
106 let program: PathBuf = opts.bob_executable.clone().unwrap_or_else(|| PathBuf::from("bob"));
107 let cwd = opts.cwd.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
108 spawn_bob_raw(program, args, api_key, cwd, run_id, callback)
109}
110
111pub fn spawn_bob_raw<F>(
118 program: PathBuf,
119 args: Vec<String>,
120 api_key: String,
121 cwd: PathBuf,
122 run_id: String,
123 callback: F,
124) -> Result<ProcessHandle, BobError>
125where
126 F: FnMut(ProcessEvent) + Send + Sync + Clone + 'static,
127{
128 let handle = spawn_streaming(
129 program,
130 args,
131 vec![("BOBSHELL_API_KEY".to_owned(), api_key)],
132 cwd,
133 run_id,
134 callback,
135 )?; Ok(handle)
137}
138
139fn build_args(opts: &RunBobOptions) -> Vec<String> {
142 vec![
143 opts.prompt.clone(),
144 "--chat-mode".to_owned(),
145 opts.chat_mode.as_cli_value().to_owned(),
146 "--output-format".to_owned(),
147 "stream-json".to_owned(),
148 "--approval-mode".to_owned(),
149 opts.approval_mode.as_cli_value().to_owned(),
150 "--accept-license".to_owned(),
151 "--max-coins".to_owned(),
152 opts.max_coins.to_string(),
153 ]
154}