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 }
114 }
115
116 fn handle_text_chunk(&mut self, chunk: &str) {
117 const THINK_START: &str = "<think>";
118 const THINK_END: &str = "</think>";
119
120 let mut remaining = chunk;
121
122 while !remaining.is_empty() {
123 if self.in_think_block {
124 if let Some(end_idx) = remaining.find(THINK_END) {
125 self.think_buffer.push_str(&remaining[..end_idx]);
126 self.flush_think();
127 self.in_think_block = false;
128 remaining = &remaining[end_idx + THINK_END.len()..];
129 } else {
130 self.think_buffer.push_str(remaining);
131 break;
132 }
133 } else if let Some(start_idx) = remaining.find(THINK_START) {
134 let visible = &remaining[..start_idx];
135 self.print_visible(visible);
136 self.in_think_block = true;
137 self.think_buffer.clear();
138 remaining = &remaining[start_idx + THINK_START.len()..];
139 } else {
140 self.print_visible(remaining);
141 break;
142 }
143 }
144 }
145
146 fn print_visible(&self, text: &str) {
147 if text.is_empty() {
148 return;
149 }
150
151 print!("{}", text);
152 let _ = io::stdout().flush();
153 }
154
155 fn flush_think(&mut self) {
156 let content = self.think_buffer.trim();
157 if content.is_empty() {
158 self.think_buffer.clear();
159 return;
160 }
161
162 print!("\n[think] {}\n", content);
163 let _ = io::stdout().flush();
164 self.think_buffer.clear();
165 }
166
167 fn finish(&mut self) {
168 if self.in_think_block {
169 self.flush_think();
170 self.in_think_block = false;
171 }
172 }
173
174 fn print_tool_call(&self, name: &str, args: &Value) {
175 print!("\n[tool-call] {} {}\n", name, args);
176 let _ = io::stdout().flush();
177 }
178
179 fn print_tool_response(&self, name: &str, response: &Value) {
180 print!("\n[tool-response] {} {}\n", name, response);
181 let _ = io::stdout().flush();
182 }
183
184 fn print_inline_data(&self, mime_type: &str, len: usize) {
185 print!("\n[inline-data] mime={} bytes={}\n", mime_type, len);
186 let _ = io::stdout().flush();
187 }
188}