Skip to main content

adk_runner/
launcher.rs

1//! Lightweight console launcher for ADK agents.
2//!
3//! Provides a simple REPL loop that works with the `minimal` feature tier —
4//! no `axum`, `rustyline`, or `clap` required. For the full-featured launcher
5//! with `--serve` mode and readline history, use `adk_cli::Launcher` (requires
6//! the `standard` feature tier).
7//!
8//! # Example
9//!
10//! ```rust,ignore
11//! use adk_runner::Launcher;
12//! use std::sync::Arc;
13//!
14//! let agent = /* build your agent */;
15//! Launcher::new(Arc::new(agent)).run().await?;
16//! ```
17
18use std::collections::HashMap;
19use std::io::{self, BufRead, Write};
20use std::sync::Arc;
21
22use adk_core::{Agent, Content, Memory, Part, Result, SessionId, UserId};
23use adk_session::{CreateRequest, InMemorySessionService, SessionService};
24use futures::StreamExt;
25
26use crate::{Runner, RunnerConfig};
27
28/// Lightweight console launcher for interactive agent sessions.
29///
30/// Reads from stdin, runs the agent, prints streaming responses to stdout.
31/// No external dependencies beyond what `adk-runner` already has.
32///
33/// For the full-featured launcher with `--serve` mode, readline history,
34/// and thinking block rendering, use `adk_cli::Launcher` with the `standard`
35/// feature tier.
36pub struct Launcher {
37    agent: Arc<dyn Agent>,
38    app_name: Option<String>,
39    session_service: Option<Arc<dyn SessionService>>,
40    memory_service: Option<Arc<dyn Memory>>,
41}
42
43impl Launcher {
44    /// Create a new launcher with the given agent.
45    pub fn new(agent: Arc<dyn Agent>) -> Self {
46        Self { agent, app_name: None, session_service: None, memory_service: None }
47    }
48
49    /// Set a custom application name (defaults to agent name).
50    pub fn app_name(mut self, name: impl Into<String>) -> Self {
51        self.app_name = Some(name.into());
52        self
53    }
54
55    /// Set a custom session service (defaults to in-memory).
56    pub fn with_session_service(mut self, service: Arc<dyn SessionService>) -> Self {
57        self.session_service = Some(service);
58        self
59    }
60
61    /// Set a custom memory service.
62    pub fn with_memory_service(mut self, service: Arc<dyn Memory>) -> Self {
63        self.memory_service = Some(service);
64        self
65    }
66
67    /// Run the interactive console loop.
68    ///
69    /// Reads lines from stdin, sends them to the agent, and prints streaming
70    /// responses. Type `exit` or `quit` to stop. Ctrl+C exits immediately.
71    pub async fn run(self) -> Result<()> {
72        let app_name = self.app_name.unwrap_or_else(|| self.agent.name().to_string());
73        let agent = self.agent;
74
75        let session_service: Arc<dyn SessionService> =
76            self.session_service.unwrap_or_else(|| Arc::new(InMemorySessionService::new()));
77
78        let session = session_service
79            .create(CreateRequest {
80                app_name: app_name.clone(),
81                user_id: "user".into(),
82                session_id: None,
83                state: HashMap::new(),
84            })
85            .await?;
86
87        let session_id = session.id().to_string();
88
89        println!();
90        println!("  ADK-Rust — {}", agent.name());
91        println!("  Type a message to chat. Type 'exit' to quit.");
92        println!();
93
94        let stdin = io::stdin();
95        let mut lines = stdin.lock().lines();
96
97        loop {
98            print!("You > ");
99            io::stdout().flush().ok();
100
101            let line = match lines.next() {
102                Some(Ok(line)) => line,
103                Some(Err(_)) | None => break,
104            };
105
106            let trimmed = line.trim();
107            if trimmed.is_empty() {
108                continue;
109            }
110            if matches!(trimmed, "exit" | "quit" | "/exit" | "/quit") {
111                println!("\nGoodbye.\n");
112                break;
113            }
114
115            let user_content = Content::new("user").with_text(trimmed);
116
117            let runner = Runner::new(RunnerConfig {
118                app_name: app_name.clone(),
119                agent: agent.clone(),
120                session_service: session_service.clone(),
121                #[cfg(feature = "artifacts")]
122                artifact_service: None,
123                memory_service: self.memory_service.clone(),
124                #[cfg(feature = "plugins")]
125                plugin_manager: None,
126                run_config: None,
127                compaction_config: None,
128                context_cache_config: None,
129                cache_capable: None,
130                request_context: None,
131                cancellation_token: None,
132                intra_compaction_config: None,
133                intra_compaction_summarizer: None,
134            })?;
135
136            let mut stream = runner
137                .run(UserId::new("user")?, SessionId::new(&session_id)?, user_content)
138                .await?;
139
140            while let Some(event) = stream.next().await {
141                match event {
142                    Ok(evt) => {
143                        if let Some(content) = evt.content() {
144                            for part in &content.parts {
145                                match part {
146                                    Part::Text { text } => {
147                                        print!("{text}");
148                                        io::stdout().flush().ok();
149                                    }
150                                    Part::Thinking { thinking, .. } => {
151                                        print!("\n[thinking] {thinking}");
152                                        io::stdout().flush().ok();
153                                    }
154                                    Part::FunctionCall { name, args, .. } => {
155                                        print!("\n[tool] {name}({args})");
156                                        io::stdout().flush().ok();
157                                    }
158                                    _ => {}
159                                }
160                            }
161                        }
162                    }
163                    Err(e) => {
164                        eprintln!("\n[error] {e}");
165                    }
166                }
167            }
168
169            println!("\n");
170        }
171
172        Ok(())
173    }
174}