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