1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7pub mod prelude {
8 pub use crate::{
9 ReasoningArtifactKind, ReasoningEffort, ReasoningError, ReasoningErrorKind, ReasoningMode,
10 ReasoningStepKind, ReasoningStrategy, ReasoningTraceStatus, ReasoningVisibility,
11 };
12}
13
14macro_rules! reasoning_enum {
15 ($name:ident { $($variant:ident => $label:literal),+ $(,)? }) => {
16 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
17 pub enum $name {
18 $($variant),+
19 }
20
21 impl $name {
22 pub const ALL: &'static [Self] = &[$(Self::$variant),+];
23
24 pub const fn as_str(self) -> &'static str {
25 match self {
26 $(Self::$variant => $label),+
27 }
28 }
29 }
30
31 impl fmt::Display for $name {
32 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
33 formatter.write_str(self.as_str())
34 }
35 }
36
37 impl FromStr for $name {
38 type Err = ReasoningError;
39
40 fn from_str(value: &str) -> Result<Self, Self::Err> {
41 match normalized_label(value)?.as_str() {
42 $($label => Ok(Self::$variant),)+
43 _ => Err(ReasoningError::UnknownLabel),
44 }
45 }
46 }
47 };
48}
49
50reasoning_enum!(ReasoningMode {
51 None => "none",
52 Direct => "direct",
53 ChainOfThoughtLike => "chain-of-thought-like",
54 TreeOfThoughtLike => "tree-of-thought-like",
55 GraphOfThoughtLike => "graph-of-thought-like",
56 ProgramAided => "program-aided",
57 ToolAugmented => "tool-augmented",
58 Reflective => "reflective",
59 Custom => "custom",
60});
61
62reasoning_enum!(ReasoningVisibility {
63 Hidden => "hidden",
64 SummaryOnly => "summary-only",
65 UserVisible => "user-visible",
66 Redacted => "redacted",
67});
68
69reasoning_enum!(ReasoningStepKind {
70 Observe => "observe",
71 Decompose => "decompose",
72 Infer => "infer",
73 Retrieve => "retrieve",
74 Calculate => "calculate",
75 Compare => "compare",
76 Verify => "verify",
77 Reflect => "reflect",
78 Decide => "decide",
79 Explain => "explain",
80 Custom => "custom",
81});
82
83reasoning_enum!(ReasoningTraceStatus {
84 Unavailable => "unavailable",
85 Available => "available",
86 Redacted => "redacted",
87 Summarized => "summarized",
88 Disallowed => "disallowed",
89});
90
91reasoning_enum!(ReasoningEffort {
92 Minimal => "minimal",
93 Low => "low",
94 Medium => "medium",
95 High => "high",
96 Max => "max",
97 Unknown => "unknown",
98});
99
100reasoning_enum!(ReasoningStrategy {
101 Deductive => "deductive",
102 Inductive => "inductive",
103 Abductive => "abductive",
104 Analogical => "analogical",
105 Causal => "causal",
106 Probabilistic => "probabilistic",
107 Geometric => "geometric",
108 Symbolic => "symbolic",
109 Custom => "custom",
110});
111
112reasoning_enum!(ReasoningArtifactKind {
113 Summary => "summary",
114 Scratchpad => "scratchpad",
115 ProofSketch => "proof-sketch",
116 Calculation => "calculation",
117 Plan => "plan",
118 Diagram => "diagram",
119 Code => "code",
120 CitationMap => "citation-map",
121 Custom => "custom",
122});
123
124reasoning_enum!(ReasoningErrorKind {
125 UnsupportedPremise => "unsupported-premise",
126 MissingEvidence => "missing-evidence",
127 Contradiction => "contradiction",
128 Hallucination => "hallucination",
129 ToolError => "tool-error",
130 CalculationError => "calculation-error",
131 Unknown => "unknown",
132});
133
134#[derive(Clone, Copy, Debug, Eq, PartialEq)]
135pub enum ReasoningError {
136 Empty,
137 UnknownLabel,
138}
139
140impl fmt::Display for ReasoningError {
141 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
142 match self {
143 Self::Empty => formatter.write_str("reasoning metadata label cannot be empty"),
144 Self::UnknownLabel => formatter.write_str("unknown reasoning metadata label"),
145 }
146 }
147}
148
149impl Error for ReasoningError {}
150
151fn normalized_label(value: &str) -> Result<String, ReasoningError> {
152 let trimmed = value.trim();
153 if trimmed.is_empty() {
154 Err(ReasoningError::Empty)
155 } else {
156 Ok(trimmed.to_ascii_lowercase().replace(['_', ' '], "-"))
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::{
163 ReasoningArtifactKind, ReasoningEffort, ReasoningError, ReasoningErrorKind, ReasoningMode,
164 ReasoningStepKind, ReasoningStrategy, ReasoningTraceStatus, ReasoningVisibility,
165 };
166 use core::{fmt, str::FromStr};
167
168 fn assert_enum_family<T>(variants: &[T]) -> Result<(), ReasoningError>
169 where
170 T: Copy + Eq + fmt::Debug + fmt::Display + FromStr<Err = ReasoningError>,
171 {
172 for variant in variants {
173 let label = variant.to_string();
174 assert_eq!(label.parse::<T>()?, *variant);
175 assert_eq!(label.replace('-', "_").parse::<T>()?, *variant);
176 assert_eq!(label.replace('-', " ").parse::<T>()?, *variant);
177 }
178 Ok(())
179 }
180
181 #[test]
182 fn displays_and_parses_reasoning_enums() -> Result<(), ReasoningError> {
183 assert_enum_family(ReasoningMode::ALL)?;
184 assert_enum_family(ReasoningVisibility::ALL)?;
185 assert_enum_family(ReasoningStepKind::ALL)?;
186 assert_enum_family(ReasoningTraceStatus::ALL)?;
187 assert_enum_family(ReasoningEffort::ALL)?;
188 assert_enum_family(ReasoningStrategy::ALL)?;
189 assert_enum_family(ReasoningArtifactKind::ALL)?;
190 assert_enum_family(ReasoningErrorKind::ALL)?;
191 assert_eq!(
192 "tool augmented".parse::<ReasoningMode>()?,
193 ReasoningMode::ToolAugmented
194 );
195 assert_eq!("".parse::<ReasoningMode>(), Err(ReasoningError::Empty));
196 Ok(())
197 }
198}