1use crate::agent::{AgentConfig, AgentSession};
4use crate::api::{Claude, Session as ClaudeSession};
5use crate::deepseek::{DeepSeek, Session as DeepSeekSession};
6use crate::utils::prettify;
7use anyhow::{anyhow, Context, Result};
8use std::io::{self, Write};
9
10pub async fn run_agent_cli(
12 use_deepseek: bool,
13 use_opus: bool,
14 use_haiku: bool,
15) -> Result<()> {
16 println!("š¤ Starting Toast Agent...\n");
17
18 let config = AgentConfig::default();
20 let mut session = AgentSession::new(config);
21
22 if use_deepseek {
23 run_with_deepseek(&mut session, use_opus, use_haiku).await
24 } else {
25 run_with_claude(&mut session, use_opus, use_haiku).await
26 }
27}
28
29async fn run_with_claude(
30 session: &mut AgentSession,
31 use_opus: bool,
32 use_haiku: bool,
33) -> Result<()> {
34 let config_dir = dirs::config_dir()
36 .ok_or_else(|| anyhow!("Could not determine config directory"))?
37 .join("toast");
38
39 let cookie = std::fs::read_to_string(config_dir.join("cookie"))
40 .context("Failed to read cookie")?
41 .trim()
42 .to_string();
43
44 let org_id = if let Ok(id) = std::fs::read_to_string(config_dir.join("org_id")) {
45 id.trim().to_string()
46 } else {
47 crate::utils::extract_org_id_from_cookie(&cookie)
48 .ok_or_else(|| anyhow!("Could not extract org_id from cookie"))?
49 };
50
51 let claude_session = ClaudeSession {
52 cookie,
53 user_agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:137.0) Gecko/20100101 Firefox/137.0".to_string(),
54 organization_id: org_id,
55 };
56
57 let model = if use_opus {
58 crate::config::OPUS_MODEL
59 } else if use_haiku {
60 crate::config::HAIKU_MODEL
61 } else {
62 crate::config::SONNET_MODEL
63 };
64
65 let claude = Claude::new(claude_session, model)?;
66 println!("Connected to Claude ({model})\n");
67
68 let chat_id = claude.create_chat().await.context("Failed to create chat")?;
70
71 let system_prompt = session.agent().get_system_prompt();
73 claude.send_message(&chat_id, &system_prompt, &[]).await
74 .context("Failed to send system prompt")?;
75
76 let stdin = io::stdin();
78 let mut stdout = io::stdout();
79
80 loop {
81 print!("You: ");
82 stdout.flush()?;
83
84 let mut input = String::new();
85 match stdin.read_line(&mut input) {
86 Ok(0) => break, Ok(_) => {
88 let input = input.trim();
89 if input.is_empty() {
90 continue;
91 }
92 if matches!(input, "exit" | "quit" | "/exit" | "x") {
93 break;
94 }
95
96 let response = claude.send_message(&chat_id, input, &[]).await
98 .context("Failed to send message")?;
99
100 println!("\nClaude: {}\n", prettify(&response));
101
102 process_agent_response_claude(session, &claude, &chat_id, &response).await?;
104
105 session.agent().reset();
107 }
108 Err(e) => {
109 eprintln!("Error reading input: {e}");
110 break;
111 }
112 }
113 }
114
115 claude.delete_chat(&chat_id).await.ok();
117 println!("\nš Goodbye!");
118 Ok(())
119}
120
121async fn run_with_deepseek(
122 session: &mut AgentSession,
123 use_opus: bool,
124 use_haiku: bool,
125) -> Result<()> {
126 let config_dir = dirs::config_dir()
128 .ok_or_else(|| anyhow!("Could not determine config directory"))?
129 .join("toast")
130 .join("deepseek");
131
132 let auth_token = std::fs::read_to_string(config_dir.join("auth_token"))
133 .context("Failed to read auth token")?
134 .trim()
135 .to_string();
136
137 let cookies = serde_json::from_str(
138 &std::fs::read_to_string(config_dir.join("cookies.json"))
139 .context("Failed to read cookies")?
140 )?;
141
142 let deepseek_session = DeepSeekSession { auth_token, cookies };
143 let mut deepseek = DeepSeek::new(deepseek_session)?;
144
145 let model = if use_opus {
146 "deepseek-r1"
147 } else if use_haiku {
148 "deepseek-lite"
149 } else {
150 "deepseek-r1"
151 };
152
153 println!("Connected to DeepSeek ({model})\n");
154
155 let chat_id = deepseek.create_chat_session().await
157 .context("Failed to create chat session")?;
158
159 let thinking_mode = if model == "deepseek-r1" {
160 crate::deepseek::ThinkingMode::Detailed
161 } else {
162 crate::deepseek::ThinkingMode::Simple
163 };
164 let search_mode = crate::deepseek::SearchMode::Disabled;
165
166 let system_prompt = session.agent().get_system_prompt();
168
169 let stdin = io::stdin();
171 let mut stdout = io::stdout();
172 let mut first_message = true;
173
174 loop {
175 print!("You: ");
176 stdout.flush()?;
177
178 let mut input = String::new();
179 match stdin.read_line(&mut input) {
180 Ok(0) => break, Ok(_) => {
182 let input = input.trim();
183 if input.is_empty() {
184 continue;
185 }
186 if matches!(input, "exit" | "quit" | "/exit" | "x") {
187 break;
188 }
189
190 let system_prompt_opt = if first_message {
192 first_message = false;
193 Some(system_prompt.as_str())
194 } else {
195 None
196 };
197
198 let response = deepseek.chat_completion(
200 &chat_id,
201 input,
202 None,
203 thinking_mode,
204 search_mode,
205 system_prompt_opt,
206 ).await.context("Failed to send message")?;
207
208 println!("\nDeepSeek: {}\n", prettify(&response));
209
210 process_agent_response_deepseek(
212 session,
213 &mut deepseek,
214 &chat_id,
215 &response,
216 thinking_mode,
217 search_mode,
218 ).await?;
219
220 session.agent().reset();
222 }
223 Err(e) => {
224 eprintln!("Error reading input: {e}");
225 break;
226 }
227 }
228 }
229
230 println!("\nš Goodbye!");
231 Ok(())
232}
233
234async fn process_agent_response_claude(
235 session: &mut AgentSession,
236 claude: &Claude,
237 chat_id: &str,
238 response: &str,
239) -> Result<()> {
240 let tool_results = session.agent().process_tool_calls(response).await?;
241
242 if !tool_results.is_empty() {
243 let mut result_message = String::from("Tool execution results:\n\n");
245 for (tool_name, output) in tool_results {
246 result_message.push_str(&format!("[{tool_name}]\n{output}\n\n"));
247 }
248
249 let follow_up = claude.send_message(chat_id, &result_message, &[]).await
251 .context("Failed to send tool results")?;
252
253 println!("Claude: {}\n", prettify(&follow_up));
254
255 Box::pin(process_agent_response_claude(session, claude, chat_id, &follow_up)).await?;
257 }
258
259 Ok(())
260}
261
262async fn process_agent_response_deepseek(
263 session: &mut AgentSession,
264 deepseek: &mut DeepSeek,
265 chat_id: &str,
266 response: &str,
267 thinking_mode: crate::deepseek::ThinkingMode,
268 search_mode: crate::deepseek::SearchMode,
269) -> Result<()> {
270 let tool_results = session.agent().process_tool_calls(response).await?;
271
272 if !tool_results.is_empty() {
273 let mut result_message = String::from("Tool execution results:\n\n");
275 for (tool_name, output) in tool_results {
276 result_message.push_str(&format!("[{tool_name}]\n{output}\n\n"));
277 }
278
279 let follow_up = deepseek.chat_completion(
281 chat_id,
282 &result_message,
283 None,
284 thinking_mode,
285 search_mode,
286 None,
287 ).await.context("Failed to send tool results")?;
288
289 println!("DeepSeek: {}\n", prettify(&follow_up));
290
291 Box::pin(process_agent_response_deepseek(
293 session,
294 deepseek,
295 chat_id,
296 &follow_up,
297 thinking_mode,
298 search_mode,
299 )).await?;
300 }
301
302 Ok(())
303}