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