mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
@@ -0,0 +1,3 @@
|
||||
|
||||
.goosehints
|
||||
.goose
|
||||
@@ -26,4 +26,6 @@ tokio = { version = "1.0", features = ["full"] }
|
||||
futures = "0.3"
|
||||
serde = { version = "1.0", features = ["derive"] } # For serialization
|
||||
serde_yaml = "0.9"
|
||||
dirs = "4.0"
|
||||
dirs = "4.0"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
@@ -1,70 +1,82 @@
|
||||
use cliclack::input;
|
||||
use crate::commands::expected_config::{get_recommended_models, RecommendedModels};
|
||||
use crate::inputs::inputs::get_user_input;
|
||||
use crate::profile::profile::Profile;
|
||||
use crate::profile::profile_handler::{find_existing_profile, profile_path, save_profile};
|
||||
use crate::profile::provider_helper::{get_provider_type, select_provider_lists, set_provider_config, PROVIDER_OPEN_AI};
|
||||
use console::style;
|
||||
use ctrlc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use goose::providers::factory;
|
||||
use goose::providers::types::message::Message;
|
||||
use std::error::Error;
|
||||
use std::fs::{create_dir_all, File};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use cliclack::spinner;
|
||||
use goose::providers::configs::ProviderConfig;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ConfigOptions {
|
||||
pub provider: Option<String>,
|
||||
pub host: Option<String>,
|
||||
pub token: Option<String>,
|
||||
pub processor: Option<String>,
|
||||
pub accelerator: Option<String>,
|
||||
}
|
||||
pub async fn handle_configure(provided_profile_name: Option<String>) -> Result<(), Box<dyn Error>> {
|
||||
cliclack::intro(style(" configure-goose ").on_cyan().black())?;
|
||||
println!("We are helping you configure your Goose CLI profile.");
|
||||
let profile_name = provided_profile_name.unwrap_or_else(|| {
|
||||
get_user_input("Enter profile name:", "default").unwrap()
|
||||
});
|
||||
let existing_profile_result = get_existing_profile(&profile_name);
|
||||
let existing_profile = existing_profile_result.as_ref();
|
||||
|
||||
pub fn handle_configure(options: ConfigOptions) -> Result<(), Box<dyn Error>> {
|
||||
ctrlc::set_handler(move || {}).expect("setting Ctrl-C handler");
|
||||
|
||||
cliclack::clear_screen()?;
|
||||
|
||||
cliclack::intro(style(" create-app ").on_cyan().black())?;
|
||||
let provider = prompt(options.provider, "Enter provider name:");
|
||||
let host = prompt(options.host, "Enter host URL:");
|
||||
let token = prompt(options.token, "Enter token:");
|
||||
let processor = prompt(options.processor, "Enter processor:");
|
||||
let accelerator = prompt(options.accelerator, "Enter accelerator:");
|
||||
|
||||
let final_config = ConfigOptions {
|
||||
provider: Some(provider),
|
||||
host: Some(host),
|
||||
token: Some(token),
|
||||
processor: Some(processor),
|
||||
accelerator: Some(accelerator),
|
||||
let provider_name = select_provider(existing_profile);
|
||||
let recommended_models = get_recommended_models(&provider_name);
|
||||
let processor = set_processor(existing_profile, &recommended_models)?;
|
||||
let accelerator = set_accelerator(existing_profile, &recommended_models)?;
|
||||
let provider_config = set_provider_config(&provider_name, processor.clone());
|
||||
let profile = Profile {
|
||||
provider: provider_name.to_string(),
|
||||
processor: processor.clone(),
|
||||
accelerator,
|
||||
};
|
||||
match save_to_yaml(&final_config) {
|
||||
Ok(path) => println!("\nConfiguration saved to: {:?}", path),
|
||||
Err(e) => eprintln!("Failed to save configuration: {}", e),
|
||||
match save_profile(profile_name.as_str(), profile) {
|
||||
Ok(()) => println!("\nProfile saved to: {:?}", profile_path()?),
|
||||
Err(e) => println!("Failed to save profile: {}", e),
|
||||
}
|
||||
check_configuration(provider_name, provider_config).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Helper function to prompt the user
|
||||
fn prompt(value: Option<String>, message: &str) -> String {
|
||||
value.unwrap_or_else(|| input(message).interact().expect("Failed to get input"))
|
||||
async fn check_configuration(provider_name: &str, provider_config: ProviderConfig) -> Result<(), Box<dyn Error>> {
|
||||
let spin = spinner();
|
||||
spin.start("Now let's check your configuration...");
|
||||
let provider = factory::get_provider(get_provider_type(provider_name), provider_config).unwrap();
|
||||
let message = Message::user("Please give a nice welcome messsage (one sentence) and let them know they are all set to use this agent ").unwrap();
|
||||
let result = provider.complete(
|
||||
"You are an AI agent called Goose. You use tools of connected systems to solve problems.",
|
||||
&[message], &[]).await?;
|
||||
spin.stop(result.0.text());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn save_to_yaml(config: &ConfigOptions) -> Result<PathBuf, Box<dyn Error>> {
|
||||
// Locate the config directory
|
||||
let mut path = dirs::home_dir().ok_or("Failed to find home directory")?;
|
||||
path.push(".config");
|
||||
path.push("goose");
|
||||
|
||||
// TODO: set to profile1.yaml temporarily to avoid overriting the existing config
|
||||
path.push("profile1.yaml");
|
||||
|
||||
// Ensure the ~/.config/goose directory exists
|
||||
if let Some(parent) = path.parent() {
|
||||
create_dir_all(parent)?; // Create the directory if it doesn't exist
|
||||
fn get_existing_profile(profile_name: &String) -> Option<Profile> {
|
||||
let existing_profile_result = find_existing_profile(profile_name.as_str());
|
||||
if existing_profile_result.is_some() {
|
||||
println!("Profile already exists. We are going to overwriting the existing profile...");
|
||||
} else {
|
||||
println!("We are creating a new profile...");
|
||||
}
|
||||
|
||||
// Serialize the configuration to YAML and save it to the file
|
||||
let yaml_string = serde_yaml::to_string(config)?;
|
||||
let mut file = File::create(&path)?;
|
||||
file.write_all(yaml_string.as_bytes())?;
|
||||
|
||||
Ok(path)
|
||||
existing_profile_result
|
||||
}
|
||||
|
||||
fn set_processor(existing_profile: Option<&Profile>, recommended_models: &RecommendedModels) -> Result<String, Box<dyn Error>> {
|
||||
let default_processor_value = existing_profile
|
||||
.map_or(recommended_models.processor, |profile| profile.processor.as_str());
|
||||
let processor = get_user_input("Enter processor:", default_processor_value)?;
|
||||
Ok(processor)
|
||||
}
|
||||
|
||||
fn set_accelerator(existing_profile: Option<&Profile>, recommended_models: &RecommendedModels) -> Result<String, Box<dyn Error>> {
|
||||
let default_accelerator_value = existing_profile
|
||||
.map_or(recommended_models.accelerator, |profile| profile.accelerator.as_str());
|
||||
let processor = get_user_input("Enter accelerator:", default_accelerator_value)?;
|
||||
Ok(processor)
|
||||
}
|
||||
|
||||
fn select_provider(existing_profile: Option<&Profile>) -> &str {
|
||||
let default_value = existing_profile
|
||||
.map_or(PROVIDER_OPEN_AI, |profile| profile.provider.as_str());
|
||||
cliclack::select("Select provider:")
|
||||
.initial_value(default_value).items(&select_provider_lists()).interact().unwrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// This is a temporary file to simulate some configuration data from the backend
|
||||
|
||||
use crate::profile::provider_helper::PROVIDER_OPEN_AI;
|
||||
|
||||
pub struct RecommendedModels {
|
||||
pub processor: &'static str,
|
||||
pub accelerator: &'static str,
|
||||
}
|
||||
pub fn get_recommended_models(provider_name: &str) -> RecommendedModels {
|
||||
if provider_name == PROVIDER_OPEN_AI {
|
||||
RecommendedModels {
|
||||
processor: "gpt-4o",
|
||||
accelerator: "gpt-4o-mini",
|
||||
}
|
||||
} else {
|
||||
RecommendedModels {
|
||||
processor: "claude-3-5-sonnet-2",
|
||||
accelerator: "claude-3-5-sonnet-2",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod configure;
|
||||
pub mod version;
|
||||
pub mod expected_config;
|
||||
@@ -1,3 +1,3 @@
|
||||
pub fn print_version() {
|
||||
println!(env!("CARGO_PKG_VERSION"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use cliclack::{input, password};
|
||||
|
||||
pub fn get_env_value_or_input(env_name: &str, input_prompt: &str, mask: bool) -> String {
|
||||
if let Ok(value) = std::env::var(env_name) {
|
||||
return value;
|
||||
}
|
||||
|
||||
let input_value = if mask {
|
||||
password(input_prompt).mask('▪').interact().unwrap()
|
||||
} else {
|
||||
input(input_prompt).interact().unwrap()
|
||||
};
|
||||
|
||||
std::env::set_var(env_name, &input_value);
|
||||
input_value
|
||||
}
|
||||
|
||||
pub fn get_user_input(message: &str, default_value: &str) -> std::io::Result<String> {
|
||||
input(message)
|
||||
.default_input(default_value)
|
||||
.interact()
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod inputs;
|
||||
@@ -1,4 +1,6 @@
|
||||
mod commands;
|
||||
mod profile;
|
||||
mod inputs;
|
||||
|
||||
use anyhow::Result;
|
||||
use bat::PrettyPrinter;
|
||||
@@ -6,18 +8,17 @@ use clap::{Parser, Subcommand};
|
||||
use cliclack::{input, spinner};
|
||||
use console::style;
|
||||
use futures::StreamExt;
|
||||
use goose::providers::factory::ProviderType;
|
||||
|
||||
use commands::configure::handle_configure;
|
||||
use commands::version::print_version;
|
||||
use goose::agent::Agent;
|
||||
use goose::developer::DeveloperSystem;
|
||||
use goose::providers::configs::OpenAiProviderConfig;
|
||||
use goose::providers::configs::{DatabricksProviderConfig, ProviderConfig};
|
||||
use goose::providers::factory;
|
||||
use goose::providers::factory::ProviderType;
|
||||
use goose::providers::types::message::Message;
|
||||
|
||||
use commands::configure::{handle_configure, ConfigOptions};
|
||||
use commands::version::print_version;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, about, long_about = None)]
|
||||
struct Cli {
|
||||
@@ -59,27 +60,8 @@ struct Cli {
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Command {
|
||||
/// Configure the provider and default systems
|
||||
Configure {
|
||||
/// Optional provider name; prompted if not provided
|
||||
#[arg(long)]
|
||||
provider: Option<String>,
|
||||
|
||||
/// Optional host URL; prompted if not provided
|
||||
#[arg(long)]
|
||||
host: Option<String>,
|
||||
|
||||
/// Optional token; prompted if not provided
|
||||
#[arg(long)]
|
||||
token: Option<String>,
|
||||
|
||||
/// Optional processor; prompted if not provided
|
||||
#[arg(long)]
|
||||
processor: Option<String>,
|
||||
|
||||
/// Optional accelerator; prompted if not provided
|
||||
#[arg(long)]
|
||||
accelerator: Option<String>,
|
||||
profile_name: Option<String>,
|
||||
},
|
||||
/// Start or resume sessions with an optional session name
|
||||
Session {
|
||||
@@ -106,26 +88,14 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
match cli.command {
|
||||
Some(Command::Configure {
|
||||
provider,
|
||||
host,
|
||||
token,
|
||||
processor,
|
||||
accelerator,
|
||||
}) => {
|
||||
let options = ConfigOptions {
|
||||
provider,
|
||||
host,
|
||||
token,
|
||||
processor,
|
||||
accelerator,
|
||||
};
|
||||
let _ = handle_configure(options);
|
||||
Some(Command::Configure {profile_name}) => {
|
||||
let _ = handle_configure(profile_name).await;
|
||||
return Ok(());
|
||||
}
|
||||
Some(Command::Session { session_name }) => {
|
||||
let session_name = session_name
|
||||
.unwrap_or_else(|| input("Session name:").placeholder("").interact().unwrap());
|
||||
let session_name = session_name.unwrap_or_else(|| {
|
||||
input("Session name:").placeholder("").interact().unwrap()
|
||||
});
|
||||
println!("Session name: {}", session_name);
|
||||
return Ok(());
|
||||
}
|
||||
@@ -220,4 +190,4 @@ fn create_provider_config(cli: &Cli) -> ProviderConfig {
|
||||
max_tokens: cli.max_tokens,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod profile;
|
||||
pub mod profile_handler;
|
||||
pub mod provider_helper;
|
||||
@@ -0,0 +1,14 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Profile {
|
||||
pub provider: String,
|
||||
pub processor: String,
|
||||
pub accelerator: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Profiles {
|
||||
pub profile_items: std::collections::HashMap<String, Profile>
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::fs::{create_dir_all, File};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use crate::profile::profile::Profile;
|
||||
|
||||
// TODO: set to profile-1.0.yaml temporarily to avoid overriting the existing config
|
||||
pub const PROFILE_CONFIG_PATH: &str = ".config/goose/profile-1.0.yaml";
|
||||
|
||||
fn save_profiles_to_file(profiles: &HashMap<String, Profile>) -> Result<(), Box<dyn Error>> {
|
||||
let path = profile_path()?;
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
let yaml_string = serde_yaml::to_string(profiles)?;
|
||||
let mut file = File::create(&path)?;
|
||||
file.write_all(yaml_string.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn profile_path() -> Result<PathBuf, Box<dyn Error>> {
|
||||
let mut path = dirs::home_dir().ok_or("Failed to find home directory")?;
|
||||
path.push(PROFILE_CONFIG_PATH);
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn save_profile(profile_name: &str, new_profile: Profile) -> Result<(), Box<dyn Error>> {
|
||||
let mut profiles = load_profiles().unwrap();
|
||||
profiles.insert(profile_name.to_string(), new_profile);
|
||||
let _ = save_profiles_to_file(&profiles);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn profile_file_exists() -> bool {
|
||||
profile_path().unwrap().exists()
|
||||
}
|
||||
pub fn load_profiles() -> Result<HashMap<String, Profile>, Box<dyn Error>> {
|
||||
let path = profile_path()?;
|
||||
if !path.exists() {
|
||||
return Ok(HashMap::new());
|
||||
}
|
||||
let file = File::open(&path)?;
|
||||
let profiles: HashMap<String, Profile> = serde_yaml::from_reader(file)?;
|
||||
Ok(profiles)
|
||||
}
|
||||
|
||||
pub fn find_existing_profile(profile_name: &str) -> Option<Profile> {
|
||||
if profile_file_exists() {
|
||||
let profiles = load_profiles().unwrap();
|
||||
profiles.get(profile_name).cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use goose::providers::factory::ProviderType;
|
||||
use strum::IntoEnumIterator;
|
||||
use goose::providers::configs::{DatabricksProviderConfig, OpenAiProviderConfig, ProviderConfig};
|
||||
use crate::inputs::inputs::get_env_value_or_input;
|
||||
|
||||
pub const PROVIDER_OPEN_AI: &str = "openai";
|
||||
pub const PROVIDER_DATABRICKS: &str = "databricks";
|
||||
|
||||
pub fn select_provider_lists() -> Vec<(&'static str, String, &'static str)> {
|
||||
ProviderType::iter()
|
||||
.map(|provider| {
|
||||
match provider {
|
||||
ProviderType::OpenAi => (PROVIDER_OPEN_AI, PROVIDER_OPEN_AI.to_string(), "Recommended"),
|
||||
ProviderType::Databricks => (PROVIDER_DATABRICKS, PROVIDER_DATABRICKS.to_string(), ""),
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
pub fn set_provider_config(provider_name: &str, processor: String) -> ProviderConfig {
|
||||
match provider_name.to_lowercase().as_str() {
|
||||
PROVIDER_OPEN_AI => ProviderConfig::OpenAi(OpenAiProviderConfig {
|
||||
host: "https://api.openai.com".to_string(),
|
||||
api_key: get_env_value_or_input("OPENAI_API_KEY", "Please enter your OpenAI API key:", true),
|
||||
model: processor,
|
||||
temperature: None,
|
||||
max_tokens: None,
|
||||
}),
|
||||
PROVIDER_DATABRICKS => ProviderConfig::Databricks(DatabricksProviderConfig {
|
||||
host: get_env_value_or_input("DATABRICKS_HOST", "Please enter your Databricks host:", false),
|
||||
token: get_env_value_or_input("DATABRICKS_TOKEN", "Please enter your Databricks token:", true),
|
||||
model: processor,
|
||||
temperature: None,
|
||||
max_tokens: None,
|
||||
}),
|
||||
_ => panic!("Invalid provider name"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_provider_type(provider_name: &str) -> ProviderType {
|
||||
match provider_name.to_lowercase().as_str() {
|
||||
PROVIDER_OPEN_AI => ProviderType::OpenAi,
|
||||
PROVIDER_DATABRICKS => ProviderType::Databricks,
|
||||
_ => panic!("Invalid provider name"),
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ uuid = { version = "1.0", features = ["v4"] }
|
||||
regex = "1.11.1"
|
||||
async-trait = "0.1"
|
||||
async-stream = "0.3"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
tera = "1.20.0"
|
||||
tokenizers = "0.20.3"
|
||||
include_dir = "0.7.4"
|
||||
|
||||
@@ -2,7 +2,9 @@ use super::{
|
||||
base::Provider, configs::ProviderConfig, databricks::DatabricksProvider, openai::OpenAiProvider,
|
||||
};
|
||||
use anyhow::Error;
|
||||
use strum_macros::{EnumIter};
|
||||
|
||||
#[derive(EnumIter, Debug)]
|
||||
pub enum ProviderType {
|
||||
OpenAi,
|
||||
Databricks,
|
||||
|
||||
Reference in New Issue
Block a user