rightkit_agent_engine_api_integrations/
discovery.rs1use std::path::{Path, PathBuf};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct BrokenPlugin {
8 pub path: PathBuf,
9 pub error: String,
10}
11
12#[derive(Debug, Clone, Default)]
16pub struct PluginDiscovery {
17 pub plugins: Vec<rightkit_agent_plugins::PluginManifest>,
18 pub broken: Vec<BrokenPlugin>,
19}
20
21pub fn discover_plugin_report(dir: &Path) -> Result<PluginDiscovery, String> {
22 let entries = match std::fs::read_dir(dir) {
23 Ok(entries) => entries,
24 Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
25 return Ok(PluginDiscovery::default())
26 }
27 Err(error) => return Err(error.to_string()),
28 };
29 let mut report = PluginDiscovery::default();
30 for entry in entries {
31 let entry = entry.map_err(|error| error.to_string())?;
32 let root = entry.path();
33 let hidden = entry.file_name().to_string_lossy().starts_with('.');
34 if hidden || !root.is_dir() {
35 continue;
36 }
37 let manifest_path = root.join("plugin.json");
38 if !manifest_path.exists() {
39 continue;
40 }
41 let parsed = std::fs::read_to_string(&manifest_path)
42 .map_err(|error| error.to_string())
43 .and_then(|raw| {
44 rightkit_agent_plugins::PluginManifest::from_json(&raw)
45 .map_err(|error| error.to_string())
46 });
47 match parsed {
48 Ok(manifest) => {
49 if report.plugins.iter().all(|plugin| plugin.id != manifest.id) {
50 report.plugins.push(manifest);
51 }
52 }
53 Err(error) => report.broken.push(BrokenPlugin {
54 path: manifest_path,
55 error,
56 }),
57 }
58 }
59 report.plugins.sort_by(|left, right| {
60 left.id
61 .cmp(&right.id)
62 .then_with(|| left.version.cmp(&right.version))
63 });
64 report
65 .broken
66 .sort_by(|left, right| left.path.cmp(&right.path));
67 Ok(report)
68}
69
70pub fn discover_plugins(dir: &Path) -> Result<Vec<rightkit_agent_plugins::PluginManifest>, String> {
73 Ok(discover_plugin_report(dir)?.plugins)
74}
75
76pub fn plugin_manifests_with_roots(
77 dir: &Path,
78) -> Vec<(rightkit_agent_plugins::PluginManifest, PathBuf)> {
79 let Ok(entries) = std::fs::read_dir(dir) else {
80 return Vec::new();
81 };
82 let mut manifests = Vec::new();
83 for entry in entries.flatten() {
84 let root = entry.path();
85 if !root.is_dir() {
86 continue;
87 }
88 let manifest_path = root.join("plugin.json");
89 let Ok(raw) = std::fs::read_to_string(&manifest_path) else {
90 continue;
91 };
92 let Ok(manifest) = rightkit_agent_plugins::PluginManifest::from_json(&raw) else {
93 continue;
94 };
95 manifests.push((manifest, root));
96 }
97 manifests.sort_by(|(left, _), (right, _)| {
98 left.id
99 .cmp(&right.id)
100 .then_with(|| left.version.cmp(&right.version))
101 });
102 manifests
103}
104
105pub fn provider_extension_documents(dir: &Path) -> Result<Vec<String>, String> {
106 let mut documents = Vec::new();
107 for (manifest, root) in plugin_manifests_with_roots(dir) {
108 if !manifest.enabled {
109 continue;
110 }
111 let path = root.join("provider.json");
112 if path.exists() {
113 documents.push(std::fs::read_to_string(path).map_err(|error| error.to_string())?);
114 }
115 }
116 Ok(documents)
117}
118
119pub fn local_plugin_middleware_definitions(
120 dir: &Path,
121) -> Vec<rightkit_agent_engine_runtime_middleware::MiddlewareDefinition> {
122 let mut definitions = Vec::new();
123 for (manifest, _) in plugin_manifests_with_roots(dir) {
124 if !manifest.enabled {
125 continue;
126 }
127 for contribution in manifest.middleware {
128 if let Some(definition) = plugin_middleware_definition(&manifest.id, contribution) {
129 definitions.push(definition);
130 }
131 }
132 }
133 definitions.sort_by(|left, right| {
134 left.order
135 .cmp(&right.order)
136 .then_with(|| left.id.cmp(&right.id))
137 });
138 definitions
139}
140
141fn plugin_middleware_definition(
142 plugin_id: &str,
143 contribution: rightkit_agent_plugins::PluginMiddlewareContribution,
144) -> Option<rightkit_agent_engine_runtime_middleware::MiddlewareDefinition> {
145 use rightkit_agent_engine_runtime_middleware::{
146 MiddlewareCondition, MiddlewareDefinition, MiddlewareSource,
147 };
148 let phase = parse_middleware_phase(&contribution.phase)?;
149 let effect = parse_middleware_effect(&contribution.effect)?;
150 let id = contribution.id.trim();
151 if id.is_empty() {
152 return None;
153 }
154 Some(MiddlewareDefinition {
155 id: format!("plugin.{plugin_id}.{id}"),
156 title: if contribution.title.trim().is_empty() {
157 format!("{plugin_id}: {id}")
158 } else {
159 contribution.title
160 },
161 source: MiddlewareSource::LocalPlugin,
162 phase,
163 effect,
164 tool: contribution
165 .tool
166 .map(|tool| tool.trim().to_string())
167 .filter(|tool| !tool.is_empty()),
168 requires_toolset: contribution
169 .requires_toolset
170 .map(|toolset| toolset.trim().to_string())
171 .filter(|toolset| !toolset.is_empty()),
172 condition: contribution.condition.map(|condition| MiddlewareCondition {
173 argument: condition
174 .argument
175 .map(|argument| argument.trim().to_string())
176 .filter(|argument| !argument.is_empty()),
177 contains: condition
178 .contains
179 .map(|contains| contains.trim().to_string())
180 .filter(|contains| !contains.is_empty()),
181 equals: condition.equals,
182 }),
183 mutate_args: contribution.mutate_args,
184 reason: contribution
185 .reason
186 .map(|reason| reason.trim().to_string())
187 .filter(|reason| !reason.is_empty()),
188 enabled: contribution.enabled.unwrap_or(true),
189 order: contribution.order.unwrap_or(100),
190 description: contribution.description,
191 })
192}
193
194fn parse_middleware_phase(
195 value: &str,
196) -> Option<rightkit_agent_engine_runtime_middleware::MiddlewarePhase> {
197 use rightkit_agent_engine_runtime_middleware::MiddlewarePhase;
198 match value {
199 "sessionStart" => Some(MiddlewarePhase::SessionStart),
200 "preLlmCall" => Some(MiddlewarePhase::PreLlmCall),
201 "postLlmCall" => Some(MiddlewarePhase::PostLlmCall),
202 "preToolCall" => Some(MiddlewarePhase::PreToolCall),
203 "postToolCall" => Some(MiddlewarePhase::PostToolCall),
204 "approvalRequested" => Some(MiddlewarePhase::ApprovalRequested),
205 "subagentStart" => Some(MiddlewarePhase::SubagentStart),
206 "subagentStop" => Some(MiddlewarePhase::SubagentStop),
207 "scheduledJobStart" => Some(MiddlewarePhase::ScheduledJobStart),
208 "scheduledJobEnd" => Some(MiddlewarePhase::ScheduledJobEnd),
209 "sessionEnd" => Some(MiddlewarePhase::SessionEnd),
210 _ => None,
211 }
212}
213
214fn parse_middleware_effect(
215 value: &str,
216) -> Option<rightkit_agent_engine_runtime_middleware::MiddlewareEffect> {
217 use rightkit_agent_engine_runtime_middleware::MiddlewareEffect;
218 match value {
219 "observe" => Some(MiddlewareEffect::Observe),
220 "mutateArgs" => Some(MiddlewareEffect::MutateArgs),
221 "requireApproval" => Some(MiddlewareEffect::RequireApproval),
222 "deny" => Some(MiddlewareEffect::Deny),
223 _ => None,
224 }
225}