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 fn pending_tool_count(&self) -> usize {
72 self.deferred_tools.len()
73 }
74
75 pub fn flow(&self) -> Option<&Flow> {
83 self.flow.as_ref()
84 }
85
86 pub fn flow_enforcement(&self) -> Enforcement {
88 self.flow_mode
89 }
90
91 pub fn phases(&self) -> &[Phase] {
93 &self.phases
94 }
95
96 pub fn initial_phase_name(&self) -> Option<&str> {
101 self.initial_phase.as_deref()
102 }
103
104 pub fn watched_keys(&self) -> Vec<String> {
106 let mut keys: Vec<String> = self.watchers.observed_keys().iter().cloned().collect();
107 keys.sort();
108 keys
109 }
110
111 pub fn extractor_count(&self) -> usize {
116 self.extractors.len()
117 }
118
119 pub fn has_persistence(&self) -> bool {
121 self.persistence.is_some()
122 }
123
124 pub fn shared_state(&self) -> Option<&State> {
127 self.state.as_ref()
128 }
129
130 pub fn teardown_hook_count(&self) -> usize {
137 self.callbacks.on_teardown.len()
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use async_trait::async_trait;
144 use gemini_adk_rs::flow::Guard;
145 use gemini_adk_rs::llm::{BaseLlm, LlmError, LlmRequest, LlmResponse};
146 use serde_json::json;
147
148 use crate::compose::T;
149 use crate::live::Live;
150
151 fn sample_tool() -> crate::compose::tools::ToolComposite {
152 T::simple("book_table", "Book a table", |_| async {
153 Ok(json!({"ok": true}))
154 })
155 }
156
157 struct InertLlm;
159
160 #[async_trait]
161 impl BaseLlm for InertLlm {
162 fn model_id(&self) -> &str {
163 "inert"
164 }
165 async fn generate(&self, _request: LlmRequest) -> Result<LlmResponse, LlmError> {
166 Err(LlmError::Other("inert".into()))
167 }
168 }
169
170 #[test]
171 fn declared_tool_names_sees_dispatcher_and_builtins() {
172 let live = Live::builder().tools(sample_tool() | T::google_search());
173 let names = live.declared_tool_names();
174 assert!(names.contains(&"book_table".to_string()), "{names:?}");
175 assert!(names.contains(&"google_search".to_string()), "{names:?}");
176 assert_eq!(
177 live.pending_tool_count(),
178 0,
179 "nothing here needs an async handshake"
180 );
181 }
182
183 #[test]
184 fn declared_tool_names_covers_agent_tools_before_connect() {
185 let verifier = crate::builder::AgentBuilder::new("verifier")
188 .instruction("Verify the caller")
189 .build(std::sync::Arc::new(InertLlm))
190 .expect("builds");
191 let live = Live::builder().agent_tool_arc("verify_identity", "Verify caller", verifier);
192 assert!(
193 live.declared_tool_names()
194 .contains(&"verify_identity".to_string())
195 );
196 }
197
198 #[test]
199 fn flow_and_phases_read_back() {
200 let flow = gemini_adk_rs::flow::Flow::new()
201 .step("book")
202 .allow(["book_table"])
203 .done(Guard::called_ok("book_table"))
204 .build()
205 .expect("valid");
206 let live = Live::builder()
207 .govern(flow)
208 .phase("greet")
209 .instruction("Say hello")
210 .done()
211 .initial_phase("greet");
212
213 assert_eq!(live.flow().map(|f| f.steps.len()), Some(1));
214 assert_eq!(
215 live.flow_enforcement(),
216 gemini_adk_rs::flow::Enforcement::Enforce
217 );
218 assert_eq!(live.phases().len(), 1);
219 assert_eq!(live.initial_phase_name(), Some("greet"));
220 }
221
222 #[test]
223 fn a_shared_state_is_the_one_the_session_will_run_on() {
224 let state = gemini_adk_rs::State::new();
230 let _ = state.set("identity_verified", true);
231 let live = Live::builder().state(state.clone());
232 assert_eq!(
233 live.shared_state()
234 .and_then(|s| s.get::<bool>("identity_verified")),
235 Some(true),
236 "the builder must hold the caller's own state, not a copy or a fresh one"
237 );
238 }
239
240 #[test]
241 fn an_unconfigured_builder_reports_nothing() {
242 let live = Live::builder();
243 assert!(live.flow().is_none());
244 assert!(live.phases().is_empty());
245 assert!(live.initial_phase_name().is_none());
246 assert!(live.watched_keys().is_empty());
247 assert_eq!(live.extractor_count(), 0);
248 assert!(!live.has_persistence());
249 assert!(live.shared_state().is_none());
250 }
251}