Skip to main content

codei_tui/
repl.rs

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}