1use std::io::{self, Write};
2use std::sync::{Arc, RwLock};
3
4use anyhow::Result;
5use codei_agent::{AgentEvent, AgentLoop};
6use codei_commands::{parse_input, Input};
7use codei_i18n::t_fmt;
8use codei_tools::{handler_for_policy, ApprovalPolicy, ToolContext};
9use tokio::sync::mpsc;
10
11use crate::launch::InteractiveLaunch;
12use crate::slash::{handle_slash, SlashContext};
13
14pub struct ReplOptions {
15 pub auto_approve: bool,
16}
17
18pub async fn run_repl(launch: InteractiveLaunch, opts: ReplOptions) -> Result<()> {
19 let InteractiveLaunch {
20 config,
21 provider,
22 provider_name,
23 model,
24 mut session,
25 store,
26 mcp,
27 } = launch;
28 let policy = if opts.auto_approve {
29 ApprovalPolicy::Never
30 } else {
31 ApprovalPolicy::OnDestructive
32 };
33 let (tx, mut rx) = mpsc::unbounded_channel();
34 let tool_ctx = ToolContext {
35 cwd: config.cwd.clone(),
36 config: Arc::clone(&config),
37 approval: Arc::from(handler_for_policy(policy)),
38 };
39 let provider_name = Arc::new(RwLock::new(provider_name));
40 let agent = AgentLoop::new(
41 Arc::clone(&config),
42 Arc::clone(&model),
43 provider,
44 provider_name.read().expect("provider lock").clone(),
45 tool_ctx,
46 mcp,
47 Some(tx),
48 );
49
50 let stdin = io::stdin();
51 let mut stdout = io::stdout();
52
53 loop {
54 while let Ok(event) = rx.try_recv() {
55 render_event(&mut stdout, &event)?;
56 }
57
58 write!(stdout, "\n> ")?;
59 stdout.flush()?;
60 let mut line = String::new();
61 if stdin.read_line(&mut line)? == 0 {
62 break;
63 }
64
65 match parse_input(&line) {
66 Input::SlashCommand(cmd) => {
67 let mut ctx = SlashContext {
68 session: &mut session,
69 store: &store,
70 model: &model,
71 provider_name: &provider_name,
72 agent: &agent,
73 };
74 match handle_slash(cmd, &mut ctx).await? {
75 crate::slash::SlashAction::Exit => break,
76 crate::slash::SlashAction::Message(text) => writeln!(stdout, "{text}")?,
77 crate::slash::SlashAction::Continue => {}
78 }
79 }
80 Input::UserMessage(msg) if msg.is_empty() => {}
81 Input::UserMessage(msg) => {
82 if let Err(err) = agent.run_turn(&mut session, &msg, &store).await {
83 writeln!(
84 stdout,
85 "{}",
86 t_fmt("tui_error_prefix", &[("message", &err.to_string())])
87 )?;
88 }
89 while let Ok(event) = rx.try_recv() {
90 render_event(&mut stdout, &event)?;
91 }
92 }
93 }
94 }
95
96 Ok(())
97}
98
99fn render_event(stdout: &mut impl Write, event: &AgentEvent) -> io::Result<()> {
100 match event {
101 AgentEvent::AssistantDelta { text } => write!(stdout, "{text}"),
102 AgentEvent::ToolStarted { name, args } => writeln!(stdout, "\n[tool:{name}] {args}"),
103 AgentEvent::ToolFinished { name, result } => {
104 writeln!(stdout, "[tool:{name}]\n{}", result.content)
105 }
106 AgentEvent::TurnComplete { .. } => writeln!(stdout),
107 AgentEvent::Error { message } => writeln!(
108 stdout,
109 "{}",
110 t_fmt("tui_error_prefix", &[("message", message)])
111 ),
112 }
113}