1use std::path::Path;
4
5use crate::datetime::current_date;
6use crate::tool::Tool;
7
8const DEFAULT_AGENT_PROMPT: &str = include_str!("../prompts/default.md");
11
12const SYSTEM_PROMPT: &str = include_str!("../prompts/system.md");
15
16pub struct PromptBuilder {
17 custom_prompt: Option<String>,
18}
19
20impl PromptBuilder {
21 pub fn new() -> Self {
22 Self::with_working_dir(None)
23 }
24
25 pub fn with_working_dir(working_dir: Option<&Path>) -> Self {
29 let local_prompt = working_dir.and_then(|cwd| {
31 let path = cwd.join(".robit/prompts/agent.md");
32 std::fs::read_to_string(&path).ok()
33 });
34
35 if local_prompt.is_some() {
36 return Self {
37 custom_prompt: local_prompt,
38 };
39 }
40
41 let global_prompt = dirs::home_dir().and_then(|home| {
43 let path = home.join(".robit/prompts/agent.md");
44 std::fs::read_to_string(&path).ok()
45 });
46
47 Self {
48 custom_prompt: global_prompt,
49 }
50 }
51
52 pub fn build_system_prompt(
61 &self,
62 tools: &[&dyn Tool],
63 skills: &[(&str, &str)],
64 working_dir: &std::path::Path,
65 ) -> String {
66 let tools_section = Self::build_tools_section(tools);
67 let skills_section = Self::build_skills_section(skills);
68 let os = std::env::consts::OS;
69 let cwd = working_dir.display().to_string();
70 let date = current_date();
71
72 let agent_prompt = self.custom_prompt.as_deref().unwrap_or(DEFAULT_AGENT_PROMPT);
74
75 let system_part = SYSTEM_PROMPT
77 .replace("{os}", os)
78 .replace("{cwd}", &cwd)
79 .replace("{date}", &date)
80 .replace("{tools_section}", &tools_section)
81 .replace("{skills_section}", &skills_section);
82
83 format!("{}\n\n{}", agent_prompt.trim(), system_part)
85 }
86
87 fn build_tools_section(tools: &[&dyn Tool]) -> String {
89 if tools.is_empty() {
90 return "(no available tools)".to_string();
91 }
92
93 let mut section = String::new();
94 for tool in tools {
95 section.push_str(&format!(
96 "- **{}**: {}{}\n",
97 tool.name(),
98 tool.description(),
99 if tool.requires_confirmation() {
100 " (requires user confirmation)"
101 } else {
102 ""
103 }
104 ));
105 }
106 section
107 }
108
109 fn build_skills_section(skills: &[(&str, &str)]) -> String {
111 if skills.is_empty() {
112 return "(no available skills)".to_string();
113 }
114
115 let mut section = String::new();
116 for (name, description) in skills {
117 section.push_str(&format!("- **{}**: {}\n", name, description));
118 }
119 section
120 }
121
122}
123
124impl Default for PromptBuilder {
125 fn default() -> Self {
126 Self::new()
127 }
128}