rig_core/providers/openai/extension/
responses.rs1use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use crate::completion::provider_options::reply_field;
8
9#[non_exhaustive]
12#[derive(Clone, Debug, Default, PartialEq, Serialize)]
13pub struct OpenAiResponsesOptions {
14 #[serde(skip_serializing_if = "Option::is_none")]
17 pub reasoning: Option<ReasoningOptions>,
18 #[serde(skip_serializing_if = "Vec::is_empty")]
21 pub include: Vec<Include>,
22 #[serde(skip_serializing_if = "Option::is_none")]
25 pub conversation: Option<String>,
26 #[serde(skip_serializing_if = "Option::is_none")]
29 pub truncation: Option<Truncation>,
30 #[serde(skip_serializing_if = "Vec::is_empty")]
32 pub context_management: Vec<ContextManagement>,
33 #[serde(skip_serializing_if = "Option::is_none")]
36 pub prompt_cache_options: Option<PromptCacheOptions>,
37 #[serde(skip_serializing_if = "Option::is_none")]
41 pub background: Option<bool>,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub max_tool_calls: Option<u32>,
45 #[serde(skip_serializing_if = "Option::is_none")]
48 pub top_logprobs: Option<u8>,
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub access_programs: Option<AccessPrograms>,
52}
53
54impl OpenAiResponsesOptions {
55 #[must_use]
57 pub fn reasoning_summary(mut self, summary: ReasoningSummary) -> Self {
58 self.reasoning.get_or_insert_with(Default::default).summary = Some(summary);
59 self
60 }
61
62 #[must_use]
64 pub fn reasoning_mode(mut self, mode: ReasoningMode) -> Self {
65 self.reasoning.get_or_insert_with(Default::default).mode = Some(mode);
66 self
67 }
68
69 #[must_use]
71 pub fn reasoning_context(mut self, context: ReasoningContext) -> Self {
72 self.reasoning.get_or_insert_with(Default::default).context = Some(context);
73 self
74 }
75
76 #[must_use]
78 pub fn include(mut self, include: impl IntoIterator<Item = Include>) -> Self {
79 self.include.extend(include);
80 self
81 }
82
83 #[must_use]
85 pub fn conversation(mut self, id: impl Into<String>) -> Self {
86 self.conversation = Some(id.into());
87 self
88 }
89
90 #[must_use]
92 pub fn truncation(mut self, truncation: Truncation) -> Self {
93 self.truncation = Some(truncation);
94 self
95 }
96
97 #[must_use]
99 pub fn context_management(
100 mut self,
101 entries: impl IntoIterator<Item = ContextManagement>,
102 ) -> Self {
103 self.context_management.extend(entries);
104 self
105 }
106
107 #[must_use]
109 pub fn prompt_cache_comparison(mut self, response_id: impl Into<String>) -> Self {
110 self.prompt_cache_options = Some(PromptCacheOptions {
111 comparison_response_id: Some(response_id.into()),
112 });
113 self
114 }
115
116 #[must_use]
118 pub fn background(mut self, background: bool) -> Self {
119 self.background = Some(background);
120 self
121 }
122
123 #[must_use]
125 pub fn max_tool_calls(mut self, max: u32) -> Self {
126 self.max_tool_calls = Some(max);
127 self
128 }
129
130 #[must_use]
132 pub fn top_logprobs(mut self, count: u8) -> Self {
133 self.top_logprobs = Some(count);
134 self
135 }
136
137 #[must_use]
139 pub fn access_programs(mut self, programs: AccessPrograms) -> Self {
140 self.access_programs = Some(programs);
141 self
142 }
143}
144
145#[non_exhaustive]
147#[derive(Clone, Debug, Default, PartialEq, Serialize)]
148pub struct ReasoningOptions {
149 #[serde(skip_serializing_if = "Option::is_none")]
151 pub summary: Option<ReasoningSummary>,
152 #[serde(skip_serializing_if = "Option::is_none")]
154 pub mode: Option<ReasoningMode>,
155 #[serde(skip_serializing_if = "Option::is_none")]
157 pub context: Option<ReasoningContext>,
158}
159
160#[non_exhaustive]
162#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
163#[serde(rename_all = "snake_case")]
164pub enum ReasoningSummary {
165 Auto,
167 Concise,
169 Detailed,
171}
172
173#[non_exhaustive]
176#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
177#[serde(rename_all = "snake_case")]
178pub enum ReasoningMode {
179 Standard,
181 Pro,
183}
184
185#[non_exhaustive]
188#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
189#[serde(rename_all = "snake_case")]
190pub enum ReasoningContext {
191 Auto,
193 AllTurns,
195 CurrentTurn,
197}
198
199#[non_exhaustive]
201#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
202pub enum Include {
203 #[serde(rename = "file_search_call.results")]
205 FileSearchCallResults,
206 #[serde(rename = "web_search_call.results")]
208 WebSearchCallResults,
209 #[serde(rename = "web_search_call.action.sources")]
211 WebSearchCallActionSources,
212 #[serde(rename = "message.input_image.image_url")]
214 MessageInputImageImageUrl,
215 #[serde(rename = "computer_call_output.output.image_url")]
217 ComputerCallOutputOutputImageUrl,
218 #[serde(rename = "code_interpreter_call.outputs")]
220 CodeInterpreterCallOutputs,
221 #[serde(rename = "reasoning.encrypted_content")]
223 ReasoningEncryptedContent,
224 #[serde(rename = "message.output_text.logprobs")]
226 MessageOutputTextLogprobs,
227}
228
229#[non_exhaustive]
231#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
232#[serde(rename_all = "snake_case")]
233pub enum Truncation {
234 Auto,
236 Disabled,
238}
239
240#[non_exhaustive]
242#[derive(Clone, Debug, PartialEq, Serialize)]
243#[serde(tag = "type", rename_all = "snake_case")]
244pub enum ContextManagement {
245 Compaction {
248 #[serde(skip_serializing_if = "Option::is_none")]
250 compact_threshold: Option<u32>,
251 },
252}
253
254impl ContextManagement {
255 pub fn compaction(threshold: Option<u32>) -> Self {
257 Self::Compaction {
258 compact_threshold: threshold,
259 }
260 }
261}
262
263#[non_exhaustive]
266#[derive(Clone, Debug, Default, PartialEq, Serialize)]
267pub struct PromptCacheOptions {
268 #[serde(skip_serializing_if = "Option::is_none")]
271 pub comparison_response_id: Option<String>,
272}
273
274#[non_exhaustive]
276#[derive(Clone, Debug, PartialEq, Serialize)]
277pub struct AccessPrograms {
278 pub cyber: CyberAccess,
280}
281
282impl AccessPrograms {
283 pub fn cyber(access: CyberAccess) -> Self {
285 Self { cyber: access }
286 }
287}
288
289#[non_exhaustive]
291#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
292#[serde(rename_all = "snake_case")]
293pub enum CyberAccess {
294 Standard,
296 DaybreakBlue,
298 DaybreakRed,
300}
301
302#[non_exhaustive]
304#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
305pub struct ItemPhase {
306 pub id: String,
308 #[serde(default)]
310 pub phase: Option<String>,
311}
312
313pub(crate) struct Envelope {
316 pub(crate) service_tier: Option<String>,
317 pub(crate) reasoning_effort: Option<String>,
318 pub(crate) reasoning_summary: Option<String>,
319 pub(crate) reasoning_mode: Option<String>,
320 pub(crate) reasoning_context: Option<String>,
321 pub(crate) prompt_cache_retention: Option<String>,
322 pub(crate) incomplete_reason: Option<String>,
323 pub(crate) phases: Option<Vec<ItemPhase>>,
324}
325
326impl Envelope {
327 pub(crate) fn from_reply(raw: &Value) -> Result<Self, serde_json::Error> {
330 let reasoning = raw.get("reasoning").filter(|value| value.is_object());
331 let reasoning = |key: &str| match reasoning {
332 Some(reasoning) => reply_field::<String>(reasoning, &format!("/{key}")),
333 None => Ok(None),
334 };
335 let messages = raw
336 .get("output")
337 .and_then(Value::as_array)
338 .into_iter()
339 .flatten()
340 .filter(|item| item.get("type").and_then(Value::as_str) == Some("message"))
341 .map(ItemPhase::deserialize)
342 .collect::<Result<Vec<_>, _>>()?;
343 Ok(Self {
344 service_tier: reply_field(raw, "/service_tier")?,
345 reasoning_effort: reasoning("effort")?,
346 reasoning_summary: reasoning("summary")?,
347 reasoning_mode: reasoning("mode")?,
348 reasoning_context: reasoning("context")?,
349 prompt_cache_retention: reply_field(raw, "/prompt_cache_retention")?,
350 incomplete_reason: reply_field(raw, "/incomplete_details/reason")?,
351 phases: (!messages.is_empty()).then_some(messages),
352 })
353 }
354}
355
356#[cfg(test)]
357mod tests;