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 ToolDiscoveryContributor, ToolResultProjector, ToolSurfaceContributor, TurnContextTransform,
19};
20use crate::ToolProvider;
21
22#[derive(Clone, Default)]
23pub struct PluginSpec {
24 pub tool_providers: Vec<Arc<dyn ToolProvider>>,
25 pub host_events: Vec<crate::HostEvent>,
26 pub prompt_contributors: Vec<PromptContributor>,
27 pub tool_surface_contributors: Vec<ToolSurfaceContributor>,
28 pub tool_discovery_contributors: Vec<ToolDiscoveryContributor>,
29 pub before_turn_hooks: Vec<BeforeTurnHook>,
30 pub before_tool_call_hooks: Vec<BeforeToolCallHook>,
31 pub after_tool_call_hooks: Vec<AfterToolCallHook>,
32 pub after_turn_hooks: Vec<AfterTurnHook>,
33 pub checkpoint_hooks: Vec<CheckpointHook>,
34 pub assistant_stream_hooks: Vec<AssistantStreamHook>,
35 pub assistant_response_hooks: Vec<AssistantResponseHook>,
36 pub tool_result_projector: Option<ToolResultProjector>,
37 pub runtime_event_hooks: Vec<PluginLifecycleEventHook>,
38 pub session_config_mutators: Vec<SessionConfigMutator>,
39 pub plugin_actions: Vec<(PluginActionDef, PluginActionHandler)>,
40 pub turn_context_transforms: Vec<(i32, Arc<dyn TurnContextTransform>)>,
41 pub context_compactors: Vec<(i32, Arc<dyn ContextCompactor>)>,
42}
43
44impl PluginSpec {
45 pub fn new() -> Self {
46 Self::default()
47 }
48
49 pub fn with_tool_provider(mut self, provider: Arc<dyn ToolProvider>) -> Self {
50 self.tool_providers.push(provider);
51 self
52 }
53
54 pub fn with_host_event(mut self, event: crate::HostEvent) -> Self {
55 self.host_events.push(event);
56 self
57 }
58
59 pub fn with_prompt_contributor(mut self, contributor: PromptContributor) -> Self {
60 self.prompt_contributors.push(contributor);
61 self
62 }
63
64 pub fn with_tool_surface_contributor(mut self, contributor: ToolSurfaceContributor) -> Self {
65 self.tool_surface_contributors.push(contributor);
66 self
67 }
68
69 pub fn with_tool_discovery_contributor(
70 mut self,
71 contributor: ToolDiscoveryContributor,
72 ) -> Self {
73 self.tool_discovery_contributors.push(contributor);
74 self
75 }
76
77 pub fn with_before_turn(mut self, hook: BeforeTurnHook) -> Self {
78 self.before_turn_hooks.push(hook);
79 self
80 }
81
82 pub fn with_before_tool_call(mut self, hook: BeforeToolCallHook) -> Self {
83 self.before_tool_call_hooks.push(hook);
84 self
85 }
86
87 pub fn with_after_tool_call(mut self, hook: AfterToolCallHook) -> Self {
88 self.after_tool_call_hooks.push(hook);
89 self
90 }
91
92 pub fn with_after_turn(mut self, hook: AfterTurnHook) -> Self {
93 self.after_turn_hooks.push(hook);
94 self
95 }
96
97 pub fn with_checkpoint(mut self, hook: CheckpointHook) -> Self {
98 self.checkpoint_hooks.push(hook);
99 self
100 }
101
102 pub fn with_assistant_stream(mut self, hook: AssistantStreamHook) -> Self {
103 self.assistant_stream_hooks.push(hook);
104 self
105 }
106
107 pub fn with_assistant_response(mut self, hook: AssistantResponseHook) -> Self {
108 self.assistant_response_hooks.push(hook);
109 self
110 }
111
112 pub fn with_tool_result_projector(mut self, projector: ToolResultProjector) -> Self {
113 self.tool_result_projector = Some(projector);
114 self
115 }
116
117 pub fn with_runtime_event(mut self, hook: PluginLifecycleEventHook) -> Self {
118 self.runtime_event_hooks.push(hook);
119 self
120 }
121
122 pub fn with_session_config_mutator(mut self, hook: SessionConfigMutator) -> Self {
123 self.session_config_mutators.push(hook);
124 self
125 }
126
127 pub fn with_plugin_action(
128 mut self,
129 def: PluginActionDef,
130 handler: PluginActionHandler,
131 ) -> Self {
132 self.plugin_actions.push((def, handler));
133 self
134 }
135
136 pub fn with_plugin_action_typed<Op, F, Fut>(self, handler: F) -> Self
137 where
138 Op: PluginAction,
139 F: Fn(super::PluginActionContext, Op::Args) -> Fut + Send + Sync + 'static,
140 Fut: std::future::Future<Output = Result<Op::Output, super::PluginActionFailure>>
141 + Send
142 + 'static,
143 {
144 self.with_plugin_action(
145 super::plugin_action_def::<Op>(),
146 Arc::new(move |ctx, args| {
147 let parsed = serde_json::from_value::<Op::Args>(args);
148 match parsed {
149 Ok(args) => {
150 let fut = handler(ctx, args);
151 Box::pin(async move {
152 match fut.await {
153 Ok(output) => match serde_json::to_value(output) {
154 Ok(value) => crate::ToolResult::ok(value),
155 Err(err) => crate::ToolResult::err(serde_json::json!(format!(
156 "failed to serialize {} output: {err}",
157 Op::NAME
158 ))),
159 },
160 Err(err) => {
161 crate::ToolResult::err(serde_json::json!(err.to_string()))
162 }
163 }
164 })
165 }
166 Err(err) => Box::pin(async move {
167 crate::ToolResult::err(serde_json::json!(format!(
168 "invalid {} args: {err}",
169 Op::NAME
170 )))
171 }),
172 }
173 }),
174 )
175 }
176
177 pub fn with_plugin_action_sync<Op, F>(self, handler: F) -> Self
178 where
179 Op: PluginAction,
180 F: Fn(
181 super::PluginActionContext,
182 Op::Args,
183 ) -> Result<Op::Output, super::PluginActionFailure>
184 + Send
185 + Sync
186 + 'static,
187 {
188 self.with_plugin_action_typed::<Op, _, _>(move |ctx, args| {
189 let result = handler(ctx, args);
190 async move { result }
191 })
192 }
193
194 pub fn with_turn_context_transform(
195 mut self,
196 priority: i32,
197 transform: Arc<dyn TurnContextTransform>,
198 ) -> Self {
199 self.turn_context_transforms.push((priority, transform));
200 self
201 }
202
203 pub fn with_context_compactor(
204 mut self,
205 priority: i32,
206 compactor: Arc<dyn ContextCompactor>,
207 ) -> Self {
208 self.context_compactors.push((priority, compactor));
209 self
210 }
211}
212
213#[derive(Clone, Debug)]
214pub struct PluginSessionContext {
215 pub session_id: String,
216 pub tool_access: SessionToolAccess,
217 pub subagent: Option<SubagentSessionContext>,
218 pub lashlang_abilities: lashlang::LashlangAbilities,
219 pub lashlang_language_features: lashlang::LashlangLanguageFeatures,
220 pub parent_session_id: Option<String>,
225}
226
227impl PluginSessionContext {
228 pub fn is_root_session(&self) -> bool {
232 self.parent_session_id.is_none()
233 }
234}
235
236#[derive(Clone)]
237pub struct SessionReadyContext {
238 pub session_id: String,
239 pub host: PluginHost,
240}
241
242pub trait SessionPlugin: Send + Sync {
243 fn id(&self) -> &'static str;
244
245 fn version(&self) -> &'static str {
246 "1"
247 }
248
249 fn register(&self, reg: &mut PluginRegistrar) -> Result<(), PluginError>;
250
251 fn snapshot(
252 &self,
253 _writer: &mut dyn SnapshotWriter,
254 ) -> Result<PluginSnapshotMeta, PluginError> {
255 Ok(PluginSnapshotMeta {
256 plugin_id: self.id().to_string(),
257 plugin_version: self.version().to_string(),
258 revision: self.snapshot_revision(),
259 state: None,
260 })
261 }
262
263 fn snapshot_revision(&self) -> u64 {
264 0
265 }
266
267 fn restore(
268 &self,
269 _meta: &PluginSnapshotMeta,
270 _reader: &dyn SnapshotReader,
271 ) -> Result<(), PluginError> {
272 Ok(())
273 }
274
275 fn session_ready(&self, _ctx: SessionReadyContext) -> Result<(), PluginError> {
276 Ok(())
277 }
278}
279
280pub trait PluginFactory: Send + Sync {
329 fn id(&self) -> &'static str;
330
331 fn lashlang_abilities(&self) -> lashlang::LashlangAbilities {
332 lashlang::LashlangAbilities::default()
333 }
334
335 fn lashlang_language_features(&self) -> lashlang::LashlangLanguageFeatures {
336 lashlang::LashlangLanguageFeatures::default()
337 }
338
339 fn lashlang_resources(&self) -> lashlang::ResourceCatalog {
345 lashlang::ResourceCatalog::new()
346 }
347
348 fn build(&self, ctx: &PluginSessionContext) -> Result<Arc<dyn SessionPlugin>, PluginError>;
351}
352
353pub type PluginSpecBuilder =
354 Arc<dyn Fn(&PluginSessionContext) -> Result<PluginSpec, PluginError> + Send + Sync>;
355
356pub struct PluginSpecFactory {
357 id: &'static str,
358 builder: PluginSpecBuilder,
359}
360
361impl PluginSpecFactory {
362 pub fn new(id: &'static str, builder: PluginSpecBuilder) -> Self {
363 Self { id, builder }
364 }
365}
366
367pub struct StaticPluginFactory {
368 id: &'static str,
369 spec: PluginSpec,
370}
371
372impl StaticPluginFactory {
373 pub fn new(id: &'static str, spec: PluginSpec) -> Self {
374 Self { id, spec }
375 }
376}
377
378struct SpecPlugin {
379 id: &'static str,
380 spec: PluginSpec,
381}
382
383impl PluginFactory for PluginSpecFactory {
384 fn id(&self) -> &'static str {
385 self.id
386 }
387
388 fn build(&self, ctx: &PluginSessionContext) -> Result<Arc<dyn SessionPlugin>, PluginError> {
389 Ok(Arc::new(SpecPlugin {
390 id: self.id,
391 spec: (self.builder)(ctx)?,
392 }))
393 }
394}
395
396impl PluginFactory for StaticPluginFactory {
397 fn id(&self) -> &'static str {
398 self.id
399 }
400
401 fn build(&self, _ctx: &PluginSessionContext) -> Result<Arc<dyn SessionPlugin>, PluginError> {
402 Ok(Arc::new(SpecPlugin {
403 id: self.id,
404 spec: self.spec.clone(),
405 }))
406 }
407}
408
409impl SessionPlugin for SpecPlugin {
410 fn id(&self) -> &'static str {
411 self.id
412 }
413
414 fn register(&self, reg: &mut PluginRegistrar) -> Result<(), PluginError> {
415 for provider in &self.spec.tool_providers {
416 reg.tools().provider(Arc::clone(provider))?;
417 }
418 for event in &self.spec.host_events {
419 reg.host_events().declare(event.clone())?;
420 }
421 for contributor in &self.spec.prompt_contributors {
422 reg.prompt().contribute(Arc::clone(contributor));
423 }
424 for contributor in &self.spec.tool_surface_contributors {
425 reg.surface().contribute(Arc::clone(contributor));
426 }
427 for contributor in &self.spec.tool_discovery_contributors {
428 reg.discovery().contribute(Arc::clone(contributor));
429 }
430 for hook in &self.spec.before_turn_hooks {
431 reg.turn().before(Arc::clone(hook));
432 }
433 for hook in &self.spec.before_tool_call_hooks {
434 reg.tool_calls().before(Arc::clone(hook));
435 }
436 for hook in &self.spec.after_tool_call_hooks {
437 reg.tool_calls().after(Arc::clone(hook));
438 }
439 for hook in &self.spec.after_turn_hooks {
440 reg.turn().after(Arc::clone(hook));
441 }
442 for hook in &self.spec.checkpoint_hooks {
443 reg.turn().checkpoint(Arc::clone(hook));
444 }
445 for hook in &self.spec.assistant_stream_hooks {
446 reg.output().stream(Arc::clone(hook));
447 }
448 for hook in &self.spec.assistant_response_hooks {
449 reg.output().response(Arc::clone(hook));
450 }
451 if let Some(projector) = &self.spec.tool_result_projector {
452 reg.tool_results().projector(Arc::clone(projector))?;
453 }
454 for hook in &self.spec.runtime_event_hooks {
455 reg.session().on_event(Arc::clone(hook));
456 }
457 for hook in &self.spec.session_config_mutators {
458 reg.session().config_mutator(Arc::clone(hook));
459 }
460 for (def, handler) in &self.spec.plugin_actions {
461 reg.actions().op(def.clone(), Arc::clone(handler))?;
462 }
463 for (priority, transform) in &self.spec.turn_context_transforms {
464 reg.context().prepare_turn(*priority, Arc::clone(transform));
465 }
466 for (priority, compactor) in &self.spec.context_compactors {
467 reg.context().compact(*priority, Arc::clone(compactor));
468 }
469 Ok(())
470 }
471}