use crate::{ElicitCommunicator, ElicitResult, ElicitationContext, StyleContext, StyleMarker};
use std::sync::{Arc, Mutex};
use tracing::instrument;
#[derive(Debug, Clone, Default)]
pub struct KnowledgeCache {
entries: Vec<String>,
}
impl KnowledgeCache {
pub fn push(&mut self, entry: impl Into<String>) {
self.entries.push(entry.into());
}
pub fn clear(&mut self) {
self.entries.clear();
}
fn format_preamble(&self) -> String {
if self.entries.is_empty() {
return String::new();
}
let mut preamble = String::from("[Context]\n");
for (i, entry) in self.entries.iter().enumerate() {
preamble.push_str(&format!("{}. {}\n", i + 1, entry));
}
preamble.push('\n');
preamble
}
}
pub type SharedKnowledge = Arc<Mutex<KnowledgeCache>>;
#[instrument]
pub fn knowledge_cache() -> SharedKnowledge {
Arc::new(Mutex::new(KnowledgeCache::default()))
}
#[derive(Clone)]
pub struct ContextualCommunicator<C> {
inner: C,
knowledge: SharedKnowledge,
}
impl<C> ContextualCommunicator<C> {
pub fn new(inner: C, knowledge: SharedKnowledge) -> Self {
Self { inner, knowledge }
}
}
impl<C: ElicitCommunicator + Clone> ElicitCommunicator for ContextualCommunicator<C> {
#[instrument(skip(self), level = "debug", fields(prompt_len = prompt.len()))]
fn send_prompt(
&self,
prompt: &str,
) -> impl std::future::Future<Output = ElicitResult<String>> + Send {
let preamble = {
let cache = self.knowledge.lock().unwrap();
cache.format_preamble()
};
let enriched = if preamble.is_empty() {
prompt.to_string()
} else {
format!("{preamble}{prompt}")
};
let inner = self.inner.clone();
tracing::debug!(prompt_len = enriched.len(), "Sending enriched prompt");
async move { inner.send_prompt(&enriched).await }
}
#[instrument(skip(self, params), level = "debug", fields(tool = %params.name))]
fn call_tool(
&self,
params: rmcp::model::CallToolRequestParams,
) -> impl std::future::Future<
Output = Result<rmcp::model::CallToolResult, rmcp::service::ServiceError>,
> + Send {
self.inner.call_tool(params)
}
fn style_context(&self) -> &StyleContext {
self.inner.style_context()
}
fn elicitation_context(&self) -> &ElicitationContext {
self.inner.elicitation_context()
}
fn with_style<T: 'static, S: StyleMarker + crate::style::ElicitationStyle + 'static>(
&self,
style: S,
) -> Self {
Self {
inner: self.inner.with_style::<T, S>(style),
knowledge: self.knowledge.clone(),
}
}
}