Skip to content

Commit

Permalink
Merge branch 'refs/heads/v1.0' into mnovich/session-management
Browse files Browse the repository at this point in the history
* refs/heads/v1.0:
  small fix (#416)
  fix: configure allows selecting the suggested option with return (#414)
  only publish default bundle of goose.app (#381)
  better icns standard size for macos
  updating goosed for dev time
  fix: Clippy for memory.rs
  fix: quick format
  fix: Remove insert command
  Lifei/configure key chain (#389)
  feat: Add welcome screen (#410)

# Conflicts:
#	ui/desktop/src/ChatWindow.tsx
#	ui/desktop/src/main.ts
  • Loading branch information
Kvadratni committed Dec 5, 2024
2 parents df80ce9 + 57163fd commit f8968f2
Show file tree
Hide file tree
Showing 17 changed files with 304 additions and 387 deletions.
17 changes: 0 additions & 17 deletions .github/workflows/desktop-app-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,20 +92,3 @@ jobs:
with:
name: Goose.zip
path: ui/desktop/out/Goose-darwin-arm64/Goose.zip

- name: Make preconfigured Goose App
run: npm run bundle:preconfigured
working-directory: ui/desktop
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
GOOSE_BUNDLE_HOST: ${{ secrets.GOOSE_BUNDLE_HOST }}
GOOSE_BUNDLE_MODEL: ${{ secrets.GOOSE_BUNDLE_MODEL }}
GOOSE_BUNDLE_TYPE: ${{ secrets.GOOSE_BUNDLE_TYPE }}

- name: Upload preconfigured
uses: actions/upload-artifact@v3
with:
name: Goose-preconfigured.app
path: ui/desktop/out/Goose-darwin-arm64/Goose.zip
179 changes: 122 additions & 57 deletions crates/goose-cli/src/commands/configure.rs
Original file line number Diff line number Diff line change
@@ -1,88 +1,153 @@
use crate::commands::expected_config::{get_recommended_models, RecommendedModels};
use crate::inputs::get_user_input;
use crate::profile::{
find_existing_profile, profile_path, save_profile, select_provider_lists, set_provider_config,
Profile, PROVIDER_OPEN_AI,
find_existing_profile, get_provider_config, profile_path, save_profile, Profile,
};
use cliclack::spinner;
use console::style;
use goose::key_manager::{get_keyring_secret, save_to_keyring, KeyRetrievalStrategy};
use goose::models::message::Message;
use goose::providers::configs::ProviderConfig;
use goose::providers::factory;
use goose::providers::ollama::OLLAMA_MODEL;
use std::error::Error;

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 profile_name = if let Some(name) = provided_profile_name {
name
} else {
cliclack::input("Which profile should we configure?")
.default_input("default")
.interact()?
};

// Use default values from existing profile
let existing_profile_result = find_existing_profile(&profile_name);
let existing_profile = existing_profile_result.as_ref();

let provider_name = select_provider(existing_profile);
let recommended_models = get_recommended_models(provider_name);
let model = set_model(existing_profile, &recommended_models)?;
let provider_config = set_provider_config(provider_name, model.clone());
if existing_profile.is_some() {
let _ = cliclack::log::info(format!(
"We are updating the existing profile for {}",
profile_name
));
}

let default_provider = existing_profile.map_or("openai", |profile| profile.provider.as_str());
let provider_name = cliclack::select("Which model provider should we use?")
.initial_value(default_provider)
.items(&[
("openai", "OpenAI", "GPT-4o etc"),
("databricks", "Databricks", "Models on AI Gateway"),
("ollama", "Ollama", "Local open source models"),
])
.interact()?;

// Depending on the provider, we now want to look for any required keys and check or set them in the keychain
for key in get_required_keys(provider_name).iter() {
// If the key is in the keyring, ask if we want to overwrite
if get_keyring_secret(key, KeyRetrievalStrategy::KeyringOnly).is_ok() {
let _ = cliclack::log::info(format!("{} is already available in the keyring", key));
if cliclack::confirm("Would you like to overwrite this value?").interact()? {
let value = cliclack::password(format!("Enter the value for {}", key))
.mask('▪')
.interact()?;

save_to_keyring(key, &value)?;
}
}
// If the key is in the env, ask if we want to save to keyring
else if let Ok(value) = get_keyring_secret(key, KeyRetrievalStrategy::EnvironmentOnly) {
let _ = cliclack::log::info(format!("Detected {} in env, we can use this from your environment.\nIt will need to continue to be set in future goose usage.", key));
if cliclack::confirm("Would you like to save it to your kerying?").interact()? {
save_to_keyring(key, &value)?;
}
}
// We don't have a value, so we prompt for one
else {
let value = cliclack::password(format!(
"Provider {} requires {}, please enter a value. (Will be saved to your keyring)",
provider_name, key
))
.mask('▪')
.interact()?;

save_to_keyring(key, &value)?;
}
}

let recommended_model = get_recommended_model(provider_name);
let default_model_value =
existing_profile.map_or(recommended_model, |profile| profile.model.as_str());
let model: String = cliclack::input("Enter a model from that provider:")
.default_input(default_model_value)
.interact()?;

// Forward any existing systems from the profile if present
let additional_systems =
existing_profile.map_or(Vec::new(), |profile| profile.additional_systems.clone());

if !additional_systems.is_empty() {
let _ = cliclack::log::info(
format!("We kept the existing systems from your {} profile. You can edit this with `goose system`", profile_name)
);
}

let profile = Profile {
provider: provider_name.to_string(),
model: model.clone(),
additional_systems,
};
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_config).await?;
Ok(())
}

async fn check_configuration(provider_config: ProviderConfig) -> Result<(), Box<dyn Error>> {
// Confirm everything is configured correctly by calling a model!
let provider_config = get_provider_config(provider_name, model.clone());
let spin = spinner();
spin.start("Now let's check your configuration...");
spin.start("Checking your configuration...");
let provider = factory::get_provider(provider_config).unwrap();
let message = Message::user().with_text("Please give a nice welcome messsage (one sentence) and let them know they are all set to use this agent");
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
.content
.first()
.and_then(|c| c.as_text())
.unwrap_or("No response"),
);
let result = provider.complete("You are an AI agent called Goose. You use tools of connected systems to solve problems.", &[message], &[]).await;

match result {
Ok((message, _usage)) => {
if let Some(content) = message.content.first() {
if let Some(text) = content.as_text() {
spin.stop(text);
} else {
spin.stop("No response text available");
}
} else {
spin.stop("No response content available");
}

let _ = match save_profile(&profile_name, profile) {
Ok(()) => cliclack::outro(format!("Profile saved to: {:?}", profile_path()?)),
Err(e) => cliclack::outro(format!("Failed to save profile: {}", e)),
};
}
Err(_) => {
spin.stop("We could not connect!");
let _ = cliclack::outro("Try rerunning configure and check your credentials.");
}
}

Ok(())
}

fn get_existing_profile(profile_name: &str) -> Option<Profile> {
let existing_profile_result = find_existing_profile(profile_name);
if existing_profile_result.is_some() {
println!("Profile already exists. We are going to overwriting the existing profile...");
pub fn get_recommended_model(provider_name: &str) -> &str {
if provider_name == "openai" {
"gpt-4o"
} else if provider_name == "databricks" {
"claude-3-5-sonnet-2"
} else if provider_name == "ollama" {
OLLAMA_MODEL
} else {
println!("We are creating a new profile...");
panic!("Invalid provider name");
}
existing_profile_result
}

fn set_model(
existing_profile: Option<&Profile>,
recommended_models: &RecommendedModels,
) -> Result<String, Box<dyn Error>> {
let default_model_value =
existing_profile.map_or(recommended_models.model, |profile| profile.model.as_str());
let model = get_user_input("Enter model:", default_model_value)?;
Ok(model)
}

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()
pub fn get_required_keys(provider_name: &str) -> Vec<&'static str> {
match provider_name {
"openai" => vec!["OPENAI_API_KEY"],
"databricks" => vec!["DATABRICKS_HOST"],
"ollama" => vec!["OLLAMA_HOST"],
_ => panic!("Invalid provider name"),
}
}
22 changes: 0 additions & 22 deletions crates/goose-cli/src/commands/expected_config.rs

This file was deleted.

41 changes: 20 additions & 21 deletions crates/goose-cli/src/commands/session.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
use console::style;
use rand::{distributions::Alphanumeric, Rng};
use std::path::{Path, PathBuf};

use goose::agent::Agent;
use goose::providers::factory;
use rand::{distributions::Alphanumeric, Rng};
use std::path::{Path, PathBuf};
use std::process;

use crate::commands::expected_config::get_recommended_models;
use crate::profile::{
load_profiles, set_provider_config, Profile, PROFILE_DEFAULT_NAME, PROVIDER_OPEN_AI,
};
use crate::profile::{get_provider_config, load_profiles, Profile};
use crate::prompt::cliclack::CliclackPrompt;
use crate::prompt::rustyline::RustylinePrompt;
use crate::prompt::Prompt;
Expand Down Expand Up @@ -40,9 +37,8 @@ pub fn build_session<'a>(

let loaded_profile = load_profile(profile);

// TODO: Reconsider fn name as we are just using the fn to ask the user if env vars are not set
let provider_config =
set_provider_config(&loaded_profile.provider, loaded_profile.model.clone());
get_provider_config(&loaded_profile.provider, loaded_profile.model.clone());

// TODO: Odd to be prepping the provider rather than having that done in the agent?
let provider = factory::get_provider(provider_config).unwrap();
Expand Down Expand Up @@ -120,26 +116,29 @@ fn generate_new_session_path(session_dir: &Path) -> PathBuf {
}

fn load_profile(profile_name: Option<String>) -> Box<Profile> {
let configure_profile_message = "Please create a profile first via goose configure.";
let profiles = load_profiles().unwrap();
let loaded_profile = if profiles.is_empty() {
let recommended_models = get_recommended_models(PROVIDER_OPEN_AI);
Box::new(Profile {
provider: PROVIDER_OPEN_AI.to_string(),
model: recommended_models.model.to_string(),
additional_systems: Vec::new(),
})
println!("No profiles found. {}", configure_profile_message);
process::exit(1);
} else {
match profile_name {
Some(name) => match profiles.get(name.as_str()) {
Some(profile) => Box::new(profile.clone()),
None => panic!("Profile '{}' not found", name),
None => {
println!(
"Profile '{}' not found. {}",
name, configure_profile_message
);
process::exit(1);
}
},
None => match profiles.get(PROFILE_DEFAULT_NAME) {
None => match profiles.get("default") {
Some(profile) => Box::new(profile.clone()),
None => panic!(
"No '{}' profile found. Run configure to create a profile.",
PROFILE_DEFAULT_NAME
),
None => {
println!("No 'default' profile found. {}", configure_profile_message);
process::exit(1);
}
},
}
};
Expand Down
20 changes: 0 additions & 20 deletions crates/goose-cli/src/inputs.rs

This file was deleted.

Loading

0 comments on commit f8968f2

Please sign in to comment.