use std::path::PathBuf;
use std::process::ExitCode;
use anyhow::{Context, Result};
use clap::{Parser, ValueEnum};
use dci_tool::{CorpusRoot, DciAgent, Limits};
use rig_core::client::{CompletionClient, ProviderClient};
use rig_core::completion::CompletionModel;
use rig_core::providers::{anthropic, ollama, openai};
#[derive(Debug, Clone, Copy, ValueEnum)]
enum Provider {
Openai,
Anthropic,
Ollama,
}
#[derive(Debug, Parser)]
#[command(name = "dci", version, about)]
struct Cli {
#[arg(short, long, default_value = ".")]
corpus: PathBuf,
question: String,
#[arg(short, long, value_enum, default_value_t = Provider::Openai)]
provider: Provider,
#[arg(short, long)]
model: Option<String>,
#[arg(long, default_value_t = dci_tool::DEFAULT_MAX_TURNS)]
max_turns: usize,
#[arg(long)]
no_gitignore: bool,
}
#[tokio::main]
async fn main() -> ExitCode {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
)
.with_writer(std::io::stderr)
.init();
match run(Cli::parse()).await {
Ok(answer) => {
println!("{answer}");
ExitCode::SUCCESS
}
Err(err) => {
eprintln!("error: {err:#}");
ExitCode::FAILURE
}
}
}
async fn run(cli: Cli) -> Result<String> {
let limits = Limits {
respect_gitignore: !cli.no_gitignore,
..Limits::default()
};
let corpus = CorpusRoot::with_limits(&cli.corpus, limits)
.with_context(|| format!("opening corpus at {}", cli.corpus.display()))?;
match cli.provider {
Provider::Openai => {
let client = openai::Client::from_env().context("initializing OpenAI client")?;
let model_id = cli.model.as_deref().unwrap_or(openai::GPT_4O).to_string();
let model = client.completion_model(&model_id);
investigate(model, corpus, &cli.question, cli.max_turns, None, &model_id).await
}
Provider::Anthropic => {
let client = anthropic::Client::from_env().context("initializing Anthropic client")?;
let model_id = cli
.model
.as_deref()
.unwrap_or(anthropic::completion::CLAUDE_SONNET_4_6)
.to_string();
let model = client.completion_model(&model_id);
investigate(
model,
corpus,
&cli.question,
cli.max_turns,
Some(4096),
&model_id,
)
.await
}
Provider::Ollama => {
let client = ollama::Client::from_env().context("initializing Ollama client")?;
let model_id = cli.model.as_deref().unwrap_or("llama3.1").to_string();
let model = client.completion_model(&model_id);
investigate(model, corpus, &cli.question, cli.max_turns, None, &model_id).await
}
}
}
async fn investigate<M: CompletionModel + 'static>(
model: M,
corpus: CorpusRoot,
question: &str,
max_turns: usize,
max_tokens: Option<u64>,
model_label: &str,
) -> Result<String> {
let mut builder = DciAgent::builder(model, corpus)
.max_turns(max_turns)
.model_label(model_label);
if let Some(max_tokens) = max_tokens {
builder = builder.max_tokens(max_tokens);
}
let agent = builder.build();
dci_tool::telemetry::with_session("cli".to_string(), || async {
agent
.investigate(question)
.await
.context("running investigation")
})
.await
}