mirror of
https://github.com/block/goose.git
synced 2026-04-28 03:29:36 +00:00
feat(cli): add --check flag to goose info for provider testing (#8289)
Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
18127f1ff0
commit
5f3c63ae41
2 changed files with 185 additions and 3 deletions
|
|
@ -709,6 +709,8 @@ enum Command {
|
|||
/// Show verbose information including current configuration
|
||||
#[arg(short, long, help = "Show verbose information including config.yaml")]
|
||||
verbose: bool,
|
||||
#[arg(long, help = "Test provider connection and show status")]
|
||||
check: bool,
|
||||
},
|
||||
|
||||
#[command(about = "Check that your Goose setup is working")]
|
||||
|
|
@ -1765,7 +1767,7 @@ pub async fn cli() -> anyhow::Result<()> {
|
|||
}
|
||||
Some(Command::Configure {}) => handle_configure().await,
|
||||
Some(Command::Doctor {}) => crate::commands::doctor::handle_doctor().await,
|
||||
Some(Command::Info { verbose }) => handle_info(verbose),
|
||||
Some(Command::Info { verbose, check }) => handle_info(verbose, check).await,
|
||||
Some(Command::Mcp { server }) => handle_mcp_command(server).await,
|
||||
Some(Command::Acp { builtins }) => goose::acp::server::run(builtins).await,
|
||||
Some(Command::Serve {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
use anyhow::Result;
|
||||
use anyhow::{anyhow, Result};
|
||||
use console::style;
|
||||
use goose::config::paths::Paths;
|
||||
use goose::config::Config;
|
||||
use goose::conversation::message::Message;
|
||||
use goose::providers::errors::ProviderError;
|
||||
use goose::session::session_manager::{DB_NAME, SESSIONS_FOLDER};
|
||||
use serde_yaml;
|
||||
use std::time::Duration;
|
||||
|
||||
fn print_aligned(label: &str, value: &str, width: usize) {
|
||||
println!(" {:<width$} {}", label, value, width = width);
|
||||
|
|
@ -32,7 +35,74 @@ fn check_path_status(path: &Path) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn handle_info(verbose: bool) -> Result<()> {
|
||||
struct ProviderCheckSuccess {
|
||||
provider: String,
|
||||
model: String,
|
||||
elapsed: Duration,
|
||||
}
|
||||
|
||||
enum ProviderCheckError {
|
||||
NotConfigured {
|
||||
label: &'static str,
|
||||
error: String,
|
||||
},
|
||||
InvalidModel(String),
|
||||
ProviderCreate {
|
||||
error: String,
|
||||
show_api_key_hint: bool,
|
||||
},
|
||||
ProviderRequest(ProviderError),
|
||||
}
|
||||
|
||||
async fn check_provider(
|
||||
config: &Config,
|
||||
) -> std::result::Result<ProviderCheckSuccess, ProviderCheckError> {
|
||||
let (provider, model) = match (config.get_goose_provider(), config.get_goose_model()) {
|
||||
(Ok(provider), Ok(model)) => (provider, model),
|
||||
(Err(e), _) => {
|
||||
return Err(ProviderCheckError::NotConfigured {
|
||||
label: "Provider:",
|
||||
error: e.to_string(),
|
||||
});
|
||||
}
|
||||
(_, Err(e)) => {
|
||||
return Err(ProviderCheckError::NotConfigured {
|
||||
label: "Model:",
|
||||
error: e.to_string(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let model_config = goose::model::ModelConfig::new(&model)
|
||||
.map_err(|e| ProviderCheckError::InvalidModel(e.to_string()))?
|
||||
.with_canonical_limits(&provider);
|
||||
|
||||
let provider_client = goose::providers::create(&provider, model_config, Vec::new())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let error = e.to_string();
|
||||
ProviderCheckError::ProviderCreate {
|
||||
show_api_key_hint: error.contains("not found") || error.contains("API_KEY"),
|
||||
error,
|
||||
}
|
||||
})?;
|
||||
|
||||
let test_msg = Message::user().with_text("Say 'ok'");
|
||||
let model_config = provider_client.get_model_config();
|
||||
let start = std::time::Instant::now();
|
||||
provider_client
|
||||
.complete(&model_config, "check", "", &[test_msg], &[])
|
||||
.await
|
||||
.map_err(ProviderCheckError::ProviderRequest)?;
|
||||
|
||||
Ok(ProviderCheckSuccess {
|
||||
provider,
|
||||
model,
|
||||
elapsed: start.elapsed(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn handle_info(verbose: bool, check: bool) -> Result<()> {
|
||||
let logs_dir = Paths::in_state_dir("logs");
|
||||
let sessions_dir = Paths::in_data_dir(SESSIONS_FOLDER);
|
||||
let sessions_db = sessions_dir.join(DB_NAME);
|
||||
|
|
@ -90,5 +160,115 @@ pub fn handle_info(verbose: bool) -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
if check {
|
||||
println!("\n{}", style("Provider Check:").cyan().bold());
|
||||
|
||||
let result = check_provider(config).await;
|
||||
match &result {
|
||||
Ok(success) => {
|
||||
print_aligned("Provider:", &success.provider, label_padding);
|
||||
print_aligned("Model:", &success.model, label_padding);
|
||||
print_aligned("Auth:", &style("ok").green().to_string(), label_padding);
|
||||
print_aligned(
|
||||
"Connection:",
|
||||
&format!(
|
||||
"{} (verified in {:.1}s)",
|
||||
style("ok").green(),
|
||||
success.elapsed.as_secs_f64()
|
||||
),
|
||||
label_padding,
|
||||
);
|
||||
}
|
||||
Err(ProviderCheckError::NotConfigured { label, error }) => {
|
||||
print_aligned(
|
||||
label,
|
||||
&format!("{} {}", style("not configured:").red(), error),
|
||||
label_padding,
|
||||
);
|
||||
print_aligned(
|
||||
"Hint:",
|
||||
&format!("Run '{}'", style("goose configure").cyan()),
|
||||
label_padding,
|
||||
);
|
||||
}
|
||||
Err(ProviderCheckError::InvalidModel(error)) => {
|
||||
print_aligned(
|
||||
"Model:",
|
||||
&format!("{} {}", style("invalid:").red(), error),
|
||||
label_padding,
|
||||
);
|
||||
}
|
||||
Err(ProviderCheckError::ProviderCreate {
|
||||
error,
|
||||
show_api_key_hint,
|
||||
}) => {
|
||||
// Split auth failures (missing/invalid credential) from provider
|
||||
// construction failures (unknown provider, malformed provider
|
||||
// config). Labeling the latter as "Auth: FAILED" misdirects
|
||||
// troubleshooting toward rotating API keys.
|
||||
if *show_api_key_hint {
|
||||
print_aligned(
|
||||
"Auth:",
|
||||
&format!("{} {}", style("FAILED").red().bold(), error),
|
||||
label_padding,
|
||||
);
|
||||
print_aligned(
|
||||
"Hint:",
|
||||
&format!(
|
||||
"Set the API key in your environment or run '{}'",
|
||||
style("goose configure").cyan()
|
||||
),
|
||||
label_padding,
|
||||
);
|
||||
} else {
|
||||
print_aligned(
|
||||
"Provider:",
|
||||
&format!("{} {}", style("FAILED").red().bold(), error),
|
||||
label_padding,
|
||||
);
|
||||
print_aligned(
|
||||
"Hint:",
|
||||
&format!(
|
||||
"Check the provider name and config, or run '{}'",
|
||||
style("goose configure").cyan()
|
||||
),
|
||||
label_padding,
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(ProviderCheckError::ProviderRequest(error)) => match error {
|
||||
ProviderError::Authentication(_) => {
|
||||
print_aligned(
|
||||
"Auth:",
|
||||
&format!("{} {}", style("FAILED").red().bold(), error),
|
||||
label_padding,
|
||||
);
|
||||
print_aligned(
|
||||
"Hint:",
|
||||
&format!(
|
||||
"Check your API key or run '{}'",
|
||||
style("goose configure").cyan()
|
||||
),
|
||||
label_padding,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
print_aligned(
|
||||
"Check:",
|
||||
&format!("{} {}", style("FAILED").red().bold(), error),
|
||||
label_padding,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Propagate non-zero exit status so automation (CI scripts, install
|
||||
// checks, health probes) can rely on `goose info --check` as a
|
||||
// pre-flight verifier.
|
||||
if result.is_err() {
|
||||
return Err(anyhow!("provider check failed"));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue