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::SessionStreamEvent>,
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::SessionStreamEvent>,
45    pub abort: Option<PluginAbort>,
46}
47
48#[derive(Clone, Debug)]
49pub struct TurnFinalization {
50    pub turn: AssembledTurn,
51    pub events: Vec<crate::SessionStreamEvent>,
52}
53
54pub(crate) async fn emit_plugin_runtime_events(
55    event_tx: &mpsc::Sender<crate::SessionStreamEvent>,
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::SessionStreamEvent> {
68    events
69        .into_iter()
70        .map(|event| crate::SessionStreamEvent::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// justification: directives are transient public plugin values and the common short-circuit output avoids another allocation.
80#[allow(clippy::large_enum_variant)]
81pub enum PluginDirective {
82    AbortTurn {
83        code: String,
84        message: String,
85    },
86    EnqueueMessages {
87        messages: Vec<PluginMessage>,
88    },
89    CreateSession {
90        request: Box<SessionCreateRequest>,
91    },
92    ReplaceToolArgs {
93        args: serde_json::Value,
94    },
95    ShortCircuitTool {
96        output: ToolCallOutput,
97    },
98    EmitRuntimeEvents {
99        events: Vec<PluginRuntimeEvent>,
100    },
101    EmitTrace {
102        name: String,
103        #[serde(default)]
104        payload: serde_json::Value,
105        #[serde(default)]
106        context: Box<lash_trace::TraceContext>,
107    },
108}
109
110impl PluginDirective {
111    pub fn short_circuit(result: ToolResult) -> Self {
112        Self::ShortCircuitTool {
113            output: result.into_done_output().unwrap_or_else(|_| {
114                ToolCallOutput::failure(crate::ToolFailure::runtime(
115                    crate::ToolFailureClass::Internal,
116                    "pending_tool_short_circuit",
117                    "plugin short-circuit directives require completed tool output",
118                ))
119            }),
120        }
121    }
122
123    pub fn into_tool_result(self) -> Option<ToolResult> {
124        match self {
125            Self::ShortCircuitTool { output } => Some(ToolResult::from_output(output)),
126            _ => None,
127        }
128    }
129
130    pub fn emit_runtime_events(events: Vec<PluginRuntimeEvent>) -> Self {
131        Self::EmitRuntimeEvents { events }
132    }
133
134    pub fn emit_trace(name: impl Into<String>, payload: serde_json::Value) -> Self {
135        Self::EmitTrace {
136            name: name.into(),
137            payload,
138            context: Box::new(lash_trace::TraceContext::default()),
139        }
140    }
141}