Skip to main content

agent_runtime_definition_builder/
skill.rs

1use async_trait::async_trait;
2use runtime_extension_api::{
3    ContextEvent, ContextHook, ContextPatch, ContextResource, ContextSensitivity, ContextSlot,
4    ExtensionContext, ExtensionError,
5};
6use sha2::{Digest, Sha256};
7
8use crate::DefinitionBuildError;
9
10/// An immutable, always-active Skill backed by UTF-8 instructions.
11///
12/// Use `StaticSkill` when Skill content is known while the Definition is built.
13/// Selectable or execution-mutating Skills should implement `ContextHook`
14/// directly and keep their state keyed by `ExecutionId`.
15pub struct StaticSkill {
16    id: String,
17    content: String,
18    version: String,
19    required: bool,
20    sensitivity: ContextSensitivity,
21    digest: String,
22}
23
24impl StaticSkill {
25    pub fn new(
26        id: impl Into<String>,
27        content: impl Into<String>,
28    ) -> Result<Self, DefinitionBuildError> {
29        let id = id.into();
30        let content = content.into();
31        if id.trim().is_empty() || content.trim().is_empty() {
32            return Err(DefinitionBuildError::InvalidSkill(
33                "id and content must not be empty".into(),
34            ));
35        }
36        let digest = format!("sha256:{:x}", Sha256::digest(content.as_bytes()));
37        Ok(Self {
38            id,
39            content,
40            version: "1".into(),
41            required: false,
42            sensitivity: ContextSensitivity::Internal,
43            digest,
44        })
45    }
46
47    pub fn version(mut self, value: impl Into<String>) -> Self {
48        self.version = value.into();
49        self
50    }
51
52    pub fn required(mut self, value: bool) -> Self {
53        self.required = value;
54        self
55    }
56
57    pub fn sensitivity(mut self, value: ContextSensitivity) -> Self {
58        self.sensitivity = value;
59        self
60    }
61}
62
63#[async_trait]
64impl ContextHook for StaticSkill {
65    fn name(&self) -> &str {
66        &self.id
67    }
68
69    fn definition_fingerprint(&self) -> String {
70        format!(
71            "static-skill:{}:{}:{}:{:?}",
72            self.version, self.digest, self.required, self.sensitivity
73        )
74    }
75
76    async fn apply(
77        &self,
78        _: &ExtensionContext,
79        _: &ContextEvent,
80        _: &runtime_extension_api::ContextFrame,
81    ) -> Result<ContextPatch, ExtensionError> {
82        Ok(ContextPatch {
83            resources: vec![ContextResource {
84                id: self.id.clone(),
85                slot: ContextSlot::Skills,
86                source: format!("definition:skill:{}", self.id),
87                content: self.content.clone(),
88                required: self.required,
89                sensitivity: self.sensitivity,
90                version: self.version.clone(),
91                digest: self.digest.clone(),
92                estimated_tokens: self.content.len().div_ceil(4) as u64,
93            }],
94            ..ContextPatch::default()
95        })
96    }
97}