ai_agent/utils/plugins/
plugin_options_storage.rs1#![allow(dead_code)]
3
4use std::collections::HashMap;
5use std::sync::Mutex;
6
7use once_cell::sync::Lazy;
8
9use super::plugin_directories::get_plugin_data_dir;
10use crate::plugin::types::LoadedPlugin;
11
12pub type PluginOptionValues = HashMap<String, serde_json::Value>;
13pub type PluginOptionSchema = HashMap<String, serde_json::Value>;
14
15static PLUGIN_OPTIONS_CACHE: Lazy<Mutex<HashMap<String, PluginOptionValues>>> =
16 Lazy::new(|| Mutex::new(HashMap::new()));
17
18pub fn get_plugin_storage_id(plugin: &LoadedPlugin) -> String {
20 plugin.source.clone()
21}
22
23pub fn load_plugin_options(plugin_id: &str) -> PluginOptionValues {
25 {
26 let cache = PLUGIN_OPTIONS_CACHE.lock().unwrap();
27 if let Some(values) = cache.get(plugin_id) {
28 return values.clone();
29 }
30 }
31
32 let merged = PluginOptionValues::new();
33
34 {
35 let mut cache = PLUGIN_OPTIONS_CACHE.lock().unwrap();
36 cache.insert(plugin_id.to_string(), merged.clone());
37 }
38
39 merged
40}
41
42pub fn clear_plugin_options_cache() {
44 let mut cache = PLUGIN_OPTIONS_CACHE.lock().unwrap();
45 cache.clear();
46}
47
48pub fn save_plugin_options(
50 _plugin_id: &str,
51 _values: &PluginOptionValues,
52 _schema: &PluginOptionSchema,
53) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
54 clear_plugin_options_cache();
55 Ok(())
56}
57
58pub fn delete_plugin_options(_plugin_id: &str) {
60 clear_plugin_options_cache();
61}
62
63pub fn get_unconfigured_options(_plugin: &LoadedPlugin) -> PluginOptionSchema {
65 HashMap::new()
67}
68
69pub fn substitute_plugin_variables(value: &str, plugin_path: &str, source: &str) -> String {
71 let normalize = |p: &str| -> String {
72 if cfg!(windows) {
73 p.replace('\\', "/")
74 } else {
75 p.to_string()
76 }
77 };
78
79 let mut out = value.replace("${CLAUDE_PLUGIN_ROOT}", &normalize(plugin_path));
80
81 out = out.replace(
82 "${CLAUDE_PLUGIN_DATA}",
83 &normalize(&get_plugin_data_dir(source)),
84 );
85
86 out
87}
88
89pub fn substitute_user_config_variables(value: &str, user_config: &PluginOptionValues) -> String {
91 let mut result = value.to_string();
92 let re = regex::Regex::new(r"\$\{user_config\.([^}]+)\}").unwrap();
93
94 for cap in re.captures_iter(value) {
95 let key = &cap[1];
96 let full_match = &cap[0];
97
98 if let Some(config_value) = user_config.get(key) {
99 result = result.replace(full_match, &config_value.to_string());
100 } else {
101 log::debug!("Missing user config value for key: {}", key);
102 }
103 }
104
105 result
106}
107
108pub fn substitute_user_config_in_content(
110 content: &str,
111 options: &PluginOptionValues,
112 schema: &PluginOptionSchema,
113) -> String {
114 let re = regex::Regex::new(r"\$\{user_config\.([^}]+)\}").unwrap();
115
116 re.replace_all(content, |caps: ®ex::Captures| {
117 let key = &caps[1];
118
119 if let Some(field_schema) = schema.get(key) {
120 let is_sensitive = field_schema
121 .get("sensitive")
122 .and_then(|s| s.as_bool())
123 .unwrap_or(false);
124
125 if is_sensitive {
126 return format!(
127 "[sensitive option '{}' not available in skill content]",
128 key
129 );
130 }
131 }
132
133 match options.get(key) {
134 Some(value) => value.to_string(),
135 None => caps[0].to_string(),
136 }
137 })
138 .to_string()
139}