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