rig_core/providers/openai/extension/
mod.rs1use std::collections::BTreeMap;
19
20use serde::Serialize;
21use serde_json::Value;
22
23use crate::completion::provider_options::reply_field;
24use crate::completion::{ExtensionOptions, ProviderExtension, ReplyExtras};
25use crate::message::Api;
26
27mod chat;
28mod responses;
29
30pub use chat::{
31 ApproximateLocation, AudioFormat, AudioOutput, ChatOptions, CompletionTokensDetails, Modality,
32 Prediction, PromptTokensDetails, SearchContextSize, UserLocation, WebSearchOptions,
33};
34pub(crate) use responses::Envelope;
35pub use responses::{
36 AccessPrograms, ContextManagement, CyberAccess, Include, ItemPhase, OpenAiResponsesOptions,
37 PromptCacheOptions, ReasoningContext, ReasoningMode, ReasoningOptions, ReasoningSummary,
38 Truncation,
39};
40
41#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
44pub struct OpenAiExt;
45
46impl ProviderExtension for OpenAiExt {
47 const PROVIDER: &'static str = crate::providers::openai::PROVIDER_NAME;
48 type Options = OpenAiOptions;
49 type Extras = OpenAiExtras;
50}
51
52#[non_exhaustive]
66#[derive(Clone, Debug, Default, PartialEq, Serialize)]
67pub struct OpenAiOptions {
68 #[serde(rename = "*")]
70 pub shared: OpenAiShared,
71 #[serde(rename = "openai.chat")]
73 pub chat: ChatOptions,
74 #[serde(rename = "openai.responses")]
76 pub responses: OpenAiResponsesOptions,
77}
78
79impl OpenAiOptions {
80 pub fn new() -> Self {
82 Self::default()
83 }
84
85 #[must_use]
87 pub fn chat(mut self, chat: ChatOptions) -> Self {
88 self.chat = chat;
89 self
90 }
91
92 #[must_use]
94 pub fn shared(mut self, shared: OpenAiShared) -> Self {
95 self.shared = shared;
96 self
97 }
98
99 #[must_use]
101 pub fn responses(mut self, responses: OpenAiResponsesOptions) -> Self {
102 self.responses = responses;
103 self
104 }
105
106 fn with_shared(mut self, set: impl FnOnce(OpenAiShared) -> OpenAiShared) -> Self {
108 self.shared = set(std::mem::take(&mut self.shared));
109 self
110 }
111
112 fn with_responses(
114 mut self,
115 set: impl FnOnce(OpenAiResponsesOptions) -> OpenAiResponsesOptions,
116 ) -> Self {
117 self.responses = set(std::mem::take(&mut self.responses));
118 self
119 }
120
121 #[must_use]
123 pub fn store(self, store: bool) -> Self {
124 self.with_shared(|shared| shared.store(store))
125 }
126
127 #[must_use]
130 pub fn metadata(self, key: impl Into<String>, value: impl Into<String>) -> Self {
131 self.with_shared(|shared| shared.metadata(key, value))
132 }
133
134 #[must_use]
137 pub fn prompt_cache_key(self, key: impl Into<String>) -> Self {
138 self.with_shared(|shared| shared.prompt_cache_key(key))
139 }
140
141 #[must_use]
144 pub fn safety_identifier(self, id: impl Into<String>) -> Self {
145 self.with_shared(|shared| shared.safety_identifier(id))
146 }
147
148 #[must_use]
152 pub fn reasoning_summary(self, summary: ReasoningSummary) -> Self {
153 self.with_responses(|responses| responses.reasoning_summary(summary))
154 }
155
156 #[must_use]
160 pub fn include(self, include: impl IntoIterator<Item = Include>) -> Self {
161 self.with_responses(|responses| responses.include(include))
162 }
163
164 #[must_use]
168 pub fn conversation(self, id: impl Into<String>) -> Self {
169 self.with_responses(|responses| responses.conversation(id))
170 }
171
172 #[must_use]
176 pub fn truncation(self, truncation: Truncation) -> Self {
177 self.with_responses(|responses| responses.truncation(truncation))
178 }
179
180 #[must_use]
184 pub fn background(self, background: bool) -> Self {
185 self.with_responses(|responses| responses.background(background))
186 }
187
188 #[must_use]
192 pub fn max_tool_calls(self, max: u32) -> Self {
193 self.with_responses(|responses| responses.max_tool_calls(max))
194 }
195}
196
197impl ExtensionOptions for OpenAiOptions {
198 type Ext = OpenAiExt;
199}
200
201#[non_exhaustive]
203#[derive(Clone, Debug, Default, PartialEq, Serialize)]
204pub struct OpenAiShared {
205 #[serde(skip_serializing_if = "Option::is_none")]
209 pub store: Option<bool>,
210 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
212 pub metadata: BTreeMap<String, String>,
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub prompt_cache_key: Option<String>,
216 #[serde(skip_serializing_if = "Option::is_none")]
218 pub safety_identifier: Option<String>,
219}
220
221impl OpenAiShared {
222 #[must_use]
224 pub fn store(mut self, store: bool) -> Self {
225 self.store = Some(store);
226 self
227 }
228
229 #[must_use]
231 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
232 self.metadata.insert(key.into(), value.into());
233 self
234 }
235
236 #[must_use]
238 pub fn prompt_cache_key(mut self, key: impl Into<String>) -> Self {
239 self.prompt_cache_key = Some(key.into());
240 self
241 }
242
243 #[must_use]
245 pub fn safety_identifier(mut self, id: impl Into<String>) -> Self {
246 self.safety_identifier = Some(id.into());
247 self
248 }
249}
250
251#[non_exhaustive]
254#[derive(Clone, Debug, Default, PartialEq)]
255pub struct OpenAiExtras {
256 pub service_tier: Option<String>,
258 pub reasoning_effort: Option<String>,
260 pub reasoning_summary: Option<String>,
262 pub reasoning_mode: Option<String>,
264 pub reasoning_context: Option<String>,
267 pub prompt_cache_retention: Option<String>,
269 pub incomplete_reason: Option<String>,
272 pub phases: Option<Vec<ItemPhase>>,
275 pub billing_payer: Option<String>,
277 pub system_fingerprint: Option<String>,
280 pub prompt_tokens_details: Option<PromptTokensDetails>,
282 pub completion_tokens_details: Option<CompletionTokensDetails>,
284 pub annotations: Option<Vec<Value>>,
286}
287
288impl ReplyExtras for OpenAiExtras {
289 fn from_reply(api: &Api, raw: &Value) -> Result<Self, serde_json::Error> {
290 if api.as_str() == "openai.chat" {
291 return Ok(Self {
292 service_tier: reply_field(raw, "/service_tier")?,
293 system_fingerprint: reply_field(raw, "/system_fingerprint")?,
294 prompt_tokens_details: reply_field(raw, "/usage/prompt_tokens_details")?,
295 completion_tokens_details: reply_field(raw, "/usage/completion_tokens_details")?,
296 annotations: reply_field(raw, "/choices/0/message/annotations")?,
297 ..Self::default()
298 });
299 }
300 let envelope = responses::Envelope::from_reply(raw)?;
301 Ok(Self {
302 service_tier: envelope.service_tier,
303 reasoning_effort: envelope.reasoning_effort,
304 reasoning_summary: envelope.reasoning_summary,
305 reasoning_mode: envelope.reasoning_mode,
306 reasoning_context: envelope.reasoning_context,
307 prompt_cache_retention: envelope.prompt_cache_retention,
308 incomplete_reason: envelope.incomplete_reason,
309 phases: envelope.phases,
310 billing_payer: reply_field(raw, "/billing/payer")?,
311 ..Self::default()
312 })
313 }
314}
315
316#[cfg(test)]
317mod tests;