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