Skip to main content

lash_core/plugin/
registrar.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::sync::Arc;
3
4use super::*;
5
6#[derive(Clone)]
7pub(crate) struct RegisteredHook<T> {
8    pub(crate) plugin_id: String,
9    pub(crate) hook: T,
10}
11
12#[derive(Clone)]
13pub(crate) struct RegisteredExclusiveHook<T> {
14    pub(crate) plugin_id: String,
15    pub(crate) hook: T,
16}
17
18pub(crate) fn current_registration_owner(registering_plugin_id: &Option<String>) -> String {
19    registering_plugin_id
20        .clone()
21        .unwrap_or_else(|| "__unknown__".to_string())
22}
23
24fn push_registered_hook<T>(
25    hooks: &mut Vec<RegisteredHook<T>>,
26    registering_plugin_id: &Option<String>,
27    hook: T,
28) {
29    hooks.push(RegisteredHook {
30        plugin_id: current_registration_owner(registering_plugin_id),
31        hook,
32    });
33}
34
35fn push_prioritized_registered_hook<T>(
36    hooks: &mut Vec<(i32, RegisteredHook<T>)>,
37    registering_plugin_id: &Option<String>,
38    priority: i32,
39    hook: T,
40) {
41    hooks.push((
42        priority,
43        RegisteredHook {
44            plugin_id: current_registration_owner(registering_plugin_id),
45            hook,
46        },
47    ));
48}
49
50fn exclusive_hook_owner(
51    existing_owner: Option<&str>,
52    registering_plugin_id: &Option<String>,
53    hook_kind: &str,
54    hook_name: &str,
55) -> Result<String, PluginError> {
56    let plugin_id = registering_plugin_id
57        .clone()
58        .ok_or_else(|| PluginError::Registration("missing registering plugin id".to_string()))?;
59    if let Some(existing) = existing_owner {
60        return Err(PluginError::Registration(format!(
61            "duplicate {hook_kind} for `{hook_name}`: `{plugin_id}` conflicts with `{existing}`"
62        )));
63    }
64    Ok(plugin_id)
65}
66
67fn register_singleton_hook<H>(
68    slot: &mut Option<RegisteredExclusiveHook<H>>,
69    registering_plugin_id: &Option<String>,
70    hook_kind: &str,
71    hook_name: &str,
72    hook: H,
73) -> Result<(), PluginError> {
74    let plugin_id = exclusive_hook_owner(
75        slot.as_ref()
76            .map(|registered| registered.plugin_id.as_str()),
77        registering_plugin_id,
78        hook_kind,
79        hook_name,
80    )?;
81    *slot = Some(RegisteredExclusiveHook { plugin_id, hook });
82    Ok(())
83}
84
85#[derive(Clone, Default)]
86pub(crate) struct PluginContributions {
87    pub(crate) tool_providers: Vec<Arc<dyn ToolProvider>>,
88    pub(crate) triggers: Vec<crate::TriggerEvent>,
89    pub(crate) prompt_contributors: Vec<RegisteredHook<PromptContributor>>,
90    pub(crate) tool_catalog_contributors: Vec<RegisteredHook<ToolCatalogContributor>>,
91    pub(crate) tool_discovery_contributors: Vec<RegisteredHook<ToolDiscoveryContributor>>,
92    pub(crate) before_turn_hooks: Vec<RegisteredHook<BeforeTurnHook>>,
93    pub(crate) before_tool_call_hooks: Vec<RegisteredHook<BeforeToolCallHook>>,
94    pub(crate) after_tool_call_hooks: Vec<RegisteredHook<AfterToolCallHook>>,
95    pub(crate) after_turn_hooks: Vec<RegisteredHook<AfterTurnHook>>,
96    pub(crate) checkpoint_hooks: Vec<RegisteredHook<CheckpointHook>>,
97    pub(crate) assistant_stream_hooks: Vec<RegisteredHook<AssistantStreamHook>>,
98    pub(crate) assistant_response_hooks: Vec<RegisteredHook<AssistantResponseHook>>,
99    pub(crate) tool_result_projector: Option<RegisteredExclusiveHook<ToolResultProjector>>,
100    pub(crate) runtime_event_hooks: Vec<RegisteredHook<PluginLifecycleEventHook>>,
101    pub(crate) session_config_mutators: Vec<SessionConfigMutator>,
102    pub(crate) plugin_queries: BTreeMap<String, RegisteredPluginQuery>,
103    pub(crate) plugin_commands: BTreeMap<String, RegisteredPluginCommand>,
104    pub(crate) plugin_tasks: BTreeMap<String, RegisteredPluginTask>,
105    pub(crate) turn_context_transforms: Vec<(i32, RegisteredHook<Arc<dyn TurnContextTransform>>)>,
106    pub(crate) context_compactors: Vec<(i32, RegisteredHook<Arc<dyn ContextCompactor>>)>,
107    pub(crate) protocol_session: Option<RegisteredExclusiveHook<Arc<dyn ProtocolSessionPlugin>>>,
108    pub(crate) protocol_driver: Option<RegisteredExclusiveHook<Arc<dyn ProtocolDriverPlugin>>>,
109    pub(crate) code_executor: Option<RegisteredExclusiveHook<Arc<dyn CodeExecutorPlugin>>>,
110    pub(crate) assistant_prose_projector:
111        Option<RegisteredExclusiveHook<Arc<dyn AssistantProseProjectorPlugin>>>,
112}
113
114pub struct PluginRegistrar {
115    pub(crate) tool_names: BTreeSet<String>,
116    pub(crate) contributions: PluginContributions,
117    pub(crate) registering_plugin_id: Option<String>,
118}
119
120pub struct ToolRegistrations<'a> {
121    reg: &'a mut PluginRegistrar,
122}
123
124impl ToolRegistrations<'_> {
125    pub fn provider(self, provider: Arc<dyn ToolProvider>) -> Result<(), PluginError> {
126        self.reg.add_tool_provider(provider)
127    }
128}
129
130pub struct TriggerEventRegistrations<'a> {
131    reg: &'a mut PluginRegistrar,
132}
133
134impl TriggerEventRegistrations<'_> {
135    pub fn declare(self, event: crate::TriggerEvent) -> Result<(), PluginError> {
136        self.reg.add_trigger(event)
137    }
138}
139
140pub struct PromptRegistrations<'a> {
141    reg: &'a mut PluginRegistrar,
142}
143
144impl PromptRegistrations<'_> {
145    pub fn contribute(self, contributor: PromptContributor) {
146        self.reg.add_prompt_contributor(contributor);
147    }
148}
149
150pub struct ToolCatalogRegistrations<'a> {
151    reg: &'a mut PluginRegistrar,
152}
153
154impl ToolCatalogRegistrations<'_> {
155    pub fn contribute(self, contributor: ToolCatalogContributor) {
156        self.reg.add_tool_catalog_contributor(contributor);
157    }
158}
159
160pub struct DiscoveryRegistrations<'a> {
161    reg: &'a mut PluginRegistrar,
162}
163
164impl DiscoveryRegistrations<'_> {
165    pub fn contribute(self, contributor: ToolDiscoveryContributor) {
166        self.reg.add_tool_discovery_contributor(contributor);
167    }
168}
169
170pub struct TurnRegistrations<'a> {
171    reg: &'a mut PluginRegistrar,
172}
173
174impl TurnRegistrations<'_> {
175    pub fn before(self, hook: BeforeTurnHook) {
176        self.reg.add_before_turn_hook(hook);
177    }
178
179    pub fn after(self, hook: AfterTurnHook) {
180        self.reg.add_after_turn_hook(hook);
181    }
182
183    pub fn checkpoint(self, hook: CheckpointHook) {
184        self.reg.add_checkpoint_hook(hook);
185    }
186}
187
188pub struct ToolCallRegistrations<'a> {
189    reg: &'a mut PluginRegistrar,
190}
191
192impl ToolCallRegistrations<'_> {
193    pub fn before(self, hook: BeforeToolCallHook) {
194        self.reg.add_before_tool_call_hook(hook);
195    }
196
197    pub fn after(self, hook: AfterToolCallHook) {
198        self.reg.add_after_tool_call_hook(hook);
199    }
200}
201
202pub struct OutputRegistrations<'a> {
203    reg: &'a mut PluginRegistrar,
204}
205
206impl OutputRegistrations<'_> {
207    pub fn stream(self, hook: AssistantStreamHook) {
208        self.reg.add_assistant_stream_hook(hook);
209    }
210
211    pub fn response(self, hook: AssistantResponseHook) {
212        self.reg.add_assistant_response_hook(hook);
213    }
214
215    pub fn assistant_prose_projector(
216        self,
217        provider: Arc<dyn AssistantProseProjectorPlugin>,
218    ) -> Result<(), PluginError> {
219        self.reg.add_assistant_prose_projector(provider)
220    }
221}
222
223pub struct ToolResultRegistrations<'a> {
224    reg: &'a mut PluginRegistrar,
225}
226
227impl ToolResultRegistrations<'_> {
228    pub fn projector(self, hook: ToolResultProjector) -> Result<(), PluginError> {
229        self.reg.add_tool_result_projector(hook)
230    }
231}
232
233pub struct SessionRegistrations<'a> {
234    reg: &'a mut PluginRegistrar,
235}
236
237impl SessionRegistrations<'_> {
238    pub fn on_event(self, hook: PluginLifecycleEventHook) {
239        push_registered_hook(
240            &mut self.reg.contributions.runtime_event_hooks,
241            &self.reg.registering_plugin_id,
242            hook,
243        );
244    }
245
246    pub fn config_mutator(self, hook: SessionConfigMutator) {
247        self.reg.contributions.session_config_mutators.push(hook);
248    }
249}
250
251pub struct PluginOperationRegistrations<'a> {
252    reg: &'a mut PluginRegistrar,
253}
254
255impl PluginOperationRegistrations<'_> {
256    pub(crate) fn query(
257        self,
258        def: PluginOperationDef,
259        handler: PluginQueryHandler,
260    ) -> Result<(), PluginError> {
261        self.reg.add_plugin_query(def, handler)
262    }
263
264    pub(crate) fn command(
265        self,
266        def: PluginOperationDef,
267        handler: PluginCommandHandler,
268    ) -> Result<(), PluginError> {
269        self.reg.add_plugin_command(def, handler)
270    }
271
272    pub(crate) fn task(
273        self,
274        def: PluginOperationDef,
275        handler: PluginTaskHandler,
276    ) -> Result<(), PluginError> {
277        self.reg.add_plugin_task(def, handler)
278    }
279
280    pub fn typed_query<Op, F, Fut>(self, handler: F) -> Result<(), PluginError>
281    where
282        Op: PluginQuery,
283        F: Fn(PluginQueryContext, Op::Args) -> Fut + Send + Sync + 'static,
284        Fut: Future<Output = Result<Op::Output, PluginOperationFailure>> + Send + 'static,
285    {
286        self.query(
287            plugin_operation_def::<Op>(PluginOperationKind::Query),
288            Arc::new(move |ctx, args| {
289                let parsed = serde_json::from_value::<Op::Args>(args);
290                match parsed {
291                    Ok(args) => {
292                        let fut = handler(ctx, args);
293                        Box::pin(async move {
294                            let output = fut.await?;
295                            serde_json::to_value(output).map_err(|err| {
296                                PluginOperationFailure::new(format!(
297                                    "failed to serialize {} output: {err}",
298                                    Op::NAME
299                                ))
300                            })
301                        }) as PluginQueryInvokeFuture
302                    }
303                    Err(err) => Box::pin(async move {
304                        Err(PluginOperationFailure::new(format!(
305                            "invalid {} args: {err}",
306                            Op::NAME
307                        )))
308                    }) as PluginQueryInvokeFuture,
309                }
310            }),
311        )
312    }
313
314    pub fn typed_command<Op, F, Fut>(self, handler: F) -> Result<(), PluginError>
315    where
316        Op: PluginCommand,
317        F: Fn(PluginCommandContext, Op::Args) -> Fut + Send + Sync + 'static,
318        Fut: Future<Output = Result<PluginCommandOutcome<Op::Output>, PluginOperationFailure>>
319            + Send
320            + 'static,
321    {
322        self.command(
323            plugin_operation_def::<Op>(PluginOperationKind::Command),
324            Arc::new(move |ctx, args| {
325                let parsed = serde_json::from_value::<Op::Args>(args);
326                match parsed {
327                    Ok(args) => {
328                        let fut = handler(ctx, args);
329                        Box::pin(async move {
330                            let outcome = fut.await?;
331                            let output = serde_json::to_value(outcome.output).map_err(|err| {
332                                PluginOperationFailure::new(format!(
333                                    "failed to serialize {} output: {err}",
334                                    Op::NAME
335                                ))
336                            })?;
337                            Ok(ErasedPluginCommandOutcome {
338                                output,
339                                events: outcome.events,
340                                directives: outcome.directives,
341                            })
342                        }) as PluginCommandInvokeFuture
343                    }
344                    Err(err) => Box::pin(async move {
345                        Err(PluginOperationFailure::new(format!(
346                            "invalid {} args: {err}",
347                            Op::NAME
348                        )))
349                    }) as PluginCommandInvokeFuture,
350                }
351            }),
352        )
353    }
354
355    pub fn typed_command_value<Op, F, Fut>(self, handler: F) -> Result<(), PluginError>
356    where
357        Op: PluginCommand,
358        F: Fn(PluginCommandContext, Op::Args) -> Fut + Send + Sync + 'static,
359        Fut: Future<Output = Result<Op::Output, PluginOperationFailure>> + Send + 'static,
360    {
361        self.typed_command::<Op, _, _>(move |ctx, args| {
362            let fut = handler(ctx, args);
363            async move { fut.await.map(PluginCommandOutcome::new) }
364        })
365    }
366
367    pub fn typed_task<Op, F, Fut>(self, handler: F) -> Result<(), PluginError>
368    where
369        Op: PluginTask,
370        F: Fn(PluginTaskContext, Op::Args) -> Fut + Send + Sync + 'static,
371        Fut: Future<Output = Result<PluginTaskOutcome<Op::Output>, PluginOperationFailure>>
372            + Send
373            + 'static,
374    {
375        self.task(
376            plugin_operation_def::<Op>(PluginOperationKind::Task),
377            Arc::new(move |ctx, args| {
378                let parsed = serde_json::from_value::<Op::Args>(args);
379                match parsed {
380                    Ok(args) => {
381                        let fut = handler(ctx, args);
382                        Box::pin(async move {
383                            let outcome = fut.await?;
384                            let output = serde_json::to_value(outcome.output).map_err(|err| {
385                                PluginOperationFailure::new(format!(
386                                    "failed to serialize {} output: {err}",
387                                    Op::NAME
388                                ))
389                            })?;
390                            Ok(ErasedPluginTaskOutcome {
391                                output,
392                                events: outcome.events,
393                                directives: outcome.directives,
394                            })
395                        }) as PluginTaskInvokeFuture
396                    }
397                    Err(err) => Box::pin(async move {
398                        Err(PluginOperationFailure::new(format!(
399                            "invalid {} args: {err}",
400                            Op::NAME
401                        )))
402                    }) as PluginTaskInvokeFuture,
403                }
404            }),
405        )
406    }
407
408    pub fn typed_task_value<Op, F, Fut>(self, handler: F) -> Result<(), PluginError>
409    where
410        Op: PluginTask,
411        F: Fn(PluginTaskContext, Op::Args) -> Fut + Send + Sync + 'static,
412        Fut: Future<Output = Result<Op::Output, PluginOperationFailure>> + Send + 'static,
413    {
414        self.typed_task::<Op, _, _>(move |ctx, args| {
415            let fut = handler(ctx, args);
416            async move { fut.await.map(PluginTaskOutcome::new) }
417        })
418    }
419}
420
421pub struct ContextRegistrations<'a> {
422    reg: &'a mut PluginRegistrar,
423}
424
425impl ContextRegistrations<'_> {
426    /// Register a per-turn context transform. Higher priority runs first.
427    pub fn prepare_turn(self, priority: i32, transform: Arc<dyn TurnContextTransform>) {
428        push_prioritized_registered_hook(
429            &mut self.reg.contributions.turn_context_transforms,
430            &self.reg.registering_plugin_id,
431            priority,
432            transform,
433        );
434    }
435
436    /// Register an explicit compaction provider. Higher priority runs first.
437    pub fn compact(self, priority: i32, compactor: Arc<dyn ContextCompactor>) {
438        push_prioritized_registered_hook(
439            &mut self.reg.contributions.context_compactors,
440            &self.reg.registering_plugin_id,
441            priority,
442            compactor,
443        );
444    }
445}
446
447pub struct ProtocolRegistrations<'a> {
448    reg: &'a mut PluginRegistrar,
449}
450
451impl ProtocolRegistrations<'_> {
452    pub fn session(self, provider: Arc<dyn ProtocolSessionPlugin>) -> Result<(), PluginError> {
453        self.reg.add_protocol_session(provider)
454    }
455
456    /// Claim the session-wide singleton protocol-driver slot. The
457    /// plugin provides a `ProtocolDriverHandle` via `build_preamble`.
458    /// The active plugin stack must install exactly one protocol driver.
459    pub fn protocol_driver(
460        self,
461        provider: Arc<dyn ProtocolDriverPlugin>,
462    ) -> Result<(), PluginError> {
463        self.reg.add_protocol_driver(provider)
464    }
465}
466
467pub struct ExecutionRegistrations<'a> {
468    reg: &'a mut PluginRegistrar,
469}
470
471impl ExecutionRegistrations<'_> {
472    pub fn code_executor(self, provider: Arc<dyn CodeExecutorPlugin>) -> Result<(), PluginError> {
473        self.reg.add_code_executor(provider)
474    }
475}
476
477impl PluginRegistrar {
478    pub(crate) fn new() -> Self {
479        Self {
480            tool_names: BTreeSet::new(),
481            contributions: PluginContributions::default(),
482            registering_plugin_id: None,
483        }
484    }
485
486    pub fn tools(&mut self) -> ToolRegistrations<'_> {
487        ToolRegistrations { reg: self }
488    }
489
490    pub fn triggers(&mut self) -> TriggerEventRegistrations<'_> {
491        TriggerEventRegistrations { reg: self }
492    }
493
494    pub fn prompt(&mut self) -> PromptRegistrations<'_> {
495        PromptRegistrations { reg: self }
496    }
497
498    pub fn tool_catalog(&mut self) -> ToolCatalogRegistrations<'_> {
499        ToolCatalogRegistrations { reg: self }
500    }
501
502    pub fn discovery(&mut self) -> DiscoveryRegistrations<'_> {
503        DiscoveryRegistrations { reg: self }
504    }
505
506    pub fn turn(&mut self) -> TurnRegistrations<'_> {
507        TurnRegistrations { reg: self }
508    }
509
510    pub fn tool_calls(&mut self) -> ToolCallRegistrations<'_> {
511        ToolCallRegistrations { reg: self }
512    }
513
514    pub fn output(&mut self) -> OutputRegistrations<'_> {
515        OutputRegistrations { reg: self }
516    }
517
518    pub fn tool_results(&mut self) -> ToolResultRegistrations<'_> {
519        ToolResultRegistrations { reg: self }
520    }
521
522    pub fn session(&mut self) -> SessionRegistrations<'_> {
523        SessionRegistrations { reg: self }
524    }
525
526    pub fn operations(&mut self) -> PluginOperationRegistrations<'_> {
527        PluginOperationRegistrations { reg: self }
528    }
529
530    pub fn context(&mut self) -> ContextRegistrations<'_> {
531        ContextRegistrations { reg: self }
532    }
533
534    pub fn protocol(&mut self) -> ProtocolRegistrations<'_> {
535        ProtocolRegistrations { reg: self }
536    }
537
538    pub fn execution(&mut self) -> ExecutionRegistrations<'_> {
539        ExecutionRegistrations { reg: self }
540    }
541
542    fn add_tool_provider(&mut self, provider: Arc<dyn ToolProvider>) -> Result<(), PluginError> {
543        for manifest in provider.tool_manifests() {
544            if !self.tool_names.insert(manifest.name.clone()) {
545                return Err(PluginError::Registration(format!(
546                    "duplicate plugin tool name `{}`",
547                    manifest.name
548                )));
549            }
550        }
551        self.contributions.tool_providers.push(provider);
552        Ok(())
553    }
554
555    fn add_trigger(&mut self, event: crate::TriggerEvent) -> Result<(), PluginError> {
556        if self
557            .contributions
558            .triggers
559            .iter()
560            .any(|existing| existing.key() == event.key())
561        {
562            return Err(PluginError::Registration(format!(
563                "duplicate trigger occurrence `{}.{}.{}`",
564                event.resource_type, event.alias, event.event
565            )));
566        }
567        self.contributions.triggers.push(event);
568        Ok(())
569    }
570
571    fn add_prompt_contributor(&mut self, contributor: PromptContributor) {
572        push_registered_hook(
573            &mut self.contributions.prompt_contributors,
574            &self.registering_plugin_id,
575            contributor,
576        );
577    }
578
579    fn add_tool_catalog_contributor(&mut self, contributor: ToolCatalogContributor) {
580        push_registered_hook(
581            &mut self.contributions.tool_catalog_contributors,
582            &self.registering_plugin_id,
583            contributor,
584        );
585    }
586
587    fn add_tool_discovery_contributor(&mut self, contributor: ToolDiscoveryContributor) {
588        push_registered_hook(
589            &mut self.contributions.tool_discovery_contributors,
590            &self.registering_plugin_id,
591            contributor,
592        );
593    }
594
595    fn add_before_turn_hook(&mut self, hook: BeforeTurnHook) {
596        push_registered_hook(
597            &mut self.contributions.before_turn_hooks,
598            &self.registering_plugin_id,
599            hook,
600        );
601    }
602
603    fn add_before_tool_call_hook(&mut self, hook: BeforeToolCallHook) {
604        push_registered_hook(
605            &mut self.contributions.before_tool_call_hooks,
606            &self.registering_plugin_id,
607            hook,
608        );
609    }
610
611    fn add_after_tool_call_hook(&mut self, hook: AfterToolCallHook) {
612        push_registered_hook(
613            &mut self.contributions.after_tool_call_hooks,
614            &self.registering_plugin_id,
615            hook,
616        );
617    }
618
619    fn add_after_turn_hook(&mut self, hook: AfterTurnHook) {
620        push_registered_hook(
621            &mut self.contributions.after_turn_hooks,
622            &self.registering_plugin_id,
623            hook,
624        );
625    }
626
627    fn add_checkpoint_hook(&mut self, hook: CheckpointHook) {
628        push_registered_hook(
629            &mut self.contributions.checkpoint_hooks,
630            &self.registering_plugin_id,
631            hook,
632        );
633    }
634
635    fn add_assistant_stream_hook(&mut self, hook: AssistantStreamHook) {
636        push_registered_hook(
637            &mut self.contributions.assistant_stream_hooks,
638            &self.registering_plugin_id,
639            hook,
640        );
641    }
642
643    fn add_assistant_response_hook(&mut self, hook: AssistantResponseHook) {
644        push_registered_hook(
645            &mut self.contributions.assistant_response_hooks,
646            &self.registering_plugin_id,
647            hook,
648        );
649    }
650
651    fn add_assistant_prose_projector(
652        &mut self,
653        provider: Arc<dyn AssistantProseProjectorPlugin>,
654    ) -> Result<(), PluginError> {
655        register_singleton_hook(
656            &mut self.contributions.assistant_prose_projector,
657            &self.registering_plugin_id,
658            "assistant prose projector",
659            "assistant_prose_projector",
660            provider,
661        )
662    }
663
664    fn add_tool_result_projector(&mut self, hook: ToolResultProjector) -> Result<(), PluginError> {
665        register_singleton_hook(
666            &mut self.contributions.tool_result_projector,
667            &self.registering_plugin_id,
668            "tool result projector",
669            "model_observation",
670            hook,
671        )
672    }
673
674    fn operation_owner(&self) -> Result<String, PluginError> {
675        self.registering_plugin_id
676            .clone()
677            .ok_or_else(|| PluginError::Registration("missing registering plugin id".to_string()))
678    }
679
680    fn ensure_unique_operation_name(&self, name: &str) -> Result<(), PluginError> {
681        if self.contributions.plugin_queries.contains_key(name)
682            || self.contributions.plugin_commands.contains_key(name)
683            || self.contributions.plugin_tasks.contains_key(name)
684        {
685            return Err(PluginError::Registration(format!(
686                "duplicate plugin operation name `{name}`"
687            )));
688        }
689        Ok(())
690    }
691
692    fn add_plugin_query(
693        &mut self,
694        def: PluginOperationDef,
695        handler: PluginQueryHandler,
696    ) -> Result<(), PluginError> {
697        self.ensure_unique_operation_name(&def.name)?;
698        let plugin_id = self.operation_owner()?;
699        self.contributions.plugin_queries.insert(
700            def.name.clone(),
701            RegisteredPluginQuery {
702                plugin_id,
703                def,
704                handler,
705            },
706        );
707        Ok(())
708    }
709
710    fn add_plugin_command(
711        &mut self,
712        def: PluginOperationDef,
713        handler: PluginCommandHandler,
714    ) -> Result<(), PluginError> {
715        self.ensure_unique_operation_name(&def.name)?;
716        let plugin_id = self.operation_owner()?;
717        self.contributions.plugin_commands.insert(
718            def.name.clone(),
719            RegisteredPluginCommand {
720                plugin_id,
721                def,
722                handler,
723            },
724        );
725        Ok(())
726    }
727
728    fn add_plugin_task(
729        &mut self,
730        def: PluginOperationDef,
731        handler: PluginTaskHandler,
732    ) -> Result<(), PluginError> {
733        self.ensure_unique_operation_name(&def.name)?;
734        let plugin_id = self.operation_owner()?;
735        self.contributions.plugin_tasks.insert(
736            def.name.clone(),
737            RegisteredPluginTask {
738                plugin_id,
739                def,
740                handler,
741            },
742        );
743        Ok(())
744    }
745
746    fn add_protocol_session(
747        &mut self,
748        provider: Arc<dyn ProtocolSessionPlugin>,
749    ) -> Result<(), PluginError> {
750        register_singleton_hook(
751            &mut self.contributions.protocol_session,
752            &self.registering_plugin_id,
753            "protocol session capability",
754            "protocol_session",
755            provider,
756        )
757    }
758
759    fn add_code_executor(
760        &mut self,
761        provider: Arc<dyn CodeExecutorPlugin>,
762    ) -> Result<(), PluginError> {
763        register_singleton_hook(
764            &mut self.contributions.code_executor,
765            &self.registering_plugin_id,
766            "code executor capability",
767            "code_executor",
768            provider,
769        )
770    }
771
772    fn add_protocol_driver(
773        &mut self,
774        provider: Arc<dyn ProtocolDriverPlugin>,
775    ) -> Result<(), PluginError> {
776        register_singleton_hook(
777            &mut self.contributions.protocol_driver,
778            &self.registering_plugin_id,
779            "protocol driver capability",
780            "protocol_driver",
781            provider,
782        )
783    }
784}