gemini_adk_fluent_rs/live/
config.rs

1//! Model, session, and tool configuration methods for `Live`.
2
3use std::sync::Arc;
4use std::time::Duration;
5
6use gemini_adk_rs::live::needs::RepairConfig;
7use gemini_adk_rs::live::persistence::SessionPersistence;
8use gemini_adk_rs::live::steering::{ContextDelivery, SteeringMode};
9use gemini_adk_rs::live::{Delivery, DeliveryConfig, ResultFormatter, ToolExecutionMode};
10use gemini_adk_rs::tool::ToolDispatcher;
11use gemini_genai_rs::prelude::*;
12
13use super::{DeferredAgentTool, Live};
14
15impl Live {
16    // -- Model & Voice --
17
18    /// Set the Gemini model.
19    pub fn model(mut self, model: GeminiModel) -> Self {
20        self.config = self.config.model(model);
21        self
22    }
23
24    /// Set the output voice.
25    pub fn voice(mut self, voice: Voice) -> Self {
26        self.config = self.config.voice(voice);
27        self
28    }
29
30    /// Set the system instruction.
31    pub fn instruction(mut self, instruction: impl Into<String>) -> Self {
32        self.config = self.config.system_instruction(instruction);
33        self
34    }
35
36    /// Switch to text-only mode (no audio output).
37    ///
38    /// Sets response modality to `Text` and disables speech config.
39    /// Use with `GeminiModel::Gemini2_0FlashLive` for text-only conversations.
40    pub fn text_only(mut self) -> Self {
41        self.config = self.config.text_only();
42        self
43    }
44
45    /// Add a raw `Tool` declaration to the session configuration.
46    ///
47    /// Use this for tools that aren't registered through the `ToolDispatcher`
48    /// (e.g., raw `FunctionDeclaration` lists, Google Search, code execution).
49    pub fn add_tool(mut self, tool: Tool) -> Self {
50        self.config = self.config.add_tool(tool);
51        self
52    }
53
54    /// Set a greeting prompt to trigger the model to initiate the conversation.
55    ///
56    /// When set, this text is sent immediately after the session connects,
57    /// causing the model to respond first (e.g. with a greeting or introduction).
58    ///
59    /// ```ignore
60    /// let handle = Live::builder()
61    ///     .model(GeminiModel::Gemini2_0FlashLive)
62    ///     .instruction("You are a friendly assistant")
63    ///     .greeting("Greet the user warmly and introduce yourself.")
64    ///     .connect_vertex(project, location, token)
65    ///     .await?;
66    /// // Model will speak first without any user input
67    /// ```
68    pub fn greeting(mut self, prompt: impl Into<String>) -> Self {
69        self.greeting = Some(prompt.into());
70        self
71    }
72
73    /// Set the temperature.
74    pub fn temperature(mut self, temp: f32) -> Self {
75        self.config = self.config.temperature(temp);
76        self
77    }
78
79    // -- Wire recording --
80
81    /// Record every wire byte (both directions) to a JSONL log at `path`.
82    ///
83    /// The log is written by a
84    /// [`FileWireRecorder`] created at connect time (a connect error is returned if the file cannot
85    /// be created). Replay it offline with `adk session replay <path>` or
86    /// [`gemini_adk_rs::live::replay::replay_session`].
87    ///
88    /// ```ignore
89    /// let handle = Live::builder()
90    ///     .model(GeminiModel::Gemini2_0FlashLive)
91    ///     .record_wire("/tmp/session.wire.jsonl")
92    ///     .connect_from_env()
93    ///     .await?;
94    /// ```
95    pub fn record_wire(mut self, path: impl Into<std::path::PathBuf>) -> Self {
96        self.record_wire_path = Some(path.into());
97        self
98    }
99
100    /// Record every wire byte to a custom
101    /// [`WireRecorder`] implementation.
102    ///
103    /// Overrides (and is overridden by) the most recent of this and
104    /// [`record_wire`](Self::record_wire).
105    pub fn wire_recorder(
106        mut self,
107        recorder: Arc<dyn gemini_genai_rs::prelude::WireRecorder>,
108    ) -> Self {
109        self.record_wire_path = None;
110        self.config = self.config.record_wire(recorder);
111        self
112    }
113
114    // -- Tools --
115
116    /// Set the tool dispatcher (auto-dispatches tool calls).
117    pub fn tools(mut self, dispatcher: ToolDispatcher) -> Self {
118        self.dispatcher = Some(dispatcher);
119        self
120    }
121
122    /// Register tools from a `T` module composition.
123    ///
124    /// ```ignore
125    /// use gemini_adk_fluent_rs::prelude::*;
126    ///
127    /// Live::builder()
128    ///     .with_tools(
129    ///         T::simple("get_weather", "Get weather", |args| async move {
130    ///             Ok(serde_json::json!({"temp": 22}))
131    ///         })
132    ///         | T::google_search()
133    ///     )
134    /// ```
135    pub fn with_tools(mut self, composite: crate::compose::tools::ToolComposite) -> Self {
136        use crate::compose::tools::ToolResolution;
137        for entry in composite.entries {
138            match entry.classify() {
139                ToolResolution::Runtime(f) => {
140                    self.dispatcher
141                        .get_or_insert_with(ToolDispatcher::new)
142                        .register_function(f);
143                }
144                ToolResolution::BuiltIn(tool) => {
145                    self.config = self.config.add_tool(tool);
146                }
147                ToolResolution::Agent {
148                    name,
149                    description,
150                    agent,
151                } => {
152                    // Reuse the deferred agent-tool path so the sub-agent shares
153                    // the session State created at connect time.
154                    self.deferred_agent_tools.push(DeferredAgentTool {
155                        name,
156                        description,
157                        agent,
158                    });
159                }
160                ToolResolution::Deferred(deferred) => {
161                    // MCP / A2A / OpenAPI / Search need an async connection;
162                    // resolve them at connect time (see build_and_connect).
163                    self.deferred_tools.push(deferred);
164                }
165            }
166        }
167        self
168    }
169
170    /// Register a text agent as a tool the live model can call.
171    ///
172    /// The agent shares the session's `State`, so it can read live-extracted
173    /// values and its mutations are visible to watchers and phase transitions.
174    ///
175    /// ```ignore
176    /// Live::builder()
177    ///     .agent_tool("verify_identity", "Verify caller identity", verifier_agent)
178    ///     .agent_tool("calc_payment", "Calculate payment plans", calc_pipeline)
179    /// ```
180    pub fn agent_tool(
181        mut self,
182        name: impl Into<String>,
183        description: impl Into<String>,
184        agent: impl gemini_adk_rs::text::TextAgent + 'static,
185    ) -> Self {
186        self.deferred_agent_tools.push(DeferredAgentTool {
187            name: name.into(),
188            description: description.into(),
189            agent: Arc::new(agent),
190        });
191        self
192    }
193
194    /// Register a text agent (already `Arc`'d) as a tool.
195    pub fn agent_tool_arc(
196        mut self,
197        name: impl Into<String>,
198        description: impl Into<String>,
199        agent: Arc<dyn gemini_adk_rs::text::TextAgent>,
200    ) -> Self {
201        self.deferred_agent_tools.push(DeferredAgentTool {
202            name: name.into(),
203            description: description.into(),
204            agent,
205        });
206        self
207    }
208
209    /// Enable Google Search built-in tool.
210    pub fn google_search(mut self) -> Self {
211        self.config = self.config.with_google_search();
212        self
213    }
214
215    /// Enable code execution built-in tool.
216    pub fn code_execution(mut self) -> Self {
217        self.config = self.config.with_code_execution();
218        self
219    }
220
221    /// Enable URL context built-in tool.
222    pub fn url_context(mut self) -> Self {
223        self.config = self.config.with_url_context();
224        self
225    }
226
227    /// Mark a tool for background execution (zero dead-air).
228    ///
229    /// When the model calls this tool, an immediate "running" acknowledgment
230    /// is sent back while the tool executes in a background task. The final
231    /// result is delivered asynchronously when complete.
232    pub fn tool_background(mut self, tool_name: impl Into<String>) -> Self {
233        self.tool_execution_modes.insert(
234            tool_name.into(),
235            ToolExecutionMode::Background {
236                formatter: None,
237                scheduling: None,
238            },
239        );
240        self
241    }
242
243    /// Mark a tool for background execution with a custom result formatter.
244    ///
245    /// The formatter controls the shape of the acknowledgment ("running"),
246    /// completion, and cancellation messages sent to the model.
247    pub fn tool_background_with_formatter(
248        mut self,
249        tool_name: impl Into<String>,
250        formatter: Arc<dyn ResultFormatter>,
251    ) -> Self {
252        self.tool_execution_modes.insert(
253            tool_name.into(),
254            ToolExecutionMode::Background {
255                formatter: Some(formatter),
256                scheduling: None,
257            },
258        );
259        self
260    }
261
262    /// Mark a tool for background execution with a specific scheduling mode.
263    ///
264    /// The scheduling mode controls how the model handles async results:
265    /// - `Interrupt`: halts current output, immediately reports the result
266    /// - `WhenIdle`: waits until current output finishes before handling
267    /// - `Silent`: integrates the result without notifying the user
268    pub fn tool_background_with_scheduling(
269        mut self,
270        tool_name: impl Into<String>,
271        scheduling: gemini_genai_rs::prelude::FunctionResponseScheduling,
272    ) -> Self {
273        self.tool_execution_modes.insert(
274            tool_name.into(),
275            ToolExecutionMode::Background {
276                formatter: None,
277                scheduling: Some(scheduling),
278            },
279        );
280        self
281    }
282
283    // -- Audio/Video Config --
284
285    /// Enable input and/or output transcription.
286    pub fn transcription(mut self, input: bool, output: bool) -> Self {
287        if input {
288            self.config = self.config.enable_input_transcription();
289        }
290        if output {
291            self.config = self.config.enable_output_transcription();
292        }
293        self
294    }
295
296    /// Enable thinking/reasoning with a token budget (Gemini 2.5+).
297    ///
298    /// Sets the thinking budget for the Live session. Use with
299    /// `.include_thoughts()` and `.on_thought()` to receive thought summaries.
300    ///
301    /// ```ignore
302    /// Live::builder()
303    ///     .thinking(1024)
304    ///     .include_thoughts()
305    ///     .on_thought(|text| println!("[Thought] {text}"))
306    /// ```
307    ///
308    /// **Platform support:** Google AI only. On Vertex AI, `thinkingConfig`
309    /// is automatically stripped from the setup message.
310    pub fn thinking(mut self, budget: u32) -> Self {
311        self.config = self.config.thinking(budget);
312        self
313    }
314
315    /// Include the model's thought summaries in responses.
316    ///
317    /// When enabled, the model emits `SessionEvent::Thought` events containing
318    /// its reasoning process. Register an `.on_thought()` callback to receive them.
319    ///
320    /// **Platform support:** Google AI only. Stripped on Vertex AI.
321    pub fn include_thoughts(mut self) -> Self {
322        self.config = self.config.include_thoughts();
323        self
324    }
325
326    /// Enable affective dialog (emotionally expressive responses).
327    pub fn affective_dialog(mut self, enabled: bool) -> Self {
328        self.config = self.config.affective_dialog(enabled);
329        self
330    }
331
332    /// Enable proactive audio.
333    pub fn proactive_audio(mut self, enabled: bool) -> Self {
334        self.config = self.config.proactive_audio(enabled);
335        self
336    }
337
338    /// Set media resolution for video/image input.
339    pub fn media_resolution(mut self, res: MediaResolution) -> Self {
340        self.config = self.config.media_resolution(res);
341        self
342    }
343
344    // -- VAD & Activity --
345
346    /// Configure server-side VAD.
347    pub fn vad(mut self, detection: AutomaticActivityDetection) -> Self {
348        self.config = self.config.server_vad(detection);
349        self
350    }
351
352    /// Set activity handling mode (interrupts vs no-interruption).
353    pub fn activity_handling(mut self, handling: ActivityHandling) -> Self {
354        self.config = self.config.activity_handling(handling);
355        self
356    }
357
358    /// Set turn coverage mode.
359    pub fn turn_coverage(mut self, coverage: TurnCoverage) -> Self {
360        self.config = self.config.turn_coverage(coverage);
361        self
362    }
363
364    // -- Session Lifecycle --
365
366    /// Enable session resumption.
367    pub fn session_resume(mut self, enabled: bool) -> Self {
368        if enabled {
369            self.config = self.config.session_resumption(None);
370        }
371        self
372    }
373
374    /// Resume a previous session from a server-issued resumption handle.
375    ///
376    /// Capture the handle from the old session before it ends — via
377    /// [`LiveHandle::resume_handle`](gemini_adk_rs::live::LiveHandle::resume_handle)
378    /// (e.g. inside the `on_go_away` callback) or from a persisted
379    /// [`SessionSnapshot`](gemini_adk_rs::live::SessionSnapshot) — and pass it
380    /// here on the next connect. Resumption stays enabled for the new session,
381    /// so fresh handles keep arriving. No automatic reconnect is performed.
382    pub fn session_resume_from(mut self, handle: impl Into<String>) -> Self {
383        self.config = self.config.session_resumption(Some(handle.into()));
384        self
385    }
386
387    /// Enable context window compression.
388    pub fn context_compression(mut self, trigger_tokens: u32, target_tokens: u32) -> Self {
389        self.config = self
390            .config
391            .context_window_compression(target_tokens)
392            .context_window_trigger_tokens(trigger_tokens);
393        self
394    }
395
396    // -- Control Plane --
397
398    /// Enable soft turn detection for proactive silence awareness.
399    ///
400    /// When `proactiveAudio` is enabled, the model may choose not to respond.
401    /// After VAD end, if the model stays silent for `timeout`, a lightweight
402    /// "soft turn" updates state and fires watchers without forcing a response.
403    pub fn soft_turn_timeout(mut self, timeout: Duration) -> Self {
404        self.soft_turn_timeout = Some(timeout);
405        self
406    }
407
408    /// Set the steering mode for how the phase machine delivers instructions.
409    ///
410    /// - `InstructionUpdate` (default): Replace system instruction on transition.
411    /// - `ContextInjection`: Inject steering via `send_client_content`.
412    /// - `Hybrid`: Instruction on transition, context injection per turn.
413    pub fn steering_mode(mut self, mode: SteeringMode) -> Self {
414        self.steering_mode = mode;
415        self
416    }
417
418    /// Set when model-role context turns are delivered to the wire.
419    ///
420    /// - `Immediate` (default): Send as a single batched frame during
421    ///   TurnComplete processing.
422    /// - `Deferred`: Queue context and flush before the next user send
423    ///   (`send_audio`/`send_text`/`send_video`).  Eliminates isolated
424    ///   WebSocket frames during silence that can confuse the model.
425    ///
426    /// ```ignore
427    /// Live::builder()
428    ///     .steering_mode(SteeringMode::ContextInjection)
429    ///     .context_delivery(ContextDelivery::Deferred)
430    ///     .phase("greeting")
431    ///         .instruction("Welcome the guest")
432    ///         .done()
433    ///     .initial_phase("greeting")
434    /// ```
435    pub fn context_delivery(mut self, mode: ContextDelivery) -> Self {
436        self.context_delivery = mode;
437        self
438    }
439
440    /// Set the fast-lane delivery (backpressure) policy for every event class.
441    ///
442    /// The event router forwards fast-lane frames (audio, text, transcripts,
443    /// thoughts, VAD, phase) to the fast-lane consumer over a bounded channel.
444    /// By default every class is [`Delivery::Lossless`] — the router awaits
445    /// (`send().await`) when the channel is full, which preserves the historical
446    /// behavior. Opt classes into [`Delivery::LossyDropNewest`] to drop the
447    /// newest frame on overflow instead of stalling the router (and thereby
448    /// stalling control-lane routing too).
449    ///
450    /// ```ignore
451    /// use gemini_adk_rs::live::{Delivery, DeliveryConfig};
452    /// Live::builder()
453    ///     .delivery(DeliveryConfig::default()
454    ///         .audio(Delivery::LossyDropNewest)
455    ///         .transcript(Delivery::LossyDropNewest))
456    /// ```
457    pub fn delivery(mut self, delivery: DeliveryConfig) -> Self {
458        self.delivery = delivery;
459        self
460    }
461
462    /// Convenience: set the audio class to [`Delivery::LossyDropNewest`] so the
463    /// router never blocks on a slow audio consumer, dropping the newest PCM
464    /// frame on overflow. Other classes keep their current policy.
465    pub fn lossy_audio(mut self) -> Self {
466        self.delivery.audio = Delivery::LossyDropNewest;
467        self
468    }
469
470    /// Convenience: set the transcript class to [`Delivery::LossyDropNewest`].
471    /// Other classes keep their current policy.
472    pub fn lossy_transcript(mut self) -> Self {
473        self.delivery.transcript = Delivery::LossyDropNewest;
474        self
475    }
476
477    /// Enable the conversation repair protocol.
478    ///
479    /// Tracks unfulfilled `needs` per phase. After `nudge_after` stalled turns,
480    /// injects a gentle nudge. After `escalate_after` turns, sets
481    /// `repair:escalation` in state for phase guards to handle.
482    pub fn repair(mut self, config: RepairConfig) -> Self {
483        self.repair_config = Some(config);
484        self
485    }
486
487    /// Set a session persistence backend for surviving process restarts.
488    pub fn persistence(mut self, backend: Arc<dyn SessionPersistence>) -> Self {
489        self.persistence = Some(backend);
490        self
491    }
492
493    /// Set the session ID for persistence.
494    pub fn session_id(mut self, id: impl Into<String>) -> Self {
495        self.session_id = Some(id.into());
496        self
497    }
498
499    /// Enable or disable tool availability advisory on phase transitions.
500    ///
501    /// When enabled (default), the SDK injects a model-role context turn
502    /// telling the model which tools are available in the new phase.
503    pub fn tool_advisory(mut self, enabled: bool) -> Self {
504        self.tool_advisory = enabled;
505        self
506    }
507}