Skip to main content

oxi/extensions/
registry.rs

1//! Extension registry and runner.
2//!
3//! Manages in-process extensions: registration, lifecycle hooks, and event dispatch.
4//! Extensions implement the `Extension` trait and are registered via
5//! `ExtensionRegistry::register()` at compile time or runtime.
6
7#![allow(unused)]
8
9use crate::extensions::types::{
10    AgentEvent, AgentToolResult, BashEvent, BeforeProviderRequestEvent, Command, ContextEmitResult,
11    ContextEvent, ExtensionError, ExtensionErrorListener, ExtensionErrorRecord, ExtensionManifest,
12    ExtensionState, InputEvent, InputEventResult, ModelSelectEvent, ProviderRequestEmitResult,
13    SessionBeforeCompactEvent, SessionBeforeEmitResult, SessionBeforeForkEvent,
14    SessionBeforeSwitchEvent, SessionBeforeTreeEvent, SessionCompactEvent, SessionShutdownEvent,
15    SessionTreeEvent, ThinkingLevelSelectEvent, ToolCallEmitResult, ToolResultEmitResult,
16};
17
18use crate::CompactionContext;
19use crate::extensions::Extension;
20use crate::extensions::context::ExtensionContext;
21use crate::store::settings::Settings;
22
23use parking_lot::RwLock;
24use serde_json::Value;
25use std::collections::HashMap;
26use std::fmt;
27use std::future::Future;
28use std::path::PathBuf;
29use std::pin::Pin;
30use std::sync::Arc;
31
32type ToolType = dyn oxi_agent::AgentTool;
33type ToolArc = Arc<ToolType>;
34
35// ═══════════════════════════════════════════════════════════════════════════
36// Extension Registry
37// ═══════════════════════════════════════════════════════════════════════════
38
39struct LoadedExtension {
40    extension: Arc<dyn Extension>,
41    enabled: bool,
42}
43
44/// pub.
45pub struct ExtensionRegistry {
46    entries: HashMap<String, LoadedExtension>,
47    errors: Arc<RwLock<Vec<ExtensionErrorRecord>>>,
48}
49
50impl Default for ExtensionRegistry {
51    fn default() -> Self {
52        Self::new()
53    }
54}
55
56impl ExtensionRegistry {
57    /// Create a new empty extension registry.
58    pub fn new() -> Self {
59        Self {
60            entries: HashMap::new(),
61            errors: Arc::new(RwLock::new(Vec::new())),
62        }
63    }
64
65    /// Register an extension.
66    pub fn register(&mut self, ext: Arc<dyn Extension>) {
67        let name = ext.name().to_string();
68        tracing::info!(name = %name, "extension registered");
69        self.entries.insert(
70            name,
71            LoadedExtension {
72                extension: ext,
73                enabled: true,
74            },
75        );
76    }
77
78    /// Unregister an extension by name.
79    pub fn unregister(&mut self, name: &str) -> bool {
80        if let Some(entry) = self.entries.remove(name) {
81            self.call_hook_safe(name, "on_unload", || {
82                entry.extension.on_unload();
83            });
84            tracing::info!(name = %name, "extension unregistered");
85            true
86        } else {
87            false
88        }
89    }
90
91    /// Disable an extension (keeps it registered but inactive).
92    pub fn disable(&mut self, name: &str) -> Result<(), ExtensionError> {
93        let ext = {
94            let entry = self
95                .entries
96                .get_mut(name)
97                .ok_or_else(|| ExtensionError::NotFound {
98                    name: name.to_string(),
99                })?;
100            if !entry.enabled {
101                return Ok(());
102            }
103            entry.enabled = false;
104            Arc::clone(&entry.extension)
105        };
106        self.call_hook_safe(name, "on_unload", || {
107            ext.on_unload();
108        });
109        tracing::info!(name = %name, "extension disabled");
110        Ok(())
111    }
112
113    /// Enable a previously disabled extension.
114    pub fn enable(&mut self, name: &str, ctx: &ExtensionContext) -> Result<(), ExtensionError> {
115        let ext = {
116            let entry = self
117                .entries
118                .get_mut(name)
119                .ok_or_else(|| ExtensionError::NotFound {
120                    name: name.to_string(),
121                })?;
122            if entry.enabled {
123                return Ok(());
124            }
125            entry.enabled = true;
126            Arc::clone(&entry.extension)
127        };
128        self.call_hook_safe(name, "on_load", || {
129            ext.on_load(ctx);
130        });
131        tracing::info!(name = %name, "extension enabled");
132        Ok(())
133    }
134
135    /// Check if an extension is enabled.
136    pub fn is_enabled(&self, name: &str) -> bool {
137        self.entries.get(name).map(|e| e.enabled).unwrap_or(false)
138    }
139
140    /// Collect all tools from all enabled extensions.
141    pub fn all_tools(&self) -> Vec<ToolArc> {
142        self.entries
143            .values()
144            .filter(|e| e.enabled)
145            .flat_map(|e| e.extension.register_tools())
146            .collect()
147    }
148
149    /// Collect all commands from all enabled extensions.
150    pub fn all_commands(&self) -> Vec<Command> {
151        self.entries
152            .values()
153            .filter(|e| e.enabled)
154            .flat_map(|e| e.extension.register_commands())
155            .collect()
156    }
157
158    /// Emit on_load to all enabled extensions.
159    pub fn emit_load(&self, ctx: &ExtensionContext) {
160        for entry in self.entries.values().filter(|e| e.enabled) {
161            let name = entry.extension.name();
162            self.call_hook_safe(name, "on_load", || {
163                entry.extension.on_load(ctx);
164            });
165        }
166    }
167
168    /// Emit on_unload to all enabled extensions.
169    pub fn emit_unload(&self) {
170        for entry in self.entries.values().filter(|e| e.enabled) {
171            let name = entry.extension.name();
172            self.call_hook_safe(name, "on_unload", || {
173                entry.extension.on_unload();
174            });
175        }
176    }
177
178    /// Emit on_message_sent to all enabled extensions.
179    pub fn emit_message_sent(&self, msg: &str) {
180        for entry in self.entries.values().filter(|e| e.enabled) {
181            let name = entry.extension.name();
182            self.call_hook_safe(name, "on_message_sent", || {
183                entry.extension.on_message_sent(msg);
184            });
185        }
186    }
187
188    /// Emit on_message_received to all enabled extensions.
189    pub fn emit_message_received(&self, msg: &str) {
190        for entry in self.entries.values().filter(|e| e.enabled) {
191            let name = entry.extension.name();
192            self.call_hook_safe(name, "on_message_received", || {
193                entry.extension.on_message_received(msg);
194            });
195        }
196    }
197
198    /// Emit on_tool_call to all enabled extensions.
199    pub fn emit_tool_call(&self, tool: &str, params: &Value) {
200        for entry in self.entries.values().filter(|e| e.enabled) {
201            let name = entry.extension.name();
202            self.call_hook_safe(name, "on_tool_call", || {
203                entry.extension.on_tool_call(tool, params);
204            });
205        }
206    }
207
208    /// Emit on_tool_result to all enabled extensions.
209    pub fn emit_tool_result(&self, tool: &str, result: &AgentToolResult) {
210        for entry in self.entries.values().filter(|e| e.enabled) {
211            let name = entry.extension.name();
212            self.call_hook_safe(name, "on_tool_result", || {
213                entry.extension.on_tool_result(tool, result);
214            });
215        }
216    }
217
218    /// Emit on_session_start to all enabled extensions.
219    pub fn emit_session_start(&self, session_id: &str) {
220        for entry in self.entries.values().filter(|e| e.enabled) {
221            let name = entry.extension.name();
222            self.call_hook_safe(name, "on_session_start", || {
223                entry.extension.on_session_start(session_id);
224            });
225        }
226    }
227
228    /// Emit on_session_end to all enabled extensions.
229    pub fn emit_session_end(&self, session_id: &str) {
230        for entry in self.entries.values().filter(|e| e.enabled) {
231            let name = entry.extension.name();
232            self.call_hook_safe(name, "on_session_end", || {
233                entry.extension.on_session_end(session_id);
234            });
235        }
236    }
237
238    /// Emit on_settings_changed to all enabled extensions.
239    pub fn emit_settings_changed(&self, settings: &Settings) {
240        for entry in self.entries.values().filter(|e| e.enabled) {
241            let name = entry.extension.name();
242            self.call_hook_safe(name, "on_settings_changed", || {
243                entry.extension.on_settings_changed(settings);
244            });
245        }
246    }
247
248    /// Emit on_event to all enabled extensions.
249    pub fn emit_event(&self, event: &AgentEvent) {
250        for entry in self.entries.values().filter(|e| e.enabled) {
251            let name = entry.extension.name();
252            self.call_hook_safe(name, "on_event", || {
253                entry.extension.on_event(event);
254            });
255        }
256    }
257
258    /// Emit on_error to all enabled extensions.
259    pub fn emit_error(&self, error: &anyhow::Error) -> Vec<(String, anyhow::Error)> {
260        let mut errors = Vec::new();
261        for entry in self.entries.values().filter(|e| e.enabled) {
262            let name = entry.extension.name();
263            if let Err(e) = entry.extension.on_error(error) {
264                tracing::warn!(extension = name, error = %e, "on_error hook failed");
265                errors.push((name.to_string(), e));
266            }
267        }
268        errors
269    }
270
271    /// Emit session_shutdown to all enabled extensions.
272    pub fn emit_session_shutdown(&self, event: &SessionShutdownEvent) {
273        for entry in self.entries.values().filter(|e| e.enabled) {
274            let name = entry.extension.name();
275            self.call_hook_safe(name, "session_shutdown", || {
276                entry.extension.session_shutdown(event);
277            });
278        }
279    }
280
281    /// Get an extension by name.
282    pub fn get(&self, name: &str) -> Option<Arc<dyn Extension>> {
283        self.entries.get(name).map(|e| Arc::clone(&e.extension))
284    }
285
286    /// Get all extension names.
287    pub fn names(&self) -> impl Iterator<Item = &str> {
288        self.entries.keys().map(|s| s.as_str())
289    }
290
291    /// Iterate over all extensions.
292    pub fn extensions(&self) -> impl Iterator<Item = &Arc<dyn Extension>> {
293        self.entries.values().map(|e| &e.extension)
294    }
295
296    /// Get manifest for an extension.
297    pub fn manifest(&self, name: &str) -> Option<ExtensionManifest> {
298        self.entries.get(name).map(|e| e.extension.manifest())
299    }
300
301    /// Number of registered extensions.
302    pub fn len(&self) -> usize {
303        self.entries.len()
304    }
305    /// Whether any extensions are registered.
306    pub fn is_empty(&self) -> bool {
307        self.entries.is_empty()
308    }
309    /// Get all recorded errors.
310    pub fn errors(&self) -> Vec<ExtensionErrorRecord> {
311        self.errors.read().clone()
312    }
313    /// Clear recorded errors.
314    pub fn clear_errors(&self) {
315        self.errors.write().clear();
316    }
317
318    fn call_hook_safe<F>(&self, ext_name: &str, hook: &str, f: F)
319    where
320        F: FnOnce(),
321    {
322        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
323        if let Err(payload) = result {
324            let msg = if let Some(s) = payload.downcast_ref::<&str>() {
325                s.to_string()
326            } else if let Some(s) = payload.downcast_ref::<String>() {
327                s.clone()
328            } else {
329                "unknown panic".to_string()
330            };
331            tracing::error!(extension = ext_name, hook = hook, error = %msg, "Extension hook panicked");
332            self.errors.write().push(ExtensionErrorRecord::new(
333                ext_name,
334                hook,
335                format!("panic: {}", msg),
336            ));
337        }
338    }
339}
340
341impl fmt::Debug for ExtensionRegistry {
342    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343        f.debug_struct("ExtensionRegistry")
344            .field("count", &self.entries.len())
345            .finish()
346    }
347}
348
349// ═══════════════════════════════════════════════════════════════════════════
350// Extension Runner
351// ═══════════════════════════════════════════════════════════════════════════
352
353/// Orchestrates extension lifecycle and event dispatch.
354pub struct ExtensionRunner {
355    registry: ExtensionRegistry,
356    states: HashMap<String, ExtensionState>,
357    order: Vec<String>,
358    error_listeners: Vec<Arc<ExtensionErrorListener>>,
359    cwd: PathBuf,
360}
361
362impl Default for ExtensionRunner {
363    fn default() -> Self {
364        Self::new(PathBuf::from("."))
365    }
366}
367
368impl ExtensionRunner {
369    /// Create a new extension runner for the given working directory.
370    pub fn new(cwd: PathBuf) -> Self {
371        Self {
372            registry: ExtensionRegistry::new(),
373            states: HashMap::new(),
374            order: Vec::new(),
375            error_listeners: Vec::new(),
376            cwd,
377        }
378    }
379
380    /// Register an error listener and return a handle for unregistering.
381    pub fn on_error<F>(&mut self, listener: F) -> ExtensionErrorHandle
382    where
383        F: Fn(&ExtensionErrorRecord) + Send + Sync + 'static,
384    {
385        let arc: Arc<ExtensionErrorListener> = Arc::new(listener);
386        self.error_listeners.push(Arc::clone(&arc));
387        ExtensionErrorHandle {
388            listener: Some(arc),
389        }
390    }
391
392    /// Emit an error record to all listeners.
393    pub fn emit_error_record(&self, record: ExtensionErrorRecord) {
394        for listener in &self.error_listeners {
395            let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
396                listener(&record);
397            }));
398        }
399        self.registry.errors.write().push(record);
400    }
401
402    /// Register an extension into the runner.
403    pub fn register(&mut self, ext: Arc<dyn Extension>, ctx: &ExtensionContext) {
404        let name = ext.name().to_string();
405        self.registry.register(ext);
406        self.set_state(&name, ExtensionState::Active);
407        self.registry.call_hook_safe(&name, "on_load", || {
408            if let Some(e) = self.registry.get(&name) {
409                e.on_load(ctx);
410            }
411        });
412    }
413
414    /// Unregister an extension by name.
415    pub fn unload_extension(&mut self, name: &str) -> bool {
416        let had = self.registry.unregister(name);
417        if had {
418            self.set_state(name, ExtensionState::Unloaded);
419            tracing::info!(name = %name, "extension unloaded");
420        }
421        had
422    }
423
424    fn set_state(&mut self, name: &str, state: ExtensionState) {
425        self.states.insert(name.to_string(), state);
426        if state == ExtensionState::Active && !self.order.contains(&name.to_string()) {
427            self.order.push(name.to_string());
428        }
429        if state == ExtensionState::Unloaded {
430            self.order.retain(|n| n != name);
431        }
432    }
433
434    /// Get the state of an extension.
435    pub fn state(&self, name: &str) -> ExtensionState {
436        self.states
437            .get(name)
438            .copied()
439            .unwrap_or(ExtensionState::Unloaded)
440    }
441    /// Get all extension states.
442    pub fn states(&self) -> &HashMap<String, ExtensionState> {
443        &self.states
444    }
445    /// Get the extension execution order.
446    pub fn extension_order(&self) -> &[String] {
447        &self.order
448    }
449
450    /// Disable an extension.
451    pub fn disable(&mut self, name: &str) -> Result<(), ExtensionError> {
452        self.registry.disable(name)?;
453        self.set_state(name, ExtensionState::Disabled);
454        Ok(())
455    }
456    /// Enable an extension.
457    pub fn enable(&mut self, name: &str, ctx: &ExtensionContext) -> Result<(), ExtensionError> {
458        self.registry.enable(name, ctx)?;
459        self.set_state(name, ExtensionState::Active);
460        Ok(())
461    }
462    /// Check if an extension is enabled.
463    pub fn is_enabled(&self, name: &str) -> bool {
464        self.registry.is_enabled(name)
465    }
466
467    /// Whether any handlers exist for an event type.
468    pub fn has_handlers(&self, _event_type: &str) -> bool {
469        self.has_enabled_extensions()
470    }
471    /// Whether any extensions are enabled.
472    pub fn has_enabled_extensions(&self) -> bool {
473        self.registry.extensions().any(|_| true)
474            && self
475                .order
476                .iter()
477                .any(|name| self.state(name) == ExtensionState::Active)
478    }
479
480    /// Collect all tools from enabled extensions.
481    pub fn all_tools(&self) -> Vec<ToolArc> {
482        let mut tools = Vec::new();
483        for name in &self.order {
484            if self.state(name) != ExtensionState::Active {
485                continue;
486            }
487            if let Some(ext) = self.registry.get(name) {
488                tools.extend(ext.register_tools());
489            }
490        }
491        tools
492    }
493
494    /// Collect all commands from enabled extensions.
495    pub fn all_commands(&self) -> Vec<Command> {
496        let mut commands = Vec::new();
497        for name in &self.order {
498            if self.state(name) != ExtensionState::Active {
499                continue;
500            }
501            if let Some(ext) = self.registry.get(name) {
502                commands.extend(ext.register_commands());
503            }
504        }
505        commands
506    }
507
508    /// Wrap a tool with extension error handling.
509    pub fn wrap_tool(&self, tool: ToolArc) -> ToolArc {
510        Arc::new(WrappedTool {
511            inner: tool,
512            runner_state: Arc::new(RwLock::new(RunnerState {
513                errors: self.registry.errors.clone(),
514                error_listeners: self.error_listeners.clone(),
515            })),
516        })
517    }
518
519    /// Wrap multiple tools with extension error handling.
520    pub fn wrap_tools(&self, tools: Vec<ToolArc>) -> Vec<ToolArc> {
521        tools.into_iter().map(|t| self.wrap_tool(t)).collect()
522    }
523
524    /// Emit before_tool_call and on_tool_call events.
525    pub fn emit_tool_call(&self, tool_name: &str, params: &Value) -> ToolCallEmitResult {
526        let mut result = ToolCallEmitResult::default();
527        for name in &self.order {
528            if self.state(name) != ExtensionState::Active {
529                continue;
530            }
531            if let Some(ext) = self.registry.get(name) {
532                if let Err(e) = ext.on_before_tool_call(tool_name, params) {
533                    let err_str = e.to_string();
534                    result.errors.push((name.clone(), err_str.clone()));
535                    self.emit_error_record(ExtensionErrorRecord::new(
536                        name,
537                        "on_before_tool_call",
538                        &err_str,
539                    ));
540                }
541                self.registry.call_hook_safe(name, "on_tool_call", || {
542                    ext.on_tool_call(tool_name, params);
543                });
544            }
545        }
546        result
547    }
548
549    /// Emit after_tool_call and on_tool_result events.
550    pub fn emit_tool_result_event(
551        &self,
552        tool_name: &str,
553        tool_result: &AgentToolResult,
554    ) -> ToolResultEmitResult {
555        let mut result = ToolResultEmitResult::default();
556        for name in &self.order {
557            if self.state(name) != ExtensionState::Active {
558                continue;
559            }
560            if let Some(ext) = self.registry.get(name) {
561                if let Err(e) = ext.on_after_tool_call(tool_name, tool_result) {
562                    result.errors.push((name.clone(), e.to_string()));
563                }
564                self.registry.call_hook_safe(name, "on_tool_result", || {
565                    ext.on_tool_result(tool_name, tool_result);
566                });
567            }
568        }
569        result
570    }
571
572    /// Emit input event through extensions.
573    pub fn emit_input_event(&self, event: &mut InputEvent) -> InputEventResult {
574        let mut final_result = InputEventResult::Continue;
575        for name in &self.order {
576            if self.state(name) != ExtensionState::Active {
577                continue;
578            }
579            if let Some(ext) = self.registry.get(name) {
580                let result =
581                    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| ext.input(event)));
582                match result {
583                    Ok(InputEventResult::Handled) => return InputEventResult::Handled,
584                    Ok(InputEventResult::Transform { text }) => {
585                        event.text = text.clone();
586                        final_result = InputEventResult::Transform { text };
587                    }
588                    Ok(InputEventResult::Continue) => {}
589                    Err(payload) => {
590                        let msg = if let Some(s) = payload.downcast_ref::<&str>() {
591                            s.to_string()
592                        } else if let Some(s) = payload.downcast_ref::<String>() {
593                            s.clone()
594                        } else {
595                            "unknown panic".to_string()
596                        };
597                        self.emit_error_record(ExtensionErrorRecord::new(
598                            name,
599                            "input",
600                            format!("panic: {}", msg),
601                        ));
602                    }
603                }
604            }
605        }
606        final_result
607    }
608
609    /// Emit context event (message modification).
610    pub fn emit_context_event(&self, messages: Vec<oxi_sdk::Message>) -> ContextEmitResult {
611        let mut current_messages = messages;
612        let mut errors = Vec::new();
613        let mut modified = false;
614        for name in &self.order {
615            if self.state(name) != ExtensionState::Active {
616                continue;
617            }
618            if let Some(ext) = self.registry.get(name) {
619                let prev_len = current_messages.len();
620                let mut event = ContextEvent {
621                    messages: current_messages.clone(),
622                };
623                if let Err(e) = ext.context(&mut event) {
624                    errors.push((name.clone(), e.to_string()));
625                } else if event.messages.len() != prev_len {
626                    current_messages = event.messages;
627                    modified = true;
628                }
629            }
630        }
631        ContextEmitResult {
632            modified,
633            messages: current_messages,
634            errors,
635        }
636    }
637
638    /// Emit before_provider_request event.
639    pub fn emit_before_provider_request_event(&self, payload: Value) -> ProviderRequestEmitResult {
640        let mut current_payload = payload;
641        let mut modified = false;
642        let mut errors = Vec::new();
643        for name in &self.order {
644            if self.state(name) != ExtensionState::Active {
645                continue;
646            }
647            if let Some(ext) = self.registry.get(name) {
648                let mut event = BeforeProviderRequestEvent {
649                    payload: current_payload.clone(),
650                };
651                if let Err(e) = ext.before_provider_request(&mut event) {
652                    errors.push((name.clone(), e.to_string()));
653                } else if event.payload != current_payload {
654                    current_payload = event.payload;
655                    modified = true;
656                }
657            }
658        }
659        ProviderRequestEmitResult {
660            modified,
661            payload: current_payload,
662            errors,
663        }
664    }
665
666    /// Emit session_before_switch event.
667    pub fn emit_session_before_switch_event(
668        &self,
669        event: &SessionBeforeSwitchEvent,
670    ) -> SessionBeforeEmitResult {
671        let mut result = SessionBeforeEmitResult::default();
672        for name in &self.order {
673            if self.state(name) != ExtensionState::Active {
674                continue;
675            }
676            if let Some(ext) = self.registry.get(name)
677                && let Err(e) = ext.session_before_switch(event)
678            {
679                result.cancelled = true;
680                result.cancelled_by = Some(name.clone());
681                result.errors.push((name.clone(), e.to_string()));
682                return result;
683            }
684        }
685        result
686    }
687
688    /// Emit session_before_fork event.
689    pub fn emit_session_before_fork_event(
690        &self,
691        event: &SessionBeforeForkEvent,
692    ) -> SessionBeforeEmitResult {
693        let mut result = SessionBeforeEmitResult::default();
694        for name in &self.order {
695            if self.state(name) != ExtensionState::Active {
696                continue;
697            }
698            if let Some(ext) = self.registry.get(name)
699                && let Err(e) = ext.session_before_fork(event)
700            {
701                result.cancelled = true;
702                result.cancelled_by = Some(name.clone());
703                result.errors.push((name.clone(), e.to_string()));
704                return result;
705            }
706        }
707        result
708    }
709
710    /// Emit session_before_compact event.
711    pub fn emit_session_before_compact_event(
712        &self,
713        event: &SessionBeforeCompactEvent,
714    ) -> SessionBeforeEmitResult {
715        let mut result = SessionBeforeEmitResult::default();
716        for name in &self.order {
717            if self.state(name) != ExtensionState::Active {
718                continue;
719            }
720            if let Some(ext) = self.registry.get(name)
721                && let Err(e) = ext.session_before_compact(event)
722            {
723                result.cancelled = true;
724                result.cancelled_by = Some(name.clone());
725                result.errors.push((name.clone(), e.to_string()));
726                return result;
727            }
728        }
729        result
730    }
731
732    /// Emit session_before_tree event.
733    pub fn emit_session_before_tree_event(
734        &self,
735        event: &SessionBeforeTreeEvent,
736    ) -> SessionBeforeEmitResult {
737        let mut result = SessionBeforeEmitResult::default();
738        for name in &self.order {
739            if self.state(name) != ExtensionState::Active {
740                continue;
741            }
742            if let Some(ext) = self.registry.get(name)
743                && let Err(e) = ext.session_before_tree(event)
744            {
745                result.cancelled = true;
746                result.cancelled_by = Some(name.clone());
747                result.errors.push((name.clone(), e.to_string()));
748                return result;
749            }
750        }
751        result
752    }
753
754    /// Emit session_shutdown event.
755    pub fn emit_session_shutdown_event(&self, event: &SessionShutdownEvent) -> bool {
756        if !self.has_enabled_extensions() {
757            return false;
758        }
759        self.registry.emit_session_shutdown(event);
760        true
761    }
762
763    /// Emit a generic agent event.
764    pub fn emit_event(&self, event: &AgentEvent) {
765        self.registry.emit_event(event);
766    }
767
768    /// Access the underlying registry.
769    pub fn registry(&self) -> &ExtensionRegistry {
770        &self.registry
771    }
772    /// Access the underlying registry mutably.
773    pub fn registry_mut(&mut self) -> &mut ExtensionRegistry {
774        &mut self.registry
775    }
776    /// Get an extension by name.
777    pub fn get(&self, name: &str) -> Option<Arc<dyn Extension>> {
778        self.registry.get(name)
779    }
780    /// Get all extension names in execution order.
781    pub fn names(&self) -> impl Iterator<Item = &str> {
782        self.order.iter().map(|s| s.as_str())
783    }
784    /// Number of extensions.
785    pub fn len(&self) -> usize {
786        self.order.len()
787    }
788    /// Whether any extensions exist.
789    pub fn is_empty(&self) -> bool {
790        self.order.is_empty()
791    }
792    /// Get all recorded errors.
793    pub fn errors(&self) -> Vec<ExtensionErrorRecord> {
794        self.registry.errors()
795    }
796    /// Clear recorded errors.
797    pub fn clear_errors(&self) {
798        self.registry.clear_errors();
799    }
800}
801
802impl fmt::Debug for ExtensionRunner {
803    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
804        f.debug_struct("ExtensionRunner")
805            .field("cwd", &self.cwd)
806            .field("extensions", &self.order)
807            .finish()
808    }
809}
810
811/// Handle returned by [`ExtensionRunner::on_error`] for unregistering a listener.
812pub struct ExtensionErrorHandle {
813    listener: Option<Arc<ExtensionErrorListener>>,
814}
815impl ExtensionErrorHandle {
816    /// Remove and return the listener, preventing further error notifications.
817    pub fn unregister(&mut self) -> Option<Arc<ExtensionErrorListener>> {
818        self.listener.take()
819    }
820}
821impl Drop for ExtensionErrorHandle {
822    fn drop(&mut self) {}
823}
824
825struct RunnerState {
826    errors: Arc<RwLock<Vec<ExtensionErrorRecord>>>,
827    error_listeners: Vec<Arc<ExtensionErrorListener>>,
828}
829struct WrappedTool {
830    inner: ToolArc,
831    runner_state: Arc<RwLock<RunnerState>>,
832}
833
834impl oxi_agent::AgentTool for WrappedTool {
835    fn name(&self) -> &str {
836        self.inner.name()
837    }
838    fn label(&self) -> &str {
839        self.inner.label()
840    }
841    fn description(&self) -> &str {
842        self.inner.description()
843    }
844    fn parameters_schema(&self) -> Value {
845        self.inner.parameters_schema()
846    }
847    fn execute<'a>(
848        &'a self,
849        tool_call_id: &'a str,
850        params: Value,
851        signal: Option<tokio::sync::oneshot::Receiver<()>>,
852        ctx: &'a oxi_agent::ToolContext,
853    ) -> Pin<Box<dyn Future<Output = Result<AgentToolResult, oxi_agent::ToolError>> + Send + 'a>>
854    {
855        Box::pin(async move { self.inner.execute(tool_call_id, params, signal, ctx).await })
856    }
857}