Skip to main content

elicitation/primitives/
duration.rs

1//! Duration type implementation for time duration elicitation.
2//!
3//! Provides both direct elicitation and generator-based creation of Duration.
4//!
5//! # Generator Pattern
6//!
7//! The Duration generator supports multiple creation strategies useful for testing:
8//!
9//! ```rust,no_run
10//! use elicitation::{DurationGenerationMode, DurationGenerator, Generator};
11//! use std::time::Duration;
12//!
13//! // Choose generation mode
14//! let mode = DurationGenerationMode::FromSecs(30); // 30 seconds
15//! // or Zero, FromMillis, FromMicros, FromNanos
16//!
17//! // Create generator
18//! let generator = DurationGenerator::new(mode);
19//!
20//! // Generate multiple durations with same strategy
21//! let d1 = generator.generate();
22//! let d2 = generator.generate();
23//! let d3 = generator.generate();
24//! ```
25
26use crate::{
27    ElicitCommunicator, ElicitError, ElicitErrorKind, ElicitResult, Elicitation, Generator, Prompt,
28    Select, mcp,
29};
30use std::time::Duration;
31
32// Generate default-only style enums
33crate::default_style!(Duration => DurationStyle);
34crate::default_style!(DurationGenerationMode => DurationGenerationModeStyle);
35
36// ============================================================================
37// Duration Generator
38// ============================================================================
39
40/// Generation mode for Duration.
41///
42/// This enum allows an agent (or user) to specify how to create a Duration:
43/// - `Zero`: Zero duration
44/// - `FromSecs`: Duration from seconds
45/// - `FromMillis`: Duration from milliseconds
46/// - `FromMicros`: Duration from microseconds
47/// - `FromNanos`: Duration from nanoseconds
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50pub enum DurationGenerationMode {
51    /// Zero duration.
52    Zero,
53    /// Duration from seconds.
54    FromSecs(u64),
55    /// Duration from milliseconds.
56    FromMillis(u64),
57    /// Duration from microseconds.
58    FromMicros(u64),
59    /// Duration from nanoseconds.
60    FromNanos(u64),
61}
62
63impl Select for DurationGenerationMode {
64    fn options() -> Vec<Self> {
65        vec![
66            DurationGenerationMode::Zero,
67            DurationGenerationMode::FromSecs(0),
68            DurationGenerationMode::FromMillis(0),
69            DurationGenerationMode::FromMicros(0),
70            DurationGenerationMode::FromNanos(0),
71        ]
72    }
73
74    fn labels() -> Vec<String> {
75        vec![
76            "Zero".to_string(),
77            "From Seconds".to_string(),
78            "From Milliseconds".to_string(),
79            "From Microseconds".to_string(),
80            "From Nanoseconds".to_string(),
81        ]
82    }
83
84    fn from_label(label: &str) -> Option<Self> {
85        match label {
86            "Zero" => Some(DurationGenerationMode::Zero),
87            "From Seconds" => Some(DurationGenerationMode::FromSecs(0)),
88            "From Milliseconds" => Some(DurationGenerationMode::FromMillis(0)),
89            "From Microseconds" => Some(DurationGenerationMode::FromMicros(0)),
90            "From Nanoseconds" => Some(DurationGenerationMode::FromNanos(0)),
91            _ => None,
92        }
93    }
94}
95
96impl Prompt for DurationGenerationMode {
97    fn prompt() -> Option<&'static str> {
98        Some("How should durations be created?")
99    }
100}
101
102impl Elicitation for DurationGenerationMode {
103    type Style = DurationGenerationModeStyle;
104
105    async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
106        // Use standard Select elicit pattern
107        let params = mcp::select_params(
108            Self::prompt().unwrap_or("Select an option:"),
109            &Self::labels(),
110        );
111
112        let result = communicator
113            .call_tool(
114                rmcp::model::CallToolRequestParams::new(mcp::tool_names::elicit_select())
115                    .with_arguments(params),
116            )
117            .await?;
118
119        let value = mcp::extract_value(result)?;
120        let label = mcp::parse_string(value)?;
121
122        let selected = Self::from_label(&label).ok_or_else(|| {
123            ElicitError::new(ElicitErrorKind::ParseError(
124                "Invalid Duration generation mode".to_string(),
125            ))
126        })?;
127
128        // If a time unit was selected, elicit the value
129        match selected {
130            DurationGenerationMode::Zero => Ok(DurationGenerationMode::Zero),
131            DurationGenerationMode::FromSecs(_) => {
132                let secs = u64::elicit(communicator).await?;
133                Ok(DurationGenerationMode::FromSecs(secs))
134            }
135            DurationGenerationMode::FromMillis(_) => {
136                let millis = u64::elicit(communicator).await?;
137                Ok(DurationGenerationMode::FromMillis(millis))
138            }
139            DurationGenerationMode::FromMicros(_) => {
140                let micros = u64::elicit(communicator).await?;
141                Ok(DurationGenerationMode::FromMicros(micros))
142            }
143            DurationGenerationMode::FromNanos(_) => {
144                let nanos = u64::elicit(communicator).await?;
145                Ok(DurationGenerationMode::FromNanos(nanos))
146            }
147        }
148    }
149
150    fn kani_proof() -> proc_macro2::TokenStream {
151        crate::verification::proof_helpers::kani_multi_variant_enum(
152            "DurationGenerationMode",
153            "Zero",
154        )
155    }
156
157    fn verus_proof() -> proc_macro2::TokenStream {
158        crate::verification::proof_helpers::verus_multi_variant_enum("DurationGenerationMode")
159    }
160
161    fn creusot_proof() -> proc_macro2::TokenStream {
162        crate::verification::proof_helpers::creusot_multi_variant_enum("DurationGenerationMode")
163    }
164}
165
166/// Generator for creating Duration values with a specified strategy.
167///
168/// Created from a [`DurationGenerationMode`] to enable consistent duration
169/// generation across multiple calls.
170#[derive(Debug, Clone, Copy)]
171#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
172pub struct DurationGenerator {
173    mode: DurationGenerationMode,
174}
175
176impl DurationGenerator {
177    /// Create a new Duration generator with the specified mode.
178    pub fn new(mode: DurationGenerationMode) -> Self {
179        Self { mode }
180    }
181
182    /// Get the generation mode.
183    pub fn mode(&self) -> DurationGenerationMode {
184        self.mode
185    }
186}
187
188impl Generator for DurationGenerator {
189    type Target = Duration;
190
191    fn generate(&self) -> Self::Target {
192        match self.mode {
193            DurationGenerationMode::Zero => Duration::ZERO,
194            DurationGenerationMode::FromSecs(secs) => Duration::from_secs(secs),
195            DurationGenerationMode::FromMillis(millis) => Duration::from_millis(millis),
196            DurationGenerationMode::FromMicros(micros) => Duration::from_micros(micros),
197            DurationGenerationMode::FromNanos(nanos) => Duration::from_nanos(nanos),
198        }
199    }
200}
201
202// ============================================================================
203// Duration Elicitation
204// ============================================================================
205
206impl Prompt for Duration {
207    fn prompt() -> Option<&'static str> {
208        Some("Choose how to create the duration:")
209    }
210}
211
212impl Elicitation for Duration {
213    type Style = DurationStyle;
214
215    #[tracing::instrument(skip(communicator))]
216    async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
217        tracing::debug!("Eliciting Duration");
218
219        // Elicit generation mode from agent
220        let mode = DurationGenerationMode::elicit(communicator).await?;
221
222        // Create generator and generate duration
223        let generator = DurationGenerator::new(mode);
224        let duration = generator.generate();
225
226        tracing::debug!(?duration, mode = ?mode, "Generated Duration");
227        Ok(duration)
228    }
229
230    fn kani_proof() -> proc_macro2::TokenStream {
231        crate::verification::proof_helpers::kani_duration()
232    }
233
234    fn verus_proof() -> proc_macro2::TokenStream {
235        crate::verification::proof_helpers::verus_duration()
236    }
237
238    fn creusot_proof() -> proc_macro2::TokenStream {
239        crate::verification::proof_helpers::creusot_duration()
240    }
241}