gemini_adk_fluent_rs/live/
introspect.rs1use gemini_adk_rs::State;
18use gemini_adk_rs::flow::{Enforcement, Flow};
19use gemini_adk_rs::live::Phase;
20use gemini_genai_rs::prelude::Tool;
21
22use super::Live;
23
24pub(crate) fn declaration_names(tools: &[Tool]) -> Vec<String> {
27 let mut names = Vec::new();
28 for tool in tools {
29 if let Some(functions) = &tool.function_declarations {
30 names.extend(functions.iter().map(|f| f.name.clone()));
31 }
32 if tool.google_search.is_some() {
33 names.push("google_search".into());
34 }
35 if tool.code_execution.is_some() {
36 names.push("code_execution".into());
37 }
38 if tool.url_context.is_some() {
39 names.push("url_context".into());
40 }
41 }
42 names
43}
44
45impl Live {
46 pub fn declared_tool_names(&self) -> Vec<String> {
56 let mut names = declaration_names(&self.config.tools);
57 if let Some(dispatcher) = &self.dispatcher {
58 names.extend(declaration_names(&dispatcher.to_tool_declarations()));
59 }
60 names.extend(self.deferred_agent_tools.iter().map(|t| t.name.clone()));
61 names.sort();
62 names.dedup();
63 names
64 }
65
66 pub(crate) fn unconfirmed_tools(&self) -> Vec<String> {
69 if self.confirmation_provider.is_some() {
70 return Vec::new();
71 }
72 self.dispatcher
73 .as_ref()
74 .map(|d| d.gated_tools().map(str::to_owned).collect())
75 .unwrap_or_default()
76 }
77
78 pub fn pending_tool_count(&self) -> usize {
84 self.deferred_tools.len()
85 }
86
87 pub fn flow(&self) -> Option<&Flow> {
95 self.flow.as_ref()
96 }
97
98 pub fn flow_enforcement(&self) -> Enforcement {
100 self.flow_mode
101 }
102
103 pub fn digressions(&self) -> &[gemini_adk_rs::flow::Overlay] {
107 &self.digressions
108 }
109
110 pub fn repair_policies(
113 &self,
114 ) -> &std::collections::BTreeMap<String, gemini_adk_rs::flow::RepairPolicy> {
115 &self.repair_policies
116 }
117
118 pub fn phases(&self) -> &[Phase] {
120 &self.phases
121 }
122
123 pub fn initial_phase_name(&self) -> Option<&str> {
128 self.initial_phase.as_deref()
129 }
130
131 pub fn watched_keys(&self) -> Vec<String> {
133 let mut keys: Vec<String> = self.watchers.observed_keys().iter().cloned().collect();
134 keys.sort();
135 keys
136 }
137
138 pub fn extractor_count(&self) -> usize {
143 self.extractors.len()
144 }
145
146 pub fn has_persistence(&self) -> bool {
148 self.persistence.is_some()
149 }
150
151 pub fn shared_state(&self) -> Option<&State> {
154 self.state.as_ref()
155 }
156
157 pub fn teardown_hook_count(&self) -> usize {
164 self.callbacks.on_teardown.len()
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use async_trait::async_trait;
171 use gemini_adk_rs::flow::Guard;
172 use gemini_adk_rs::llm::{BaseLlm, LlmError, LlmRequest, LlmResponse};
173 use serde_json::json;
174
175 use crate::compose::T;
176 use crate::live::Live;
177
178 fn sample_tool() -> crate::compose::tools::ToolComposite {
179 T::simple("book_table", "Book a table", |_| async {
180 Ok(json!({"ok": true}))
181 })
182 }
183
184 struct InertLlm;
186
187 #[async_trait]
188 impl BaseLlm for InertLlm {
189 fn model_id(&self) -> &str {
190 "inert"
191 }
192 async fn generate(&self, _request: LlmRequest) -> Result<LlmResponse, LlmError> {
193 Err(LlmError::Other("inert".into()))
194 }
195 }
196
197 #[test]
198 fn a_custom_dispatcher_keeps_tools_registered_before_it() {
199 let mut custom = gemini_adk_rs::tool::ToolDispatcher::new();
200 custom.register_function(std::sync::Arc::new(gemini_adk_rs::tool::SimpleTool::new(
201 "stream_quotes",
202 "Stream quotes",
203 None,
204 |_| async { Ok(json!({})) },
205 )));
206 let live = Live::builder().tools(sample_tool()).dispatcher(custom);
207 let names = live.declared_tool_names();
208 assert!(names.contains(&"book_table".to_string()), "{names:?}");
209 assert!(names.contains(&"stream_quotes".to_string()), "{names:?}");
210 }
211
212 #[test]
215 fn a_confirm_tool_without_a_provider_is_reported() {
216 let gated = T::confirm(sample_tool(), "Books cost money");
217 let live = Live::builder().tools(gated.clone());
218 assert!(
219 crate::testing::check_live(&live).contains(
220 &crate::testing::LiveViolation::UnconfirmedTools {
221 tools: vec!["book_table".into()]
222 }
223 ),
224 "{:?}",
225 crate::testing::check_live(&live)
226 );
227
228 let confirmed = Live::builder().tools(gated).confirmation_provider(
229 gemini_adk_rs::confirmation::StaticConfirmation::deny_all("no"),
230 );
231 assert!(confirmed.unconfirmed_tools().is_empty());
232 }
233
234 #[test]
235 fn declared_tool_names_sees_dispatcher_and_builtins() {
236 let live = Live::builder().tools(sample_tool() + T::google_search());
237 let names = live.declared_tool_names();
238 assert!(names.contains(&"book_table".to_string()), "{names:?}");
239 assert!(names.contains(&"google_search".to_string()), "{names:?}");
240 assert_eq!(
241 live.pending_tool_count(),
242 0,
243 "nothing here needs an async handshake"
244 );
245 }
246
247 #[test]
248 fn declared_tool_names_covers_agent_tools_before_connect() {
249 let verifier = crate::builder::AgentBuilder::new("verifier")
252 .instruction("Verify the caller")
253 .build(std::sync::Arc::new(InertLlm))
254 .expect("builds");
255 let live = Live::builder().agent_tool("verify_identity", "Verify caller", verifier);
256 assert!(
257 live.declared_tool_names()
258 .contains(&"verify_identity".to_string())
259 );
260 }
261
262 #[test]
263 fn flow_and_phases_read_back() {
264 let flow = gemini_adk_rs::flow::Flow::new()
265 .step("book")
266 .allow(["book_table"])
267 .done(Guard::called_ok("book_table"))
268 .build()
269 .expect("valid");
270 let live = Live::builder()
271 .govern(flow)
272 .phase("greet")
273 .instruction("Say hello")
274 .done()
275 .initial_phase("greet");
276
277 assert_eq!(live.flow().map(|f| f.steps.len()), Some(1));
278 assert_eq!(
279 live.flow_enforcement(),
280 gemini_adk_rs::flow::Enforcement::Enforce
281 );
282 assert_eq!(live.phases().len(), 1);
283 assert_eq!(live.initial_phase_name(), Some("greet"));
284 }
285
286 #[test]
287 fn a_shared_state_is_the_one_the_session_will_run_on() {
288 let state = gemini_adk_rs::State::new();
294 let _ = state.set("identity_verified", true);
295 let live = Live::builder().state(state.clone());
296 assert_eq!(
297 live.shared_state()
298 .and_then(|s| s.get::<bool>("identity_verified")),
299 Some(true),
300 "the builder must hold the caller's own state, not a copy or a fresh one"
301 );
302 }
303
304 #[test]
305 fn an_unconfigured_builder_reports_nothing() {
306 let live = Live::builder();
307 assert!(live.flow().is_none());
308 assert!(live.phases().is_empty());
309 assert!(live.initial_phase_name().is_none());
310 assert!(live.watched_keys().is_empty());
311 assert_eq!(live.extractor_count(), 0);
312 assert!(!live.has_persistence());
313 assert!(live.shared_state().is_none());
314 }
315}