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}