Skip to main content

openai_compat/types/responses/
config.rs

1//! Shared Responses config types: `text.format`, `reasoning`, `include`,
2//! `truncation`. Mirrors `openai-python/src/openai/types/responses/`
3//! (`response_text_config.py`, `response_format_text_config.py`,
4//! `reasoning.py`, `response_includable.py`).
5
6use serde::{Deserialize, Serialize};
7
8/// Reasoning effort for reasoning models.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum ReasoningEffort {
12    None,
13    Minimal,
14    Low,
15    Medium,
16    High,
17    Xhigh,
18}
19
20/// Reasoning configuration for reasoning models (`o1`/`o3`/`gpt-5`-class).
21#[derive(Debug, Clone, Default, Serialize, Deserialize)]
22pub struct Reasoning {
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub effort: Option<ReasoningEffort>,
25    /// `"auto" | "concise" | "detailed"`.
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub summary: Option<String>,
28}
29
30impl Reasoning {
31    pub fn effort(effort: ReasoningEffort) -> Self {
32        Self {
33            effort: Some(effort),
34            summary: None,
35        }
36    }
37}
38
39/// The `text.format` structured-output configuration.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(tag = "type", rename_all = "snake_case")]
42pub enum ResponseFormatTextConfig {
43    Text,
44    JsonObject,
45    JsonSchema {
46        name: String,
47        schema: serde_json::Value,
48        #[serde(default, skip_serializing_if = "Option::is_none")]
49        description: Option<String>,
50        #[serde(default, skip_serializing_if = "Option::is_none")]
51        strict: Option<bool>,
52    },
53}
54
55impl ResponseFormatTextConfig {
56    pub fn json_schema(name: impl Into<String>, schema: serde_json::Value, strict: bool) -> Self {
57        Self::JsonSchema {
58            name: name.into(),
59            schema,
60            description: None,
61            strict: Some(strict),
62        }
63    }
64}
65
66/// `text` request field: output format + verbosity.
67#[derive(Debug, Clone, Default, Serialize, Deserialize)]
68pub struct ResponseTextConfig {
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub format: Option<ResponseFormatTextConfig>,
71    /// `"low" | "medium" | "high"`.
72    #[serde(default, skip_serializing_if = "Option::is_none")]
73    pub verbosity: Option<String>,
74}
75
76impl ResponseTextConfig {
77    pub fn format(format: ResponseFormatTextConfig) -> Self {
78        Self {
79            format: Some(format),
80            verbosity: None,
81        }
82    }
83}
84
85/// Additional output data to include (`include` request param).
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
87pub enum ResponseIncludable {
88    #[serde(rename = "file_search_call.results")]
89    FileSearchCallResults,
90    #[serde(rename = "web_search_call.results")]
91    WebSearchCallResults,
92    #[serde(rename = "web_search_call.action.sources")]
93    WebSearchCallActionSources,
94    #[serde(rename = "message.input_image.image_url")]
95    MessageInputImageImageUrl,
96    #[serde(rename = "computer_call_output.output.image_url")]
97    ComputerCallOutputOutputImageUrl,
98    #[serde(rename = "code_interpreter_call.outputs")]
99    CodeInterpreterCallOutputs,
100    #[serde(rename = "reasoning.encrypted_content")]
101    ReasoningEncryptedContent,
102    #[serde(rename = "message.output_text.logprobs")]
103    MessageOutputTextLogprobs,
104}
105
106impl ResponseIncludable {
107    /// The wire value, for building repeated `include[]` query parameters.
108    pub fn as_str(&self) -> &'static str {
109        match self {
110            Self::FileSearchCallResults => "file_search_call.results",
111            Self::WebSearchCallResults => "web_search_call.results",
112            Self::WebSearchCallActionSources => "web_search_call.action.sources",
113            Self::MessageInputImageImageUrl => "message.input_image.image_url",
114            Self::ComputerCallOutputOutputImageUrl => "computer_call_output.output.image_url",
115            Self::CodeInterpreterCallOutputs => "code_interpreter_call.outputs",
116            Self::ReasoningEncryptedContent => "reasoning.encrypted_content",
117            Self::MessageOutputTextLogprobs => "message.output_text.logprobs",
118        }
119    }
120}
121
122/// Context-window truncation strategy.
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
124#[serde(rename_all = "snake_case")]
125pub enum Truncation {
126    Auto,
127    Disabled,
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn reasoning_serializes() {
136        let reasoning = Reasoning::effort(ReasoningEffort::Medium);
137        assert_eq!(
138            serde_json::to_value(&reasoning).unwrap(),
139            serde_json::json!({"effort": "medium"})
140        );
141    }
142
143    #[test]
144    fn text_json_schema_serializes() {
145        let text = ResponseTextConfig::format(ResponseFormatTextConfig::json_schema(
146            "out",
147            serde_json::json!({"type": "object"}),
148            true,
149        ));
150        assert_eq!(
151            serde_json::to_value(&text).unwrap(),
152            serde_json::json!({
153                "format": {
154                    "type": "json_schema",
155                    "name": "out",
156                    "schema": {"type": "object"},
157                    "strict": true
158                }
159            })
160        );
161    }
162
163    #[test]
164    fn includable_uses_dotted_wire_values() {
165        assert_eq!(
166            serde_json::to_value(ResponseIncludable::FileSearchCallResults).unwrap(),
167            "file_search_call.results"
168        );
169    }
170}