Skip to main content

rig_core/providers/chatgpt/
extension.rs

1//! ChatGPT's typed request options and reply extras, keyed by
2//! [`PROVIDER_NAME`](crate::providers::chatgpt::PROVIDER_NAME). The backend
3//! speaks only the Responses format, so its options are one
4//! `"openai.responses"` section. An `openai` entry is not read here.
5//!
6//! ```
7//! use rig_core::completion::CompletionRequest;
8//! use rig_core::providers::chatgpt::extension::{ChatGptOptions};
9//!
10//! let options = ChatGptOptions::default().prompt_cache_key("conversation-1");
11//! let request = CompletionRequest::new("hi").provider_option(options);
12//! # let _ = request;
13//! ```
14
15use std::collections::BTreeMap;
16
17use serde::Serialize;
18use serde_json::Value;
19
20use crate::completion::{ExtensionOptions, ProviderExtension, ReplyExtras};
21use crate::message::Api;
22use crate::providers::openai::extension::{AccessPrograms, Envelope, ItemPhase};
23
24/// ChatGPT's provider extension.
25#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
26pub struct ChatGptExt;
27
28impl ProviderExtension for ChatGptExt {
29    const PROVIDER: &'static str = crate::providers::chatgpt::PROVIDER_NAME;
30    type Options = ChatGptOptions;
31    type Extras = ChatGptExtras;
32}
33
34/// ChatGPT's request options. Serialize-only; an unset field is not sent.
35/// The backend stores nothing, so there is no `store`: the wire always
36/// sends `false`.
37#[non_exhaustive]
38#[derive(Clone, Debug, Default, PartialEq, Serialize)]
39pub struct ChatGptOptions {
40    /// The Responses fields, the only route the backend takes.
41    #[serde(rename = "openai.responses")]
42    pub responses: ChatGptResponses,
43}
44
45impl ChatGptOptions {
46    /// Send `prompt_cache_key`.
47    #[must_use]
48    pub fn prompt_cache_key(mut self, key: impl Into<String>) -> Self {
49        self.responses.prompt_cache_key = Some(key.into());
50        self
51    }
52
53    /// Add `key: value` to `client_metadata`.
54    #[must_use]
55    pub fn client_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
56        self.responses
57            .client_metadata
58            .insert(key.into(), value.into());
59        self
60    }
61
62    /// Send `access_programs`.
63    #[must_use]
64    pub fn access_programs(mut self, programs: AccessPrograms) -> Self {
65        self.responses.access_programs = Some(programs);
66        self
67    }
68}
69
70impl ExtensionOptions for ChatGptOptions {
71    type Ext = ChatGptExt;
72}
73
74/// The fields the ChatGPT backend takes at the top level of a Responses
75/// body.
76#[non_exhaustive]
77#[derive(Clone, Debug, Default, PartialEq, Serialize)]
78pub struct ChatGptResponses {
79    /// `prompt_cache_key`: the key the backend routes cached prompts by. A
80    /// stable one, such as a conversation id, keeps a conversation's cache.
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub prompt_cache_key: Option<String>,
83    /// `client_metadata`: string pairs describing the client.
84    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
85    pub client_metadata: BTreeMap<String, String>,
86    /// `access_programs`: the access programs the request runs under.
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub access_programs: Option<AccessPrograms>,
89}
90
91/// The typed view of a ChatGPT reply: the terminal response object of its
92/// event stream. A field the reply does not carry is `None`.
93#[non_exhaustive]
94#[derive(Clone, Debug, Default, PartialEq)]
95pub struct ChatGptExtras {
96    /// `/service_tier`: the tier that served the request.
97    pub service_tier: Option<String>,
98    /// `/reasoning/effort`: the effort the model used.
99    pub reasoning_effort: Option<String>,
100    /// `/reasoning/summary`: `auto`, `concise` or `detailed`.
101    pub reasoning_summary: Option<String>,
102    /// `/reasoning/mode`: `standard` or `pro`.
103    pub reasoning_mode: Option<String>,
104    /// `/reasoning/context`: `auto`, `all_turns` or `current_turn`.
105    pub reasoning_context: Option<String>,
106    /// `/prompt_cache_retention`: the backend keeps prompts for `24h`.
107    pub prompt_cache_retention: Option<String>,
108    /// `/incomplete_details/reason`, such as `max_output_tokens`.
109    pub incomplete_reason: Option<String>,
110    /// The `phase` of each `message` item in `/output`, which the reply's
111    /// `raw` rebuilds from the items the stream finished; `None` when the
112    /// output holds no message.
113    pub phases: Option<Vec<ItemPhase>>,
114}
115
116impl ReplyExtras for ChatGptExtras {
117    fn from_reply(_api: &Api, raw: &Value) -> Result<Self, serde_json::Error> {
118        let envelope = Envelope::from_reply(raw)?;
119        Ok(Self {
120            service_tier: envelope.service_tier,
121            reasoning_effort: envelope.reasoning_effort,
122            reasoning_summary: envelope.reasoning_summary,
123            reasoning_mode: envelope.reasoning_mode,
124            reasoning_context: envelope.reasoning_context,
125            prompt_cache_retention: envelope.prompt_cache_retention,
126            incomplete_reason: envelope.incomplete_reason,
127            phases: envelope.phases,
128        })
129    }
130}
131
132#[cfg(test)]
133mod tests;