gemini_adk_fluent_rs/live/phases.rs
1//! Phase machine, instruction templating, computed state, watchers, and
2//! temporal pattern configuration methods for `Live`.
3
4use std::future::Future;
5use std::sync::Arc;
6use std::time::Duration;
7
8use serde_json::Value;
9
10use gemini_adk_rs::State;
11use gemini_adk_rs::live::{
12 ComputedVar, Phase, RateDetector, SustainedDetector, TemporalPattern, TurnCountDetector,
13 Watcher,
14};
15use gemini_genai_rs::prelude::*;
16use gemini_genai_rs::session::SessionWriter;
17
18use crate::live_builders::{PhaseBuilder, PhaseDefaults, WatchBuilder};
19
20use super::Live;
21
22impl Live {
23 /// State-reactive system instruction template.
24 ///
25 /// Called after extractors run on each turn. If it returns `Some(instruction)`,
26 /// the system instruction is updated mid-session (deduped — same instruction
27 /// is not sent twice). Returns `None` to leave the instruction unchanged.
28 ///
29 /// # Example
30 /// ```no_run
31 /// # use gemini_adk_fluent_rs::prelude::*;
32 /// Live::builder().instruction_template(|state| {
33 /// let phase: String = state.get("phase").unwrap_or_default();
34 /// match phase.as_str() {
35 /// "ordering" => Some("Focus on taking the order accurately.".into()),
36 /// "confirming" => Some("Summarize and confirm the order.".into()),
37 /// _ => None,
38 /// }
39 /// });
40 /// ```
41 pub fn instruction_template(
42 mut self,
43 f: impl Fn(&gemini_adk_rs::State) -> Option<String> + Send + Sync + 'static,
44 ) -> Self {
45 self.callbacks.instruction_template = Some(Arc::new(f));
46 self
47 }
48
49 /// State-reactive instruction amendment (additive, not replacement).
50 ///
51 /// Unlike `instruction_template` (which replaces the entire instruction),
52 /// this appends to the current phase instruction. The developer never needs
53 /// to know or repeat the base instruction.
54 ///
55 /// # Example
56 /// ```no_run
57 /// # use gemini_adk_fluent_rs::prelude::*;
58 /// Live::builder().instruction_amendment(|state| {
59 /// let risk: String = state.get("derived:risk").unwrap_or_default();
60 /// if risk == "high" {
61 /// Some("[IMPORTANT: Use empathetic language. Do not threaten.]".into())
62 /// } else {
63 /// None
64 /// }
65 /// });
66 /// ```
67 pub fn instruction_amendment(
68 mut self,
69 f: impl Fn(&gemini_adk_rs::State) -> Option<String> + Send + Sync + 'static,
70 ) -> Self {
71 self.callbacks.instruction_amendment = Some(Arc::new(f));
72 self
73 }
74
75 // -- Computed State --
76
77 /// Register a computed (derived) state variable.
78 ///
79 /// The compute function receives the full `State` and returns `Some(value)`
80 /// to write to `derived:{key}`, or `None` to skip.
81 ///
82 /// A dependency cycle among computed variables is a configuration error;
83 /// it is reported by `connect` (as `AgentError::Config`), never a panic.
84 pub fn computed(
85 mut self,
86 key: impl Into<String>,
87 deps: &[&str],
88 f: impl Fn(&State) -> Option<Value> + Send + Sync + 'static,
89 ) -> Self {
90 if let Err(err) = self.computed.register(ComputedVar {
91 key: key.into(),
92 dependencies: deps.iter().map(std::string::ToString::to_string).collect(),
93 compute: Arc::new(f),
94 }) {
95 self.config_errors.extend(err.issues);
96 }
97 self
98 }
99
100 // -- Phase Machine --
101
102 /// Set default modifiers and `prompt_on_enter` inherited by all phases.
103 ///
104 /// Phase-specific modifiers are applied *after* defaults, so they extend (not replace).
105 ///
106 /// ```no_run
107 /// # use gemini_adk_fluent_rs::prelude::*;
108 /// Live::builder()
109 /// .phase_defaults(|p| {
110 /// p.show_state(&["emotional_state", "risk_level"])
111 /// .when(|s| s.get::<String>("risk").unwrap_or_default() == "high", "Show extra empathy.")
112 /// .prompt_on_enter()
113 /// })
114 /// .phase("greet").instruction("...").done()
115 /// .phase("close").instruction("...").done();
116 /// // Both phases inherit the modifiers and prompt_on_enter.
117 /// ```
118 pub fn phase_defaults(mut self, f: impl FnOnce(PhaseDefaults) -> PhaseDefaults) -> Self {
119 let defaults = f(PhaseDefaults::new());
120 self.phase_default_modifiers = defaults.modifiers;
121 self.phase_default_prompt_on_enter = defaults.prompt_on_enter;
122 self
123 }
124
125 /// Start building a conversation phase.
126 ///
127 /// Returns a [`PhaseBuilder`] that flows back to this `Live` via `.done()`.
128 pub fn phase(self, name: impl Into<String>) -> PhaseBuilder {
129 PhaseBuilder::new(self, name)
130 }
131
132 /// Set the initial phase name (must match a registered phase).
133 pub fn initial_phase(mut self, name: impl Into<String>) -> Self {
134 self.initial_phase = Some(name.into());
135 self
136 }
137
138 /// Internal method called by [`PhaseBuilder::done`].
139 pub(crate) fn add_phase(&mut self, phase: Phase) {
140 self.phases.push(phase);
141 }
142
143 // -- Watchers --
144
145 /// Start building a state watcher.
146 ///
147 /// Returns a [`WatchBuilder`] that flows back to this `Live` via `.then()`.
148 pub fn watch(self, key: impl Into<String>) -> WatchBuilder {
149 WatchBuilder::new(self, key)
150 }
151
152 /// Internal method called by [`WatchBuilder::then`].
153 pub(crate) fn add_watcher(&mut self, watcher: Watcher) {
154 self.watchers.register(watcher);
155 }
156
157 // -- Temporal Patterns --
158
159 /// Register a sustained condition pattern.
160 ///
161 /// Fires when the condition remains true for at least `duration`.
162 pub fn when_sustained<F, Fut>(
163 mut self,
164 name: impl Into<String>,
165 condition: impl Fn(&State) -> bool + Send + Sync + 'static,
166 duration: Duration,
167 action: F,
168 ) -> Self
169 where
170 F: Fn(State, Arc<dyn SessionWriter>) -> Fut + Send + Sync + 'static,
171 Fut: Future<Output = ()> + Send + 'static,
172 {
173 let detector = SustainedDetector::new(Arc::new(condition), duration);
174 self.temporal.register(TemporalPattern::new(
175 name,
176 Box::new(detector),
177 Arc::new(move |s, w| Box::pin(action(s, w))),
178 None,
179 ));
180 self
181 }
182
183 /// Register a rate detection pattern.
184 ///
185 /// Fires when at least `count` matching events occur within `window`.
186 pub fn when_rate<F, Fut>(
187 mut self,
188 name: impl Into<String>,
189 filter: impl Fn(&SessionEvent) -> bool + Send + Sync + 'static,
190 count: u32,
191 window: Duration,
192 action: F,
193 ) -> Self
194 where
195 F: Fn(State, Arc<dyn SessionWriter>) -> Fut + Send + Sync + 'static,
196 Fut: Future<Output = ()> + Send + 'static,
197 {
198 let detector = RateDetector::new(Arc::new(filter), count, window);
199 self.temporal.register(TemporalPattern::new(
200 name,
201 Box::new(detector),
202 Arc::new(move |s, w| Box::pin(action(s, w))),
203 None,
204 ));
205 self
206 }
207
208 /// Register a turn count pattern.
209 ///
210 /// Fires when the condition is true for `turn_count` consecutive turns.
211 pub fn when_turns<F, Fut>(
212 mut self,
213 name: impl Into<String>,
214 condition: impl Fn(&State) -> bool + Send + Sync + 'static,
215 turn_count: u32,
216 action: F,
217 ) -> Self
218 where
219 F: Fn(State, Arc<dyn SessionWriter>) -> Fut + Send + Sync + 'static,
220 Fut: Future<Output = ()> + Send + 'static,
221 {
222 let detector = TurnCountDetector::new(Arc::new(condition), turn_count);
223 self.temporal.register(TemporalPattern::new(
224 name,
225 Box::new(detector),
226 Arc::new(move |s, w| Box::pin(action(s, w))),
227 None,
228 ));
229 self
230 }
231}