use rig::agent::{Agent, AgentBuilder};
use rig::completion::CompletionModel;
use std::sync::Arc;
use crate::agent::model_family::resolve_family;
use crate::agent::prompt::PROJECT_SKILLS_PREAMBLE;
use crate::agent::tools::ToolCache;
use crate::cli::Cli;
use crate::config::Config;
use crate::context::ContextFiles;
use super::{
append_memory_to_preamble, append_mode_reminder, assemble_base_preamble,
model_steering_fragment,
};
pub async fn build_agent_inner<M: CompletionModel + 'static>(
model: M,
cli: &Cli,
cfg: &Config,
context: &ContextFiles,
active_provider: &str,
active_model: &str,
) -> (
Agent<M>,
ToolCache,
// dirge-7tvq: surface the constructed MemoryProvider so the
// caller (provider::build_agent) can attach it to AnyAgent for
// session-lifecycle hook dispatch. `None` when load failed.
Option<Arc<dyn crate::extras::memory_provider::MemoryProvider>>,
) {
let mut preamble = assemble_base_preamble();
if let Some(agents) = &context.agents {
preamble.push_str("\n\n");
preamble.push_str(agents);
}
if let Some(prompt) = &context.current_prompt {
preamble.push_str("\n\n---\n\n");
preamble.push_str(prompt);
}
if let Ok(cwd) = std::env::current_dir() {
let cwd_str = cwd.display();
preamble.push_str(&format!("\n\nCurrent working directory: {}", cwd_str));
}
preamble.push_str(&format!("\nOS: {}", std::env::consts::OS));
if let Ok(shell) = std::env::var("SHELL") {
preamble.push_str(&format!("\nShell: {}", shell));
}
let git_branch_fut = tokio::task::spawn_blocking(|| {
std::process::Command::new("git")
.args(["rev-parse", "--abbrev-ref", "HEAD"])
.output()
.ok()
.and_then(|output| {
if output.status.success() {
let branch = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !branch.is_empty() {
Some(branch)
} else {
None
}
} else {
None
}
})
});
let git_branch =
match tokio::time::timeout(std::time::Duration::from_secs(2), git_branch_fut).await {
Ok(Ok(branch)) => branch,
_ => None,
};
if let Some(branch) = git_branch {
preamble.push_str(&format!("\nGit branch: {}", branch));
}
let paths = std::env::current_dir()
.map(|c| crate::extras::dirge_paths::ProjectPaths::new(&c))
.unwrap_or_else(|_| {
crate::extras::dirge_paths::ProjectPaths::new(std::path::Path::new("."))
});
let paths_for_mem = paths.clone();
let memory_load_result: Result<crate::extras::memory_db::SqliteMemoryStore, String> =
tokio::task::spawn_blocking(move || {
crate::extras::memory_db::SqliteMemoryStore::load(&paths_for_mem)
})
.await
.unwrap_or_else(|_| Err("spawn_blocking join failed".to_string()));
let memory_store: Option<Arc<dyn crate::extras::memory_provider::MemoryProvider>> =
match memory_load_result {
Ok(store) => {
let provider: Arc<dyn crate::extras::memory_provider::MemoryProvider> =
Arc::new(store);
append_memory_to_preamble(&mut preamble, &provider);
Some(provider)
}
Err(_) => None,
};
if let Ok(global) =
tokio::task::spawn_blocking(crate::extras::memory_db::SqliteMemoryStore::load_global)
.await
.unwrap_or_else(|_| Err("spawn_blocking join failed".to_string()))
{
let global_provider: Arc<dyn crate::extras::memory_provider::MemoryProvider> =
Arc::new(global);
crate::agent::builder::preamble::append_global_memory_to_preamble(
&mut preamble,
&global_provider,
);
}
let paths_for_spec = paths.clone();
if let Ok(block) = tokio::task::spawn_blocking(move || {
crate::extras::spec_db::SpecStore::open(&paths_for_spec)
.map(|s| s.format_active_change_for_prompt())
})
.await
.unwrap_or_else(|_| Err("spawn_blocking join failed".to_string()))
&& !block.trim().is_empty()
{
preamble.push_str(&block);
}
let skill_manager = crate::extras::skills::manager::SkillManager::new(&paths);
let mut usage_store = crate::extras::skills::usage::UsageStore::load(&paths).ok();
match skill_manager.list() {
Ok(names) if !names.is_empty() => {
let mut skill_lines = Vec::new();
for name in &names {
if let Ok(content) = skill_manager.read_content(name)
&& let Some(spec) =
crate::extras::skills::format::parse_skill_spec(&content, name)
{
let desc = if spec.description.is_empty() {
"(no description)".to_string()
} else {
spec.description.clone()
};
skill_lines.push(format!(" - **{name}**: {desc}"));
}
}
if !skill_lines.is_empty() {
preamble.push_str(PROJECT_SKILLS_PREAMBLE);
for line in &skill_lines {
preamble.push_str(line);
preamble.push('\n');
}
if let Some(ref mut u) = usage_store {
for name in &names {
u.record_view(name);
}
}
}
}
_ => {}
}
if let Some(prompt_name) = &context.current_prompt_name {
let plan_exists = std::env::current_dir()
.unwrap_or_else(|_| ".".into())
.join("PLAN.md")
.exists();
append_mode_reminder(&mut preamble, prompt_name, plan_exists);
}
let family = resolve_family(active_provider, active_model);
if let Some(fragment) = model_steering_fragment(family) {
preamble.push_str("\n\n---\n\n");
preamble.push_str(fragment);
}
let mut builder = AgentBuilder::new(model).preamble(&preamble);
let max_tokens = cli.resolve_max_tokens(cfg);
builder = builder.max_tokens(max_tokens);
let max_turns = cli.resolve_max_agent_turns(cfg);
builder = builder.default_max_turns(max_turns);
if let Some(temp) = cli.resolve_temperature(cfg) {
let clamped = temp.clamp(0.0, 2.0);
if (clamped - temp).abs() > f64::EPSILON {
static WARNED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
if WARNED.set(()).is_ok() {
eprintln!(
"warning: temperature {} clamped to {} (valid range 0.0..=2.0)",
temp, clamped,
);
}
}
builder = builder.temperature(clamped);
}
crate::agent::tools::output_relay::set_thresholds(
cfg.tools
.as_ref()
.and_then(|t| t.bash_output_inline_max_bytes),
cfg.tools
.as_ref()
.and_then(|t| t.webfetch_output_inline_max_bytes),
cfg.tools
.as_ref()
.and_then(|t| t.task_output_inline_max_bytes),
);
(builder.build(), ToolCache::new(), memory_store)
}