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