1use std::sync::Arc;
11
12use super::{
13 AfterToolCallHook, AfterTurnHook, AssistantResponseHook, AssistantStreamHook,
14 BeforeToolCallHook, BeforeTurnHook, CheckpointHook, ContextCompactor, PluginAction,
15 PluginActionDef, PluginActionHandler, PluginError, PluginHost, PluginLifecycleEventHook,
16 PluginRegistrar, PluginSnapshotMeta, PromptContributor, SessionConfigMutator,
17 SessionToolAccess, SnapshotReader, SnapshotWriter, SubagentSessionContext,
18 ToolCatalogContributor, ToolDiscoveryContributor, ToolResultProjector, TurnContextTransform,
19};
20use crate::{PluginOptions, ToolProvider};
21
22#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
23pub struct PluginExtensionContribution {
24 pub extension_id: String,
25 #[serde(default)]
26 pub payload: serde_json::Value,
27}
28
29impl PluginExtensionContribution {
30 pub fn new(
31 extension_id: impl Into<String>,
32 payload: impl serde::Serialize,
33 ) -> Result<Self, serde_json::Error> {
34 Ok(Self {
35 extension_id: extension_id.into(),
36 payload: serde_json::to_value(payload)?,
37 })
38 }
39
40 pub fn from_value(extension_id: impl Into<String>, payload: serde_json::Value) -> Self {
41 Self {
42 extension_id: extension_id.into(),
43 payload,
44 }
45 }
46}
47
48#[derive(Clone, Debug, Default, PartialEq, Eq)]
49pub struct PluginExtensions {
50 contributions: std::collections::BTreeMap<String, Vec<serde_json::Value>>,
51}
52
53impl PluginExtensions {
54 pub fn from_contributions(
55 contributions: impl IntoIterator<Item = PluginExtensionContribution>,
56 ) -> Self {
57 let mut extensions = Self::default();
58 for contribution in contributions {
59 extensions.insert(contribution);
60 }
61 extensions
62 }
63
64 pub fn insert(&mut self, contribution: PluginExtensionContribution) {
65 self.contributions
66 .entry(contribution.extension_id)
67 .or_default()
68 .push(contribution.payload);
69 }
70
71 pub fn payloads(&self, extension_id: &str) -> &[serde_json::Value] {
72 self.contributions
73 .get(extension_id)
74 .map(Vec::as_slice)
75 .unwrap_or(&[])
76 }
77
78 pub fn is_empty(&self) -> bool {
79 self.contributions.is_empty()
80 }
81}
82
83#[derive(Clone, Default)]
84pub struct PluginSpec {
85 pub tool_providers: Vec<Arc<dyn ToolProvider>>,
86 pub triggers: Vec<crate::TriggerEvent>,
87 pub prompt_contributors: Vec<PromptContributor>,
88 pub tool_catalog_contributors: Vec<ToolCatalogContributor>,
89 pub tool_discovery_contributors: Vec<ToolDiscoveryContributor>,
90 pub before_turn_hooks: Vec<BeforeTurnHook>,
91 pub before_tool_call_hooks: Vec<BeforeToolCallHook>,
92 pub after_tool_call_hooks: Vec<AfterToolCallHook>,
93 pub after_turn_hooks: Vec<AfterTurnHook>,
94 pub checkpoint_hooks: Vec<CheckpointHook>,
95 pub assistant_stream_hooks: Vec<AssistantStreamHook>,
96 pub assistant_response_hooks: Vec<AssistantResponseHook>,
97 pub tool_result_projector: Option<ToolResultProjector>,
98 pub runtime_event_hooks: Vec<PluginLifecycleEventHook>,
99 pub session_config_mutators: Vec<SessionConfigMutator>,
100 pub plugin_actions: Vec<(PluginActionDef, PluginActionHandler)>,
101 pub turn_context_transforms: Vec<(i32, Arc<dyn TurnContextTransform>)>,
102 pub context_compactors: Vec<(i32, Arc<dyn ContextCompactor>)>,
103}
104
105impl PluginSpec {
106 pub fn new() -> Self {
107 Self::default()
108 }
109
110 pub fn with_tool_provider(mut self, provider: Arc<dyn ToolProvider>) -> Self {
111 self.tool_providers.push(provider);
112 self
113 }
114
115 pub fn with_trigger_event(mut self, event: crate::TriggerEvent) -> Self {
116 self.triggers.push(event);
117 self
118 }
119
120 pub fn with_prompt_contributor(mut self, contributor: PromptContributor) -> Self {
121 self.prompt_contributors.push(contributor);
122 self
123 }
124
125 pub fn with_tool_catalog_contributor(mut self, contributor: ToolCatalogContributor) -> Self {
126 self.tool_catalog_contributors.push(contributor);
127 self
128 }
129
130 pub fn with_tool_discovery_contributor(
131 mut self,
132 contributor: ToolDiscoveryContributor,
133 ) -> Self {
134 self.tool_discovery_contributors.push(contributor);
135 self
136 }
137
138 pub fn with_before_turn(mut self, hook: BeforeTurnHook) -> Self {
139 self.before_turn_hooks.push(hook);
140 self
141 }
142
143 pub fn with_before_tool_call(mut self, hook: BeforeToolCallHook) -> Self {
144 self.before_tool_call_hooks.push(hook);
145 self
146 }
147
148 pub fn with_after_tool_call(mut self, hook: AfterToolCallHook) -> Self {
149 self.after_tool_call_hooks.push(hook);
150 self
151 }
152
153 pub fn with_after_turn(mut self, hook: AfterTurnHook) -> Self {
154 self.after_turn_hooks.push(hook);
155 self
156 }
157
158 pub fn with_checkpoint(mut self, hook: CheckpointHook) -> Self {
159 self.checkpoint_hooks.push(hook);
160 self
161 }
162
163 pub fn with_assistant_stream(mut self, hook: AssistantStreamHook) -> Self {
164 self.assistant_stream_hooks.push(hook);
165 self
166 }
167
168 pub fn with_assistant_response(mut self, hook: AssistantResponseHook) -> Self {
169 self.assistant_response_hooks.push(hook);
170 self
171 }
172
173 pub fn with_tool_result_projector(mut self, projector: ToolResultProjector) -> Self {
174 self.tool_result_projector = Some(projector);
175 self
176 }
177
178 pub fn with_runtime_event(mut self, hook: PluginLifecycleEventHook) -> Self {
179 self.runtime_event_hooks.push(hook);
180 self
181 }
182
183 pub fn with_session_config_mutator(mut self, hook: SessionConfigMutator) -> Self {
184 self.session_config_mutators.push(hook);
185 self
186 }
187
188 pub fn with_plugin_action(
189 mut self,
190 def: PluginActionDef,
191 handler: PluginActionHandler,
192 ) -> Self {
193 self.plugin_actions.push((def, handler));
194 self
195 }
196
197 pub fn with_plugin_action_typed<Op, F, Fut>(self, handler: F) -> Self
198 where
199 Op: PluginAction,
200 F: Fn(super::PluginActionContext, Op::Args) -> Fut + Send + Sync + 'static,
201 Fut: std::future::Future<Output = Result<Op::Output, super::PluginActionFailure>>
202 + Send
203 + 'static,
204 {
205 self.with_plugin_action(
206 super::plugin_action_def::<Op>(),
207 Arc::new(move |ctx, args| {
208 let parsed = serde_json::from_value::<Op::Args>(args);
209 match parsed {
210 Ok(args) => {
211 let fut = handler(ctx, args);
212 Box::pin(async move {
213 match fut.await {
214 Ok(output) => match serde_json::to_value(output) {
215 Ok(value) => crate::ToolResult::ok(value),
216 Err(err) => crate::ToolResult::err(serde_json::json!(format!(
217 "failed to serialize {} output: {err}",
218 Op::NAME
219 ))),
220 },
221 Err(err) => {
222 crate::ToolResult::err(serde_json::json!(err.to_string()))
223 }
224 }
225 })
226 }
227 Err(err) => Box::pin(async move {
228 crate::ToolResult::err(serde_json::json!(format!(
229 "invalid {} args: {err}",
230 Op::NAME
231 )))
232 }),
233 }
234 }),
235 )
236 }
237
238 pub fn with_plugin_action_sync<Op, F>(self, handler: F) -> Self
239 where
240 Op: PluginAction,
241 F: Fn(
242 super::PluginActionContext,
243 Op::Args,
244 ) -> Result<Op::Output, super::PluginActionFailure>
245 + Send
246 + Sync
247 + 'static,
248 {
249 self.with_plugin_action_typed::<Op, _, _>(move |ctx, args| {
250 let result = handler(ctx, args);
251 async move { result }
252 })
253 }
254
255 pub fn with_turn_context_transform(
256 mut self,
257 priority: i32,
258 transform: Arc<dyn TurnContextTransform>,
259 ) -> Self {
260 self.turn_context_transforms.push((priority, transform));
261 self
262 }
263
264 pub fn with_context_compactor(
265 mut self,
266 priority: i32,
267 compactor: Arc<dyn ContextCompactor>,
268 ) -> Self {
269 self.context_compactors.push((priority, compactor));
270 self
271 }
272}
273
274#[derive(Clone, Debug)]
275pub struct PluginSessionContext {
276 pub session_id: String,
277 pub tool_access: SessionToolAccess,
278 pub subagent: Option<SubagentSessionContext>,
279 pub plugin_options: PluginOptions,
280 pub extensions: PluginExtensions,
281 pub parent_session_id: Option<String>,
286}
287
288impl PluginSessionContext {
289 pub fn is_root_session(&self) -> bool {
293 self.parent_session_id.is_none()
294 }
295}
296
297#[derive(Clone)]
298pub struct SessionReadyContext {
299 pub session_id: String,
300 pub host: PluginHost,
301}
302
303pub trait SessionPlugin: Send + Sync {
304 fn id(&self) -> &'static str;
305
306 fn version(&self) -> &'static str {
307 "1"
308 }
309
310 fn register(&self, reg: &mut PluginRegistrar) -> Result<(), PluginError>;
311
312 fn snapshot(
313 &self,
314 _writer: &mut dyn SnapshotWriter,
315 ) -> Result<PluginSnapshotMeta, PluginError> {
316 Ok(PluginSnapshotMeta {
317 plugin_id: self.id().to_string(),
318 plugin_version: self.version().to_string(),
319 revision: self.snapshot_revision(),
320 state: None,
321 })
322 }
323
324 fn snapshot_revision(&self) -> u64 {
325 0
326 }
327
328 fn restore(
329 &self,
330 _meta: &PluginSnapshotMeta,
331 _reader: &dyn SnapshotReader,
332 ) -> Result<(), PluginError> {
333 Ok(())
334 }
335
336 fn session_ready(&self, _ctx: SessionReadyContext) -> Result<(), PluginError> {
337 Ok(())
338 }
339}
340
341pub trait PluginFactory: Send + Sync {
390 fn id(&self) -> &'static str;
391
392 fn extension_contributions(&self) -> Vec<PluginExtensionContribution> {
393 Vec::new()
394 }
395
396 fn build(&self, ctx: &PluginSessionContext) -> Result<Arc<dyn SessionPlugin>, PluginError>;
399}
400
401pub type PluginSpecBuilder =
402 Arc<dyn Fn(&PluginSessionContext) -> Result<PluginSpec, PluginError> + Send + Sync>;
403
404pub struct PluginSpecFactory {
405 id: &'static str,
406 builder: PluginSpecBuilder,
407}
408
409impl PluginSpecFactory {
410 pub fn new(id: &'static str, builder: PluginSpecBuilder) -> Self {
411 Self { id, builder }
412 }
413}
414
415pub struct StaticPluginFactory {
416 id: &'static str,
417 spec: PluginSpec,
418}
419
420impl StaticPluginFactory {
421 pub fn new(id: &'static str, spec: PluginSpec) -> Self {
422 Self { id, spec }
423 }
424}
425
426struct SpecPlugin {
427 id: &'static str,
428 spec: PluginSpec,
429}
430
431impl PluginFactory for PluginSpecFactory {
432 fn id(&self) -> &'static str {
433 self.id
434 }
435
436 fn build(&self, ctx: &PluginSessionContext) -> Result<Arc<dyn SessionPlugin>, PluginError> {
437 Ok(Arc::new(SpecPlugin {
438 id: self.id,
439 spec: (self.builder)(ctx)?,
440 }))
441 }
442}
443
444impl PluginFactory for StaticPluginFactory {
445 fn id(&self) -> &'static str {
446 self.id
447 }
448
449 fn build(&self, _ctx: &PluginSessionContext) -> Result<Arc<dyn SessionPlugin>, PluginError> {
450 Ok(Arc::new(SpecPlugin {
451 id: self.id,
452 spec: self.spec.clone(),
453 }))
454 }
455}
456
457impl SessionPlugin for SpecPlugin {
458 fn id(&self) -> &'static str {
459 self.id
460 }
461
462 fn register(&self, reg: &mut PluginRegistrar) -> Result<(), PluginError> {
463 for provider in &self.spec.tool_providers {
464 reg.tools().provider(Arc::clone(provider))?;
465 }
466 for event in &self.spec.triggers {
467 reg.triggers().declare(event.clone())?;
468 }
469 for contributor in &self.spec.prompt_contributors {
470 reg.prompt().contribute(Arc::clone(contributor));
471 }
472 for contributor in &self.spec.tool_catalog_contributors {
473 reg.tool_catalog().contribute(Arc::clone(contributor));
474 }
475 for contributor in &self.spec.tool_discovery_contributors {
476 reg.discovery().contribute(Arc::clone(contributor));
477 }
478 for hook in &self.spec.before_turn_hooks {
479 reg.turn().before(Arc::clone(hook));
480 }
481 for hook in &self.spec.before_tool_call_hooks {
482 reg.tool_calls().before(Arc::clone(hook));
483 }
484 for hook in &self.spec.after_tool_call_hooks {
485 reg.tool_calls().after(Arc::clone(hook));
486 }
487 for hook in &self.spec.after_turn_hooks {
488 reg.turn().after(Arc::clone(hook));
489 }
490 for hook in &self.spec.checkpoint_hooks {
491 reg.turn().checkpoint(Arc::clone(hook));
492 }
493 for hook in &self.spec.assistant_stream_hooks {
494 reg.output().stream(Arc::clone(hook));
495 }
496 for hook in &self.spec.assistant_response_hooks {
497 reg.output().response(Arc::clone(hook));
498 }
499 if let Some(projector) = &self.spec.tool_result_projector {
500 reg.tool_results().projector(Arc::clone(projector))?;
501 }
502 for hook in &self.spec.runtime_event_hooks {
503 reg.session().on_event(Arc::clone(hook));
504 }
505 for hook in &self.spec.session_config_mutators {
506 reg.session().config_mutator(Arc::clone(hook));
507 }
508 for (def, handler) in &self.spec.plugin_actions {
509 reg.actions().op(def.clone(), Arc::clone(handler))?;
510 }
511 for (priority, transform) in &self.spec.turn_context_transforms {
512 reg.context().prepare_turn(*priority, Arc::clone(transform));
513 }
514 for (priority, compactor) in &self.spec.context_compactors {
515 reg.context().compact(*priority, Arc::clone(compactor));
516 }
517 Ok(())
518 }
519}