use crate::character::service::CharacterService;
use crate::core::app::picker::PickerController;
use crate::core::app::ui_state::UiState;
use crate::core::config::data::Config;
use crate::core::message::AppMessageKind;
use crate::core::providers::ProviderSession;
use crate::mcp::client::McpClientManager;
use crate::mcp::permissions::ToolPermissionStore;
pub mod actions;
pub mod conversation;
pub mod inspect;
pub mod picker;
pub mod session;
pub mod settings;
pub mod ui_state;
#[allow(clippy::module_inception)]
mod app;
mod pickers;
mod streaming;
#[cfg(test)]
mod tests;
mod ui_helpers;
pub use actions::{
apply_actions, AppAction, AppActionContext, AppActionDispatcher, AppActionEnvelope, AppCommand,
CommandAction, ComposeAction, FilePromptAction, InputAction, InspectAction, McpPromptAction,
PickerAction, PromptAction, StatusAction, StreamingAction,
};
pub use inspect::{InspectController, InspectMode, InspectState, ToolInspectKind, ToolInspectView};
#[cfg(test)]
pub use picker::PickerData;
pub use picker::PickerMode;
pub use pickers::ModelPickerRequest;
pub use session::{SessionBootstrap, SessionContext, UninitializedSessionBootstrap};
pub use ui_state::ActivityKind;
pub struct AppInitConfig {
pub model: String,
pub log_file: Option<String>,
pub provider: Option<String>,
pub env_only: bool,
pub pre_resolved_session: Option<ProviderSession>,
pub character: Option<String>,
pub persona: Option<String>,
pub preset: Option<String>,
pub disable_mcp: bool,
}
#[allow(clippy::too_many_arguments)]
fn build_app(
session: SessionContext,
ui: UiState,
picker: PickerController,
character_service: CharacterService,
persona_manager: crate::core::persona::PersonaManager,
preset_manager: crate::core::preset::PresetManager,
config: Config,
mcp: McpClientManager,
) -> App {
let mut app = App {
session,
ui,
picker,
inspect: InspectController::new(),
character_service,
persona_manager,
preset_manager,
config,
mcp,
mcp_permissions: ToolPermissionStore::default(),
};
app.ui.set_input_text(String::new());
app.configure_textarea_appearance();
let display_name = app.persona_manager.get_display_name();
app.ui.update_user_display_name(display_name);
app
}
pub async fn new_with_auth(
init_config: AppInitConfig,
config: &Config,
mut character_service: CharacterService,
) -> Result<App, Box<dyn std::error::Error>> {
let mut effective_config = config.clone();
if init_config.disable_mcp {
for server in &mut effective_config.mcp_servers {
server.enabled = Some(false);
}
}
let SessionBootstrap {
session,
theme,
startup_requires_provider,
mut startup_errors,
} = session::prepare_with_auth(session::PrepareWithAuthInput {
model: init_config.model,
log_file: init_config.log_file,
provider: init_config.provider,
env_only: init_config.env_only,
config: &effective_config,
pre_resolved_session: init_config.pre_resolved_session,
character: init_config.character,
character_service: &mut character_service,
})
.await?;
let mut persona_manager =
crate::core::persona::PersonaManager::load_personas(&effective_config)?;
if let Some(persona_id) = init_config.persona {
persona_manager.set_active_persona(&persona_id)?;
} else {
if let Some(default_persona_id) =
persona_manager.get_default_for_provider_model(&session.provider_name, &session.model)
{
let default_persona_id = default_persona_id.to_string(); if let Err(e) = persona_manager.set_active_persona(&default_persona_id) {
startup_errors.push(format!(
"Could not load default persona '{}': {}",
default_persona_id, e
));
}
}
}
let mut preset_manager = crate::core::preset::PresetManager::load_presets(&effective_config)?;
if let Some(preset_id) = init_config.preset {
preset_manager.set_active_preset(&preset_id)?;
} else if let Some(default_preset_id) =
preset_manager.get_default_for_provider_model(&session.provider_name, &session.model)
{
let default_preset_id = default_preset_id.to_string();
if let Err(e) = preset_manager.set_active_preset(&default_preset_id) {
startup_errors.push(format!(
"Could not load default preset '{}': {}",
default_preset_id, e
));
}
}
let ui = UiState::from_config(theme, &effective_config);
let picker = PickerController::new();
let mcp = McpClientManager::from_config(&effective_config);
let mut app = build_app(
session,
ui,
picker,
character_service,
persona_manager,
preset_manager,
effective_config.clone(),
mcp,
);
app.session.mcp_disabled = init_config.disable_mcp;
if app.session.logging.is_active() {
let timestamp = chrono::Local::now()
.format("%Y-%m-%d %H:%M:%S %Z")
.to_string();
let log_message = format!("Logging started at {}", timestamp);
app.conversation()
.add_app_message(AppMessageKind::Log, log_message);
}
if startup_requires_provider {
app.picker.startup_requires_provider = true;
}
if !startup_errors.is_empty() {
let mut conversation = app.conversation();
for error in startup_errors {
conversation.add_app_message(AppMessageKind::Error, error);
}
}
Ok(app)
}
pub async fn new_uninitialized(
log_file: Option<String>,
disable_mcp: bool,
mut character_service: CharacterService,
) -> Result<App, Box<dyn std::error::Error>> {
let UninitializedSessionBootstrap {
session,
theme,
mut config,
startup_requires_provider,
} = session::prepare_uninitialized(log_file, &mut character_service).await?;
if disable_mcp {
for server in &mut config.mcp_servers {
server.enabled = Some(false);
}
}
let persona_manager = crate::core::persona::PersonaManager::load_personas(&config)?;
let preset_manager = crate::core::preset::PresetManager::load_presets(&config)?;
let ui = UiState::from_config(theme, &config);
let picker = PickerController::new();
let mcp = McpClientManager::from_config(&config);
let mut app = build_app(
session,
ui,
picker,
character_service,
persona_manager,
preset_manager,
config.clone(),
mcp,
);
app.session.mcp_disabled = disable_mcp;
if startup_requires_provider {
app.picker.startup_requires_provider = true;
}
Ok(app)
}
pub struct App {
pub session: SessionContext,
pub ui: UiState,
pub picker: PickerController,
pub inspect: InspectController,
pub character_service: CharacterService,
pub persona_manager: crate::core::persona::PersonaManager,
pub preset_manager: crate::core::preset::PresetManager,
pub config: Config,
pub mcp: McpClientManager,
pub mcp_permissions: ToolPermissionStore,
}