codineer_plugins/
resolve.rs1use std::path::{Path, PathBuf};
2
3use crate::error::PluginError;
4use crate::lifecycle::run_lifecycle_commands;
5use crate::types::{
6 BundledPlugin, ExternalPlugin, Plugin, PluginHooks, PluginLifecycle, PluginMetadata,
7 PluginTool, PluginToolDefinition, PluginToolManifest,
8};
9
10pub(crate) fn resolve_hooks(root: &Path, hooks: &PluginHooks) -> PluginHooks {
11 PluginHooks {
12 pre_tool_use: hooks
13 .pre_tool_use
14 .iter()
15 .map(|entry| resolve_hook_entry(root, entry))
16 .collect(),
17 post_tool_use: hooks
18 .post_tool_use
19 .iter()
20 .map(|entry| resolve_hook_entry(root, entry))
21 .collect(),
22 }
23}
24
25pub(crate) fn resolve_lifecycle(root: &Path, lifecycle: &PluginLifecycle) -> PluginLifecycle {
26 PluginLifecycle {
27 init: lifecycle
28 .init
29 .iter()
30 .map(|entry| resolve_hook_entry(root, entry))
31 .collect(),
32 shutdown: lifecycle
33 .shutdown
34 .iter()
35 .map(|entry| resolve_hook_entry(root, entry))
36 .collect(),
37 }
38}
39
40pub(crate) fn resolve_tools(
41 root: &Path,
42 plugin_id: &str,
43 plugin_name: &str,
44 tools: &[PluginToolManifest],
45) -> Vec<PluginTool> {
46 tools
47 .iter()
48 .map(|tool| {
49 PluginTool::new(
50 plugin_id,
51 plugin_name,
52 PluginToolDefinition {
53 name: tool.name.clone(),
54 description: Some(tool.description.clone()),
55 input_schema: tool.input_schema.clone(),
56 },
57 resolve_hook_entry(root, &tool.command),
58 tool.args.clone(),
59 tool.required_permission,
60 Some(root.to_path_buf()),
61 )
62 })
63 .collect()
64}
65
66fn validate_hook_paths(root: Option<&Path>, hooks: &PluginHooks) -> Result<(), PluginError> {
67 let Some(root) = root else {
68 return Ok(());
69 };
70 for entry in hooks.pre_tool_use.iter().chain(hooks.post_tool_use.iter()) {
71 validate_command_path(root, entry, "hook")?;
72 }
73 Ok(())
74}
75
76fn validate_lifecycle_paths(
77 root: Option<&Path>,
78 lifecycle: &PluginLifecycle,
79) -> Result<(), PluginError> {
80 let Some(root) = root else {
81 return Ok(());
82 };
83 for entry in lifecycle.init.iter().chain(lifecycle.shutdown.iter()) {
84 validate_command_path(root, entry, "lifecycle command")?;
85 }
86 Ok(())
87}
88
89fn validate_tool_paths(root: Option<&Path>, tools: &[PluginTool]) -> Result<(), PluginError> {
90 let Some(root) = root else {
91 return Ok(());
92 };
93 for tool in tools {
94 validate_command_path(root, &tool.command, "tool")?;
95 }
96 Ok(())
97}
98
99fn validate_command_path(root: &Path, entry: &str, kind: &str) -> Result<(), PluginError> {
100 if is_literal_command(entry) {
101 return Ok(());
102 }
103 let path = if Path::new(entry).is_absolute() {
104 PathBuf::from(entry)
105 } else {
106 root.join(entry)
107 };
108 if !path.exists() {
109 return Err(PluginError::InvalidManifest(format!(
110 "{kind} path `{}` does not exist",
111 path.display()
112 )));
113 }
114 Ok(())
115}
116
117fn resolve_hook_entry(root: &Path, entry: &str) -> String {
118 if is_literal_command(entry) {
119 entry.to_string()
120 } else {
121 root.join(entry).display().to_string()
122 }
123}
124
125use crate::constants::is_literal_command;
126
127impl Plugin for BundledPlugin {
128 fn metadata(&self) -> &PluginMetadata {
129 &self.metadata
130 }
131
132 fn hooks(&self) -> &PluginHooks {
133 &self.hooks
134 }
135
136 fn lifecycle(&self) -> &PluginLifecycle {
137 &self.lifecycle
138 }
139
140 fn tools(&self) -> &[PluginTool] {
141 &self.tools
142 }
143
144 fn validate(&self) -> Result<(), PluginError> {
145 validate_hook_paths(self.metadata.root.as_deref(), &self.hooks)?;
146 validate_lifecycle_paths(self.metadata.root.as_deref(), &self.lifecycle)?;
147 validate_tool_paths(self.metadata.root.as_deref(), &self.tools)
148 }
149
150 fn initialize(&self) -> Result<(), PluginError> {
151 run_lifecycle_commands(
152 self.metadata(),
153 self.lifecycle(),
154 "init",
155 &self.lifecycle.init,
156 )
157 }
158
159 fn shutdown(&self) -> Result<(), PluginError> {
160 run_lifecycle_commands(
161 self.metadata(),
162 self.lifecycle(),
163 "shutdown",
164 &self.lifecycle.shutdown,
165 )
166 }
167}
168
169impl Plugin for ExternalPlugin {
170 fn metadata(&self) -> &PluginMetadata {
171 &self.metadata
172 }
173
174 fn hooks(&self) -> &PluginHooks {
175 &self.hooks
176 }
177
178 fn lifecycle(&self) -> &PluginLifecycle {
179 &self.lifecycle
180 }
181
182 fn tools(&self) -> &[PluginTool] {
183 &self.tools
184 }
185
186 fn validate(&self) -> Result<(), PluginError> {
187 validate_hook_paths(self.metadata.root.as_deref(), &self.hooks)?;
188 validate_lifecycle_paths(self.metadata.root.as_deref(), &self.lifecycle)?;
189 validate_tool_paths(self.metadata.root.as_deref(), &self.tools)
190 }
191
192 fn initialize(&self) -> Result<(), PluginError> {
193 run_lifecycle_commands(
194 self.metadata(),
195 self.lifecycle(),
196 "init",
197 &self.lifecycle.init,
198 )
199 }
200
201 fn shutdown(&self) -> Result<(), PluginError> {
202 run_lifecycle_commands(
203 self.metadata(),
204 self.lifecycle(),
205 "shutdown",
206 &self.lifecycle.shutdown,
207 )
208 }
209}