lash_core/plugin/
surface.rs1use 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}