1mod callbacks;
28mod config;
29mod connect;
30mod contract;
31mod extraction;
32mod phases;
33
34use std::collections::HashMap;
35use std::sync::Arc;
36use std::time::Duration;
37
38pub use gemini_adk_rs::live::extractor::TurnExtractor;
39pub use gemini_adk_rs::live::needs::RepairConfig;
40pub use gemini_adk_rs::live::persistence::SessionPersistence;
41pub use gemini_adk_rs::live::steering::{ContextDelivery, SteeringMode};
42pub use gemini_adk_rs::live::{
43 ComputedRegistry, EventCallbacks, InstructionModifier, Phase, TemporalRegistry,
44 ToolExecutionMode, WatcherRegistry,
45};
46use gemini_adk_rs::llm::BaseLlm;
47use gemini_adk_rs::tool::ToolDispatcher;
48use gemini_genai_rs::prelude::*;
49
50pub use gemini_adk_rs::live::{
56 BackendInputVad, BackendVadSnapshot, BackgroundAgentDispatcher, BackgroundToolTracker,
57 CallbackMode, ComputedContract, ComputedVar, ConsecutiveFailureDetector, ContextBuilder,
58 ControlContract, DefaultResultFormatter, DeferredWriter, EffectMode, EffectPolicy,
59 ExtractionTrigger, ExtractorContract, FieldPromotion, FsPersistence, LiveEffect,
60 LiveEffectExecutor, LiveEvent, LiveEventStream, LiveHandle, LiveReactor, LiveSessionBuilder,
61 LlmExtractor, MemoryPersistence, MergePolicy, NeedsFulfillment, PatternDetector,
62 PendingContext, PhaseContract, PhaseInstruction, PhaseMachine, PhasePreparation,
63 PhaseTransition, PredicateFn, PreparationContract, PromotionContract, RateDetector, Reaction,
64 ReactorEvent, ReactorRule, RepairAction, ResultFormatter, RuntimeContract, SessionSignals,
65 SessionSnapshot, SessionTelemetry, SessionType, SoftTurnDetector, SustainedDetector,
66 ToolCallSummary, ToolContract, TranscriptBuffer, TranscriptTurn, TranscriptWindow, Transition,
67 TransitionContract, TransitionEvaluation, TransitionResult, TransitionTrigger,
68 TurnCountDetector, VoiceRuntimeState, WatchPredicate, Watcher, WatcherContract,
69};
70pub use gemini_adk_rs::live::replay::{
72 attach_session, collect_events_until_idle, replay_session, ReplaySession,
73};
74
75pub(crate) struct DeferredAgentTool {
77 pub(crate) name: String,
78 pub(crate) description: String,
79 pub(crate) agent: Arc<dyn gemini_adk_rs::text::TextAgent>,
80}
81
82pub struct Live {
125 pub(crate) config: SessionConfig,
126 pub(crate) callbacks: EventCallbacks,
127 pub(crate) dispatcher: Option<ToolDispatcher>,
128 pub(crate) extractors: Vec<Arc<dyn TurnExtractor>>,
129 pub(crate) computed: ComputedRegistry,
131 pub(crate) phases: Vec<Phase>,
132 pub(crate) initial_phase: Option<String>,
133 pub(crate) watchers: WatcherRegistry,
134 pub(crate) temporal: TemporalRegistry,
135 pub(crate) greeting: Option<String>,
136 pub(crate) phase_default_modifiers: Vec<InstructionModifier>,
138 pub(crate) phase_default_prompt_on_enter: bool,
139 pub(crate) tool_execution_modes: HashMap<String, ToolExecutionMode>,
141 pub(crate) deferred_agent_tools: Vec<DeferredAgentTool>,
143 pub(crate) deferred_tools: Vec<crate::compose::tools::DeferredTool>,
146 pub(crate) warm_up_llms: Vec<Arc<dyn BaseLlm>>,
148 pub(crate) soft_turn_timeout: Option<Duration>,
150 pub(crate) steering_mode: SteeringMode,
151 pub(crate) context_delivery: ContextDelivery,
152 pub(crate) delivery: gemini_adk_rs::live::DeliveryConfig,
153 pub(crate) repair_config: Option<RepairConfig>,
154 pub(crate) persistence: Option<Arc<dyn SessionPersistence>>,
155 pub(crate) session_id: Option<String>,
156 pub(crate) tool_advisory: bool,
157 pub(crate) telemetry_interval: Option<Duration>,
158 pub(crate) middleware_layers: Vec<Arc<dyn gemini_adk_rs::middleware::Middleware>>,
160 pub(crate) confirmation_provider:
162 Option<Arc<dyn gemini_adk_rs::confirmation::ConfirmationProvider>>,
163 pub(crate) flow: Option<gemini_adk_rs::flow::Flow>,
165 pub(crate) flow_mode: gemini_adk_rs::flow::Enforcement,
166 pub(crate) flow_actions: Vec<(
168 String,
169 Arc<dyn gemini_adk_rs::text::TextAgent>,
170 gemini_adk_rs::orchestration::Mode,
171 )>,
172 pub(crate) record_wire_path: Option<std::path::PathBuf>,
174}
175
176impl Live {
177 pub fn builder() -> Self {
218 Self {
219 config: SessionConfig::from_endpoint(ApiEndpoint::google_ai("")),
220 callbacks: EventCallbacks::default(),
221 dispatcher: None,
222 extractors: Vec::new(),
223 computed: ComputedRegistry::new(),
224 phases: Vec::new(),
225 initial_phase: None,
226 watchers: WatcherRegistry::new(),
227 temporal: TemporalRegistry::new(),
228 greeting: None,
229 phase_default_modifiers: Vec::new(),
230 phase_default_prompt_on_enter: false,
231 tool_execution_modes: HashMap::new(),
232 deferred_agent_tools: Vec::new(),
233 deferred_tools: Vec::new(),
234 warm_up_llms: Vec::new(),
235 soft_turn_timeout: None,
236 steering_mode: SteeringMode::default(),
237 context_delivery: ContextDelivery::default(),
238 delivery: gemini_adk_rs::live::DeliveryConfig::default(),
239 repair_config: None,
240 persistence: None,
241 session_id: None,
242 tool_advisory: true,
243 telemetry_interval: None,
244 middleware_layers: Vec::new(),
245 confirmation_provider: None,
246 flow: None,
247 flow_mode: gemini_adk_rs::flow::Enforcement::Enforce,
248 flow_actions: Vec::new(),
249 record_wire_path: None,
250 }
251 }
252
253 pub fn govern(mut self, flow: gemini_adk_rs::flow::Flow) -> Self {
257 self.flow = Some(flow);
258 self.flow_mode = gemini_adk_rs::flow::Enforcement::Enforce;
259 self
260 }
261
262 pub fn observe(mut self, flow: gemini_adk_rs::flow::Flow) -> Self {
265 self.flow = Some(flow);
266 self.flow_mode = gemini_adk_rs::flow::Enforcement::Observe;
267 self
268 }
269
270 pub fn govern_compiled(self, flow: gemini_adk_rs::flow::CompiledFlow) -> Self {
279 self.govern(flow.into_flow())
280 }
281
282 pub fn observe_compiled(self, flow: gemini_adk_rs::flow::CompiledFlow) -> Self {
288 self.observe(flow.into_flow())
289 }
290
291 pub fn on_enter(
304 mut self,
305 step: impl Into<String>,
306 agent: Arc<dyn gemini_adk_rs::text::TextAgent>,
307 mode: gemini_adk_rs::orchestration::Mode,
308 ) -> Self {
309 self.flow_actions.push((step.into(), agent, mode));
310 self
311 }
312
313 pub fn confirmation_provider(
324 mut self,
325 provider: Arc<dyn gemini_adk_rs::confirmation::ConfirmationProvider>,
326 ) -> Self {
327 self.confirmation_provider = Some(provider);
328 self
329 }
330
331 pub fn middleware(
341 mut self,
342 composite: crate::compose::middleware::MiddlewareComposite,
343 ) -> Self {
344 self.middleware_layers.extend(composite.layers);
345 self
346 }
347
348 pub fn telemetry_interval(mut self, interval: Duration) -> Self {
353 self.telemetry_interval = Some(interval);
354 self
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361 use std::sync::Arc;
362 use std::time::Duration;
363
364 #[test]
365 fn builder_chain_compiles() {
366 let _live = Live::builder()
367 .model(GeminiModel::Gemini2_0FlashLive)
368 .voice(Voice::Kore)
369 .instruction("Test")
370 .temperature(0.7)
371 .google_search()
372 .transcription(true, true)
373 .affective_dialog(true)
374 .session_resume(true)
375 .context_compression(4000, 2000)
376 .on_audio(|_data| {})
377 .on_text(|_t| {})
378 .on_vad_start(|| {})
379 .on_interrupted(|| async {})
380 .on_turn_complete(|| async {})
381 .on_go_away(|_d| async {})
382 .on_connected(|_writer| async {})
383 .on_disconnected(|_r| async {})
384 .on_error(|_e| async {});
385 }
387
388 #[test]
389 fn govern_compiled_attaches_precompiled_flow_without_recompiling() {
390 use gemini_adk_rs::flow::{Enforcement, Flow, Guard};
391
392 let compiled = Flow::new()
393 .step("greet")
394 .done(Guard::is_true("greeted"))
395 .step("end")
396 .after("greet")
397 .terminal()
398 .build()
399 .expect("valid flow")
400 .compile()
401 .expect("flow compiles");
402
403 let live = Live::builder().govern_compiled(compiled.clone());
405 assert!(live.flow.is_some(), "compiled flow attached");
406 assert_eq!(live.flow_mode, Enforcement::Enforce);
407
408 let live = Live::builder().observe_compiled(compiled);
410 assert!(live.flow.is_some(), "compiled flow attached");
411 assert_eq!(live.flow_mode, Enforcement::Observe);
412 }
413
414 #[test]
415 fn builder_with_extraction_compiles() {
416 use gemini_adk_rs::llm::{BaseLlm, LlmError, LlmRequest, LlmResponse};
417 use schemars::JsonSchema;
418
419 #[derive(serde::Deserialize, serde::Serialize, JsonSchema)]
420 struct OrderState {
421 phase: String,
422 items: Vec<String>,
423 }
424
425 struct FakeLlm;
426
427 #[async_trait::async_trait]
428 impl BaseLlm for FakeLlm {
429 fn model_id(&self) -> &str {
430 "fake"
431 }
432 async fn generate(&self, _req: LlmRequest) -> Result<LlmResponse, LlmError> {
433 unimplemented!()
434 }
435 }
436
437 let _live = Live::builder()
438 .model(GeminiModel::Gemini2_0FlashLive)
439 .instruction("Restaurant order assistant")
440 .extract_turns::<OrderState>(
441 Arc::new(FakeLlm),
442 "Extract order state: items, quantities, phase",
443 )
444 .on_extracted(|name, value| async move {
445 let _ = (name, value);
446 })
447 .before_tool_response(|responses, _state| async move {
449 responses })
451 .on_turn_boundary(|_state, _writer| async move {
452 })
454 .instruction_template(|state| {
455 let phase: String = state.get("phase").unwrap_or_default();
456 match phase.as_str() {
457 "ordering" => Some("Take orders accurately.".into()),
458 _ => None,
459 }
460 });
461 }
463
464 #[test]
465 fn builder_with_computed_state_compiles() {
466 let _live = Live::builder()
467 .model(GeminiModel::Gemini2_0FlashLive)
468 .instruction("Test computed state")
469 .computed("doubled", &["app:count"], |state| {
470 let count: i64 = state.get("app:count")?;
471 Some(serde_json::json!(count * 2))
472 })
473 .computed("level", &["app:score"], |state| {
474 let score: f64 = state.get("app:score")?;
475 if score > 0.5 {
476 Some(serde_json::json!("high"))
477 } else {
478 Some(serde_json::json!("low"))
479 }
480 });
481 }
482
483 #[test]
484 fn builder_with_phases_compiles() {
485 let _live = Live::builder()
486 .model(GeminiModel::Gemini2_0FlashLive)
487 .phase("greeting")
488 .instruction("Welcome the user warmly")
489 .transition("main", |s| s.get::<bool>("greeted").unwrap_or(false))
490 .on_enter(|state, _writer| async move {
491 let _ = state.set("entered_greeting", true);
492 })
493 .done()
494 .phase("main")
495 .dynamic_instruction(|s| {
496 let topic: String = s.get("topic").unwrap_or_default();
497 format!("Discuss {topic}")
498 })
499 .tools(vec!["search".into(), "lookup".into()])
500 .transition("farewell", |s| s.get::<bool>("done").unwrap_or(false))
501 .done()
502 .phase("farewell")
503 .instruction("Say goodbye")
504 .terminal()
505 .done()
506 .initial_phase("greeting");
507 }
508
509 #[test]
510 fn builder_with_phase_guard_compiles() {
511 let _live = Live::builder()
512 .model(GeminiModel::Gemini2_0FlashLive)
513 .phase("start")
514 .instruction("Begin")
515 .transition("secure", |_| true)
516 .done()
517 .phase("secure")
518 .instruction("Secure area")
519 .guard(|s| s.get::<bool>("verified").unwrap_or(false))
520 .on_exit(|state, _writer| async move {
521 let _ = state.set("left_secure", true);
522 })
523 .terminal()
524 .done()
525 .initial_phase("start");
526 }
527
528 #[test]
529 fn builder_with_watchers_compiles() {
530 let _live = Live::builder()
531 .model(GeminiModel::Gemini2_0FlashLive)
532 .watch("app:score")
533 .crossed_above(0.9)
534 .then(|_old, _new, state| async move {
535 let _ = state.set("high_score_alert", true);
536 })
537 .watch("app:status")
538 .changed_to(serde_json::json!("complete"))
539 .blocking()
540 .then(|_old, _new, _state| async move {
541 })
543 .watch("app:flag")
544 .became_true()
545 .then(|_old, _new, _state| async move {
546 });
548 }
549
550 #[test]
551 fn builder_with_temporal_patterns_compiles() {
552 let _live = Live::builder()
553 .model(GeminiModel::Gemini2_0FlashLive)
554 .when_sustained(
555 "user_confused",
556 |s| s.get::<bool>("confused").unwrap_or(false),
557 Duration::from_secs(30),
558 |_state, _writer| async move {
559 },
561 )
562 .when_rate(
563 "rapid_errors",
564 |evt| matches!(evt, SessionEvent::TextDelta(_)),
565 5,
566 Duration::from_secs(10),
567 |_state, _writer| async move {
568 },
570 )
571 .when_turns(
572 "stuck_in_loop",
573 |s| s.get::<bool>("repeating").unwrap_or(false),
574 3,
575 |_state, _writer| async move {
576 },
578 );
579 }
580
581 #[test]
582 fn builder_full_l1_chain_compiles() {
583 let _live = Live::builder()
585 .model(GeminiModel::Gemini2_0FlashLive)
586 .voice(Voice::Kore)
587 .instruction("Full featured agent")
588 .computed("sentiment_level", &["app:sentiment_score"], |state| {
590 let score: f64 = state.get("app:sentiment_score")?;
591 if score > 0.7 {
592 Some(serde_json::json!("positive"))
593 } else if score < 0.3 {
594 Some(serde_json::json!("negative"))
595 } else {
596 Some(serde_json::json!("neutral"))
597 }
598 })
599 .phase("greeting")
601 .instruction("Greet the user")
602 .transition("help", |s| s.get::<bool>("needs_help").unwrap_or(false))
603 .done()
604 .phase("help")
605 .instruction("Help the user")
606 .terminal()
607 .done()
608 .initial_phase("greeting")
609 .watch("app:sentiment_score")
611 .crossed_below(0.2)
612 .then(|_old, _new, state| async move {
613 let _ = state.set("alert:low_sentiment", true);
614 })
615 .when_turns(
617 "repeated_confusion",
618 |s| s.get::<bool>("confused").unwrap_or(false),
619 3,
620 |_state, _writer| async move {},
621 )
622 .on_audio(|_data| {})
624 .on_text(|_t| {})
625 .on_turn_complete(|| async {});
626 }
627
628 #[test]
629 fn builder_with_callback_modes_compiles() {
630 let _live = Live::builder()
631 .model(GeminiModel::Gemini2_0FlashLive)
632 .on_turn_complete_concurrent(|| async {})
633 .on_error_concurrent(|_e| async {})
634 .on_extracted_concurrent(|_name, _val| async {})
635 .on_extraction_error_concurrent(|_name, _err| async {})
636 .on_connected_concurrent(|_w| async {})
637 .on_disconnected_concurrent(|_r| async {})
638 .on_go_away_concurrent(|_d| async {});
639 }
640
641 #[test]
642 fn builder_with_background_tools_compiles() {
643 use gemini_adk_rs::live::DefaultResultFormatter;
644
645 let _live = Live::builder()
646 .model(GeminiModel::Gemini2_0FlashLive)
647 .tool_background("search_kb")
648 .tool_background_with_formatter("analyze_document", Arc::new(DefaultResultFormatter));
649 }
650
651 #[test]
652 fn builder_mixed_callback_modes_and_bg_tools() {
653 use gemini_adk_rs::live::DefaultResultFormatter;
654
655 let _live = Live::builder()
656 .model(GeminiModel::Gemini2_0FlashLive)
657 .voice(Voice::Kore)
658 .instruction("Full featured agent")
659 .tool_background("slow_tool")
660 .tool_background_with_formatter("kb_search", Arc::new(DefaultResultFormatter))
661 .on_turn_complete_concurrent(|| async {})
662 .on_extracted_concurrent(|_name, _val| async {})
663 .on_audio(|_data| {})
664 .on_text(|_t| {})
665 .on_interrupted(|| async {});
666 }
667}