1use std::collections::BTreeMap;
11use std::sync::Arc;
12
13use serde::de::DeserializeOwned;
14use serde::{Deserialize, Serialize};
15
16use super::SessionAppendNode;
17use crate::runtime::RuntimeSessionState;
18use crate::{
19 ExecRequest, ExecResponse, LlmRequest, PromptUsage, RuntimeExecutionContext, SessionReadView,
20};
21
22#[async_trait::async_trait]
29pub trait ProtocolSessionPlugin: Send + Sync {
30 async fn initialize_session(
31 &self,
32 _ctx: ProtocolSessionContext<'_>,
33 ) -> Result<(), crate::SessionError> {
34 Ok(())
35 }
36
37 async fn restore_session(
38 &self,
39 _ctx: ProtocolSessionContext<'_>,
40 _state: &RuntimeSessionState,
41 ) -> Result<(), crate::SessionError> {
42 Ok(())
43 }
44
45 async fn append_session_nodes(
46 &self,
47 _ctx: ProtocolSessionContext<'_>,
48 _nodes: &[SessionAppendNode],
49 ) -> Result<(), crate::SessionError> {
50 Ok(())
51 }
52
53 async fn apply_session_extension(
54 &self,
55 _extension: crate::ProtocolSessionExtensionHandle,
56 ) -> Result<(), crate::SessionError> {
57 Err(crate::SessionError::Protocol(
58 "protocol does not accept session extensions".to_string(),
59 ))
60 }
61
62 async fn validate_turn_extension(
63 &self,
64 _extension: &crate::ProtocolTurnExtensionHandle,
65 ) -> Result<(), crate::SessionError> {
66 Ok(())
67 }
68
69 fn configure_runtime_on_materialize(
79 &self,
80 _ctx: ProtocolRuntimeContext<'_>,
81 _materialization: ProtocolSessionMaterialization<'_>,
82 ) -> Result<(), crate::SessionError> {
83 Ok(())
84 }
85
86 async fn before_llm_call(
87 &self,
88 _ctx: ProtocolBeforeLlmCallContext<'_>,
89 _request: &LlmRequest,
90 ) -> Result<Option<ProtocolLlmCallAction>, crate::PluginError> {
91 Ok(None)
92 }
93}
94
95pub struct ProtocolSessionContext<'a> {
104 session_id: &'a str,
105}
106
107impl<'a> ProtocolSessionContext<'a> {
108 pub(crate) fn new(_session: &'a mut crate::Session, session_id: &'a str) -> Self {
109 Self { session_id }
110 }
111
112 pub fn session_id(&self) -> &str {
115 self.session_id
116 }
117}
118
119pub struct ProtocolBeforeLlmCallContext<'run> {
120 pub session_id: String,
121 pub sessions: Arc<dyn crate::plugin::SessionStateService>,
122 pub session_graph: Arc<dyn crate::plugin::SessionGraphService>,
123 pub processes: Arc<dyn crate::ProcessService>,
124 pub state: SessionReadView,
125 pub latest_prompt_usage: Option<PromptUsage>,
126 pub(crate) direct_completions: crate::DirectCompletionClient<'run>,
127 pub(crate) process_parent_invocation: crate::RuntimeInvocation,
128 pub(crate) effect_controller: crate::runtime::RuntimeEffectControllerHandle<'run>,
129}
130
131impl ProtocolBeforeLlmCallContext<'_> {
132 pub async fn direct_llm_completion(
133 &self,
134 request: crate::LlmRequest,
135 usage_source: &str,
136 ) -> Result<crate::DirectLlmCompletion, crate::PluginError> {
137 self.direct_completions
138 .direct_llm_completion(request, usage_source)
139 .await
140 }
141
142 pub fn process_scope(&self) -> crate::ProcessOpScope<'_> {
143 crate::ProcessOpScope::new(self.effect_controller.scoped())
144 .with_parent_invocation(Some(self.process_parent_invocation.clone()))
145 }
146}
147
148#[derive(Clone, Debug, PartialEq, Eq)]
149pub enum ProtocolLlmCallAction {
150 SwitchAgentFrame { frame_id: String, task: String },
151}
152
153pub struct ProtocolRuntimeContext<'a> {
160 runtime: &'a mut crate::runtime::LashRuntime,
161}
162
163impl<'a> ProtocolRuntimeContext<'a> {
164 pub(crate) fn new(runtime: &'a mut crate::runtime::LashRuntime) -> Self {
165 Self { runtime }
166 }
167
168 pub fn protocol_turn_options(&self) -> &crate::ProtocolTurnOptions {
172 self.runtime.protocol_turn_options()
173 }
174
175 pub fn set_protocol_turn_options(&mut self, options: crate::ProtocolTurnOptions) {
178 self.runtime.set_protocol_turn_options(options);
179 }
180
181 pub fn set_protocol_turn_options_all_frames(&mut self, options: crate::ProtocolTurnOptions) {
185 self.runtime.set_protocol_turn_options_all_frames(options);
186 }
187}
188
189pub struct ProtocolSessionMaterialization<'a> {
192 pub plugin_options: &'a PluginOptions,
195 pub is_root_session: bool,
197}
198
199#[async_trait::async_trait]
200pub trait CodeExecutorPlugin: Send + Sync {
201 async fn execute_code(
202 &self,
203 ctx: RuntimeExecutionContext<'_>,
204 request: ExecRequest,
205 ) -> Result<ExecResponse, crate::SessionError>;
206
207 fn execution_state_dirty(&self) -> bool {
208 false
209 }
210
211 async fn snapshot_execution_state(
212 &self,
213 _ctx: ProtocolSessionContext<'_>,
214 ) -> Result<Option<Vec<u8>>, crate::SessionError> {
215 Ok(None)
216 }
217
218 async fn restore_execution_state(
219 &self,
220 _ctx: ProtocolSessionContext<'_>,
221 _data: &[u8],
222 ) -> Result<(), crate::SessionError> {
223 Ok(())
224 }
225}
226
227pub trait AssistantProseProjectorPlugin: Send + Sync {
228 fn project_assistant_prose(&self, text: &str) -> String;
229}
230
231pub trait ProtocolDriverPlugin: Send + Sync {
240 fn build_preamble(&self, input: crate::ProtocolBuildInput) -> crate::TurnDriverPreamble;
243}
244
245#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
247pub struct PluginOptions {
248 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
249 pub plugins: BTreeMap<String, serde_json::Value>,
250}
251
252impl PluginOptions {
253 pub fn empty() -> Self {
254 Self::default()
255 }
256
257 pub fn typed<T>(plugin_id: impl Into<String>, extras: T) -> Result<Self, serde_json::Error>
258 where
259 T: Serialize,
260 {
261 let mut options = Self::default();
262 options.insert_typed(plugin_id, extras)?;
263 Ok(options)
264 }
265
266 pub fn insert_typed<T>(
267 &mut self,
268 plugin_id: impl Into<String>,
269 extras: T,
270 ) -> Result<(), serde_json::Error>
271 where
272 T: Serialize,
273 {
274 self.plugins
275 .insert(plugin_id.into(), serde_json::to_value(extras)?);
276 Ok(())
277 }
278
279 pub fn decode<T>(&self, plugin_id: &str) -> Result<Option<T>, serde_json::Error>
280 where
281 T: DeserializeOwned,
282 {
283 self.plugins
284 .get(plugin_id)
285 .cloned()
286 .map(serde_json::from_value)
287 .transpose()
288 }
289}