1use std::collections::BTreeMap;
11use std::sync::Arc;
12
13use serde::de::DeserializeOwned;
14use serde::{Deserialize, Serialize};
15
16use super::{SessionAppendNode, SessionCreateRequest};
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_from_request(
70 &self,
71 _ctx: ProtocolRuntimeContext<'_>,
72 _request: &SessionCreateRequest,
73 ) -> Result<(), crate::SessionError> {
74 Ok(())
75 }
76
77 async fn before_llm_call(
78 &self,
79 _ctx: ProtocolBeforeLlmCallContext<'_>,
80 _request: &LlmRequest,
81 ) -> Result<Option<ProtocolLlmCallAction>, crate::PluginError> {
82 Ok(None)
83 }
84}
85
86pub struct ProtocolSessionContext<'a> {
95 session_id: &'a str,
96}
97
98impl<'a> ProtocolSessionContext<'a> {
99 pub(crate) fn new(_session: &'a mut crate::Session, session_id: &'a str) -> Self {
100 Self { session_id }
101 }
102
103 pub fn session_id(&self) -> &str {
106 self.session_id
107 }
108}
109
110pub struct ProtocolBeforeLlmCallContext<'run> {
111 pub session_id: String,
112 pub sessions: Arc<dyn crate::plugin::SessionStateService>,
113 pub session_graph: Arc<dyn crate::plugin::SessionGraphService>,
114 pub processes: Arc<dyn crate::ProcessService>,
115 pub state: SessionReadView,
116 pub latest_prompt_usage: Option<PromptUsage>,
117 pub(crate) direct_completions: crate::DirectCompletionClient<'run>,
118 pub(crate) process_parent_invocation: crate::RuntimeInvocation,
119 pub(crate) effect_controller: crate::runtime::RuntimeEffectControllerHandle<'run>,
120}
121
122impl ProtocolBeforeLlmCallContext<'_> {
123 pub async fn direct_llm_completion(
124 &self,
125 request: crate::LlmRequest,
126 usage_source: &str,
127 ) -> Result<crate::DirectLlmCompletion, crate::PluginError> {
128 self.direct_completions
129 .direct_llm_completion(request, usage_source)
130 .await
131 }
132
133 pub fn process_scope(&self) -> crate::ProcessOpScope<'_> {
134 crate::ProcessOpScope::new(self.effect_controller.scoped())
135 .with_parent_invocation(Some(self.process_parent_invocation.clone()))
136 }
137}
138
139#[derive(Clone, Debug, PartialEq, Eq)]
140pub enum ProtocolLlmCallAction {
141 SwitchAgentFrame { frame_id: String },
142}
143
144pub struct ProtocolRuntimeContext<'a> {
151 runtime: &'a mut crate::runtime::LashRuntime,
152}
153
154impl<'a> ProtocolRuntimeContext<'a> {
155 pub(crate) fn new(runtime: &'a mut crate::runtime::LashRuntime) -> Self {
156 Self { runtime }
157 }
158
159 pub fn set_protocol_turn_options(&mut self, options: crate::ProtocolTurnOptions) {
160 self.runtime.set_protocol_turn_options(options);
161 }
162}
163
164#[async_trait::async_trait]
165pub trait CodeExecutorPlugin: Send + Sync {
166 async fn execute_code(
167 &self,
168 ctx: RuntimeExecutionContext<'_>,
169 request: ExecRequest,
170 ) -> Result<ExecResponse, crate::SessionError>;
171
172 fn execution_state_dirty(&self) -> bool {
173 false
174 }
175
176 async fn snapshot_execution_state(
177 &self,
178 _ctx: ProtocolSessionContext<'_>,
179 ) -> Result<Option<Vec<u8>>, crate::SessionError> {
180 Ok(None)
181 }
182
183 async fn restore_execution_state(
184 &self,
185 _ctx: ProtocolSessionContext<'_>,
186 _data: &[u8],
187 ) -> Result<(), crate::SessionError> {
188 Ok(())
189 }
190}
191
192pub trait AssistantProseProjectorPlugin: Send + Sync {
193 fn project_assistant_prose(&self, text: &str) -> String;
194}
195
196pub trait ProtocolDriverPlugin: Send + Sync {
201 fn build_preamble(&self, input: crate::ProtocolBuildInput) -> crate::TurnDriverPreamble;
204}
205
206#[derive(Clone, Debug, Default, Serialize, Deserialize)]
208pub struct PluginOptions {
209 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
210 pub plugins: BTreeMap<String, serde_json::Value>,
211}
212
213impl PluginOptions {
214 pub fn empty() -> Self {
215 Self::default()
216 }
217
218 pub fn typed<T>(plugin_id: impl Into<String>, extras: T) -> Result<Self, serde_json::Error>
219 where
220 T: Serialize,
221 {
222 let mut options = Self::default();
223 options.insert_typed(plugin_id, extras)?;
224 Ok(options)
225 }
226
227 pub fn insert_typed<T>(
228 &mut self,
229 plugin_id: impl Into<String>,
230 extras: T,
231 ) -> Result<(), serde_json::Error>
232 where
233 T: Serialize,
234 {
235 self.plugins
236 .insert(plugin_id.into(), serde_json::to_value(extras)?);
237 Ok(())
238 }
239
240 pub fn decode<T>(&self, plugin_id: &str) -> Result<Option<T>, serde_json::Error>
241 where
242 T: DeserializeOwned,
243 {
244 self.plugins
245 .get(plugin_id)
246 .cloned()
247 .map(serde_json::from_value)
248 .transpose()
249 }
250}