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:
@@ -709,6 +709,8 @@ enum Command {
|
|||||||
/// Show verbose information including current configuration
|
/// Show verbose information including current configuration
|
||||||
#[arg(short, long, help = "Show verbose information including config.yaml")]
|
#[arg(short, long, help = "Show verbose information including config.yaml")]
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
|
#[arg(long, help = "Test provider connection and show status")]
|
||||||
|
check: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[command(about = "Check that your Goose setup is working")]
|
#[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::Configure {}) => handle_configure().await,
|
||||||
Some(Command::Doctor {}) => crate::commands::doctor::handle_doctor().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::Mcp { server }) => handle_mcp_command(server).await,
|
||||||
Some(Command::Acp { builtins }) => goose::acp::server::run(builtins).await,
|
Some(Command::Acp { builtins }) => goose::acp::server::run(builtins).await,
|
||||||
Some(Command::Serve {
|
Some(Command::Serve {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
use console::style;
|
use console::style;
|
||||||
use goose::config::paths::Paths;
|
use goose::config::paths::Paths;
|
||||||
use goose::config::Config;
|
use goose::config::Config;
|
||||||
|
use goose::conversation::message::Message;
|
||||||
|
use goose::providers::errors::ProviderError;
|
||||||
use goose::session::session_manager::{DB_NAME, SESSIONS_FOLDER};
|
use goose::session::session_manager::{DB_NAME, SESSIONS_FOLDER};
|
||||||
use serde_yaml;
|
use serde_yaml;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
fn print_aligned(label: &str, value: &str, width: usize) {
|
fn print_aligned(label: &str, value: &str, width: usize) {
|
||||||
println!(" {:<width$} {}", label, value, width = width);
|
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 logs_dir = Paths::in_state_dir("logs");
|
||||||
let sessions_dir = Paths::in_data_dir(SESSIONS_FOLDER);
|
let sessions_dir = Paths::in_data_dir(SESSIONS_FOLDER);
|
||||||
let sessions_db = sessions_dir.join(DB_NAME);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user