Skip to main content

lash_core/plugin/
surface.rs

1use lash_sansio::ToolCallOutput;
2use serde::{Deserialize, Serialize};
3use tokio::sync::mpsc;
4
5use super::*;
6
7#[derive(Clone)]
8pub struct ToolSurfaceContext {
9    pub session_id: String,
10    pub mode: ExecutionMode,
11    pub tools: Vec<ToolManifest>,
12    pub resolve_contract: Option<lash_sansio::ToolContractResolver>,
13    pub tool_access: SessionToolAccess,
14    pub subagent: Option<SubagentSessionAuthority>,
15}
16
17#[derive(Clone, Debug)]
18pub struct ToolDiscoveryContext {
19    pub session_id: String,
20    pub mode: ExecutionMode,
21    pub catalog: Vec<serde_json::Value>,
22}
23
24#[derive(Clone, Debug, Default, Serialize, Deserialize)]
25pub struct ToolDiscoveryContribution {
26    pub tools: Vec<ToolDiscoveryToolContribution>,
27}
28
29#[derive(Clone, Debug, Default, Serialize, Deserialize)]
30pub struct ToolDiscoveryToolContribution {
31    pub tool_name: String,
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub namespace: Option<String>,
34    #[serde(default, skip_serializing_if = "Vec::is_empty")]
35    pub aliases: Vec<String>,
36}
37
38#[derive(Clone, Debug)]
39pub struct PluginAbort {
40    pub code: String,
41    pub message: String,
42}
43
44#[derive(Clone, Debug, Default)]
45pub struct TurnPreparation {
46    pub messages: crate::MessageSequence,
47    pub events: Vec<crate::SessionEvent>,
48    pub abort: Option<PluginAbort>,
49}
50
51#[derive(Clone)]
52pub struct PrepareTurnRequest {
53    pub session_id: String,
54    pub state: SessionReadView,
55    pub messages: crate::MessageSequence,
56    pub host: Arc<dyn TurnHookHost>,
57    pub turn_context: crate::TurnContext,
58}
59
60#[derive(Clone, Debug, Default)]
61pub struct CheckpointApplication {
62    pub messages: Vec<PluginMessage>,
63    pub events: Vec<crate::SessionEvent>,
64    pub abort: Option<PluginAbort>,
65}
66
67#[derive(Clone, Debug)]
68pub struct TurnFinalization {
69    pub turn: AssembledTurn,
70    pub events: Vec<crate::SessionEvent>,
71}
72
73pub(crate) async fn emit_plugin_surface_events(
74    event_tx: &mpsc::Sender<crate::SessionEvent>,
75    plugin_id: &str,
76    events: Vec<PluginSurfaceEvent>,
77) {
78    for event in plugin_surface_session_events(plugin_id, events) {
79        crate::session_model::send_event(event_tx, event).await;
80    }
81}
82
83pub(crate) fn plugin_surface_session_events(
84    plugin_id: &str,
85    events: Vec<PluginSurfaceEvent>,
86) -> Vec<crate::SessionEvent> {
87    events
88        .into_iter()
89        .map(|event| crate::SessionEvent::PluginEvent {
90            plugin_id: plugin_id.to_string(),
91            event,
92        })
93        .collect()
94}
95
96#[derive(Clone, Debug, Serialize, Deserialize)]
97#[serde(tag = "kind", rename_all = "snake_case")]
98#[allow(clippy::large_enum_variant)]
99pub enum PluginDirective {
100    AbortTurn {
101        code: String,
102        message: String,
103    },
104    EnqueueMessages {
105        messages: Vec<PluginMessage>,
106    },
107    CreateSession {
108        request: Box<SessionCreateRequest>,
109    },
110    HandoffSession {
111        session_id: String,
112    },
113    ReplaceToolArgs {
114        args: serde_json::Value,
115    },
116    ShortCircuitTool {
117        output: ToolCallOutput,
118    },
119    EmitEvents {
120        events: Vec<PluginSurfaceEvent>,
121    },
122    EmitTrace {
123        name: String,
124        #[serde(default)]
125        payload: serde_json::Value,
126        #[serde(default)]
127        context: Box<lash_trace::TraceContext>,
128    },
129}
130
131impl PluginDirective {
132    pub fn short_circuit(result: ToolResult) -> Self {
133        Self::ShortCircuitTool {
134            output: *result.output,
135        }
136    }
137
138    pub fn into_tool_result(self) -> Option<ToolResult> {
139        match self {
140            Self::ShortCircuitTool { output } => Some(ToolResult::from_output(output)),
141            _ => None,
142        }
143    }
144
145    pub fn emit_events(events: Vec<PluginSurfaceEvent>) -> Self {
146        Self::EmitEvents { events }
147    }
148
149    pub fn emit_trace(name: impl Into<String>, payload: serde_json::Value) -> Self {
150        Self::EmitTrace {
151            name: name.into(),
152            payload,
153            context: Box::new(lash_trace::TraceContext::default()),
154        }
155    }
156}