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}