1use adk_core::{Agent, Content, Part};
2use adk_runner::{Runner, RunnerConfig};
3use adk_session::{CreateRequest, InMemorySessionService, SessionService};
4use anyhow::Result;
5use futures::StreamExt;
6use rustyline::DefaultEditor;
7use serde_json::Value;
8use std::collections::HashMap;
9use std::io::{self, Write};
10use std::sync::Arc;
11
12#[allow(dead_code)] pub async fn run_console(agent: Arc<dyn Agent>, app_name: String, user_id: String) -> Result<()> {
14 let session_service = Arc::new(InMemorySessionService::new());
15
16 let session = session_service
17 .create(CreateRequest {
18 app_name: app_name.clone(),
19 user_id: user_id.clone(),
20 session_id: None,
21 state: HashMap::new(),
22 })
23 .await?;
24
25 let runner = Runner::new(RunnerConfig {
26 app_name: app_name.clone(),
27 agent: agent.clone(),
28 session_service: session_service.clone(),
29 artifact_service: None,
30 memory_service: None,
31 run_config: None,
32 })?;
33
34 let mut rl = DefaultEditor::new()?;
35
36 println!("ADK Console Mode");
37 println!("Agent: {}", agent.name());
38 println!("Type your message and press Enter. Ctrl+C to exit.\n");
39
40 loop {
41 let readline = rl.readline("User -> ");
42 match readline {
43 Ok(line) => {
44 if line.trim().is_empty() {
45 continue;
46 }
47
48 rl.add_history_entry(&line)?;
49
50 let user_content = Content::new("user").with_text(line);
51
52 print!("\nAgent -> ");
53
54 let session_id = session.id().to_string();
55 let mut events = runner.run(user_id.clone(), session_id, user_content).await?;
56
57 let mut stream_printer = StreamPrinter::default();
58
59 while let Some(event) = events.next().await {
60 match event {
61 Ok(evt) => {
62 if let Some(content) = &evt.llm_response.content {
63 for part in &content.parts {
64 stream_printer.handle_part(part);
65 }
66 }
67 }
68 Err(e) => {
69 eprintln!("\nError: {}", e);
70 }
71 }
72 }
73
74 stream_printer.finish();
75 println!("\n");
76 }
77 Err(rustyline::error::ReadlineError::Interrupted) => {
78 println!("Interrupted");
79 break;
80 }
81 Err(rustyline::error::ReadlineError::Eof) => {
82 println!("EOF");
83 break;
84 }
85 Err(err) => {
86 eprintln!("Error: {}", err);
87 break;
88 }
89 }
90 }
91
92 Ok(())
93}
94
95#[derive(Default)]
100struct StreamPrinter {
101 in_think_block: bool,
102 think_buffer: String,
103}
104
105impl StreamPrinter {
106 fn handle_part(&mut self, part: &Part) {
107 match part {
108 Part::Text { text } => self.handle_text_chunk(text),
109 Part::FunctionCall { name, args, .. } => self.print_tool_call(name, args),
110 Part::FunctionResponse { function_response, .. } => {
111 self.print_tool_response(&function_response.name, &function_response.response)
112 }
113 Part::InlineData { mime_type, data } => self.print_inline_data(mime_type, data.len()),
114 Part::FileData { mime_type, file_uri } => self.print_file_data(mime_type, file_uri),
115 }
116 }
117
118 fn handle_text_chunk(&mut self, chunk: &str) {
119 const THINK_START: &str = "<think>";
120 const THINK_END: &str = "</think>";
121
122 let mut remaining = chunk;
123
124 while !remaining.is_empty() {
125 if self.in_think_block {
126 if let Some(end_idx) = remaining.find(THINK_END) {
127 self.think_buffer.push_str(&remaining[..end_idx]);
128 self.flush_think();
129 self.in_think_block = false;
130 remaining = &remaining[end_idx + THINK_END.len()..];
131 } else {
132 self.think_buffer.push_str(remaining);
133 break;
134 }
135 } else if let Some(start_idx) = remaining.find(THINK_START) {
136 let visible = &remaining[..start_idx];
137 self.print_visible(visible);
138 self.in_think_block = true;
139 self.think_buffer.clear();
140 remaining = &remaining[start_idx + THINK_START.len()..];
141 } else {
142 self.print_visible(remaining);
143 break;
144 }
145 }
146 }
147
148 fn print_visible(&self, text: &str) {
149 if text.is_empty() {
150 return;
151 }
152
153 print!("{}", text);
154 let _ = io::stdout().flush();
155 }
156
157 fn flush_think(&mut self) {
158 let content = self.think_buffer.trim();
159 if content.is_empty() {
160 self.think_buffer.clear();
161 return;
162 }
163
164 print!("\n[think] {}\n", content);
165 let _ = io::stdout().flush();
166 self.think_buffer.clear();
167 }
168
169 fn finish(&mut self) {
170 if self.in_think_block {
171 self.flush_think();
172 self.in_think_block = false;
173 }
174 }
175
176 fn print_tool_call(&self, name: &str, args: &Value) {
177 print!("\n[tool-call] {} {}\n", name, args);
178 let _ = io::stdout().flush();
179 }
180
181 fn print_tool_response(&self, name: &str, response: &Value) {
182 print!("\n[tool-response] {} {}\n", name, response);
183 let _ = io::stdout().flush();
184 }
185
186 fn print_inline_data(&self, mime_type: &str, len: usize) {
187 print!("\n[inline-data] mime={} bytes={}\n", mime_type, len);
188 let _ = io::stdout().flush();
189 }
190
191 fn print_file_data(&self, mime_type: &str, file_uri: &str) {
192 print!("\n[file-data] mime={} uri={}\n", mime_type, file_uri);
193 let _ = io::stdout().flush();
194 }
195}