Skip to main content

lash_core/plugin/
tool_catalog.rs

1use lash_sansio::ToolCallOutput;
2use serde::{Deserialize, Serialize};
3use tokio::sync::mpsc;
4
5use super::*;
6
7#[derive(Clone)]
8pub struct ToolCatalogContext {
9    pub session_id: String,
10    pub tools: Vec<ToolManifest>,
11    pub resolve_contract: Option<lash_sansio::ToolContractResolver>,
12    pub tool_access: SessionToolAccess,
13    pub subagent: Option<SubagentSessionContext>,
14    pub extensions: PluginExtensions,
15}
16
17#[derive(Clone, Debug)]
18pub struct PluginAbort {
19    pub code: String,
20    pub message: String,
21}
22
23#[derive(Clone, Debug, Default)]
24pub struct TurnPreparation {
25    pub messages: crate::MessageSequence,
26    pub events: Vec<crate::SessionEvent>,
27    pub abort: Option<PluginAbort>,
28}
29
30#[derive(Clone)]
31pub struct PrepareTurnRequest {
32    pub session_id: String,
33    pub state: SessionReadView,
34    pub messages: crate::MessageSequence,
35    pub sessions: Arc<dyn SessionStateService>,
36    pub session_lifecycle: Arc<dyn SessionLifecycleService>,
37    pub session_graph: Arc<dyn SessionGraphService>,
38    pub turn_context: crate::TurnContext,
39}
40
41#[derive(Clone, Debug, Default)]
42pub struct CheckpointApplication {
43    pub messages: Vec<PluginMessage>,
44    pub events: Vec<crate::SessionEvent>,
45    pub abort: Option<PluginAbort>,
46}
47
48#[derive(Clone, Debug)]
49pub struct TurnFinalization {
50    pub turn: AssembledTurn,
51    pub events: Vec<crate::SessionEvent>,
52}
53
54pub(crate) async fn emit_plugin_runtime_events(
55    event_tx: &mpsc::Sender<crate::SessionEvent>,
56    plugin_id: &str,
57    events: Vec<PluginRuntimeEvent>,
58) {
59    for event in plugin_runtime_session_events(plugin_id, events) {
60        crate::session_model::send_event(event_tx, event).await;
61    }
62}
63
64pub(crate) fn plugin_runtime_session_events(
65    plugin_id: &str,
66    events: Vec<PluginRuntimeEvent>,
67) -> Vec<crate::SessionEvent> {
68    events
69        .into_iter()
70        .map(|event| crate::SessionEvent::PluginEvent {
71            plugin_id: plugin_id.to_string(),
72            event,
73        })
74        .collect()
75}
76
77#[derive(Clone, Debug, Serialize, Deserialize)]
78#[serde(tag = "kind", rename_all = "snake_case")]
79#[allow(clippy::large_enum_variant)]
80pub enum PluginDirective {
81    AbortTurn {
82        code: String,
83        message: String,
84    },
85    EnqueueMessages {
86        messages: Vec<PluginMessage>,
87    },
88    CreateSession {
89        request: Box<SessionCreateRequest>,
90    },
91    ReplaceToolArgs {
92        args: serde_json::Value,
93    },
94    ShortCircuitTool {
95        output: ToolCallOutput,
96    },
97    EmitRuntimeEvents {
98        events: Vec<PluginRuntimeEvent>,
99    },
100    EmitTrace {
101        name: String,
102        #[serde(default)]
103        payload: serde_json::Value,
104        #[serde(default)]
105        context: Box<lash_trace::TraceContext>,
106    },
107}
108
109impl PluginDirective {
110    pub fn short_circuit(result: ToolResult) -> Self {
111        Self::ShortCircuitTool {
112            output: result.into_done_output().unwrap_or_else(|_| {
113                ToolCallOutput::failure(crate::ToolFailure::runtime(
114                    crate::ToolFailureClass::Internal,
115                    "pending_tool_short_circuit",
116                    "plugin short-circuit directives require completed tool output",
117                ))
118            }),
119        }
120    }
121
122    pub fn into_tool_result(self) -> Option<ToolResult> {
123        match self {
124            Self::ShortCircuitTool { output } => Some(ToolResult::from_output(output)),
125            _ => None,
126        }
127    }
128
129    pub fn emit_runtime_events(events: Vec<PluginRuntimeEvent>) -> Self {
130        Self::EmitRuntimeEvents { events }
131    }
132
133    pub fn emit_trace(name: impl Into<String>, payload: serde_json::Value) -> Self {
134        Self::EmitTrace {
135            name: name.into(),
136            payload,
137            context: Box::new(lash_trace::TraceContext::default()),
138        }
139    }
140}