Skip to main content

objectiveai_sdk/agent/openrouter/
reasoning.rs

1//! Reasoning configuration for models that support extended thinking.
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6/// Configuration for model reasoning/thinking capabilities.
7///
8/// Some models (like o1, o3, Claude with extended thinking) support
9/// explicit reasoning modes where they can "think" before responding.
10/// This struct configures those capabilities.
11///
12/// **Note:** The `max_tokens`, `effort`, and `summary_verbosity` fields are
13/// only supported by some models. Unsupported fields are silently ignored.
14#[derive(
15    Debug,
16    Clone,
17    Copy,
18    PartialEq,
19    Serialize,
20    Deserialize,
21    JsonSchema,
22    arbitrary::Arbitrary,
23)]
24#[schemars(rename = "agent.openrouter.Reasoning")]
25pub struct Reasoning {
26    /// Whether reasoning is enabled. Defaults to `true` if other fields are set.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    #[schemars(extend("omitempty" = true))]
29    pub enabled: Option<bool>,
30    /// Maximum tokens for the reasoning/thinking output.
31    ///
32    /// Only supported by some models.
33    #[serde(skip_serializing_if = "Option::is_none")]
34    #[schemars(extend("omitempty" = true))]
35    #[arbitrary(with = crate::arbitrary_util::arbitrary_option_u64)]
36    pub max_tokens: Option<u64>,
37    /// The reasoning effort level.
38    ///
39    /// Only supported by some models.
40    #[serde(skip_serializing_if = "Option::is_none")]
41    #[schemars(extend("omitempty" = true))]
42    pub effort: Option<ReasoningEffort>,
43    /// Verbosity of reasoning summaries in the response.
44    ///
45    /// Only supported by some models.
46    #[serde(skip_serializing_if = "Option::is_none")]
47    #[schemars(extend("omitempty" = true))]
48    pub summary_verbosity: Option<ReasoningSummaryVerbosity>,
49}
50
51impl Reasoning {
52    /// Normalizes the reasoning configuration for deterministic hashing.
53    pub fn prepare(mut self) -> Option<Self> {
54        if let Some(0) = self.max_tokens {
55            self.max_tokens = None;
56        }
57        if let Some(ReasoningSummaryVerbosity::Auto) = self.summary_verbosity {
58            self.summary_verbosity = None;
59        }
60        if self.enabled.is_none()
61            && (self.max_tokens.is_some()
62                || self.effort.is_some()
63                || self.summary_verbosity.is_some())
64        {
65            self.enabled = Some(true);
66        }
67        if self.enabled.is_some()
68            || self.max_tokens.is_some()
69            || self.effort.is_some()
70            || self.summary_verbosity.is_some()
71        {
72            Some(self)
73        } else {
74            None
75        }
76    }
77
78    /// Validates the reasoning configuration.
79    pub fn validate(&self) -> Result<(), String> {
80        if self
81            .max_tokens
82            .is_some_and(|max_tokens| max_tokens > i32::MAX as u64)
83        {
84            Err(format!(
85                "`reasoning.max_tokens` must be at most {}",
86                i32::MAX
87            ))
88        } else if let Some(false) = self.enabled {
89            if self.max_tokens.is_some() {
90                Err(
91                    "`reasoning.enabled` cannot be false if `reasoning.max_tokens` is set"
92                        .to_string(),
93                )
94            } else if self.effort.is_some() {
95                Err("`reasoning.enabled` cannot be false if `reasoning.effort` is set".to_string())
96            } else if self.summary_verbosity.is_some() {
97                Err(
98                    "`reasoning.enabled` cannot be false if `reasoning.summary_verbosity` is set"
99                        .to_string(),
100                )
101            } else {
102                Ok(())
103            }
104        } else {
105            Ok(())
106        }
107    }
108}
109
110/// The level of effort the model should put into reasoning.
111///
112/// Only supported by some models.
113#[derive(
114    Debug,
115    Clone,
116    Copy,
117    PartialEq,
118    Serialize,
119    Deserialize,
120    JsonSchema,
121    arbitrary::Arbitrary,
122)]
123#[serde(rename_all = "snake_case")]
124#[schemars(rename = "agent.openrouter.ReasoningEffort")]
125pub enum ReasoningEffort {
126    /// No reasoning.
127    #[schemars(title = "None")]
128    None,
129    /// Minimal reasoning effort.
130    #[schemars(title = "Minimal")]
131    Minimal,
132    /// Low reasoning effort.
133    #[schemars(title = "Low")]
134    Low,
135    /// Medium reasoning effort.
136    #[schemars(title = "Medium")]
137    Medium,
138    /// High reasoning effort.
139    #[schemars(title = "High")]
140    High,
141    /// Maximum reasoning effort.
142    #[schemars(title = "Xhigh")]
143    Xhigh,
144}
145
146/// Verbosity of the reasoning summary included in responses.
147///
148/// Only supported by some models.
149#[derive(
150    Debug,
151    Clone,
152    Copy,
153    PartialEq,
154    Serialize,
155    Deserialize,
156    JsonSchema,
157    arbitrary::Arbitrary,
158)]
159#[serde(rename_all = "snake_case")]
160#[schemars(rename = "agent.openrouter.ReasoningSummaryVerbosity")]
161pub enum ReasoningSummaryVerbosity {
162    /// Let the model decide (default, normalized away).
163    #[schemars(title = "Auto")]
164    Auto,
165    /// Brief summary of reasoning.
166    #[schemars(title = "Concise")]
167    Concise,
168    /// Thorough summary of reasoning.
169    #[schemars(title = "Detailed")]
170    Detailed,
171}