llm_tool/types.rs
1//! Core types for `llm-tool`: tool output, errors, context, and definitions.
2//!
3//! Defines the types that tools produce ([`ToolOutput`], [`ToolError`]),
4//! the execution context ([`ToolContext`]) shared across tool calls, and
5//! the [`ToolDefinition`] metadata sent to models.
6
7use alloc::{
8 boxed::Box,
9 format,
10 string::{String, ToString},
11};
12
13/// Re-export the `#[llm_tool]` proc macro for defining tools from plain functions.
14///
15/// # Usage
16///
17/// ```
18/// use llm_tool::{RustTool, ToolContext, ToolRegistry, llm_tool};
19///
20/// /// Adds two numbers together (with a twist).
21/// #[llm_tool]
22/// fn wonky_add(
23/// /// First number.
24/// a: i64,
25/// /// Second number.
26/// b: i64,
27/// ) -> Result<String, String> {
28/// Ok(format!("{}", a + b + 1))
29/// }
30///
31/// let mut registry = ToolRegistry::new();
32/// registry.register(WonkyAdd);
33/// assert_eq!(registry.definitions().len(), 1);
34/// ```
35pub use llm_tool_macros::{llm_prompt, llm_resource, llm_tool};
36// Re-export `JsonSchema` derive so tool authors can write `use llm_tool::JsonSchema;`
37// without adding `schemars` to their own `Cargo.toml`.
38pub use schemars::JsonSchema;
39use serde::{Deserialize, Serialize};
40
41use crate::compat::HashMap;
42
43/// Human-readable JSON type name for error messages.
44const fn other_type_name(value: &serde_json::Value) -> &'static str {
45 match value {
46 serde_json::Value::Null => "null",
47 serde_json::Value::Bool(_) => "bool",
48 serde_json::Value::Number(_) => "number",
49 serde_json::Value::String(_) => "string",
50 serde_json::Value::Array(_) => "array",
51 serde_json::Value::Object(_) => "object",
52 }
53}
54
55/// The return value of a Rust tool execution.
56///
57/// Every tool produces a `ToolOutput` containing:
58/// - **`content`**: the text sent back to the model.
59/// - **`metadata`**: an optional structured key-value map available to hooks,
60/// policies, and logging pipelines — but **never** sent to the model.
61///
62/// # Ergonomics
63///
64/// `ToolOutput` implements `From<String>`, `From<&str>`, and `Display`, so
65/// simple tools can return plain strings without ceremony:
66///
67/// ```rust
68/// use llm_tool::ToolOutput;
69/// use serde::Serialize;
70///
71/// // From a String
72/// let out: ToolOutput = "hello".to_string().into();
73/// assert_eq!(out.content(), "hello");
74/// assert!(out.metadata().is_empty());
75///
76/// // From &str
77/// let out: ToolOutput = "world".into();
78/// assert_eq!(out.content(), "world");
79///
80/// // Structured metadata from a typed struct (preferred)
81/// #[derive(Serialize)]
82/// struct ReadMeta {
83/// bytes_read: usize,
84/// cached: bool,
85/// }
86///
87/// let out = ToolOutput::new("file contents…")
88/// .with_metadata(&ReadMeta {
89/// bytes_read: 1024,
90/// cached: true,
91/// })
92/// .unwrap();
93/// assert_eq!(out.metadata()["bytes_read"], 1024);
94/// assert_eq!(out.metadata()["cached"], true);
95///
96/// // Single ad-hoc entry
97/// let out = ToolOutput::new("done").with_meta("exit_code", serde_json::Value::from(0));
98/// assert_eq!(out.metadata()["exit_code"], 0);
99/// ```
100#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101pub struct ToolOutput {
102 /// Text content sent to the LLM.
103 pub(crate) content: String,
104 /// Structured metadata attached to the output.
105 /// NOT sent to the LLM.
106 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
107 pub(crate) metadata: HashMap<String, serde_json::Value>,
108}
109
110impl ToolOutput {
111 /// Create a new `ToolOutput` with the given content and no metadata.
112 pub fn new(content: impl Into<String>) -> Self {
113 Self {
114 content: content.into(),
115 metadata: HashMap::new(),
116 }
117 }
118
119 /// Serialize a value to JSON and wrap it as tool output.
120 ///
121 /// The JSON string becomes the content sent to the model, but no
122 /// metadata is attached. For the zero-redundancy path that populates
123 /// **both** content and metadata from the same struct, use
124 /// [`from_metadata`](Self::from_metadata).
125 ///
126 /// ```rust
127 /// use llm_tool::{ToolError, ToolOutput};
128 /// use serde::Serialize;
129 ///
130 /// #[derive(Serialize)]
131 /// struct Reading {
132 /// temp: i32,
133 /// unit: &'static str,
134 /// }
135 ///
136 /// let data = Reading {
137 /// temp: 72,
138 /// unit: "F",
139 /// };
140 /// let output = ToolOutput::json(&data).unwrap();
141 /// assert!(output.content().contains("72"));
142 /// assert!(output.metadata().is_empty()); // no metadata attached
143 /// ```
144 ///
145 /// # Errors
146 ///
147 /// Returns `Err(ToolError)` if serialization fails.
148 pub fn json<T: serde::Serialize>(value: &T) -> Result<Self, ToolError> {
149 serde_json::to_string(value)
150 .map(Self::new)
151 .map_err(|e| ToolError::new(format!("serialization failed: {e}")))
152 }
153
154 /// Create a `ToolOutput` where **both** the content and metadata come
155 /// from the same serializable value.
156 ///
157 /// - **Content** (sent to the model): the JSON representation of `value`.
158 /// - **Metadata** (hooks / policies / logging): the flattened object fields.
159 ///
160 /// This is the zero-redundancy path: define one struct, derive
161 /// `Serialize`, and everything is populated automatically.
162 ///
163 /// # Errors
164 ///
165 /// Returns `Err(ToolError)` if `value` doesn't serialize to a JSON object.
166 ///
167 /// # Example
168 ///
169 /// ```rust
170 /// use llm_tool::ToolOutput;
171 /// use serde::Serialize;
172 ///
173 /// #[derive(Serialize)]
174 /// struct Weather {
175 /// location: String,
176 /// temp_f: i32,
177 /// condition: String,
178 /// }
179 ///
180 /// let out = ToolOutput::from_metadata(&Weather {
181 /// location: "Seattle".into(),
182 /// temp_f: 72,
183 /// condition: "Sunny".into(),
184 /// })
185 /// .unwrap();
186 ///
187 /// // Model sees the JSON string
188 /// assert!(out.content().contains("Seattle"));
189 /// assert!(out.content().contains("72"));
190 ///
191 /// // Hooks see typed fields
192 /// assert_eq!(out.metadata()["location"], "Seattle");
193 /// assert_eq!(out.metadata()["temp_f"], 72);
194 /// ```
195 pub fn from_metadata<T: serde::Serialize>(value: &T) -> Result<Self, ToolError> {
196 let json_value = serde_json::to_value(value)
197 .map_err(|e| ToolError::new(format!("metadata serialization failed: {e}")))?;
198 // Serialize to string *before* destructuring so we borrow the Value
199 // instead of cloning the inner Map.
200 let content = json_value.to_string();
201 match json_value {
202 serde_json::Value::Object(map) => Ok(Self {
203 content,
204 metadata: map.into_iter().collect(),
205 }),
206 other => Err(ToolError::new(format!(
207 "metadata must serialize to a JSON object, got {}",
208 other_type_name(&other),
209 ))),
210 }
211 }
212
213 /// Attach a single metadata key-value pair. Chainable.
214 ///
215 /// For attaching multiple fields at once, prefer
216 /// [`with_metadata`](Self::with_metadata) with a typed struct.
217 #[must_use]
218 pub fn with_meta(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
219 self.metadata.insert(key.into(), value);
220 self
221 }
222
223 /// Attach structured metadata from a serializable value.
224 ///
225 /// The value is serialized to a JSON object and its fields are **merged**
226 /// into the metadata map. This is the preferred way to attach metadata
227 /// because it avoids stringly-typed keys and data duplication.
228 ///
229 /// # Errors
230 ///
231 /// Returns `Err(ToolError)` if `value` doesn't serialize to a JSON object
232 /// (e.g. it serializes to a scalar or array).
233 ///
234 /// # Example
235 ///
236 /// ```rust
237 /// use llm_tool::ToolOutput;
238 /// use serde::Serialize;
239 ///
240 /// #[derive(Serialize)]
241 /// struct FileMeta {
242 /// bytes_read: usize,
243 /// source: String,
244 /// }
245 ///
246 /// let out = ToolOutput::new("file contents")
247 /// .with_metadata(&FileMeta {
248 /// bytes_read: 1024,
249 /// source: "/etc/hosts".into(),
250 /// })
251 /// .unwrap();
252 /// assert_eq!(out.metadata()["bytes_read"], 1024);
253 /// assert_eq!(out.metadata()["source"], "/etc/hosts");
254 /// ```
255 pub fn with_metadata<T: serde::Serialize>(mut self, value: &T) -> Result<Self, ToolError> {
256 let json = serde_json::to_value(value)
257 .map_err(|e| ToolError::new(format!("metadata serialization failed: {e}")))?;
258 match json {
259 serde_json::Value::Object(map) => {
260 self.metadata.extend(map);
261 Ok(self)
262 }
263 other => Err(ToolError::new(format!(
264 "metadata must serialize to a JSON object, got {}",
265 other_type_name(&other),
266 ))),
267 }
268 }
269
270 /// The text content sent back to the model.
271 #[must_use]
272 pub fn content(&self) -> &str {
273 &self.content
274 }
275
276 /// Consume self and return the owned content string.
277 #[must_use]
278 pub fn into_content(self) -> String {
279 self.content
280 }
281
282 /// The structured metadata map.
283 #[must_use]
284 pub const fn metadata(&self) -> &HashMap<String, serde_json::Value> {
285 &self.metadata
286 }
287
288 /// Truncate the output text content according to the given [`TruncationPolicy`].
289 #[must_use]
290 pub fn truncated(mut self, policy: &TruncationPolicy) -> Self {
291 truncation::truncate_output(&mut self, policy);
292 self
293 }
294
295 /// Truncate the output text content according to the given [`TruncationPolicy`],
296 /// returning a detailed [`TruncationOutcome`].
297 ///
298 /// # Errors
299 ///
300 /// Returns [`SpillError`] if an [`OutputSpillSink`] is configured and fails.
301 pub fn truncate_detailed(
302 &mut self,
303 policy: &TruncationPolicy,
304 ) -> Result<TruncationOutcome<'static>, SpillError> {
305 truncation::truncate_output_detailed(self, policy)
306 }
307
308 /// Truncate the output text content with caller context according to [`TruncationPolicy`].
309 ///
310 /// # Errors
311 ///
312 /// Returns [`SpillError`] if an [`OutputSpillSink`] is configured and fails.
313 pub fn truncate_detailed_with_context(
314 &mut self,
315 policy: &TruncationPolicy,
316 ctx: &SpillContext<'_>,
317 ) -> Result<TruncationOutcome<'static>, SpillError> {
318 truncation::truncate_output_detailed_with_context(self, policy, ctx)
319 }
320}
321
322impl core::fmt::Display for ToolOutput {
323 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
324 f.write_str(&self.content)
325 }
326}
327
328impl From<String> for ToolOutput {
329 fn from(content: String) -> Self {
330 Self::new(content)
331 }
332}
333
334impl From<&str> for ToolOutput {
335 fn from(content: &str) -> Self {
336 Self::new(content)
337 }
338}
339
340impl From<i64> for ToolOutput {
341 fn from(value: i64) -> Self {
342 Self::new(value.to_string())
343 }
344}
345
346impl From<f64> for ToolOutput {
347 fn from(value: f64) -> Self {
348 Self::new(value.to_string())
349 }
350}
351
352impl From<bool> for ToolOutput {
353 fn from(value: bool) -> Self {
354 Self::new(value.to_string())
355 }
356}
357
358impl From<serde_json::Value> for ToolOutput {
359 fn from(value: serde_json::Value) -> Self {
360 // serde_json::Value::to_string() never fails.
361 Self::new(value.to_string())
362 }
363}
364
365/// Wrapper for returning serializable values as JSON tool output.
366///
367/// Implements `From<Json<T>> for ToolOutput` so it works with the
368/// `#[llm_tool]` macro's `.into()` conversion — no `Result` wrapper needed
369/// for infallible serialization.
370///
371/// # Panics
372///
373/// The `From` conversion panics if `serde_json::to_string` fails.
374/// This only happens with broken `Serialize` implementations (e.g.,
375/// maps with non-string keys). For explicit error handling, use
376/// [`ToolOutput::json()`] instead.
377///
378/// # Example
379///
380/// ```rust
381/// use llm_tool::{Json, ToolOutput};
382/// use serde::Serialize;
383///
384/// #[derive(Serialize)]
385/// struct Weather {
386/// temp: f64,
387/// city: String,
388/// }
389///
390/// let output: ToolOutput = Json(Weather {
391/// temp: 72.0,
392/// city: "NYC".into(),
393/// })
394/// .into();
395/// assert!(output.content().contains("72"));
396/// ```
397pub struct Json<T>(pub T);
398
399impl<T: serde::Serialize> From<Json<T>> for ToolOutput {
400 fn from(json: Json<T>) -> Self {
401 let json_value = serde_json::to_value(&json.0)
402 .expect("Json<T> serialization failed — this is a bug in the Serialize impl");
403 let content = json_value.to_string();
404 match json_value {
405 serde_json::Value::Object(map) => Self {
406 content,
407 metadata: map.into_iter().collect(),
408 },
409 _ => Self::new(content),
410 }
411 }
412}
413
414/// The category of a registered entity (tool, prompt, or resource).
415///
416/// Used by [`ToolError::not_found`] and the registry dispatchers to
417/// provide clear, type-safe identification of which kind of item was
418/// looked up.
419///
420/// # Example
421///
422/// ```
423/// use llm_tool::RegistryItem;
424///
425/// assert_eq!(RegistryItem::Tool.to_string(), "tool");
426/// assert_eq!(RegistryItem::Prompt.to_string(), "prompt");
427/// assert_eq!(RegistryItem::Resource.to_string(), "resource");
428/// ```
429#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
430pub enum RegistryItem {
431 /// An executable tool.
432 Tool,
433 /// A prompt template.
434 Prompt,
435 /// A readable resource.
436 Resource,
437}
438
439impl core::fmt::Display for RegistryItem {
440 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
441 match self {
442 Self::Tool => write!(f, "tool"),
443 Self::Prompt => write!(f, "prompt"),
444 Self::Resource => write!(f, "resource"),
445 }
446 }
447}
448
449/// An error returned from a tool execution.
450///
451/// The error message is sent back to the model as the tool's error response.
452/// Structured metadata can be attached for hooks and logging — it is **not**
453/// sent to the model.
454///
455/// Implements `From<String>` and `From<&str>` for ergonomic construction.
456///
457/// # Example
458///
459/// ```
460/// use llm_tool::ToolError;
461/// use serde::Serialize;
462///
463/// let err: ToolError = "something went wrong".into();
464/// assert_eq!(err.to_string(), "something went wrong");
465///
466/// let err = ToolError::new(format!("failed to read {}", "file.txt"));
467/// assert!(err.to_string().contains("file.txt"));
468///
469/// // Structured metadata from a typed struct (preferred)
470/// #[derive(Serialize)]
471/// struct HttpErrorMeta {
472/// status_code: u16,
473/// url: String,
474/// }
475///
476/// let err = ToolError::new("HTTP request failed")
477/// .with_metadata(&HttpErrorMeta {
478/// status_code: 503,
479/// url: "https://example.com".into(),
480/// })
481/// .unwrap();
482/// assert_eq!(err.metadata()["status_code"], 503);
483///
484/// // Single ad-hoc entry
485/// let err = ToolError::new("timeout").with_meta("retry_after_secs", serde_json::Value::from(30));
486/// assert_eq!(err.metadata()["retry_after_secs"], 30);
487/// ```
488#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
489pub struct ToolError {
490 /// Human-readable error message sent to the model.
491 pub message: String,
492 /// Structured metadata for hooks / policies / logging.
493 /// NOT sent to the model.
494 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
495 metadata: HashMap<String, serde_json::Value>,
496}
497
498/// Structured category metadata attached to built-in [`ToolError`] constructors.
499#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
500pub struct ErrorKindMetadata {
501 /// Structured error category (`"not_registered"`, `"destructive_in_parallel_batch"`, or `io::ErrorKind`).
502 pub error_kind: String,
503}
504
505/// Structured classification metadata attached when converting [`serde_json::Error`] into [`ToolError`].
506#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
507pub struct JsonErrorCategoryMetadata {
508 /// `serde_json::error::Category` classification (`"Io"`, `"Syntax"`, `"Data"`, `"Eof"`).
509 pub category: String,
510}
511
512impl ToolError {
513 /// Create a new tool error with no metadata.
514 pub fn new(message: impl Into<String>) -> Self {
515 Self {
516 message: message.into(),
517 metadata: HashMap::new(),
518 }
519 }
520
521 /// Metadata key identifying the structured category of an error.
522 pub const ERROR_KIND_KEY: &'static str = "error_kind";
523 /// [`ERROR_KIND_KEY`](Self::ERROR_KIND_KEY) value used by
524 /// [`not_found`](Self::not_found) to mark a registry-lookup miss.
525 pub const KIND_NOT_REGISTERED: &'static str = "not_registered";
526 /// [`ERROR_KIND_KEY`](Self::ERROR_KIND_KEY) value used by
527 /// [`destructive_in_parallel_batch`](Self::destructive_in_parallel_batch) when a
528 /// destructive tool is rejected during parallel batch execution.
529 pub const KIND_DESTRUCTIVE_BATCH: &'static str = "destructive_in_parallel_batch";
530
531 fn with_infallible_metadata<T: serde::Serialize>(self, meta: &T) -> Self {
532 match self.with_metadata(meta) {
533 Ok(err) | Err(err) => err,
534 }
535 }
536
537 /// Construct an error for a registry lookup that found no entry named
538 /// `name` of the given [`RegistryItem`].
539 ///
540 /// The human-readable message is suitable to hand back to the model so it
541 /// can self-correct (e.g. retry with a valid name). An
542 /// [`ERROR_KIND_KEY`](Self::ERROR_KIND_KEY) metadata field is attached —
543 /// never sent to the model — so hosts can still distinguish a routing miss
544 /// from a genuine execution failure for telemetry or policy decisions.
545 /// Prefer [`is_not_found`](Self::is_not_found) over inspecting the metadata
546 /// directly.
547 ///
548 /// # Example
549 ///
550 /// ```
551 /// use llm_tool::{RegistryItem, ToolError};
552 ///
553 /// let err = ToolError::not_found(RegistryItem::Tool, "add_nummbers");
554 /// assert!(err.to_string().contains("add_nummbers"));
555 /// assert!(err.is_not_found());
556 /// ```
557 #[must_use]
558 pub fn not_found(kind: RegistryItem, name: &str) -> Self {
559 Self::new(format!("no {kind} named '{name}' is registered")).with_infallible_metadata(
560 &ErrorKindMetadata {
561 error_kind: Self::KIND_NOT_REGISTERED.to_string(),
562 },
563 )
564 }
565
566 /// Construct a typed error when a tool with [`ToolEffect::Destructive`] is rejected
567 /// in unconfirmed parallel batch dispatch.
568 #[must_use]
569 pub fn destructive_in_parallel_batch(tool_name: &str) -> Self {
570 Self::new(format!(
571 "Tool '{tool_name}' has Destructive effect and cannot be executed in Parallel batch mode without explicit confirmation"
572 ))
573 .with_infallible_metadata(&ErrorKindMetadata {
574 error_kind: Self::KIND_DESTRUCTIVE_BATCH.to_string(),
575 })
576 }
577
578 /// Attach a single metadata key-value pair. Chainable.
579 ///
580 /// For attaching multiple fields at once, prefer
581 /// [`with_metadata`](Self::with_metadata) with a typed struct.
582 #[must_use]
583 pub fn with_meta(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
584 self.metadata.insert(key.into(), value);
585 self
586 }
587
588 /// Attach structured metadata from a serializable value.
589 ///
590 /// The value is serialized to a JSON object and its fields are **merged**
591 /// into the metadata map. See [`ToolOutput::with_metadata`] for details.
592 ///
593 /// # Errors
594 ///
595 /// Returns `Err(self)` if `value` doesn't serialize to a JSON object.
596 pub fn with_metadata<T: serde::Serialize>(mut self, value: &T) -> Result<Self, Self> {
597 let json = match serde_json::to_value(value) {
598 Ok(j) => j,
599 Err(e) => {
600 self.message =
601 format!("{} (metadata serialization also failed: {e})", self.message);
602 return Err(self);
603 }
604 };
605 match json {
606 serde_json::Value::Object(map) => {
607 self.metadata.extend(map);
608 Ok(self)
609 }
610 other => {
611 self.message = format!(
612 "{} (metadata must serialize to a JSON object, got {})",
613 self.message,
614 other_type_name(&other),
615 );
616 Err(self)
617 }
618 }
619 }
620
621 /// The structured metadata map.
622 #[must_use]
623 pub const fn metadata(&self) -> &HashMap<String, serde_json::Value> {
624 &self.metadata
625 }
626
627 /// Whether this error denotes a registry-lookup miss, i.e. it was produced
628 /// by [`not_found`](Self::not_found).
629 ///
630 /// Lets hosts branch on "the model asked for something that isn't
631 /// registered" versus "a registered handler failed" without matching on
632 /// message strings.
633 ///
634 /// # Example
635 ///
636 /// ```
637 /// use llm_tool::{RegistryItem, ToolError};
638 ///
639 /// assert!(ToolError::not_found(RegistryItem::Prompt, "summarize").is_not_found());
640 /// assert!(!ToolError::new("handler blew up").is_not_found());
641 /// ```
642 #[must_use]
643 pub fn is_not_found(&self) -> bool {
644 matches!(
645 self.metadata.get(Self::ERROR_KIND_KEY),
646 Some(serde_json::Value::String(kind)) if kind == Self::KIND_NOT_REGISTERED
647 )
648 }
649
650 /// Whether this error denotes a [`ToolEffect::Destructive`] tool rejected
651 /// during parallel batch dispatch.
652 #[must_use]
653 pub fn is_destructive_batch_rejected(&self) -> bool {
654 matches!(
655 self.metadata.get(Self::ERROR_KIND_KEY),
656 Some(serde_json::Value::String(kind)) if kind == Self::KIND_DESTRUCTIVE_BATCH
657 )
658 }
659}
660
661impl core::fmt::Display for ToolError {
662 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
663 f.write_str(&self.message)
664 }
665}
666
667impl core::error::Error for ToolError {}
668
669impl From<String> for ToolError {
670 fn from(message: String) -> Self {
671 Self::new(message)
672 }
673}
674
675impl From<&str> for ToolError {
676 fn from(message: &str) -> Self {
677 Self::new(message)
678 }
679}
680
681#[cfg(feature = "std")]
682impl From<std::io::Error> for ToolError {
683 fn from(e: std::io::Error) -> Self {
684 Self::new(e.to_string()).with_infallible_metadata(&ErrorKindMetadata {
685 error_kind: format!("{:?}", e.kind()),
686 })
687 }
688}
689
690impl From<serde_json::Error> for ToolError {
691 fn from(e: serde_json::Error) -> Self {
692 Self::new(e.to_string()).with_infallible_metadata(&JsonErrorCategoryMetadata {
693 category: format!("{:?}", e.classify()),
694 })
695 }
696}
697
698impl From<Box<dyn core::error::Error + Send + Sync>> for ToolError {
699 fn from(e: Box<dyn core::error::Error + Send + Sync>) -> Self {
700 Self::new(e.to_string())
701 }
702}
703
704impl From<core::convert::Infallible> for ToolError {
705 fn from(never: core::convert::Infallible) -> Self {
706 match never {}
707 }
708}
709
710pub mod context;
711pub mod definition;
712pub mod prompt;
713pub mod quarantine;
714pub mod resource;
715pub mod sanitized;
716pub mod surrogate;
717pub mod truncation;
718
719pub use context::*;
720pub use definition::*;
721pub use prompt::*;
722pub use quarantine::*;
723pub use resource::*;
724pub use sanitized::*;
725pub use surrogate::*;
726pub use truncation::*;
727
728#[cfg(all(test, feature = "std"))]
729mod tests;