Skip to main content

lash_core/plugin/
hooks.rs

1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4
5use super::*;
6
7pub type PluginFuture<T> = Pin<Box<dyn Future<Output = Result<T, PluginError>> + Send>>;
8pub type PluginRuntimeEventHook = Arc<dyn Fn(PluginRuntimeEvent) -> PluginFuture<()> + Send + Sync>;
9pub type PluginSessionTask = PluginFuture<()>;
10pub type SessionConfigMutator = Arc<
11    dyn Fn(SessionConfigChangedContext, SessionPolicy) -> PluginFuture<SessionPolicy> + Send + Sync,
12>;
13pub type BeforeTurnHook =
14    Arc<dyn Fn(TurnHookContext) -> PluginFuture<Vec<PluginDirective>> + Send + Sync>;
15pub type BeforeToolCallHook =
16    Arc<dyn Fn(ToolCallHookContext) -> PluginFuture<Vec<PluginDirective>> + Send + Sync>;
17pub type AfterToolCallHook =
18    Arc<dyn Fn(ToolResultHookContext) -> PluginFuture<Vec<PluginDirective>> + Send + Sync>;
19pub type ToolResultProjector =
20    Arc<dyn Fn(ToolResultProjectionContext) -> PluginFuture<crate::ModelToolReturn> + Send + Sync>;
21pub type AfterTurnHook =
22    Arc<dyn Fn(TurnResultHookContext) -> PluginFuture<Vec<PluginDirective>> + Send + Sync>;
23pub type CheckpointHook =
24    Arc<dyn Fn(CheckpointHookContext) -> PluginFuture<Vec<PluginDirective>> + Send + Sync>;
25pub type PromptContributor =
26    Arc<dyn Fn(PromptHookContext) -> PluginFuture<Vec<PromptContribution>> + Send + Sync>;
27pub type ToolSurfaceContributor =
28    Arc<dyn Fn(ToolSurfaceContext) -> Result<ToolSurfaceContribution, PluginError> + Send + Sync>;
29pub type ToolDiscoveryContributor = Arc<
30    dyn Fn(ToolDiscoveryContext) -> Result<ToolDiscoveryContribution, PluginError> + Send + Sync,
31>;
32pub type AssistantStreamHook =
33    Arc<dyn Fn(AssistantStreamHookContext) -> PluginFuture<AssistantStreamTransform> + Send + Sync>;
34pub type AssistantResponseHook = Arc<
35    dyn Fn(AssistantResponseHookContext) -> PluginFuture<AssistantResponseTransform> + Send + Sync,
36>;
37
38#[derive(Clone)]
39pub struct PromptHookContext {
40    pub session_id: String,
41    pub host: Arc<dyn PromptHookHost>,
42    pub state: SessionReadView,
43    pub mode_turn_options: ModeTurnOptions,
44    pub turn_context: crate::TurnContext,
45}
46
47#[derive(Clone)]
48pub struct TurnHookContext {
49    pub session_id: String,
50    pub state: SessionReadView,
51    pub host: Arc<dyn TurnHookHost>,
52    pub turn_context: crate::TurnContext,
53}
54
55#[derive(Clone)]
56pub struct SessionConfigChangedContext {
57    pub session_id: String,
58    pub previous: SessionPolicy,
59    pub current: SessionPolicy,
60    pub host: Arc<dyn TaskHost>,
61}
62
63#[derive(Clone)]
64pub struct SessionStateChangedContext {
65    pub session_id: String,
66    pub state: SessionReadView,
67    pub host: Arc<dyn HistoryHost>,
68}
69
70#[derive(Clone)]
71pub enum PluginRuntimeEvent {
72    TurnCommitted(Arc<AssembledTurn>),
73    TurnPersisted(SessionStateChangedContext),
74    SessionRestored(SessionReadView),
75    SessionConfigChanged(Box<SessionConfigChangedContext>),
76}
77
78#[derive(Clone, Debug)]
79pub struct TurnResultSummary {
80    pub outcome: crate::TurnOutcome,
81    pub assistant_output: crate::runtime::AssistantOutput,
82    pub execution: crate::runtime::ExecutionSummary,
83    pub token_usage: crate::TokenUsage,
84    pub tool_calls: Arc<Vec<crate::ToolCallRecord>>,
85    pub errors: Arc<Vec<crate::runtime::TurnIssue>>,
86}
87
88impl TurnResultSummary {
89    pub fn from_assembled(turn: &AssembledTurn) -> Self {
90        Self {
91            outcome: turn.outcome.clone(),
92            assistant_output: turn.assistant_output.clone(),
93            execution: turn.execution.clone(),
94            token_usage: turn.token_usage.clone(),
95            tool_calls: Arc::new(turn.tool_calls.clone()),
96            errors: Arc::new(turn.errors.clone()),
97        }
98    }
99}
100
101#[derive(Clone)]
102pub struct ToolCallHookContext {
103    pub session_id: String,
104    pub tool_name: String,
105    pub args: serde_json::Value,
106    pub turn_context: crate::TurnContext,
107    pub(crate) host: Arc<dyn ToolHookHost>,
108}
109
110impl ToolCallHookContext {
111    pub fn new(
112        session_id: String,
113        tool_name: String,
114        args: serde_json::Value,
115        turn_context: crate::TurnContext,
116        host: Arc<dyn ToolHookHost>,
117    ) -> Self {
118        Self {
119            session_id,
120            tool_name,
121            args,
122            turn_context,
123            host,
124        }
125    }
126
127    pub async fn session_snapshot(&self) -> Result<SessionSnapshot, PluginError> {
128        self.host.snapshot_session(&self.session_id).await
129    }
130
131    pub async fn set_tools_availability(
132        &self,
133        names: &[String],
134        availability: Option<crate::ToolAvailability>,
135    ) -> Result<u64, PluginError> {
136        self.host
137            .set_tools_availability(&self.session_id, names, availability)
138            .await
139    }
140}
141
142#[derive(Clone)]
143pub struct ToolResultHookContext {
144    pub session_id: String,
145    pub tool_name: String,
146    pub args: serde_json::Value,
147    pub result: ToolResult,
148    pub duration_ms: u64,
149    pub turn_context: crate::TurnContext,
150    pub(crate) host: Arc<dyn ToolHookHost>,
151}
152
153impl ToolResultHookContext {
154    pub fn new(
155        session_id: String,
156        tool_name: String,
157        args: serde_json::Value,
158        result: ToolResult,
159        duration_ms: u64,
160        turn_context: crate::TurnContext,
161        host: Arc<dyn ToolHookHost>,
162    ) -> Self {
163        Self {
164            session_id,
165            tool_name,
166            args,
167            result,
168            duration_ms,
169            turn_context,
170            host,
171        }
172    }
173
174    pub async fn session_snapshot(&self) -> Result<SessionSnapshot, PluginError> {
175        self.host.snapshot_session(&self.session_id).await
176    }
177
178    pub async fn set_tools_availability(
179        &self,
180        names: &[String],
181        availability: Option<crate::ToolAvailability>,
182    ) -> Result<u64, PluginError> {
183        self.host
184            .set_tools_availability(&self.session_id, names, availability)
185            .await
186    }
187}
188
189#[derive(Clone)]
190pub struct ToolResultProjectionContext {
191    pub session_id: String,
192    pub call_id: String,
193    pub tool_name: String,
194    pub args: serde_json::Value,
195    pub output: crate::ToolCallOutput,
196    pub duration_ms: u64,
197}
198
199#[derive(Clone)]
200pub struct TurnResultHookContext {
201    pub session_id: String,
202    pub turn: Arc<TurnResultSummary>,
203    pub host: Arc<dyn TurnResultHookHost>,
204}
205
206#[derive(Clone)]
207pub struct CheckpointHookContext {
208    pub session_id: String,
209    pub checkpoint: CheckpointKind,
210    pub state: SessionReadView,
211    pub host: Arc<dyn CheckpointHookHost>,
212}
213
214#[derive(Clone)]
215pub struct AssistantStreamHookContext {
216    pub session_id: String,
217    pub chunk: String,
218}
219
220#[derive(Clone, Debug, Default)]
221pub struct AssistantStreamTransform {
222    pub chunk: String,
223    pub reasoning_deltas: Vec<String>,
224    pub events: Vec<PluginSurfaceEvent>,
225    /// When `true`, the runtime cancels the in-flight LLM call the
226    /// moment this hook returns and finalizes the turn using whatever
227    /// text has been streamed so far. Any plugin may set this — the
228    /// first to raise it wins. Used by mode plugins to enforce
229    /// one-block-per-turn contracts (e.g. the RLM stream mask aborts
230    /// as soon as the first lashlang fence closes).
231    pub abort_stream: bool,
232}
233
234#[derive(Clone)]
235pub struct AssistantResponseHookContext {
236    pub session_id: String,
237    pub response: crate::LlmResponse,
238}
239
240#[derive(Clone, Debug)]
241pub struct AssistantResponseTransform {
242    pub response: crate::LlmResponse,
243    pub events: Vec<PluginSurfaceEvent>,
244}