use crate::plugin::auth::{AuthAvailability, AuthState};
use crate::plugin::effective_tools::UnavailableReason;
use crate::plugin::manifest::ExportedTool;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PluginStatus {
pub plugin_id: String,
pub health: PluginHealth,
pub auth: AuthAvailability,
pub runtime_status: PluginRuntimeStatus,
pub status_message: String,
pub ready: bool,
pub available_tool_count: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PluginHealth {
Configured,
Loading,
Healthy,
Error,
Disabled,
}
impl PluginHealth {
pub fn is_healthy(&self) -> bool {
matches!(self, Self::Healthy)
}
pub fn is_error(&self) -> bool {
matches!(self, Self::Error)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PluginRuntimeStatus {
Configured,
Loading,
Ready,
AwaitingAuth,
Partial,
Error,
Disabled,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PerToolAvailability {
pub name: String,
pub metadata: ExportedTool,
pub available: bool,
#[serde(default)]
pub unavailable_reason: Option<UnavailableReason>,
pub requires_auth: bool,
pub auth_satisfied: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginInfo {
pub identity_id: Option<String>,
pub name: Option<String>,
pub version: Option<String>,
pub publisher_name: Option<String>,
pub publisher_url: Option<String>,
pub description: Option<String>,
pub category: Option<String>,
pub health: PluginHealth,
pub runtime_status: PluginRuntimeStatus,
pub ready: bool,
pub last_error: Option<String>,
pub declared_tool_count: usize,
pub available_tool_count: usize,
pub tool_names: Vec<String>,
pub source: Option<String>,
pub checksum_verified: bool,
pub installed_at: Option<DateTime<Utc>>,
pub auth_required: bool,
pub auth_state: AuthState,
}
impl PluginStatus {
pub fn compute_runtime_status(
health: PluginHealth,
_auth: &AuthAvailability,
total_tools: usize,
available_tools: usize,
) -> PluginRuntimeStatus {
match health {
PluginHealth::Disabled => PluginRuntimeStatus::Disabled,
PluginHealth::Error => PluginRuntimeStatus::Error,
PluginHealth::Configured => PluginRuntimeStatus::Configured,
PluginHealth::Loading => PluginRuntimeStatus::Loading,
PluginHealth::Healthy => {
if total_tools == 0 {
PluginRuntimeStatus::Configured
} else if available_tools == total_tools {
PluginRuntimeStatus::Ready
} else if available_tools > 0 {
PluginRuntimeStatus::Partial
} else {
PluginRuntimeStatus::AwaitingAuth
}
}
}
}
pub fn generate_status_message(
runtime_status: PluginRuntimeStatus,
plugin_name: &str,
) -> String {
match runtime_status {
PluginRuntimeStatus::Ready => format!("{} is ready to use", plugin_name),
PluginRuntimeStatus::AwaitingAuth => format!("{} needs authentication", plugin_name),
PluginRuntimeStatus::Partial => format!("{} has limited functionality", plugin_name),
PluginRuntimeStatus::Error => format!("{} encountered an error", plugin_name),
PluginRuntimeStatus::Loading => format!("{} is loading...", plugin_name),
PluginRuntimeStatus::Configured => format!("{} is configured", plugin_name),
PluginRuntimeStatus::Disabled => format!("{} is disabled", plugin_name),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_runtime_status_ready() {
let health = PluginHealth::Healthy;
let auth = AuthAvailability {
state: AuthState::Authenticated,
auth_required: true,
can_authenticate: false,
message: "Authenticated".to_string(),
action_hint: crate::plugin::auth::AuthActionHint::None,
};
assert_eq!(
PluginStatus::compute_runtime_status(health, &auth, 3, 3),
PluginRuntimeStatus::Ready
);
}
#[test]
fn test_compute_runtime_status_awaiting_auth() {
let health = PluginHealth::Healthy;
let auth = AuthAvailability {
state: AuthState::Unauthenticated,
auth_required: true,
can_authenticate: true,
message: "Needs auth".to_string(),
action_hint: crate::plugin::auth::AuthActionHint::StartOAuth,
};
assert_eq!(
PluginStatus::compute_runtime_status(health, &auth, 2, 0),
PluginRuntimeStatus::AwaitingAuth
);
}
#[test]
fn test_compute_runtime_status_partial() {
let health = PluginHealth::Healthy;
let auth = AuthAvailability {
state: AuthState::Authenticating,
auth_required: true,
can_authenticate: false,
message: "Authenticating".to_string(),
action_hint: crate::plugin::auth::AuthActionHint::None,
};
assert_eq!(
PluginStatus::compute_runtime_status(health, &auth, 3, 1),
PluginRuntimeStatus::Partial
);
}
#[test]
fn test_compute_runtime_status_no_tools_configured() {
let health = PluginHealth::Healthy;
let auth = AuthAvailability {
state: AuthState::Unauthenticated,
auth_required: false,
can_authenticate: false,
message: "No auth".to_string(),
action_hint: crate::plugin::auth::AuthActionHint::None,
};
assert_eq!(
PluginStatus::compute_runtime_status(health, &auth, 0, 0),
PluginRuntimeStatus::Configured
);
}
#[test]
fn test_compute_runtime_status_error() {
let auth = AuthAvailability {
state: AuthState::Unauthenticated,
auth_required: false,
can_authenticate: false,
message: "No auth".to_string(),
action_hint: crate::plugin::auth::AuthActionHint::None,
};
assert_eq!(
PluginStatus::compute_runtime_status(PluginHealth::Error, &auth, 1, 0),
PluginRuntimeStatus::Error
);
}
#[test]
fn test_compute_runtime_status_disabled() {
let auth = AuthAvailability {
state: AuthState::Unauthenticated,
auth_required: false,
can_authenticate: false,
message: "No auth".to_string(),
action_hint: crate::plugin::auth::AuthActionHint::None,
};
assert_eq!(
PluginStatus::compute_runtime_status(PluginHealth::Disabled, &auth, 1, 0),
PluginRuntimeStatus::Disabled
);
}
#[test]
fn test_compute_runtime_status_loading() {
let auth = AuthAvailability {
state: AuthState::Unauthenticated,
auth_required: false,
can_authenticate: false,
message: "No auth".to_string(),
action_hint: crate::plugin::auth::AuthActionHint::None,
};
assert_eq!(
PluginStatus::compute_runtime_status(PluginHealth::Loading, &auth, 1, 0),
PluginRuntimeStatus::Loading
);
}
}