use crate::error::BobError;
use crate::keychain::resolve_api_key;
use cli_stream::{spawn_streaming, ProcessEvent, ProcessHandle};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BobChatMode {
Plan,
Code,
Advanced,
Ask,
}
impl BobChatMode {
pub fn as_cli_value(self) -> &'static str {
match self {
Self::Plan => "plan",
Self::Code => "code",
Self::Advanced => "advanced",
Self::Ask => "ask",
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BobApprovalMode {
Default,
AutoEdit,
Yolo,
}
impl BobApprovalMode {
pub fn as_cli_value(self) -> &'static str {
match self {
Self::Default => "default",
Self::AutoEdit => "auto_edit",
Self::Yolo => "yolo",
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RunBobOptions {
pub prompt: String,
#[serde(default = "default_chat_mode")]
pub chat_mode: BobChatMode,
#[serde(default = "default_approval_mode")]
pub approval_mode: BobApprovalMode,
#[serde(default = "default_max_coins")]
pub max_coins: u32,
pub cwd: Option<PathBuf>,
#[serde(default)]
pub bob_executable: Option<PathBuf>,
}
fn default_chat_mode() -> BobChatMode { BobChatMode::Ask }
fn default_approval_mode() -> BobApprovalMode { BobApprovalMode::Default }
fn default_max_coins() -> u32 { 30 }
pub fn spawn_bob<F>(
opts: RunBobOptions,
run_id: String,
callback: F,
) -> Result<ProcessHandle, BobError>
where
F: FnMut(ProcessEvent) + Send + Sync + Clone + 'static,
{
let args = build_args(&opts);
let api_key = resolve_api_key().map(|(value, _)| value).unwrap_or_default();
let program: PathBuf = opts.bob_executable.clone().unwrap_or_else(|| PathBuf::from("bob"));
let cwd = opts.cwd.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
spawn_bob_raw(program, args, api_key, cwd, run_id, callback)
}
pub fn spawn_bob_raw<F>(
program: PathBuf,
args: Vec<String>,
api_key: String,
cwd: PathBuf,
run_id: String,
callback: F,
) -> Result<ProcessHandle, BobError>
where
F: FnMut(ProcessEvent) + Send + Sync + Clone + 'static,
{
let handle = spawn_streaming(
program,
args,
vec![("BOBSHELL_API_KEY".to_owned(), api_key)],
cwd,
run_id,
callback,
)?; Ok(handle)
}
fn build_args(opts: &RunBobOptions) -> Vec<String> {
vec![
opts.prompt.clone(),
"--chat-mode".to_owned(),
opts.chat_mode.as_cli_value().to_owned(),
"--output-format".to_owned(),
"stream-json".to_owned(),
"--approval-mode".to_owned(),
opts.approval_mode.as_cli_value().to_owned(),
"--accept-license".to_owned(),
"--max-coins".to_owned(),
opts.max_coins.to_string(),
]
}