use std::collections::BTreeMap;
use std::sync::Arc;
use async_trait::async_trait;
use entelix_core::context::ExecutionContext;
use entelix_core::error::Result;
use entelix_core::skills::{LoadedSkill, Skill, SkillResource, SkillResourceContent};
#[derive(Clone, Debug)]
pub struct InMemorySkill {
name: String,
description: String,
version: Option<String>,
instructions: String,
resources: Arc<BTreeMap<String, Arc<dyn SkillResource>>>,
}
impl InMemorySkill {
#[must_use]
pub fn builder(name: impl Into<String>) -> InMemorySkillBuilder {
InMemorySkillBuilder {
name: name.into(),
description: String::new(),
version: None,
instructions: String::new(),
resources: BTreeMap::new(),
}
}
}
#[async_trait]
impl Skill for InMemorySkill {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
&self.description
}
fn version(&self) -> Option<&str> {
self.version.as_deref()
}
async fn load(&self, _ctx: &ExecutionContext) -> Result<LoadedSkill> {
Ok(LoadedSkill {
instructions: self.instructions.clone(),
resources: (*self.resources).clone(),
})
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use entelix_core::context::ExecutionContext;
use entelix_core::skills::Skill;
use super::*;
#[tokio::test]
async fn loaded_skill_carries_instructions_and_resource_keys() {
let skill = InMemorySkill::builder("t")
.with_description("d")
.with_instructions("body")
.with_text_resource("k", "v")
.build()
.unwrap();
let loaded = skill.load(&ExecutionContext::new()).await.unwrap();
assert_eq!(loaded.instructions, "body");
assert_eq!(loaded.resource_keys(), vec!["k"]);
}
}
#[derive(Debug)]
pub struct InMemorySkillBuilder {
name: String,
description: String,
version: Option<String>,
instructions: String,
resources: BTreeMap<String, Arc<dyn SkillResource>>,
}
impl InMemorySkillBuilder {
#[must_use]
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
#[must_use]
pub fn with_version(mut self, version: impl Into<String>) -> Self {
self.version = Some(version.into());
self
}
#[must_use]
pub fn with_instructions(mut self, instructions: impl Into<String>) -> Self {
self.instructions = instructions.into();
self
}
#[must_use]
pub fn with_resource(
mut self,
key: impl Into<String>,
resource: Arc<dyn SkillResource>,
) -> Self {
self.resources.insert(key.into(), resource);
self
}
#[must_use]
pub fn with_text_resource(self, key: impl Into<String>, text: impl Into<String>) -> Self {
self.with_resource(
key,
Arc::new(StaticResource::text(text)) as Arc<dyn SkillResource>,
)
}
pub fn build(self) -> Result<InMemorySkill> {
Ok(InMemorySkill {
name: self.name,
description: self.description,
version: self.version,
instructions: self.instructions,
resources: Arc::new(self.resources),
})
}
}
#[derive(Clone, Debug)]
pub struct StaticResource {
content: SkillResourceContent,
}
impl StaticResource {
#[must_use]
pub fn text(text: impl Into<String>) -> Self {
Self {
content: SkillResourceContent::Text(text.into()),
}
}
#[must_use]
pub fn binary(mime_type: impl Into<String>, bytes: Vec<u8>) -> Self {
Self {
content: SkillResourceContent::Binary {
mime_type: mime_type.into(),
bytes,
},
}
}
}
#[async_trait]
impl SkillResource for StaticResource {
async fn read(&self, _ctx: &ExecutionContext) -> Result<SkillResourceContent> {
Ok(self.content.clone())
}
}