use clap::Parser;
use futures_util::StreamExt;
use microagents_core::{
agent::{MicroAgentBuilder, SupportedProvider},
types::{Agent, AgentError, ToolExecutionContext},
};
use microagents_events::types::AgentEvent;
use microagents_storage::types::AgentStorageChoice;
use std::{str::FromStr, sync::Arc};
use crate::init_env::initialize_environment;
mod init_env;
mod processing;
mod search;
mod tools;
mod tui;
#[derive(Parser, Debug)]
#[command(version = "0.1.0")]
#[command(name = "microag")]
#[command(about, long_about = None)]
struct Args {
#[arg(long, default_value = None)]
model: Option<String>,
#[arg(long)]
skill: Vec<String>,
#[arg(long, default_value = None)]
provider: Option<String>,
#[arg(long, default_value = None)]
storage: Option<String>,
#[arg(long = "session-id", value_name = "ID")]
session_id: Option<String>,
#[arg(long, short, default_value = None)]
prompt: Option<String>,
#[arg(long, short, default_value_t = false)]
verbose: bool,
}
fn storage_choice(storage: Option<String>) -> AgentStorageChoice {
match storage {
Some(s) => match s.as_str() {
"jsonl" => AgentStorageChoice::Jsonl,
"sqlite" => AgentStorageChoice::Sqlite,
_ => AgentStorageChoice::Jsonl,
},
None => AgentStorageChoice::Jsonl,
}
}
async fn build_storage(
storage: Option<String>,
) -> Result<Box<dyn microagents_storage::types::AgentStorage>, AgentError> {
let st = storage_choice(storage);
let builder = MicroAgentBuilder::<()>::new(ToolExecutionContext::new(()));
let builder = builder
.storage(st)
.await
.map_err(|e| AgentError::ClientInitFailed(e.to_string()))?;
Ok(builder.storage)
}
async fn build_agent(
provider: Option<String>,
model: Option<String>,
storage: Option<String>,
skills: Vec<String>,
) -> Result<microagents_core::agent::MicroAgent<()>, AgentError> {
let st = storage_choice(storage);
let prov = SupportedProvider::from_str(&provider.clone().unwrap_or("openrouter".to_string()))
.map_err(|e| AgentError::ClientInitFailed(e.to_string()))?;
let base_builder = MicroAgentBuilder::<()>::new(ToolExecutionContext::new(()))
.model(model.unwrap_or(prov.default_model().to_string()))
.provider(provider.unwrap_or("openrouter".into()))?
.storage(st)
.await?
.add_tool(Arc::new(tools::WriteTool))?
.add_tool(Arc::new(tools::EditTool))?
.add_tool(Arc::new(tools::ShellExecuteTool))?
.add_tool(Arc::new(tools::SearchTool))?
.add_tool(Arc::new(tools::ReadTool))?;
let base_builder = if skills.is_empty() {
base_builder.find_skills()?
} else {
let mut builder = base_builder;
for skill in skills {
builder = builder.add_skill(skill)?;
}
builder
};
Ok(base_builder.build()?)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
if let Some(p) = args.prompt {
initialize_environment(args.verbose).await?;
let agent = build_agent(args.provider, args.model, args.storage, args.skill).await?;
let mut stream = agent
.run(p, args.session_id)
.await
.map_err(|e| e.to_string())?;
while let Some(ev) = stream.next().await {
match ev {
Ok(e) => {
let jsonrpc = serde_json::to_string(&e.to_jsonrpc())?;
println!("{jsonrpc}");
}
Err(err) => {
let jsonrpc = serde_json::to_string(&serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"error": { "code": -32603, "message": "Internal Server Error", "data": &err.to_string() }
}))?;
println!("{jsonrpc}");
return Ok(());
}
}
}
return Ok(());
}
initialize_environment(true).await?;
println!("Launching TUI...");
let initial_session = args.session_id.clone();
let load_history_storage = args.storage.clone();
tui::run_with_session(
initial_session,
move |prompt, session_id| {
let prov_c = args.provider.clone();
let model_c = args.model.clone();
let skill_c = args.skill.clone();
let storage_c = args.storage.clone();
async move {
let agent = build_agent(prov_c, model_c, storage_c, skill_c).await?;
agent.run(prompt, session_id).await
}
},
move |session_id| {
let storage_c = load_history_storage.clone();
async move {
let storage = build_storage(storage_c).await?;
storage.get_session(&session_id).await.map_err(|e| {
microagents_core::types::AgentError::SessionLoadError(e.to_string())
})
}
},
)
.await?;
Ok(())
}