use std::io;
use agent_core_runtime::agent::AgentCore;
use crate::app::{App, AppConfig};
use crate::commands::SlashCommand;
use crate::keys::{DefaultKeyHandler, ExitHandler, KeyBindings, KeyHandler};
use crate::layout::LayoutTemplate;
use crate::widgets::{widget_ids, ConversationView, ConversationViewFactory, SessionInfo, Widget};
pub struct TuiRunner {
agent: AgentCore,
conversation_factory: Option<ConversationViewFactory>,
widgets_to_register: Vec<Box<dyn Widget>>,
layout_template: Option<LayoutTemplate>,
key_handler: Option<Box<dyn KeyHandler>>,
exit_handler: Option<Box<dyn ExitHandler>>,
commands: Option<Vec<Box<dyn SlashCommand>>>,
command_extension: Option<Box<dyn std::any::Any + Send>>,
custom_status_bar: Option<Box<dyn Widget>>,
hide_status_bar: bool,
}
impl TuiRunner {
pub fn new(agent: AgentCore) -> Self {
Self {
agent,
conversation_factory: None,
widgets_to_register: Vec::new(),
layout_template: None,
key_handler: None,
exit_handler: None,
commands: None,
command_extension: None,
custom_status_bar: None,
hide_status_bar: false,
}
}
pub fn agent_mut(&mut self) -> &mut AgentCore {
&mut self.agent
}
pub fn agent(&self) -> &AgentCore {
&self.agent
}
pub fn set_conversation_factory<F>(&mut self, factory: F) -> &mut Self
where
F: Fn() -> Box<dyn ConversationView> + Send + Sync + 'static,
{
self.conversation_factory = Some(Box::new(factory));
self
}
pub fn set_layout(&mut self, template: LayoutTemplate) -> &mut Self {
self.layout_template = Some(template);
self
}
pub fn set_key_handler<H: KeyHandler>(&mut self, handler: H) -> &mut Self {
self.key_handler = Some(Box::new(handler));
self
}
pub fn set_key_bindings(&mut self, bindings: KeyBindings) -> &mut Self {
self.key_handler = Some(Box::new(DefaultKeyHandler::new(bindings)));
self
}
pub fn set_exit_handler<H: ExitHandler>(&mut self, handler: H) -> &mut Self {
self.exit_handler = Some(Box::new(handler));
self
}
pub fn set_commands(&mut self, commands: Vec<Box<dyn SlashCommand>>) -> &mut Self {
self.commands = Some(commands);
self
}
pub fn set_command_extension<T: std::any::Any + Send + 'static>(&mut self, ext: T) -> &mut Self {
self.command_extension = Some(Box::new(ext));
self
}
pub fn register_widget<W: Widget>(&mut self, widget: W) -> &mut Self {
self.widgets_to_register.push(Box::new(widget));
self
}
pub fn set_status_bar<W: Widget>(&mut self, status_bar: W) -> &mut Self {
self.custom_status_bar = Some(Box::new(status_bar));
self
}
pub fn hide_status_bar(&mut self) -> &mut Self {
self.hide_status_bar = true;
self
}
pub fn run(mut self) -> io::Result<()> {
let name = self.agent.name().to_string();
tracing::info!("{} starting", name);
self.agent.start_background_tasks();
let app_config = AppConfig {
agent_name: name.clone(),
version: self.agent.version().to_string(),
commands: self.commands.take(),
command_extension: self.command_extension.take(),
error_no_session: self.agent.error_no_session().map(|s| s.to_string()),
..Default::default()
};
let mut app = App::with_config(app_config);
if let Some(factory) = self.conversation_factory.take() {
app.set_conversation_factory(move || factory());
}
if self.hide_status_bar {
app.widgets.remove(widget_ids::STATUS_BAR);
} else if let Some(custom_status_bar) = self.custom_status_bar.take() {
app.widgets.insert(widget_ids::STATUS_BAR, custom_status_bar);
}
for widget in self.widgets_to_register.drain(..) {
let id = widget.id();
app.widgets.insert(id, widget);
}
app.rebuild_priority_order();
app.set_to_controller(self.agent.to_controller_tx());
if let Some(rx) = self.agent.take_from_controller_rx() {
app.set_from_controller(rx);
}
app.set_controller(self.agent.controller().clone());
app.set_runtime_handle(self.agent.runtime_handle());
app.set_user_interaction_registry(self.agent.user_interaction_registry().clone());
app.set_permission_registry(self.agent.permission_registry().clone());
if let Some(layout) = self.layout_template.take() {
app.set_layout(layout);
}
if let Some(handler) = self.key_handler.take() {
app.set_key_handler_boxed(handler);
}
if let Some(handler) = self.exit_handler.take() {
app.set_exit_handler_boxed(handler);
}
match self.agent.create_initial_session() {
Ok((session_id, model, context_limit)) => {
let session_info = SessionInfo::new(session_id, model.clone(), context_limit);
app.add_session(session_info);
app.set_session_id(session_id);
app.set_model_name(&model);
app.set_context_limit(context_limit);
tracing::info!(
session_id = session_id,
model = %model,
"Auto-created session on startup"
);
}
Err(e) => {
tracing::warn!(error = %e, "No initial session created");
}
}
if let Some(registry) = self.agent.take_llm_registry() {
app.set_llm_registry(registry);
}
let result = app.run();
self.agent.shutdown();
tracing::info!("{} stopped", name);
result
}
}
pub trait AgentCoreExt {
fn into_tui(self) -> TuiRunner;
}
impl AgentCoreExt for AgentCore {
fn into_tui(self) -> TuiRunner {
TuiRunner::new(self)
}
}