use async_trait::async_trait;
use runtime_extension_api::{
ContextEvent, ContextHook, ContextPatch, ContextResource, ContextSensitivity, ContextSlot,
ExtensionContext, ExtensionError,
};
use sha2::{Digest, Sha256};
use crate::DefinitionBuildError;
pub struct StaticSkill {
id: String,
content: String,
version: String,
required: bool,
sensitivity: ContextSensitivity,
digest: String,
}
impl StaticSkill {
pub fn new(
id: impl Into<String>,
content: impl Into<String>,
) -> Result<Self, DefinitionBuildError> {
let id = id.into();
let content = content.into();
if id.trim().is_empty() || content.trim().is_empty() {
return Err(DefinitionBuildError::InvalidSkill(
"id and content must not be empty".into(),
));
}
let digest = format!("sha256:{:x}", Sha256::digest(content.as_bytes()));
Ok(Self {
id,
content,
version: "1".into(),
required: false,
sensitivity: ContextSensitivity::Internal,
digest,
})
}
pub fn version(mut self, value: impl Into<String>) -> Self {
self.version = value.into();
self
}
pub fn required(mut self, value: bool) -> Self {
self.required = value;
self
}
pub fn sensitivity(mut self, value: ContextSensitivity) -> Self {
self.sensitivity = value;
self
}
}
#[async_trait]
impl ContextHook for StaticSkill {
fn name(&self) -> &str {
&self.id
}
fn definition_fingerprint(&self) -> String {
format!(
"static-skill:{}:{}:{}:{:?}",
self.version, self.digest, self.required, self.sensitivity
)
}
async fn apply(
&self,
_: &ExtensionContext,
_: &ContextEvent,
_: &runtime_extension_api::ContextFrame,
) -> Result<ContextPatch, ExtensionError> {
Ok(ContextPatch {
resources: vec![ContextResource {
id: self.id.clone(),
slot: ContextSlot::Skills,
source: format!("definition:skill:{}", self.id),
content: self.content.clone(),
required: self.required,
sensitivity: self.sensitivity,
version: self.version.clone(),
digest: self.digest.clone(),
estimated_tokens: self.content.len().div_ceil(4) as u64,
}],
..ContextPatch::default()
})
}
}