1use std::path::PathBuf;
4
5use crate::tool::Tool;
6
7const DEFAULT_PROMPT: &str = include_str!("../prompts/default.md");
10
11pub struct PromptBuilder {
12 custom_prompt: Option<String>,
13}
14
15impl PromptBuilder {
16 pub fn new() -> Self {
17 let custom_path = Self::custom_prompt_path();
19 let custom_prompt = if let Some(path) = custom_path {
20 std::fs::read_to_string(&path).ok()
21 } else {
22 None
23 };
24
25 Self { custom_prompt }
26 }
27
28 pub fn build_system_prompt(
32 &self,
33 tools: &[&dyn Tool],
34 skills: &[(&str, &str)],
35 working_dir: &std::path::Path,
36 ) -> String {
37 let tools_section = Self::build_tools_section(tools);
38 let skills_section = Self::build_skills_section(skills);
39 let os = std::env::consts::OS;
40 let cwd = working_dir.display().to_string();
41 let date = chrono_date();
42
43 if let Some(custom) = &self.custom_prompt {
44 custom
46 .replace("{os}", os)
47 .replace("{cwd}", &cwd)
48 .replace("{date}", &date)
49 .replace("{tools_section}", &tools_section)
50 .replace("{skills_section}", &skills_section)
51 } else {
52 DEFAULT_PROMPT
53 .replace("{os}", os)
54 .replace("{cwd}", &cwd)
55 .replace("{date}", &date)
56 .replace("{tools_section}", &tools_section)
57 .replace("{skills_section}", &skills_section)
58 }
59 }
60
61 fn build_tools_section(tools: &[&dyn Tool]) -> String {
63 if tools.is_empty() {
64 return "(no available tools)".to_string();
65 }
66
67 let mut section = String::new();
68 for tool in tools {
69 section.push_str(&format!(
70 "- **{}**: {}{}\n",
71 tool.name(),
72 tool.description(),
73 if tool.requires_confirmation() {
74 " (requires user confirmation)"
75 } else {
76 ""
77 }
78 ));
79 }
80 section
81 }
82
83 fn build_skills_section(skills: &[(&str, &str)]) -> String {
85 if skills.is_empty() {
86 return "(no available skills)".to_string();
87 }
88
89 let mut section = String::new();
90 for (name, description) in skills {
91 section.push_str(&format!("- **{}**: {}\n", name, description));
92 }
93 section
94 }
95
96 fn custom_prompt_path() -> Option<PathBuf> {
97 dirs::home_dir().map(|home| home.join(".robit/prompts/system.txt"))
98 }
99}
100
101impl Default for PromptBuilder {
102 fn default() -> Self {
103 Self::new()
104 }
105}
106
107fn chrono_date() -> String {
109 use std::time::SystemTime;
112 let now = SystemTime::now();
113 let duration = now
114 .duration_since(SystemTime::UNIX_EPOCH)
115 .unwrap_or_default();
116 let secs = duration.as_secs();
117
118 let days = secs / 86400;
120 let (year, month, day) = days_to_date(days);
121 format!("{:04}-{:02}-{:02}", year, month, day)
122}
123
124fn days_to_date(days: u64) -> (u64, u64, u64) {
126 let z = days + 719468;
128 let era = z / 146097;
129 let doe = z - era * 146097;
130 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
131 let y = yoe + era * 400;
132 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
133 let mp = (5 * doy + 2) / 153;
134 let d = doy - (153 * mp + 2) / 5 + 1;
135 let m = if mp < 10 { mp + 3 } else { mp - 9 };
136 let y = if m <= 2 { y + 1 } else { y };
137 (y, m, d)
138}