1use gemini_genai_rs::prelude::{Content, FunctionResponse};
14use gemini_genai_rs::session::{SessionError, SessionEvent, SessionHandle, SessionWriter};
15use std::sync::Arc;
16use tokio::sync::broadcast;
17
18use crate::error::AgentError;
19use crate::state::State;
20
21#[derive(Debug, Clone)]
24pub enum InputEvent {
25 Audio(bytes::Bytes),
27 Text(String),
29 ActivityStart,
31 ActivityEnd,
33}
34
35#[derive(Clone)]
40pub struct AgentSession {
41 writer: Arc<dyn SessionWriter>,
43 event_tx: broadcast::Sender<SessionEvent>,
45 input_broadcast: broadcast::Sender<InputEvent>,
48 state: State,
50}
51
52impl AgentSession {
53 pub fn new(session: SessionHandle) -> Self {
55 let (input_broadcast, _) = broadcast::channel(256);
56 let event_tx = session.event_sender().clone();
57 Self {
58 writer: Arc::new(session),
59 event_tx,
60 input_broadcast,
61 state: State::new(),
62 }
63 }
64
65 pub fn from_writer(
67 writer: Arc<dyn SessionWriter>,
68 event_tx: broadcast::Sender<SessionEvent>,
69 ) -> Self {
70 let (input_broadcast, _) = broadcast::channel(256);
71 Self {
72 writer,
73 event_tx,
74 input_broadcast,
75 state: State::new(),
76 }
77 }
78
79 pub async fn send_audio(&self, data: impl Into<bytes::Bytes>) -> Result<(), AgentError> {
81 let data: bytes::Bytes = data.into();
82 if self.input_broadcast.receiver_count() > 0 {
84 let _ = self.input_broadcast.send(InputEvent::Audio(data.clone()));
85 }
86 self.writer
88 .send_audio(data)
89 .await
90 .map_err(AgentError::Session)
91 }
92
93 pub async fn send_text(&self, text: impl Into<String>) -> Result<(), AgentError> {
95 let t = text.into();
96 if self.input_broadcast.receiver_count() > 0 {
97 let _ = self.input_broadcast.send(InputEvent::Text(t.clone()));
98 }
99 self.writer.send_text(t).await.map_err(AgentError::Session)
100 }
101
102 pub async fn send_tool_response(
104 &self,
105 responses: Vec<FunctionResponse>,
106 ) -> Result<(), AgentError> {
107 self.writer
108 .send_tool_response(responses)
109 .await
110 .map_err(AgentError::Session)
111 }
112
113 pub async fn send_client_content(
115 &self,
116 turns: Vec<Content>,
117 turn_complete: bool,
118 ) -> Result<(), AgentError> {
119 self.writer
120 .send_client_content(turns, turn_complete)
121 .await
122 .map_err(AgentError::Session)
123 }
124
125 pub async fn send_video(&self, jpeg_data: impl Into<bytes::Bytes>) -> Result<(), AgentError> {
127 self.writer
128 .send_video(jpeg_data.into())
129 .await
130 .map_err(AgentError::Session)
131 }
132
133 pub async fn update_instruction(
135 &self,
136 instruction: impl Into<String>,
137 ) -> Result<(), AgentError> {
138 self.writer
139 .update_instruction(instruction.into())
140 .await
141 .map_err(AgentError::Session)
142 }
143
144 pub async fn signal_activity_start(&self) -> Result<(), AgentError> {
146 if self.input_broadcast.receiver_count() > 0 {
147 let _ = self.input_broadcast.send(InputEvent::ActivityStart);
148 }
149 self.writer
150 .signal_activity_start()
151 .await
152 .map_err(AgentError::Session)
153 }
154
155 pub async fn signal_activity_end(&self) -> Result<(), AgentError> {
157 if self.input_broadcast.receiver_count() > 0 {
158 let _ = self.input_broadcast.send(InputEvent::ActivityEnd);
159 }
160 self.writer
161 .signal_activity_end()
162 .await
163 .map_err(AgentError::Session)
164 }
165
166 pub async fn disconnect(&self) -> Result<(), AgentError> {
168 self.writer.disconnect().await.map_err(AgentError::Session)
169 }
170
171 pub fn subscribe_input(&self) -> broadcast::Receiver<InputEvent> {
173 self.input_broadcast.subscribe()
174 }
175
176 pub fn subscribe_events(&self) -> broadcast::Receiver<SessionEvent> {
178 self.event_tx.subscribe()
179 }
180
181 pub fn writer(&self) -> &dyn SessionWriter {
183 &*self.writer
184 }
185
186 pub fn state(&self) -> &State {
188 &self.state
189 }
190
191 pub fn input_subscriber_count(&self) -> usize {
193 self.input_broadcast.receiver_count()
194 }
195}
196
197pub struct NoOpSessionWriter;
200
201#[async_trait::async_trait]
202impl SessionWriter for NoOpSessionWriter {
203 async fn send_audio(&self, _data: bytes::Bytes) -> Result<(), SessionError> {
204 Ok(())
205 }
206 async fn send_text(&self, _text: String) -> Result<(), SessionError> {
207 Ok(())
208 }
209 async fn send_tool_response(
210 &self,
211 _responses: Vec<FunctionResponse>,
212 ) -> Result<(), SessionError> {
213 Ok(())
214 }
215 async fn send_client_content(
216 &self,
217 _turns: Vec<Content>,
218 _turn_complete: bool,
219 ) -> Result<(), SessionError> {
220 Ok(())
221 }
222 async fn send_video(&self, _jpeg_data: bytes::Bytes) -> Result<(), SessionError> {
223 Ok(())
224 }
225 async fn update_instruction(&self, _instruction: String) -> Result<(), SessionError> {
226 Ok(())
227 }
228 async fn signal_activity_start(&self) -> Result<(), SessionError> {
229 Ok(())
230 }
231 async fn signal_activity_end(&self) -> Result<(), SessionError> {
232 Ok(())
233 }
234 async fn disconnect(&self) -> Result<(), SessionError> {
235 Ok(())
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use gemini_genai_rs::session::{SessionHandle, SessionPhase, SessionState};
243 use std::sync::Arc;
244 use tokio::sync::{broadcast, mpsc, watch};
245
246 fn mock_session_handle() -> SessionHandle {
247 let (cmd_tx, _cmd_rx) = mpsc::channel(16);
248 let (evt_tx, _) = broadcast::channel(16);
249 let (phase_tx, phase_rx) = watch::channel(SessionPhase::Disconnected);
250 let state = Arc::new(SessionState::new(phase_tx));
251 SessionHandle::new(cmd_tx, evt_tx, state, phase_rx)
252 }
253
254 #[tokio::test]
255 async fn send_audio_without_subscribers_no_broadcast() {
256 let handle = mock_session_handle();
257 let session = AgentSession::new(handle);
258 assert_eq!(session.input_subscriber_count(), 0);
259 }
260
261 #[tokio::test]
262 async fn send_audio_with_subscriber_broadcasts() {
263 let handle = mock_session_handle();
264 let session = AgentSession::new(handle);
265 let mut input_rx = session.subscribe_input();
266 assert_eq!(session.input_subscriber_count(), 1);
267
268 let data = vec![1, 2, 3, 4];
271 let _ = session.send_audio(bytes::Bytes::from(data.clone())).await;
272
273 match input_rx.try_recv() {
274 Ok(InputEvent::Audio(received)) => assert_eq!(received, data),
275 other => panic!("expected Audio, got {other:?}"),
276 }
277 }
278
279 #[test]
280 fn agent_session_is_clone() {
281 let handle = mock_session_handle();
282 let session = AgentSession::new(handle);
283 let _clone = session.clone();
284 }
285
286 #[test]
287 fn state_accessible() {
288 let handle = mock_session_handle();
289 let session = AgentSession::new(handle);
290 let _ = session.state().set("key", "value");
291 assert_eq!(
292 session.state().get::<String>("key"),
293 Some("value".to_string())
294 );
295 }
296
297 #[tokio::test]
298 async fn text_broadcast() {
299 let handle = mock_session_handle();
300 let session = AgentSession::new(handle);
301 let mut input_rx = session.subscribe_input();
302
303 let _ = session.send_text("hello").await;
304
305 match input_rx.try_recv() {
306 Ok(InputEvent::Text(t)) => assert_eq!(t, "hello"),
307 other => panic!("expected Text, got {other:?}"),
308 }
309 }
310
311 #[tokio::test]
312 async fn activity_signals_broadcast() {
313 let handle = mock_session_handle();
314 let session = AgentSession::new(handle);
315 let mut input_rx = session.subscribe_input();
316
317 let _ = session.signal_activity_start().await;
318 let _ = session.signal_activity_end().await;
319
320 assert!(matches!(input_rx.try_recv(), Ok(InputEvent::ActivityStart)));
321 assert!(matches!(input_rx.try_recv(), Ok(InputEvent::ActivityEnd)));
322 }
323
324 #[tokio::test]
325 async fn from_writer_with_mock() {
326 let handle = mock_session_handle();
328 let event_tx = handle.event_sender().clone();
329 let writer: Arc<dyn SessionWriter> = Arc::new(handle);
330 let session = AgentSession::from_writer(writer, event_tx);
331 assert_eq!(session.input_subscriber_count(), 0);
332 }
333}