use crate::core::metadata::SkillMetadata;
use crate::core::service::ServiceError;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
use tracing::warn;
#[async_trait]
pub trait ProgressiveLoadingService: Send + Sync {
async fn load_metadata(&self, skill_ids: &[String])
-> Result<Vec<SkillMetadata>, ServiceError>;
async fn load_skill_content(
&self,
skill_ids: &[String],
context: Option<LoadingContext>,
) -> Result<Vec<LoadedSkill>, ServiceError>;
async fn load_reference_content(
&self,
skill_id: &str,
reference_path: &str,
) -> Result<String, ServiceError>;
async fn preload_relevant_skills(
&self,
context: LoadingContext,
) -> Result<Vec<PreloadedSkill>, ServiceError>;
async fn optimize_loading_strategy(
&self,
context: LoadingContext,
) -> Result<LoadingStrategy, ServiceError>;
async fn clear_cache(&self) -> Result<(), ServiceError>;
async fn get_cache_stats(&self) -> Result<CacheStats, ServiceError>;
}
#[derive(Debug, Clone)]
pub struct LoadingContext {
pub query: String,
pub available_tokens: usize,
pub urgency: LoadingUrgency,
pub conversation_history: Vec<String>,
pub user_preferences: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LoadingUrgency {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone)]
pub struct LoadingStrategy {
pub priority: Vec<String>,
pub load_levels: HashMap<String, LoadLevel>,
pub estimated_tokens: usize,
pub reasoning: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum LoadLevel {
Metadata,
Content,
References,
}
#[derive(Debug, Clone)]
pub struct LoadedSkill {
pub metadata: SkillMetadata,
pub content: Option<String>,
pub references: Option<HashMap<String, String>>,
pub script_contents: Option<HashMap<String, String>>,
pub asset_paths: Vec<PathBuf>,
pub execution_ready: bool,
pub loaded_at: Instant,
pub load_level: LoadLevel,
}
#[derive(Debug, Clone)]
pub struct PreloadedSkill {
pub metadata: SkillMetadata,
pub relevance_score: f64,
pub recommended_load_level: LoadLevel,
}
#[derive(Debug, Clone, Default)]
pub struct CacheStats {
pub metadata_cache_size: usize,
pub content_cache_size: usize,
pub metadata_hit_rate: f64,
pub content_hit_rate: f64,
pub memory_usage_mb: f64,
pub avg_metadata_load_time_ms: f64,
pub avg_content_load_time_ms: f64,
}
pub struct ProgressiveLoadingServiceImpl {
metadata_cache: Arc<RwLock<HashMap<String, (SkillMetadata, Instant)>>>,
content_cache: Arc<RwLock<HashMap<String, (String, Instant)>>>,
skills_base_path: PathBuf,
metadata_cache_ttl: Duration,
content_cache_ttl: Duration,
max_metadata_cache_size: usize,
max_content_cache_size: usize,
metadata_cache_hits: Arc<RwLock<usize>>,
metadata_cache_misses: Arc<RwLock<usize>>,
content_cache_hits: Arc<RwLock<usize>>,
content_cache_misses: Arc<RwLock<usize>>,
}
impl ProgressiveLoadingServiceImpl {
pub fn new(skills_base_path: PathBuf) -> Self {
Self {
metadata_cache: Arc::new(RwLock::new(HashMap::new())),
content_cache: Arc::new(RwLock::new(HashMap::new())),
skills_base_path,
metadata_cache_ttl: Duration::from_secs(3600), content_cache_ttl: Duration::from_secs(1800), max_metadata_cache_size: 1000,
max_content_cache_size: 100,
metadata_cache_hits: Arc::new(RwLock::new(0)),
metadata_cache_misses: Arc::new(RwLock::new(0)),
content_cache_hits: Arc::new(RwLock::new(0)),
content_cache_misses: Arc::new(RwLock::new(0)),
}
}
async fn load_skill_metadata(&self, skill_id: &str) -> Result<SkillMetadata, ServiceError> {
{
let cache = self.metadata_cache.read().await;
if let Some((metadata, loaded_at)) = cache.get(skill_id) {
if loaded_at.elapsed() < self.metadata_cache_ttl {
let mut hits = self.metadata_cache_hits.write().await;
*hits += 1;
return Ok(metadata.clone());
}
}
}
let mut misses = self.metadata_cache_misses.write().await;
*misses += 1;
let skill_path = self.skills_base_path.join(skill_id).join("SKILL.md");
if !skill_path.exists() {
return Err(ServiceError::Custom(format!(
"Skill file not found: {}",
skill_path.display()
)));
}
let content = tokio::fs::read_to_string(&skill_path).await?;
let metadata = self.parse_skill_metadata(skill_id, &content)?;
{
let mut cache = self.metadata_cache.write().await;
if cache.len() >= self.max_metadata_cache_size {
self.cleanup_expired_metadata(&mut cache).await;
}
cache.insert(skill_id.to_string(), (metadata.clone(), Instant::now()));
}
Ok(metadata)
}
fn parse_skill_metadata(
&self,
skill_id: &str,
content: &str,
) -> Result<SkillMetadata, ServiceError> {
let lines = content.lines();
let mut in_frontmatter = false;
let mut frontmatter_lines = Vec::new();
for line in lines {
if line.trim() == "---" {
if in_frontmatter {
break; } else {
in_frontmatter = true;
continue;
}
}
if in_frontmatter {
frontmatter_lines.push(line);
}
}
let name = skill_id.replace("-", " ").to_string();
let description = content
.lines()
.next()
.unwrap_or("No description")
.to_string();
Ok(SkillMetadata {
id: crate::core::service::SkillId::new(skill_id.to_string())?,
name,
description,
version: "1.0.0".to_string(),
author: None,
enabled: true,
token_estimate: content.len() / 4, last_updated: std::time::SystemTime::now().into(),
})
}
async fn load_skill_content_internal(&self, skill_id: &str) -> Result<String, ServiceError> {
{
let cache = self.content_cache.read().await;
if let Some((content, loaded_at)) = cache.get(skill_id) {
if loaded_at.elapsed() < self.content_cache_ttl {
let mut hits = self.content_cache_hits.write().await;
*hits += 1;
return Ok(content.clone());
}
}
}
let mut misses = self.content_cache_misses.write().await;
*misses += 1;
let skill_path = self.skills_base_path.join(skill_id).join("SKILL.md");
if !skill_path.exists() {
return Err(ServiceError::Custom(format!(
"Skill file not found: {}",
skill_path.display()
)));
}
let content = tokio::fs::read_to_string(&skill_path).await?;
{
let mut cache = self.content_cache.write().await;
if cache.len() >= self.max_content_cache_size {
self.cleanup_expired_content(&mut cache).await;
}
cache.insert(skill_id.to_string(), (content.clone(), Instant::now()));
}
Ok(content)
}
async fn cleanup_expired_metadata(
&self,
cache: &mut HashMap<String, (SkillMetadata, Instant)>,
) {
let now = Instant::now();
cache.retain(|_, (_, loaded_at)| now.duration_since(*loaded_at) < self.metadata_cache_ttl);
if cache.len() >= self.max_metadata_cache_size {
let entries: Vec<_> = cache.iter().map(|(k, (_, _))| k.clone()).collect();
let to_remove = cache.len() - self.max_metadata_cache_size + 10;
for key in entries.iter().take(to_remove) {
cache.remove(key);
}
}
}
async fn cleanup_expired_content(&self, cache: &mut HashMap<String, (String, Instant)>) {
let now = Instant::now();
cache.retain(|_, (_, loaded_at)| now.duration_since(*loaded_at) < self.content_cache_ttl);
if cache.len() >= self.max_content_cache_size {
let entries: Vec<_> = cache.iter().map(|(k, (_, _))| k.clone()).collect();
let to_remove = cache.len() - self.max_content_cache_size + 5;
for key in entries.iter().take(to_remove) {
cache.remove(key);
}
}
}
pub async fn get_cache_stats(&self) -> CacheStats {
let metadata_cache_size = self.metadata_cache.read().await.len();
let content_cache_size = self.content_cache.read().await.len();
let metadata_hits = *self.metadata_cache_hits.read().await;
let metadata_misses = *self.metadata_cache_misses.read().await;
let content_hits = *self.content_cache_hits.read().await;
let content_misses = *self.content_cache_misses.read().await;
let metadata_total = metadata_hits + metadata_misses;
let content_total = content_hits + content_misses;
let metadata_hit_rate = if metadata_total > 0 {
metadata_hits as f64 / metadata_total as f64
} else {
0.0
};
let content_hit_rate = if content_total > 0 {
content_hits as f64 / content_total as f64
} else {
0.0
};
let memory_usage_mb =
(metadata_cache_size * 1024 + content_cache_size * 2048) as f64 / (1024.0 * 1024.0);
CacheStats {
metadata_cache_size,
content_cache_size,
metadata_hit_rate,
content_hit_rate,
memory_usage_mb,
avg_metadata_load_time_ms: 50.0, avg_content_load_time_ms: 100.0, }
}
pub async fn clear_cache(&self) {
self.metadata_cache.write().await.clear();
self.content_cache.write().await.clear();
*self.metadata_cache_hits.write().await = 0;
*self.metadata_cache_misses.write().await = 0;
*self.content_cache_hits.write().await = 0;
*self.content_cache_misses.write().await = 0;
}
}
#[async_trait]
impl ProgressiveLoadingService for ProgressiveLoadingServiceImpl {
async fn load_metadata(
&self,
skill_ids: &[String],
) -> Result<Vec<SkillMetadata>, ServiceError> {
let mut results = Vec::new();
for skill_id in skill_ids {
match self.load_skill_metadata(skill_id).await {
Ok(metadata) => results.push(metadata),
Err(e) => {
warn!("Failed to load metadata for skill {}: {}", skill_id, e);
}
}
}
Ok(results)
}
async fn load_skill_content(
&self,
skill_ids: &[String],
context: Option<LoadingContext>,
) -> Result<Vec<LoadedSkill>, ServiceError> {
let mut results = Vec::new();
for skill_id in skill_ids {
let metadata = self.load_skill_metadata(skill_id).await?;
let load_level = self.determine_load_level(&metadata, context.as_ref());
let (content, references, execution_ready) = match load_level {
LoadLevel::Metadata => (None, None, false),
LoadLevel::Content => {
let skill_content = self.load_skill_content_internal(skill_id).await?;
(Some(skill_content), None, true)
}
LoadLevel::References => {
let skill_content = self.load_skill_content_internal(skill_id).await?;
let skill_references = self.load_reference_files(skill_id).await?;
(Some(skill_content), Some(skill_references), true)
}
};
results.push(LoadedSkill {
metadata,
content,
references,
script_contents: None, asset_paths: self.get_asset_paths(skill_id),
execution_ready,
loaded_at: Instant::now(),
load_level,
});
}
Ok(results)
}
async fn load_reference_content(
&self,
skill_id: &str,
reference_path: &str,
) -> Result<String, ServiceError> {
let ref_path = self
.skills_base_path
.join(skill_id)
.join("references")
.join(reference_path);
if !ref_path.exists() {
return Err(ServiceError::Custom(format!(
"Reference file not found: {}",
ref_path.display()
)));
}
tokio::fs::read_to_string(&ref_path)
.await
.map_err(|e| ServiceError::Custom(format!("Failed to read reference file: {}", e)))
}
async fn preload_relevant_skills(
&self,
_context: LoadingContext,
) -> Result<Vec<PreloadedSkill>, ServiceError> {
Ok(Vec::new())
}
async fn optimize_loading_strategy(
&self,
context: LoadingContext,
) -> Result<LoadingStrategy, ServiceError> {
let mut strategy = LoadingStrategy {
priority: Vec::new(),
load_levels: HashMap::new(),
estimated_tokens: 0,
reasoning: vec!["Basic strategy based on query analysis".to_string()],
};
strategy
.load_levels
.insert("default".to_string(), LoadLevel::Metadata);
strategy.estimated_tokens = context.available_tokens / 2;
Ok(strategy)
}
async fn clear_cache(&self) -> Result<(), ServiceError> {
self.clear_cache().await;
Ok(())
}
async fn get_cache_stats(&self) -> Result<CacheStats, ServiceError> {
Ok(self.get_cache_stats().await)
}
}
impl ProgressiveLoadingServiceImpl {
fn determine_load_level(
&self,
_metadata: &SkillMetadata,
context: Option<&LoadingContext>,
) -> LoadLevel {
match context {
Some(ctx) => {
match ctx.urgency {
LoadingUrgency::Critical => LoadLevel::References,
LoadingUrgency::High => LoadLevel::Content,
LoadingUrgency::Medium => {
if ctx.query.len() > 100 || ctx.available_tokens > 2000 {
LoadLevel::Content
} else {
LoadLevel::Metadata
}
}
LoadingUrgency::Low => LoadLevel::Metadata,
}
}
None => LoadLevel::Metadata, }
}
async fn load_reference_files(
&self,
skill_id: &str,
) -> Result<HashMap<String, String>, ServiceError> {
let references_dir = self.skills_base_path.join(skill_id).join("references");
if !references_dir.exists() {
return Ok(HashMap::new());
}
let mut references = HashMap::new();
let mut read_dir = tokio::fs::read_dir(&references_dir).await?;
while let Some(entry) = read_dir.next_entry().await? {
if entry.path().is_file() {
let file_name = entry.file_name().to_string_lossy().to_string();
let content = tokio::fs::read_to_string(entry.path()).await?;
references.insert(file_name, content);
}
}
Ok(references)
}
fn get_asset_paths(&self, skill_id: &str) -> Vec<PathBuf> {
let assets_dir = self.skills_base_path.join(skill_id).join("assets");
if !assets_dir.exists() {
return Vec::new();
}
Vec::new() }
}
pub struct LoadingService(ProgressiveLoadingServiceImpl);
impl Default for LoadingService {
fn default() -> Self {
Self::new()
}
}
impl LoadingService {
pub fn new() -> Self {
Self(ProgressiveLoadingServiceImpl::new(PathBuf::from(
"./skills",
)))
}
}
#[async_trait]
impl ProgressiveLoadingService for LoadingService {
async fn load_metadata(
&self,
skill_ids: &[String],
) -> Result<Vec<SkillMetadata>, ServiceError> {
self.0.load_metadata(skill_ids).await
}
async fn load_skill_content(
&self,
skill_ids: &[String],
context: Option<LoadingContext>,
) -> Result<Vec<LoadedSkill>, ServiceError> {
self.0.load_skill_content(skill_ids, context).await
}
async fn load_reference_content(
&self,
skill_id: &str,
reference_path: &str,
) -> Result<String, ServiceError> {
self.0
.load_reference_content(skill_id, reference_path)
.await
}
async fn preload_relevant_skills(
&self,
context: LoadingContext,
) -> Result<Vec<PreloadedSkill>, ServiceError> {
self.0.preload_relevant_skills(context).await
}
async fn optimize_loading_strategy(
&self,
context: LoadingContext,
) -> Result<LoadingStrategy, ServiceError> {
self.0.optimize_loading_strategy(context).await
}
async fn clear_cache(&self) -> Result<(), ServiceError> {
self.0.clear_cache().await;
Ok(())
}
async fn get_cache_stats(&self) -> Result<CacheStats, ServiceError> {
Ok(self.0.get_cache_stats().await)
}
}