telos_agent/agent/context/
skill_injection.rs1use std::sync::Arc;
2use std::{
3 collections::hash_map::DefaultHasher,
4 hash::{Hash, Hasher},
5};
6
7use crate::knowledge::skills::SkillRegistry;
8use crate::model::message::SystemReminder;
9
10pub struct SkillInjector {
11 registry: Arc<SkillRegistry>,
12 max_skills: usize,
13}
14
15pub struct SkillInjection {
16 pub reminder: SystemReminder,
17 pub fingerprint: u64,
18}
19
20impl SkillInjector {
21 pub fn new(registry: Arc<SkillRegistry>) -> Self {
22 Self { registry, max_skills: 5 }
23 }
24
25 pub fn with_max_skills(mut self, max: usize) -> Self {
26 self.max_skills = max;
27 self
28 }
29
30 pub fn inject_for_query(&self, query: &str) -> Option<SkillInjection> {
31 let skills = self.registry.retrieve(query, self.max_skills);
32 if skills.is_empty() {
33 return None;
34 }
35
36 let mut lines = vec![
37 "## Recommended Skills".to_string(),
38 "Use the Skill tool only with the skills listed below; do not guess skill names."
39 .to_string(),
40 String::new(),
41 ];
42 for skill in skills {
43 let when = skill
44 .when_to_use
45 .as_ref()
46 .map(|when| format!(" Use when: {when}"))
47 .unwrap_or_default();
48 lines.push(format!("- **{}**: {}{}", skill.name, skill.description, when));
49 }
50
51 let content = lines.join("\n");
52 let mut hasher = DefaultHasher::new();
53 content.hash(&mut hasher);
54 Some(SkillInjection {
55 reminder: SystemReminder::SkillDiscovery { content },
56 fingerprint: hasher.finish(),
57 })
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use crate::knowledge::skills::{Skill, SkillRegistry, SkillSource};
65
66 fn test_skill(name: &str, description: &str, body: &str) -> Skill {
67 Skill {
68 name: name.into(),
69 description: description.into(),
70 when_to_use: Some("When the task matches".into()),
71 prompt: "Prompt".into(),
72 arguments: vec![],
73 body: body.into(),
74 source: SkillSource::Bundled,
75 }
76 }
77
78 #[test]
79 fn inject_for_query_returns_relevant_skills() {
80 let mut registry = SkillRegistry::new();
81 registry.register(test_skill("rust-fix", "Fix Rust compiler errors", "cargo check"));
82 registry.register(test_skill("react-ui", "Adjust React UI layout", "jsx css layout"));
83 let injector = SkillInjector::new(Arc::new(registry));
84
85 let injection = injector.inject_for_query("fix rust compile error").expect("injection");
86 let rendered = injection.reminder.render();
87 assert!(rendered.contains("Recommended Skills"));
88 assert!(rendered.contains("rust-fix"));
89 assert!(!rendered.contains("react-ui"));
90 }
91}