1mod api;
26mod client;
27
28use std::marker::PhantomData;
29
30use chat_core::types::provider_meta::ProviderMeta;
31
32pub use crate::api::types::error::{
33 ResponsesErrorDetail, ResponsesErrorResponse, handle_responses_error,
34};
35pub use crate::api::types::request::{ReasoningConfig, ResponsesRequest, ResponsesRequestConfig};
36pub use crate::api::types::response::{
37 ResponsesApiResponse, ResponsesContentPart, ResponsesFunctionCall,
38 ResponsesImageGenerationCall, ResponsesMessage, ResponsesOutputItem, ResponsesReasoning,
39 ResponsesSummaryPart, ResponsesUsage, ResponsesWebSearchCall, output_items_to_parts,
40};
41pub use crate::client::ResponsesClient;
42pub use chat_core::error::{ChatError, ChatFailure};
43pub use chat_core::transport::{Request, ReqwestTransport, Response, Transport, TransportError};
44
45use serde_json::Value;
46
47pub struct WithoutModel;
48pub struct WithModel;
49
50pub struct WithoutUrl;
51pub struct WithUrl;
52
53pub struct ResponsesBuilder<M = WithoutModel, U = WithoutUrl, T: Transport = ReqwestTransport> {
54 model_name: Option<String>,
55 api_key: Option<String>,
56 scheme: String,
57 host: String,
58 base_path: String,
59 extra_tool_declarations: Vec<Value>,
60 reasoning_effort: Option<String>,
61 use_previous_response_id: bool,
62 store: Option<bool>,
63 transport: Option<T>,
64 meta: ProviderMeta,
65 _m: PhantomData<M>,
66 _u: PhantomData<U>,
67}
68
69impl Default for ResponsesBuilder<WithoutModel, WithoutUrl, ReqwestTransport> {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl ResponsesBuilder<WithoutModel, WithoutUrl, ReqwestTransport> {
76 pub fn new() -> Self {
77 Self {
78 model_name: None,
79 api_key: None,
80 scheme: String::new(),
81 host: String::new(),
82 base_path: String::new(),
83 extra_tool_declarations: Vec::new(),
84 reasoning_effort: None,
85 use_previous_response_id: true,
86 store: None,
87 transport: Some(ReqwestTransport::default()),
88 meta: ProviderMeta::default(),
89 _m: PhantomData,
90 _u: PhantomData,
91 }
92 }
93}
94
95impl<M, U, T: Transport> ResponsesBuilder<M, U, T> {
96 pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
97 self.api_key = Some(api_key.into());
98 self
99 }
100
101 pub fn with_reasoning_effort(mut self, effort: impl Into<String>) -> Self {
102 self.reasoning_effort = Some(effort.into());
103 self
104 }
105
106 pub fn without_previous_response_id(mut self) -> Self {
107 self.use_previous_response_id = false;
108 self
109 }
110
111 pub fn with_store(mut self, store: bool) -> Self {
112 self.store = Some(store);
113 self
114 }
115
116 pub fn with_description(mut self, description: impl Into<String>) -> Self {
117 self.meta.description = Some(description.into());
118 self
119 }
120
121 pub fn with_metadata(
122 mut self,
123 key: impl Into<String>,
124 value: impl std::any::Any + Send + Sync + 'static,
125 ) -> Self {
126 self.meta.data.insert(key.into(), Box::new(value));
127 self
128 }
129
130 pub fn with_meta(mut self, meta: ProviderMeta) -> Self {
134 self.meta = meta;
135 self
136 }
137
138 pub fn with_tool_declaration(mut self, declaration: Value) -> Self {
142 self.extra_tool_declarations.push(declaration);
143 self
144 }
145
146 pub fn with_tool_declarations(mut self, declarations: impl IntoIterator<Item = Value>) -> Self {
147 self.extra_tool_declarations.extend(declarations);
148 self
149 }
150
151 pub fn with_transport<T2: Transport>(self, transport: T2) -> ResponsesBuilder<M, U, T2> {
152 ResponsesBuilder {
153 model_name: self.model_name,
154 api_key: self.api_key,
155 scheme: self.scheme,
156 host: self.host,
157 base_path: self.base_path,
158 extra_tool_declarations: self.extra_tool_declarations,
159 reasoning_effort: self.reasoning_effort,
160 use_previous_response_id: self.use_previous_response_id,
161 store: self.store,
162 transport: Some(transport),
163 meta: self.meta,
164 _m: PhantomData,
165 _u: PhantomData,
166 }
167 }
168}
169
170impl<U, T: Transport> ResponsesBuilder<WithoutModel, U, T> {
171 pub fn with_model(self, model: impl Into<String>) -> ResponsesBuilder<WithModel, U, T> {
172 ResponsesBuilder {
173 model_name: Some(model.into()),
174 api_key: self.api_key,
175 scheme: self.scheme,
176 host: self.host,
177 base_path: self.base_path,
178 extra_tool_declarations: self.extra_tool_declarations,
179 reasoning_effort: self.reasoning_effort,
180 use_previous_response_id: self.use_previous_response_id,
181 store: self.store,
182 transport: self.transport,
183 meta: self.meta,
184 _m: PhantomData,
185 _u: PhantomData,
186 }
187 }
188}
189
190impl<M, T: Transport> ResponsesBuilder<M, WithoutUrl, T> {
191 pub fn with_base_url(self, url: impl Into<String>) -> ResponsesBuilder<M, WithUrl, T> {
192 let url = url.into();
193 let parsed = url::Url::parse(&url).expect("Invalid base URL");
194 let scheme = parsed.scheme().to_string();
195 let host = parsed.host_str().expect("No host in URL").to_string()
196 + &parsed.port().map(|p| format!(":{p}")).unwrap_or_default();
197 let base_path = parsed.path().trim_end_matches('/').to_string();
198 ResponsesBuilder {
199 model_name: self.model_name,
200 api_key: self.api_key,
201 scheme,
202 host,
203 base_path,
204 extra_tool_declarations: self.extra_tool_declarations,
205 reasoning_effort: self.reasoning_effort,
206 use_previous_response_id: self.use_previous_response_id,
207 store: self.store,
208 transport: self.transport,
209 meta: self.meta,
210 _m: PhantomData,
211 _u: PhantomData,
212 }
213 }
214}
215
216impl<T: Transport> ResponsesBuilder<WithModel, WithUrl, T> {
217 pub fn build(self) -> ResponsesClient<T> {
218 let api_key = self
219 .api_key
220 .expect("No API key. Call .with_api_key() before .build().");
221
222 let transport = self.transport.expect("transport set");
223 let model = self.model_name.expect("model set");
224
225 ResponsesClient {
226 model_name: model,
227 api_key,
228 scheme: self.scheme,
229 host: self.host,
230 base_path: self.base_path,
231 transport,
232 extra_tool_declarations: self.extra_tool_declarations,
233 reasoning_effort: self.reasoning_effort,
234 use_previous_response_id: self.use_previous_response_id,
235 last_response_id: None,
236 store: self.store,
237 meta: self.meta,
238 }
239 }
240}