gemini_adk_fluent_rs/live/
scripted.rs1use std::time::Duration;
5
6use gemini_adk_rs::State;
7use gemini_adk_rs::error::AgentError;
8use gemini_adk_rs::live::replay::collect_events_until_idle;
9use gemini_adk_rs::live::{LiveEvent, LiveHandle};
10use gemini_genai_rs::transport::{ReplayControl, ReplayTransport};
11use serde_json::{Value, json};
12
13use super::Live;
14
15#[derive(Debug, Clone)]
56pub struct ScriptedServer {
57 frames: Vec<Value>,
58 next_call: usize,
59 idle: Duration,
60}
61
62impl Default for ScriptedServer {
63 fn default() -> Self {
64 Self::new()
65 }
66}
67
68impl ScriptedServer {
69 pub fn new() -> Self {
71 Self {
72 frames: vec![json!({ "setupComplete": {} })],
73 next_call: 0,
74 idle: Duration::from_millis(200),
75 }
76 }
77
78 pub fn says(self, text: impl Into<String>) -> Self {
80 self.text(text).turn_complete()
81 }
82
83 pub fn text(self, text: impl Into<String>) -> Self {
85 self.frame(json!({
86 "serverContent": { "modelTurn": { "parts": [{ "text": text.into() }] } }
87 }))
88 }
89
90 pub fn turn_complete(self) -> Self {
92 self.frame(json!({ "serverContent": { "turnComplete": true } }))
93 }
94
95 pub fn hears(self, text: impl Into<String>) -> Self {
97 self.frame(json!({
98 "serverContent": { "inputTranscription": { "text": text.into() } }
99 }))
100 }
101
102 pub fn speaks(self, text: impl Into<String>) -> Self {
104 self.frame(json!({
105 "serverContent": { "outputTranscription": { "text": text.into() } }
106 }))
107 }
108
109 pub fn calls(mut self, name: impl Into<String>, args: Value) -> Self {
112 self.next_call += 1;
113 let id = format!("call-{}", self.next_call);
114 self.frame(json!({
115 "toolCall": { "functionCalls": [{ "name": name.into(), "args": args, "id": id }] }
116 }))
117 }
118
119 pub fn interrupts(self) -> Self {
121 self.frame(json!({ "serverContent": { "interrupted": true } }))
122 }
123
124 pub fn frame(mut self, message: Value) -> Self {
126 self.frames.push(message);
127 self
128 }
129
130 pub fn settle_after(mut self, idle: Duration) -> Self {
133 self.idle = idle;
134 self
135 }
136
137 fn awaited_call_ids(&self) -> Vec<String> {
140 let ids = |frame: &Value, pointer: &str| -> Vec<String> {
141 frame
142 .pointer(pointer)
143 .and_then(Value::as_array)
144 .into_iter()
145 .flatten()
146 .filter_map(|v| v.get("id").or(Some(v)).and_then(Value::as_str))
147 .map(str::to_owned)
148 .collect()
149 };
150 let cancelled: Vec<String> = self
151 .frames
152 .iter()
153 .flat_map(|f| ids(f, "/toolCallCancellation/ids"))
154 .collect();
155 self.frames
156 .iter()
157 .flat_map(|f| ids(f, "/toolCall/functionCalls"))
158 .filter(|id| !cancelled.contains(id))
159 .collect()
160 }
161
162 pub fn into_transport(self) -> (ReplayTransport, ReplayControl) {
166 ReplayTransport::from_frames(
167 self.frames
168 .iter()
169 .map(|frame| frame.to_string().into_bytes())
170 .collect(),
171 )
172 }
173
174 pub async fn play(self, live: Live) -> Result<ScriptedRun, AgentError> {
179 let idle = self.idle;
180 let mut awaiting = self.awaited_call_ids();
181 let (transport, control) = self.into_transport();
182 let handle = live.connect_with_transport(transport).await?;
183 let mut rx = handle.events();
184 control.release();
185 control.drained().await;
186 let deadline = tokio::time::Instant::now() + Duration::from_secs(30);
189 let mut events = Vec::new();
190 loop {
191 let left = deadline.saturating_duration_since(tokio::time::Instant::now());
192 events.extend(collect_events_until_idle(&mut rx, idle, left).await);
193 awaiting.retain(|id| !answered(&control, id));
194 if awaiting.is_empty() || left.is_zero() {
195 break;
196 }
197 }
198 Ok(ScriptedRun {
199 handle,
200 events,
201 control,
202 })
203 }
204}
205
206fn answered(control: &ReplayControl, id: &str) -> bool {
208 control.outbound_frames().iter().any(|frame| {
209 serde_json::from_slice::<Value>(frame)
210 .ok()
211 .and_then(|m| m.pointer("/toolResponse/functionResponses").cloned())
212 .and_then(|r| r.as_array().cloned())
213 .is_some_and(|rs| rs.iter().any(|r| r["id"] == id))
214 })
215}
216
217pub struct ScriptedRun {
219 handle: LiveHandle,
220 events: Vec<LiveEvent>,
221 control: ReplayControl,
222}
223
224impl ScriptedRun {
225 pub fn handle(&self) -> &LiveHandle {
227 &self.handle
228 }
229
230 pub fn state(&self) -> &State {
232 self.handle.state()
233 }
234
235 pub fn events(&self) -> &[LiveEvent] {
237 &self.events
238 }
239
240 pub fn transcript_text(&self) -> String {
242 self.events
243 .iter()
244 .filter_map(|event| match event {
245 LiveEvent::TextDelta(text) => Some(text.as_str()),
246 _ => None,
247 })
248 .collect()
249 }
250
251 pub fn sent(&self) -> Vec<Value> {
254 self.control
255 .outbound_frames()
256 .iter()
257 .filter_map(|frame| serde_json::from_slice(frame).ok())
258 .collect()
259 }
260
261 pub fn setup(&self) -> Option<Value> {
263 self.sent()
264 .into_iter()
265 .find_map(|m| m.get("setup").cloned())
266 }
267
268 pub fn tool_responses(&self) -> Vec<Value> {
270 self.sent()
271 .iter()
272 .filter_map(|m| m.pointer("/toolResponse/functionResponses"))
273 .filter_map(Value::as_array)
274 .flatten()
275 .cloned()
276 .collect()
277 }
278
279 pub async fn disconnect(self) {
281 let _ = self.handle.disconnect().await;
282 }
283}