use crate::session::Message;
const DEFAULT_SYSTEM_PROMPT: &str = r#"You are ZeptoClaw, an ultra-lightweight personal AI assistant.
You have access to tools to help accomplish tasks. Use them when needed.
Be concise but helpful. Focus on completing the user's request efficiently."#;
pub struct ContextBuilder {
system_prompt: String,
skills_prompt: Option<String>,
}
impl ContextBuilder {
pub fn new() -> Self {
Self {
system_prompt: DEFAULT_SYSTEM_PROMPT.to_string(),
skills_prompt: None,
}
}
pub fn with_system_prompt(mut self, prompt: &str) -> Self {
self.system_prompt = prompt.to_string();
self
}
pub fn with_skills(mut self, skills_content: &str) -> Self {
self.skills_prompt = Some(skills_content.to_string());
self
}
pub fn build_system_message(&self) -> Message {
let mut content = self.system_prompt.clone();
if let Some(ref skills) = self.skills_prompt {
content.push_str("\n\n## Available Skills\n\n");
content.push_str(skills);
}
Message::system(&content)
}
pub fn build_messages(&self, history: Vec<Message>, user_input: &str) -> Vec<Message> {
let mut messages = vec![self.build_system_message()];
messages.extend(history);
if !user_input.is_empty() {
messages.push(Message::user(user_input));
}
messages
}
pub fn system_prompt(&self) -> &str {
&self.system_prompt
}
pub fn has_skills(&self) -> bool {
self.skills_prompt.is_some()
}
}
impl Default for ContextBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::session::Role;
#[test]
fn test_context_builder_new() {
let builder = ContextBuilder::new();
assert!(builder.system_prompt().contains("ZeptoClaw"));
assert!(!builder.has_skills());
}
#[test]
fn test_context_builder_default() {
let builder = ContextBuilder::default();
assert!(builder.system_prompt().contains("ZeptoClaw"));
}
#[test]
fn test_context_builder_custom_system_prompt() {
let builder = ContextBuilder::new().with_system_prompt("Custom prompt here");
assert_eq!(builder.system_prompt(), "Custom prompt here");
}
#[test]
fn test_context_builder_with_skills() {
let builder = ContextBuilder::new().with_skills("- /test: Test skill");
assert!(builder.has_skills());
let system = builder.build_system_message();
assert!(system.content.contains("Available Skills"));
assert!(system.content.contains("/test"));
}
#[test]
fn test_build_system_message() {
let builder = ContextBuilder::new();
let system = builder.build_system_message();
assert_eq!(system.role, Role::System);
assert!(system.content.contains("ZeptoClaw"));
}
#[test]
fn test_build_messages_empty_input() {
let builder = ContextBuilder::new();
let messages = builder.build_messages(vec![], "");
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].role, Role::System);
}
#[test]
fn test_build_messages_with_input() {
let builder = ContextBuilder::new();
let messages = builder.build_messages(vec![], "Hello");
assert_eq!(messages.len(), 2);
assert_eq!(messages[0].role, Role::System);
assert_eq!(messages[1].role, Role::User);
assert_eq!(messages[1].content, "Hello");
}
#[test]
fn test_build_messages_with_history() {
let builder = ContextBuilder::new();
let history = vec![
Message::user("Previous message"),
Message::assistant("Previous response"),
];
let messages = builder.build_messages(history, "New message");
assert_eq!(messages.len(), 4);
assert_eq!(messages[0].role, Role::System);
assert_eq!(messages[1].role, Role::User);
assert_eq!(messages[1].content, "Previous message");
assert_eq!(messages[2].role, Role::Assistant);
assert_eq!(messages[3].role, Role::User);
assert_eq!(messages[3].content, "New message");
}
#[test]
fn test_build_messages_preserves_history_order() {
let builder = ContextBuilder::new();
let history = vec![
Message::user("First"),
Message::assistant("Second"),
Message::user("Third"),
Message::assistant("Fourth"),
];
let messages = builder.build_messages(history, "");
assert_eq!(messages.len(), 5);
assert_eq!(messages[1].content, "First");
assert_eq!(messages[2].content, "Second");
assert_eq!(messages[3].content, "Third");
assert_eq!(messages[4].content, "Fourth");
}
#[test]
fn test_context_builder_chaining() {
let builder = ContextBuilder::new()
.with_system_prompt("Custom prompt")
.with_skills("- /skill1: Do something");
let system = builder.build_system_message();
assert!(system.content.contains("Custom prompt"));
assert!(system.content.contains("/skill1"));
}
#[test]
fn test_build_messages_with_tool_calls_in_history() {
use crate::session::ToolCall;
let builder = ContextBuilder::new();
let history = vec![
Message::user("Search for rust"),
Message::assistant_with_tools(
"Let me search for that.",
vec![ToolCall::new("call_1", "search", r#"{"q": "rust"}"#)],
),
Message::tool_result("call_1", "Found 100 results"),
Message::assistant("I found 100 results about Rust."),
];
let messages = builder.build_messages(history, "Thanks!");
assert_eq!(messages.len(), 6);
assert!(messages[2].has_tool_calls());
assert!(messages[3].is_tool_result());
}
}