Skip to main content

telos_agent/agent/context/
skill_injection.rs

1use 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}