lash_core/plugin/
tool_catalog.rs1use 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#[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}