tea_context/providers/
skills.rs1use std::str::FromStr;
2
3use crate::{
4 BudgetBehavior, CacheScope, ContextError, ContextErrorCode, ContextProvider,
5 ContextProviderFuture, ContextProviderId, ContextRequest, PromptAuthority, PromptModule,
6 PromptModuleId, PromptPriority, PromptProvenance, PromptSegment, PromptSegmentId,
7 SkillMetadata, TrustLevel,
8};
9
10pub const MAX_ACTIVE_SKILLS: usize = 128;
12
13#[derive(Debug, Clone)]
15pub struct SkillMetadataProvider {
16 id: ContextProviderId,
17 skills: Vec<SkillMetadata>,
18}
19
20impl SkillMetadataProvider {
21 pub fn new(mut skills: Vec<SkillMetadata>) -> Result<Self, ContextError> {
27 if skills.len() > MAX_ACTIVE_SKILLS {
28 return Err(ContextError::new(
29 ContextErrorCode::BoundsExceeded,
30 "active skill collection is too large",
31 ));
32 }
33 skills.sort_by(|left, right| left.id().cmp(right.id()));
34 if skills
35 .windows(2)
36 .any(|items| items[0].id() == items[1].id())
37 {
38 return Err(ContextError::new(
39 ContextErrorCode::DuplicateIdentity,
40 "active skill ID is duplicated",
41 ));
42 }
43 Ok(Self {
44 id: ContextProviderId::from_str("builtin.skill_metadata").map_err(value_error)?,
45 skills,
46 })
47 }
48}
49
50impl ContextProvider for SkillMetadataProvider {
51 fn id(&self) -> &ContextProviderId {
52 &self.id
53 }
54
55 fn provide(&self, _request: ContextRequest) -> ContextProviderFuture<'_> {
56 let id = self.id.clone();
57 let skills = self.skills.clone();
58 Box::pin(async move {
59 if skills.is_empty() {
60 return Ok(Vec::new());
61 }
62 let segments = skills
63 .into_iter()
64 .map(|skill| {
65 let segment_id = format!("skill.{}.metadata", skill.id().as_str());
66 PromptSegment::new(
67 PromptSegmentId::from_str(&segment_id).map_err(value_error)?,
68 format!(
69 "Skill `{}`: {} Invoke explicitly with `{}`.",
70 skill.id(),
71 skill.description(),
72 skill.invocation()
73 ),
74 PromptProvenance::new(
75 id.clone(),
76 "skill_metadata",
77 Some(skill.id().to_string()),
78 )
79 .map_err(value_error)?,
80 TrustLevel::Delegated,
81 CacheScope::Profile,
82 BudgetBehavior::Omit,
83 )
84 .map_err(value_error)
85 })
86 .collect::<Result<Vec<_>, ContextError>>()?;
87 Ok(vec![
88 PromptModule::new(
89 PromptModuleId::from_str("skill.active_metadata").map_err(value_error)?,
90 PromptAuthority::Skill,
91 PromptPriority::new(0),
92 segments,
93 )
94 .map_err(value_error)?,
95 ])
96 })
97 }
98}
99
100fn value_error(error: impl std::fmt::Display) -> ContextError {
101 ContextError::new(ContextErrorCode::InvalidValue, error.to_string())
102}