use serde::Serialize;
use crate::{
config::app::ProviderId,
providers::{descriptor::descriptor, probe::ProviderProbeResult},
};
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub(crate) enum CheckId {
System,
Pi,
Providers,
Pairing,
Tunnel,
}
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub(crate) enum CheckStatus {
#[allow(dead_code)]
Checking,
Ready,
#[allow(dead_code)]
Warning,
ActionRequired,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub(crate) struct CheckResult {
pub id: CheckId,
pub status: CheckStatus,
pub required: bool,
pub detail: Option<String>,
pub action: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub actions: Vec<ReadinessAction>,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub(crate) struct ReadinessAction {
pub(crate) title: String,
pub(crate) steps: Vec<String>,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub(crate) struct ReadinessReport {
pub checks: Vec<CheckResult>,
pub start_allowed: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub iroh_endpoint_id: Option<String>,
}
impl ReadinessReport {
pub(crate) fn from_checks(checks: Vec<CheckResult>) -> Self {
let start_allowed = checks.iter().all(|check| {
!check.required || matches!(check.status, CheckStatus::Ready | CheckStatus::Warning)
});
Self {
checks,
start_allowed,
iroh_endpoint_id: None,
}
}
}
#[cfg(test)]
pub(crate) fn unselected_provider_action() -> ReadinessAction {
ReadinessAction {
title: "Select a model provider.".to_owned(),
steps: vec![
"Run `regy-pc-agent setup` in a terminal.".to_owned(),
"Select OpenAI Codex, Claude Code, or Antigravity.".to_owned(),
"Follow the displayed browser/login instructions.".to_owned(),
"Run `regy-pc-agent doctor` and confirm Providers is ready.".to_owned(),
"Run `regy-pc-agent start`, then open https://regy.one.".to_owned(),
],
}
}
pub(crate) fn provider_authentication_summary(provider: ProviderId) -> String {
descriptor(provider).setup_steps.join(" ")
}
pub(crate) struct ProviderSetupGuidance {
pub(crate) display_name: &'static str,
pub(crate) summary: String,
#[cfg(test)]
pub(crate) action: ReadinessAction,
}
pub(crate) fn provider_setup_guidance(result: &ProviderProbeResult) -> ProviderSetupGuidance {
let descriptor = descriptor(result.id);
let (summary, _title, _steps) = if !result.executable_ready {
let summary = "Install the Claude Code executable.".to_owned();
(
summary.clone(),
format!("{} requires setup.", descriptor.display_name),
vec![summary],
)
} else if !result.extension_ready {
let summary = match result.id {
ProviderId::ClaudeCode => "Install the reviewed Claude Code Pi extension.",
ProviderId::Antigravity => "Install the reviewed Antigravity Pi extension.",
ProviderId::OpenaiCodex => "Configure the OpenAI Codex provider.",
}
.to_owned();
(
summary.clone(),
format!("{} requires setup.", descriptor.display_name),
vec![summary],
)
} else if !result.authenticated {
(
provider_authentication_summary(result.id),
format!("{} requires authentication.", descriptor.display_name),
descriptor
.setup_steps
.iter()
.map(|step| (*step).to_owned())
.collect(),
)
} else {
let summary = "Complete provider setup.".to_owned();
(
summary.clone(),
format!("{} requires setup.", descriptor.display_name),
vec![summary],
)
};
ProviderSetupGuidance {
display_name: descriptor.display_name,
summary,
#[cfg(test)]
action: ReadinessAction {
title: _title,
steps: _steps,
},
}
}
#[cfg(test)]
pub(crate) fn provider_readiness(results: &[ProviderProbeResult]) -> CheckResult {
if results.iter().any(ProviderProbeResult::ready) {
return CheckResult {
id: CheckId::Providers,
status: CheckStatus::Ready,
required: true,
detail: Some("At least one model provider is ready.".to_owned()),
action: None,
actions: Vec::new(),
};
}
let guidance = results.first().map(provider_setup_guidance);
CheckResult {
id: CheckId::Providers,
status: CheckStatus::ActionRequired,
required: true,
detail: Some("No model provider is ready.".to_owned()),
action: guidance
.as_ref()
.map(|guidance| guidance.summary.clone())
.or_else(|| Some("Configure and log in to a model provider.".to_owned())),
actions: guidance
.map(|guidance| guidance.action)
.into_iter()
.collect(),
}
}