ai_agent/utils/plugins/
loader.rs1use std::fs;
9use std::path::PathBuf;
10
11use super::types::{KnownMarketplacesFile, PluginMarketplace, PluginMarketplaceEntry};
12use crate::utils::config::get_global_config_path;
13
14fn get_known_marketplaces_file() -> PathBuf {
16 get_global_config_path().join("known_marketplaces.json")
17}
18
19async fn read_cached_marketplace(install_location: &str) -> Option<PluginMarketplace> {
21 let marketplace_path = PathBuf::from(install_location)
22 .join(".ai-plugin")
23 .join("marketplace.json");
24
25 if !marketplace_path.exists() {
26 return None;
27 }
28
29 match fs::read_to_string(&marketplace_path) {
30 Ok(content) => match serde_json::from_str::<PluginMarketplace>(&content) {
31 Ok(marketplace) => Some(marketplace),
32 Err(e) => {
33 eprintln!(
34 "Failed to parse marketplace at {}: {}",
35 marketplace_path.display(),
36 e
37 );
38 None
39 }
40 },
41 Err(e) => {
42 eprintln!(
43 "Failed to read marketplace at {}: {}",
44 marketplace_path.display(),
45 e
46 );
47 None
48 }
49 }
50}
51
52async fn load_known_marketplaces_config() -> Option<KnownMarketplacesFile> {
54 let config_file = get_known_marketplaces_file();
55
56 if !config_file.exists() {
57 return None;
58 }
59
60 match fs::read_to_string(&config_file) {
61 Ok(content) => match serde_json::from_str::<KnownMarketplacesFile>(&content) {
62 Ok(config) => Some(config),
63 Err(e) => {
64 eprintln!("Failed to parse known marketplaces: {}", e);
65 None
66 }
67 },
68 Err(e) => {
69 eprintln!("Failed to read known marketplaces file: {}", e);
70 None
71 }
72 }
73}
74
75pub fn parse_plugin_identifier(plugin_id: &str) -> (Option<String>, Option<String>) {
83 if let Some(at_pos) = plugin_id.rfind('@') {
84 let name = plugin_id[..at_pos].to_string();
85 let marketplace = plugin_id[at_pos + 1..].to_string();
86 if !name.is_empty() && !marketplace.is_empty() {
87 return (Some(name), Some(marketplace));
88 }
89 }
90 (None, None)
91}
92
93pub async fn get_marketplace_cache_only(name: &str) -> Option<PluginMarketplace> {
103 let config_file = get_known_marketplaces_file();
104
105 if !config_file.exists() {
106 return None;
107 }
108
109 match fs::read_to_string(&config_file) {
110 Ok(content) => {
111 match serde_json::from_str::<KnownMarketplacesFile>(&content) {
112 Ok(config) => {
113 if let Some(entry) = config.get(name) {
114 if let Some(marketplace) =
116 read_cached_marketplace(&entry.install_location).await
117 {
118 return Some(marketplace);
119 }
120 }
121 None
122 }
123 Err(e) => {
124 eprintln!("Failed to parse known marketplaces config: {}", e);
125 None
126 }
127 }
128 }
129 Err(_) => None,
130 }
131}
132
133pub async fn get_plugin_by_id_cache_only(
141 plugin_id: &str,
142) -> Option<(PluginMarketplaceEntry, String)> {
143 let (plugin_name, marketplace_name) = parse_plugin_identifier(plugin_id);
144 let plugin_name = plugin_name?;
145 let marketplace_name = marketplace_name?;
146
147 let config_file = get_known_marketplaces_file();
148
149 if !config_file.exists() {
150 return None;
151 }
152
153 match fs::read_to_string(&config_file) {
154 Ok(content) => {
155 match serde_json::from_str::<KnownMarketplacesFile>(&content) {
156 Ok(config) => {
157 let marketplace_config = config.get(&marketplace_name)?;
159
160 let marketplace = get_marketplace_cache_only(&marketplace_name).await?;
162
163 marketplace
165 .plugins
166 .into_iter()
167 .find(|p| p.name == plugin_name)
168 .map(|entry| (entry, marketplace_config.install_location.clone()))
169 }
170 Err(_) => None,
171 }
172 }
173 Err(_) => None,
174 }
175}
176
177pub async fn get_known_marketplace_names() -> Vec<String> {
179 match load_known_marketplaces_config().await {
180 Some(config) => config.keys().cloned().collect(),
181 None => vec![],
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_parse_plugin_identifier_basic() {
191 let (name, marketplace) = parse_plugin_identifier("my-plugin@my-marketplace");
192 assert_eq!(name, Some("my-plugin".to_string()));
193 assert_eq!(marketplace, Some("my-marketplace".to_string()));
194 }
195
196 #[test]
197 fn test_parse_plugin_identifier_invalid() {
198 let (name, marketplace) = parse_plugin_identifier("invalid");
199 assert_eq!(name, None);
200 assert_eq!(marketplace, None);
201 }
202
203 #[test]
204 fn test_parse_plugin_identifier_empty_marketplace() {
205 let (name, marketplace) = parse_plugin_identifier("my-plugin@");
206 assert_eq!(name, None);
207 assert_eq!(marketplace, None);
208 }
209}