Skip to main content

research_agent/
composition.rs

1//! Composition root: the single place where concrete adapters are wired onto
2//! the application ports. All entry points (CLI and MCP server) build
3//! their dependencies through this module so wiring exists exactly once.
4
5use std::path::{Path, PathBuf};
6
7use crate::adapters::llm_research_engine::LlmResearchEngine;
8use crate::adapters::sqlite_store::SqliteStore;
9use crate::config::{Config, default_config_path, default_db_path};
10use crate::error::{ResearchError, Result};
11
12/// Open the workspace SQLite store at `db_path`.
13pub fn open_store(db_path: &Path) -> Result<SqliteStore> {
14    SqliteStore::open(db_path)
15}
16
17/// The database path: the global `--db` flag wins, otherwise the default
18/// under `~/.research`.
19pub fn resolve_db(opt: &Option<PathBuf>) -> PathBuf {
20    opt.clone().unwrap_or_else(default_db_path)
21}
22
23/// Load `config.toml`, materializing the default file when absent.
24pub fn load_config() -> Result<Config> {
25    Config::load(&default_config_path()).map_err(|e| ResearchError::Config(e.to_string()))
26}
27
28/// Build the LLM research engine from `config.toml` `[llm]`. A missing
29/// `[llm]` section yields the default (provider-configured) engine; a missing
30/// API key is only warned about here — the engine resolves it at call time.
31pub fn make_llm_engine(store: SqliteStore) -> Result<LlmResearchEngine> {
32    let config = load_config()?;
33    if let Some(llm_cfg) = config.llm {
34        if llm_cfg.resolve_api_key().is_none() {
35            tracing::warn!(
36                api_key_env = %llm_cfg.api_key_env,
37                "[llm] api_key_env not set — LLM calls will fail"
38            );
39        }
40        use llm_kernel::llm::ModelConfig;
41        let model_config = ModelConfig {
42            provider: llm_cfg.provider,
43            model: llm_cfg.model,
44            api_key_env: llm_cfg.api_key_env,
45            base_url: llm_cfg.base_url,
46            ..ModelConfig::default()
47        };
48        return Ok(LlmResearchEngine::with_config(
49            Box::new(store),
50            model_config,
51        ));
52    }
53    Ok(LlmResearchEngine::new(Box::new(store)))
54}