aidale_core/
plugin.rs

1//! Plugin system for runtime-level extensibility.
2
3use crate::error::AiError;
4use crate::provider::TextStream;
5use crate::types::*;
6use async_trait::async_trait;
7use std::fmt::Debug;
8use std::sync::Arc;
9
10/// Plugin execution phase
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum PluginPhase {
13    /// Execute before normal plugins
14    Pre,
15    /// Execute in normal order
16    Normal,
17    /// Execute after normal plugins
18    Post,
19}
20
21/// Plugin trait for runtime-level hooks.
22///
23/// Plugins provide application-level functionality that works across
24/// multiple providers and models. Unlike layers which wrap providers,
25/// plugins hook into the runtime execution flow.
26#[async_trait]
27pub trait Plugin: Send + Sync + Debug + 'static {
28    /// Plugin name
29    fn name(&self) -> &str;
30
31    /// Plugin execution phase
32    fn enforce(&self) -> PluginPhase {
33        PluginPhase::Normal
34    }
35
36    // ==================== First Hooks ====================
37    // These hooks execute until the first plugin returns Some.
38    // Only the first non-None result is used.
39
40    /// Resolve model ID
41    ///
42    /// Allows plugins to intercept and modify model selection.
43    async fn resolve_model(
44        &self,
45        _model_id: &str,
46        _ctx: &RequestContext,
47    ) -> Result<Option<String>, AiError> {
48        Ok(None)
49    }
50
51    /// Load template
52    ///
53    /// Allows plugins to provide message templates.
54    async fn load_template(
55        &self,
56        _template_name: &str,
57        _ctx: &RequestContext,
58    ) -> Result<Option<Vec<Message>>, AiError> {
59        Ok(None)
60    }
61
62    // ==================== Sequential Hooks ====================
63    // These hooks execute in sequence, with each plugin transforming
64    // the result of the previous one.
65
66    /// Transform request parameters
67    ///
68    /// Plugins can modify parameters before sending to the provider.
69    async fn transform_params(
70        &self,
71        params: TextParams,
72        _ctx: &RequestContext,
73    ) -> Result<TextParams, AiError> {
74        Ok(params)
75    }
76
77    /// Transform result
78    ///
79    /// Plugins can modify the result after receiving from the provider.
80    async fn transform_result(
81        &self,
82        result: TextResult,
83        _ctx: &RequestContext,
84    ) -> Result<TextResult, AiError> {
85        Ok(result)
86    }
87
88    // ==================== Parallel Hooks ====================
89    // These hooks execute concurrently and are used for side effects.
90
91    /// Hook called when a request starts
92    async fn on_request_start(&self, _ctx: &RequestContext) -> Result<(), AiError> {
93        Ok(())
94    }
95
96    /// Hook called when a request ends successfully
97    async fn on_request_end(
98        &self,
99        _ctx: &RequestContext,
100        _result: &TextResult,
101    ) -> Result<(), AiError> {
102        Ok(())
103    }
104
105    /// Hook called when an error occurs
106    async fn on_error(&self, _error: &AiError, _ctx: &RequestContext) -> Result<(), AiError> {
107        Ok(())
108    }
109
110    // ==================== Stream Hooks ====================
111    // These hooks transform streaming responses.
112
113    /// Transform text stream
114    ///
115    /// Plugins can wrap or modify the stream of text chunks.
116    fn transform_stream(&self, stream: Box<TextStream>) -> Box<TextStream> {
117        stream
118    }
119}
120
121/// Plugin execution engine.
122///
123/// Manages plugin lifecycle and execution order.
124#[derive(Debug, Clone)]
125pub struct PluginEngine {
126    plugins: Vec<Arc<dyn Plugin>>,
127}
128
129impl PluginEngine {
130    /// Create a new plugin engine
131    pub fn new(mut plugins: Vec<Arc<dyn Plugin>>) -> Self {
132        // Sort plugins by phase
133        plugins.sort_by_key(|p| match p.enforce() {
134            PluginPhase::Pre => 0,
135            PluginPhase::Normal => 1,
136            PluginPhase::Post => 2,
137        });
138
139        Self { plugins }
140    }
141
142    /// Get all plugins
143    pub fn plugins(&self) -> &[Arc<dyn Plugin>] {
144        &self.plugins
145    }
146
147    // ==================== First Hook Execution ====================
148
149    /// Run a first hook (returns on first Some)
150    pub async fn resolve_model(
151        &self,
152        model_id: &str,
153        ctx: &RequestContext,
154    ) -> Result<String, AiError> {
155        for plugin in &self.plugins {
156            if let Some(resolved) = plugin.resolve_model(model_id, ctx).await? {
157                return Ok(resolved);
158            }
159        }
160        Ok(model_id.to_string())
161    }
162
163    /// Load template
164    pub async fn load_template(
165        &self,
166        template_name: &str,
167        ctx: &RequestContext,
168    ) -> Result<Option<Vec<Message>>, AiError> {
169        for plugin in &self.plugins {
170            if let Some(messages) = plugin.load_template(template_name, ctx).await? {
171                return Ok(Some(messages));
172            }
173        }
174        Ok(None)
175    }
176
177    // ==================== Sequential Hook Execution ====================
178
179    /// Run sequential transform_params hooks
180    pub async fn transform_params(
181        &self,
182        mut params: TextParams,
183        ctx: &RequestContext,
184    ) -> Result<TextParams, AiError> {
185        for plugin in &self.plugins {
186            params = plugin.transform_params(params, ctx).await?;
187        }
188        Ok(params)
189    }
190
191    /// Run sequential transform_result hooks
192    pub async fn transform_result(
193        &self,
194        mut result: TextResult,
195        ctx: &RequestContext,
196    ) -> Result<TextResult, AiError> {
197        for plugin in &self.plugins {
198            result = plugin.transform_result(result, ctx).await?;
199        }
200        Ok(result)
201    }
202
203    // ==================== Parallel Hook Execution ====================
204
205    /// Run parallel on_request_start hooks
206    pub async fn on_request_start(&self, ctx: &RequestContext) -> Result<(), AiError> {
207        use futures::future::try_join_all;
208
209        let futures = self
210            .plugins
211            .iter()
212            .map(|p| p.on_request_start(ctx))
213            .collect::<Vec<_>>();
214
215        try_join_all(futures).await?;
216        Ok(())
217    }
218
219    /// Run parallel on_request_end hooks
220    pub async fn on_request_end(
221        &self,
222        ctx: &RequestContext,
223        result: &TextResult,
224    ) -> Result<(), AiError> {
225        use futures::future::try_join_all;
226
227        let futures = self
228            .plugins
229            .iter()
230            .map(|p| p.on_request_end(ctx, result))
231            .collect::<Vec<_>>();
232
233        try_join_all(futures).await?;
234        Ok(())
235    }
236
237    /// Run parallel on_error hooks
238    pub async fn on_error(&self, error: &AiError, ctx: &RequestContext) -> Result<(), AiError> {
239        use futures::future::try_join_all;
240
241        let futures = self
242            .plugins
243            .iter()
244            .map(|p| p.on_error(error, ctx))
245            .collect::<Vec<_>>();
246
247        try_join_all(futures).await?;
248        Ok(())
249    }
250
251    // ==================== Stream Hook Execution ====================
252
253    /// Apply stream transformations
254    pub fn apply_stream_transforms(&self, stream: Box<TextStream>) -> Box<TextStream> {
255        self.plugins
256            .iter()
257            .fold(stream, |stream, plugin| plugin.transform_stream(stream))
258    }
259}
260
261impl Default for PluginEngine {
262    fn default() -> Self {
263        Self::new(Vec::new())
264    }
265}