telos_agent/agent/prompt/
builtins.rs1use async_trait::async_trait;
2use std::sync::Arc;
3
4use crate::agent::prompt::{PromptSection, PromptStability};
5use crate::config::TaskPath;
6use crate::tools::api::ToolRegistry;
7
8mod context;
9
10pub use context::{
11 CwdSection, DateSection, GitStatusSection, McpSection, MemorySection, ProfileSection,
12 SkillsSection,
13};
14
15macro_rules! prompt_template {
18 ($name:expr) => {
19 include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../prompt/", $name))
20 };
21}
22
23static IDENTITY_TEMPLATE: &str = prompt_template!("identity.md");
24static TONE_STYLE_TEMPLATE: &str = prompt_template!("tone_style.md");
25static TASK_GUIDANCE_TEMPLATE: &str = prompt_template!("task_guidance.md");
26static SAFETY_TEMPLATE: &str = prompt_template!("safety.md");
27static PATH_FAST_TEMPLATE: &str = prompt_template!("path_fast.md");
28static PATH_STANDARD_TEMPLATE: &str = prompt_template!("path_standard.md");
29static PATH_HEAVY_TEMPLATE: &str = prompt_template!("path_heavy.md");
30static TOOL_USAGE_TEMPLATE: &str = prompt_template!("tool_usage.md");
31
32pub struct IdentitySection {
41 base: Option<String>,
42}
43
44impl IdentitySection {
45 pub fn new(base_prompt: Option<String>) -> Self {
46 Self { base: base_prompt }
47 }
48}
49
50#[async_trait]
51impl PromptSection for IdentitySection {
52 fn name(&self) -> &str {
53 "identity"
54 }
55 fn stability(&self) -> PromptStability {
56 PromptStability::Static
57 }
58
59 async fn render(&self, _ctx: &()) -> String {
60 let base_block = match &self.base {
61 Some(base) => format!("\nAdditional instructions from the user:\n{base}"),
62 None => String::new(),
63 };
64 IDENTITY_TEMPLATE.replace("{{BASE}}", &base_block)
65 }
66}
67
68pub struct ToneStyleSection;
72
73#[async_trait]
74impl PromptSection for ToneStyleSection {
75 fn name(&self) -> &str {
76 "tone_style"
77 }
78 fn stability(&self) -> PromptStability {
79 PromptStability::Static
80 }
81
82 async fn render(&self, _ctx: &()) -> String {
83 TONE_STYLE_TEMPLATE.to_string()
84 }
85}
86
87pub struct TaskGuidanceSection;
91
92#[async_trait]
93impl PromptSection for TaskGuidanceSection {
94 fn name(&self) -> &str {
95 "task_guidance"
96 }
97 fn stability(&self) -> PromptStability {
98 PromptStability::Static
99 }
100
101 async fn render(&self, _ctx: &()) -> String {
102 TASK_GUIDANCE_TEMPLATE.to_string()
103 }
104}
105
106pub struct SafetySection;
110
111#[async_trait]
112impl PromptSection for SafetySection {
113 fn name(&self) -> &str {
114 "safety"
115 }
116 fn stability(&self) -> PromptStability {
117 PromptStability::Static
118 }
119
120 async fn render(&self, _ctx: &()) -> String {
121 SAFETY_TEMPLATE.to_string()
122 }
123}
124
125pub struct PathSection {
131 path: TaskPath,
132}
133
134impl PathSection {
135 pub fn new(path: TaskPath) -> Self {
136 Self { path }
137 }
138}
139
140#[async_trait]
141impl PromptSection for PathSection {
142 fn name(&self) -> &str {
143 "task_path"
144 }
145 fn stability(&self) -> PromptStability {
146 PromptStability::Static
147 }
148
149 async fn render(&self, _ctx: &()) -> String {
150 match self.path {
151 TaskPath::Fast => PATH_FAST_TEMPLATE.to_string(),
152 TaskPath::Standard => PATH_STANDARD_TEMPLATE.to_string(),
153 TaskPath::Heavy => PATH_HEAVY_TEMPLATE.to_string(),
154 }
155 }
156}
157
158pub struct ToolUsageSection;
162
163pub struct ShellAwareToolUsageSection {
165 tools: Arc<ToolRegistry>,
166}
167
168impl ShellAwareToolUsageSection {
169 pub fn new(tools: Arc<ToolRegistry>) -> Self {
170 Self { tools }
171 }
172
173 fn default_shell(&self) -> &'static str {
174 if self.tools.definitions().iter().any(|definition| definition.name == "PowerShell") {
175 "PowerShell"
176 } else {
177 "Bash"
178 }
179 }
180}
181
182fn render_tool_usage(shell_tool: &str) -> String {
183 TOOL_USAGE_TEMPLATE.replace("{{SHELL}}", shell_tool)
184}
185
186#[async_trait]
187impl PromptSection for ToolUsageSection {
188 fn name(&self) -> &str {
189 "tool_usage"
190 }
191 fn stability(&self) -> PromptStability {
192 PromptStability::Static
193 }
194
195 async fn render(&self, _ctx: &()) -> String {
196 render_tool_usage("Bash")
197 }
198}
199
200#[async_trait]
201impl PromptSection for ShellAwareToolUsageSection {
202 fn name(&self) -> &str {
203 "tool_usage"
204 }
205 fn stability(&self) -> PromptStability {
206 PromptStability::Static
207 }
208
209 async fn render(&self, _ctx: &()) -> String {
210 render_tool_usage(self.default_shell())
211 }
212}
213
214pub struct ToolsSection {
217 tools: Arc<ToolRegistry>,
218}
219
220impl ToolsSection {
221 pub fn new(tools: Arc<ToolRegistry>) -> Self {
222 Self { tools }
223 }
224}
225
226#[async_trait]
227impl PromptSection for ToolsSection {
228 fn name(&self) -> &str {
229 "tools"
230 }
231 fn stability(&self) -> PromptStability {
232 PromptStability::Static
233 }
234
235 async fn render(&self, _ctx: &()) -> String {
236 let defs = self.tools.definitions();
237 if defs.is_empty() {
238 return String::new();
239 }
240 let mut lines = vec!["## Available Tools".to_string()];
241 for def in &defs {
242 lines.push(format!("- **{}**: {}", def.name, def.description));
243 }
244 lines.join("\n")
245 }
246}
247
248pub struct ToolPromptsSection {
252 tools: Arc<ToolRegistry>,
253}
254
255impl ToolPromptsSection {
256 pub fn new(tools: Arc<ToolRegistry>) -> Self {
257 Self { tools }
258 }
259}
260
261#[async_trait]
262impl PromptSection for ToolPromptsSection {
263 fn name(&self) -> &str {
264 "tool_prompts"
265 }
266 fn stability(&self) -> PromptStability {
267 PromptStability::Static
268 }
269
270 async fn render(&self, _ctx: &()) -> String {
271 let mut entries: Vec<(String, String)> = self
272 .tools
273 .iter()
274 .filter_map(|(name, tool)| {
275 tool.prompt_text().map(|text| (name.clone(), text.to_string()))
276 })
277 .collect();
278 if entries.is_empty() {
279 return String::new();
280 }
281 entries.sort_by(|a, b| a.0.cmp(&b.0));
282 let mut lines = vec!["## Tool-specific guidance".to_string()];
283 for (name, text) in entries {
284 lines.push(format!("### {name}"));
285 for line in text.lines() {
286 lines.push(line.to_string());
287 }
288 }
289 lines.join("\n")
290 }
291}