Skip to main content

tea_context/providers/
tool_hints.rs

1use std::str::FromStr;
2
3use crate::{
4    BudgetBehavior, CacheScope, ContextError, ContextProvider, ContextProviderFuture,
5    ContextProviderId, ContextRequest, PromptAuthority, PromptModule, PromptModuleId,
6    PromptPriority, PromptProvenance, PromptSegment, PromptSegmentId, TrustLevel,
7};
8
9/// Generates guidance from prompt hints on the active tool snapshot.
10#[derive(Debug, Clone)]
11pub struct ToolHintProvider {
12    id: ContextProviderId,
13}
14
15impl ToolHintProvider {
16    /// Creates the built-in active-tool provider.
17    ///
18    /// # Errors
19    ///
20    /// Returns an error only when the crate's static provider ID is invalid.
21    pub fn new() -> Result<Self, crate::ContextIdentityError> {
22        Ok(Self {
23            id: ContextProviderId::from_str("builtin.tool_hints")?,
24        })
25    }
26}
27
28impl ContextProvider for ToolHintProvider {
29    fn id(&self) -> &ContextProviderId {
30        &self.id
31    }
32
33    fn provide(&self, request: ContextRequest) -> ContextProviderFuture<'_> {
34        let provider_id = self.id.clone();
35        Box::pin(async move {
36            let snippets = request
37                .active_tools()
38                .iter()
39                .filter_map(|tool| tool.prompt_snippet().map(|snippet| (tool, snippet)))
40                .map(|(tool, snippet)| {
41                    let segment_id = format!("tool.{}.snippet", tool.name().as_str());
42                    let locator = format!("{}@{}", tool.name(), tool.version());
43                    PromptSegment::new(
44                        PromptSegmentId::from_str(&segment_id).map_err(value_error)?,
45                        format!("Tool `{}`: {snippet}", tool.name()),
46                        PromptProvenance::new(provider_id.clone(), "tool_spec", Some(locator))
47                            .map_err(value_error)?,
48                        TrustLevel::Delegated,
49                        CacheScope::Profile,
50                        BudgetBehavior::Omit,
51                    )
52                    .map_err(value_error)
53                })
54                .collect::<Result<Vec<_>, ContextError>>()?;
55            let guidelines = request
56                .active_tools()
57                .iter()
58                .filter(|tool| !tool.prompt_guidelines().is_empty())
59                .map(|tool| {
60                    let segment_id = format!("tool.{}.guidelines", tool.name().as_str());
61                    let locator = format!("{}@{}", tool.name(), tool.version());
62                    let guidelines = tool
63                        .prompt_guidelines()
64                        .iter()
65                        .map(|guideline| format!("- {guideline}"))
66                        .collect::<Vec<_>>()
67                        .join("\n");
68                    PromptSegment::new(
69                        PromptSegmentId::from_str(&segment_id).map_err(value_error)?,
70                        format!("Tool `{}` guidelines:\n{guidelines}", tool.name()),
71                        PromptProvenance::new(provider_id.clone(), "tool_spec", Some(locator))
72                            .map_err(value_error)?,
73                        TrustLevel::Delegated,
74                        CacheScope::Profile,
75                        BudgetBehavior::Omit,
76                    )
77                    .map_err(value_error)
78                })
79                .collect::<Result<Vec<_>, ContextError>>()?;
80            let mut modules = Vec::new();
81            if !snippets.is_empty() {
82                modules.push(
83                    PromptModule::new(
84                        PromptModuleId::from_str("tool.active_snippets").map_err(value_error)?,
85                        PromptAuthority::Tool,
86                        PromptPriority::new(1),
87                        snippets,
88                    )
89                    .map_err(value_error)?,
90                );
91            }
92            if !guidelines.is_empty() {
93                modules.push(
94                    PromptModule::new(
95                        PromptModuleId::from_str("tool.active_guidelines").map_err(value_error)?,
96                        PromptAuthority::Tool,
97                        PromptPriority::new(0),
98                        guidelines,
99                    )
100                    .map_err(value_error)?,
101                );
102            }
103            Ok(modules)
104        })
105    }
106}
107
108fn value_error(error: impl std::fmt::Display) -> ContextError {
109    ContextError::new(crate::ContextErrorCode::InvalidValue, error.to_string())
110}