use agent_diva_core::bus::InboundMessage;
use agent_diva_core::session::Session;
use agent_diva_providers::Message;
use agent_diva_tooling::ToolRegistry;
use std::path::PathBuf;
#[derive(Clone, Debug)]
pub struct NanoSoulSettings {
pub enabled: bool,
pub max_chars: usize,
pub bootstrap_once: bool,
}
impl Default for NanoSoulSettings {
fn default() -> Self {
Self {
enabled: true,
max_chars: 4000,
bootstrap_once: true,
}
}
}
pub struct NanoContextBuilder {
workspace: PathBuf,
soul_settings: NanoSoulSettings,
soul_content: Option<String>,
system_prompt: Option<String>,
}
impl NanoContextBuilder {
pub fn new(workspace: PathBuf) -> Self {
Self {
workspace,
soul_settings: NanoSoulSettings::default(),
soul_content: None,
system_prompt: None,
}
}
pub fn with_soul_settings(mut self, settings: NanoSoulSettings) -> Self {
self.soul_settings = settings;
self
}
pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
self.system_prompt = Some(prompt.into());
self
}
pub fn load_soul(&mut self) -> Result<(), Box<dyn std::error::Error>> {
if !self.soul_settings.enabled {
return Ok(());
}
let soul_path = self.workspace.join("SOUL.md");
if soul_path.exists() {
let content = std::fs::read_to_string(&soul_path)?;
let truncated = if content.len() > self.soul_settings.max_chars {
content.chars().take(self.soul_settings.max_chars).collect()
} else {
content
};
self.soul_content = Some(truncated);
}
Ok(())
}
pub fn build_messages(
&self,
msg: &InboundMessage,
session: &Session,
tools: &ToolRegistry,
memory_window: usize,
) -> Result<Vec<Message>, Box<dyn std::error::Error>> {
let mut messages = Vec::new();
let system = self.build_system_message(tools)?;
messages.push(Message::system(system));
let history = session.get_history(memory_window);
for chat_msg in history.iter() {
match chat_msg.role.as_str() {
"user" => {
messages.push(Message::user(&chat_msg.content));
}
"assistant" => {
let content = if let Some(ref reasoning) = chat_msg.reasoning_content {
if !reasoning.is_empty() {
format!("{}\n\n{}", reasoning, chat_msg.content)
} else {
chat_msg.content.clone()
}
} else {
chat_msg.content.clone()
};
let mut msg = Message::assistant(content);
if let Some(ref tool_calls) = chat_msg.tool_calls {
msg.tool_calls = Some(
tool_calls.iter()
.filter_map(|tc| {
serde_json::from_value(tc.clone()).ok()
})
.collect()
);
}
messages.push(msg);
}
"tool" => {
if let Some(ref tool_call_id) = chat_msg.tool_call_id {
messages.push(Message::tool(&chat_msg.content, tool_call_id));
}
}
_ => {}
}
}
messages.push(Message::user(&msg.content));
Ok(messages)
}
fn build_system_message(&self, tools: &ToolRegistry) -> Result<String, Box<dyn std::error::Error>> {
let mut system = String::new();
if let Some(ref prompt) = self.system_prompt {
system.push_str(prompt);
system.push_str("\n\n");
} else {
system.push_str("You are a helpful AI assistant.\n\n");
}
if let Some(ref soul) = self.soul_content {
system.push_str("--- Identity Context ---\n");
system.push_str(soul);
system.push_str("\n--- End Identity Context ---\n\n");
}
if !tools.is_empty() {
system.push_str("You have access to the following tools:\n");
for name in tools.tool_names() {
if let Some(tool) = tools.get(&name) {
system.push_str(&format!("- {}: {}\n", tool.name(), tool.description()));
}
}
system.push_str("\nWhen you need to use a tool, the model will generate a tool call with the tool name and arguments.\n");
}
Ok(system)
}
}