1use alloc::{borrow::Cow, boxed::Box, format, string::ToString, vec::Vec};
4use core::{future::Future, pin::Pin};
5
6use super::types::{
7 PromptArgumentDefinition, PromptDefinition, PromptOutput, RegistryItem, ToolError,
8};
9use crate::compat::{HashMap, HashMapIter};
10
11pub trait RustPrompt: Send + Sync {
13 type Params: serde::de::DeserializeOwned + schemars::JsonSchema + Send;
15
16 const NAME: &'static str;
18
19 const DESCRIPTION: &'static str;
21
22 fn description(&self) -> Cow<'static, str> {
24 Cow::Borrowed(Self::DESCRIPTION)
25 }
26
27 fn render(
29 &self,
30 params: Self::Params,
31 ) -> impl Future<Output = Result<PromptOutput, ToolError>> + Send;
32}
33
34pub fn definition_of_prompt<T: RustPrompt>(prompt: &T) -> Result<PromptDefinition, ToolError> {
40 let schema = schemars::schema_for!(T::Params);
41 let val = serde_json::to_value(&schema).map_err(|e| {
42 ToolError::new(format!(
43 "Failed to serialize schema for prompt '{}': {e}",
44 T::NAME
45 ))
46 })?;
47 let mut arguments = Vec::new();
48
49 if let Some(obj) = val.as_object() {
50 let required_fields: Vec<&str> = match obj.get("required").and_then(|r| r.as_array()) {
51 Some(arr) => arr.iter().filter_map(|v| v.as_str()).collect(),
52 None => Vec::new(),
54 };
55 if let Some(props) = obj.get("properties").and_then(|p| p.as_object()) {
56 for (name, prop) in props {
57 let description = prop
58 .get("description")
59 .and_then(|d| d.as_str())
60 .unwrap_or("")
61 .to_string();
62 let required = required_fields.contains(&name.as_str());
63 arguments.push(PromptArgumentDefinition {
64 name: name.clone(),
65 description,
66 required,
67 });
68 }
69 }
70 }
71
72 Ok(PromptDefinition {
73 name: T::NAME.to_string(),
74 description: prompt.description().into_owned(),
75 arguments,
76 })
77}
78
79pub(crate) type BoxPromptFuture<'a> =
81 Pin<Box<dyn Future<Output = Result<PromptOutput, ToolError>> + Send + 'a>>;
82
83pub(crate) trait ErasedPrompt: Send + Sync {
88 fn render_erased(&self, args: serde_json::Value) -> BoxPromptFuture<'_>;
90}
91
92impl<T: RustPrompt> ErasedPrompt for T {
93 fn render_erased(&self, args: serde_json::Value) -> BoxPromptFuture<'_> {
94 Box::pin(async move {
95 let params: T::Params = serde_json::from_value(args).map_err(|e| {
96 ToolError::new(format!("Failed to deserialize prompt parameters: {e}"))
97 })?;
98 self.render(params).await
99 })
100 }
101}
102
103struct RegisteredPrompt {
105 definition: PromptDefinition,
106 erased: Box<dyn ErasedPrompt>,
107}
108
109#[derive(Default)]
116pub struct PromptRegistry {
117 prompts: HashMap<&'static str, RegisteredPrompt>,
118}
119
120impl core::fmt::Debug for PromptRegistry {
121 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
122 let names: Vec<&str> = self.prompts.keys().copied().collect();
123 f.debug_struct("PromptRegistry")
124 .field("prompt_count", &self.prompts.len())
125 .field("prompt_names", &names)
126 .finish()
127 }
128}
129
130impl PromptRegistry {
131 #[must_use]
133 pub fn new() -> Self {
134 Self {
135 prompts: HashMap::new(),
136 }
137 }
138
139 pub fn register<P: RustPrompt + 'static>(&mut self, prompt: P) -> &mut Self {
149 if let Err(e) = self.try_register(prompt) {
150 panic!("Failed to build definition for prompt '{}': {e}", P::NAME);
151 }
152 self
153 }
154
155 pub fn try_register<P: RustPrompt + 'static>(
166 &mut self,
167 prompt: P,
168 ) -> Result<&mut Self, ToolError> {
169 let definition = definition_of_prompt(&prompt)?;
170 self.prompts.insert(
171 P::NAME,
172 RegisteredPrompt {
173 definition,
174 erased: Box::new(prompt),
175 },
176 );
177 Ok(self)
178 }
179
180 #[must_use]
187 pub fn with_prompt<P: RustPrompt + 'static>(mut self, prompt: P) -> Self {
188 self.register(prompt);
189 self
190 }
191
192 #[must_use]
196 pub fn definitions(&self) -> Vec<PromptDefinition> {
197 self.prompts
198 .values()
199 .map(|entry| entry.definition.clone())
200 .collect()
201 }
202
203 #[must_use]
205 pub fn len(&self) -> usize {
206 self.prompts.len()
207 }
208
209 #[must_use]
211 pub fn is_empty(&self) -> bool {
212 self.prompts.is_empty()
213 }
214
215 #[must_use]
217 pub fn contains(&self, name: &str) -> bool {
218 self.prompts.contains_key(name)
219 }
220
221 #[must_use]
226 pub fn definition(&self, name: &str) -> Option<&PromptDefinition> {
227 self.prompts.get(name).map(|entry| &entry.definition)
228 }
229
230 #[must_use]
234 pub fn iter(&self) -> PromptDefinitions<'_> {
235 PromptDefinitions {
236 inner: self.prompts.iter(),
237 }
238 }
239
240 pub async fn render(
248 &self,
249 name: &str,
250 args: serde_json::Value,
251 ) -> Result<PromptOutput, ToolError> {
252 let Some(entry) = self.prompts.get(name) else {
253 return Err(ToolError::not_found(RegistryItem::Prompt, name));
254 };
255 entry.erased.render_erased(args).await
256 }
257}
258
259pub struct PromptDefinitions<'a> {
264 inner: HashMapIter<'a, &'static str, RegisteredPrompt>,
265}
266
267impl Iterator for PromptDefinitions<'_> {
268 type Item = (&'static str, PromptDefinition);
269
270 fn next(&mut self) -> Option<Self::Item> {
271 self.inner
272 .next()
273 .map(|(name, entry)| (*name, entry.definition.clone()))
274 }
275
276 fn size_hint(&self) -> (usize, Option<usize>) {
277 self.inner.size_hint()
278 }
279}
280
281impl ExactSizeIterator for PromptDefinitions<'_> {
282 fn len(&self) -> usize {
283 self.inner.len()
284 }
285}
286
287impl<'a> IntoIterator for &'a PromptRegistry {
289 type Item = (&'static str, PromptDefinition);
290 type IntoIter = PromptDefinitions<'a>;
291
292 fn into_iter(self) -> Self::IntoIter {
293 self.iter()
294 }
295}