1use std::collections::HashMap;
2
3use derive_builder::Builder;
4use futures_util::{StreamExt, stream::BoxStream};
5use reqwest::Client as HttpClient;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9use crate::{
10 api::chat::{CacheControl, DebugOptions, Plugin, TraceOptions},
11 error::OpenRouterError,
12 strip_option_map_setter, strip_option_vec_setter,
13 transport::{
14 request as transport_request, response as transport_response, sse::response_lines,
15 },
16 types::{OpenRouterExperimentalMetadata, ProviderPreferences},
17 utils::parse_sse_frames,
18};
19
20#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
22#[builder(build_fn(error = "OpenRouterError"))]
23#[non_exhaustive]
24pub struct ResponsesRequest {
25 #[builder(setter(strip_option), default)]
26 #[serde(skip_serializing_if = "Option::is_none")]
27 input: Option<Value>,
28
29 #[builder(setter(into, strip_option), default)]
30 #[serde(skip_serializing_if = "Option::is_none")]
31 instructions: Option<String>,
32
33 #[builder(setter(custom), default)]
34 #[serde(skip_serializing_if = "Option::is_none")]
35 metadata: Option<HashMap<String, String>>,
36
37 #[builder(setter(custom), default)]
38 #[serde(skip_serializing_if = "Option::is_none")]
39 tools: Option<Vec<Value>>,
40
41 #[builder(setter(strip_option), default)]
42 #[serde(skip_serializing_if = "Option::is_none")]
43 tool_choice: Option<Value>,
44
45 #[builder(setter(strip_option), default)]
46 #[serde(skip_serializing_if = "Option::is_none")]
47 parallel_tool_calls: Option<bool>,
48
49 #[builder(setter(into, strip_option), default)]
50 #[serde(skip_serializing_if = "Option::is_none")]
51 model: Option<String>,
52
53 #[builder(setter(custom), default)]
54 #[serde(skip_serializing_if = "Option::is_none")]
55 models: Option<Vec<String>>,
56
57 #[builder(setter(strip_option), default)]
58 #[serde(skip_serializing_if = "Option::is_none")]
59 text: Option<Value>,
60
61 #[builder(setter(strip_option), default)]
62 #[serde(skip_serializing_if = "Option::is_none")]
63 reasoning: Option<Value>,
64
65 #[builder(setter(strip_option), default)]
66 #[serde(skip_serializing_if = "Option::is_none")]
67 max_output_tokens: Option<u32>,
68
69 #[builder(setter(strip_option), default)]
70 #[serde(skip_serializing_if = "Option::is_none")]
71 temperature: Option<f64>,
72
73 #[builder(setter(strip_option), default)]
74 #[serde(skip_serializing_if = "Option::is_none")]
75 top_p: Option<f64>,
76
77 #[builder(setter(strip_option), default)]
78 #[serde(skip_serializing_if = "Option::is_none")]
79 top_logprobs: Option<u32>,
80
81 #[builder(setter(strip_option), default)]
82 #[serde(skip_serializing_if = "Option::is_none")]
83 max_tool_calls: Option<u32>,
84
85 #[builder(setter(strip_option), default)]
86 #[serde(skip_serializing_if = "Option::is_none")]
87 presence_penalty: Option<f64>,
88
89 #[builder(setter(strip_option), default)]
90 #[serde(skip_serializing_if = "Option::is_none")]
91 frequency_penalty: Option<f64>,
92
93 #[builder(setter(strip_option), default)]
94 #[serde(skip_serializing_if = "Option::is_none")]
95 top_k: Option<f64>,
96
97 #[builder(setter(custom), default)]
98 #[serde(skip_serializing_if = "Option::is_none")]
99 image_config: Option<HashMap<String, Value>>,
100
101 #[builder(setter(custom), default)]
102 #[serde(skip_serializing_if = "Option::is_none")]
103 modalities: Option<Vec<String>>,
104
105 #[builder(setter(into, strip_option), default)]
106 #[serde(skip_serializing_if = "Option::is_none")]
107 prompt_cache_key: Option<String>,
108
109 #[builder(setter(into, strip_option), default)]
110 #[serde(skip_serializing_if = "Option::is_none")]
111 previous_response_id: Option<String>,
112
113 #[builder(setter(strip_option), default)]
114 #[serde(skip_serializing_if = "Option::is_none")]
115 prompt: Option<Value>,
116
117 #[builder(setter(custom), default)]
118 #[serde(skip_serializing_if = "Option::is_none")]
119 include: Option<Vec<String>>,
120
121 #[builder(setter(strip_option), default)]
122 #[serde(skip_serializing_if = "Option::is_none")]
123 background: Option<bool>,
124
125 #[builder(setter(into, strip_option), default)]
126 #[serde(skip_serializing_if = "Option::is_none")]
127 safety_identifier: Option<String>,
128
129 #[builder(setter(strip_option), default)]
130 #[serde(skip_serializing_if = "Option::is_none")]
131 store: Option<bool>,
132
133 #[builder(setter(into, strip_option), default)]
134 #[serde(skip_serializing_if = "Option::is_none")]
135 service_tier: Option<String>,
136
137 #[builder(setter(into, strip_option), default)]
138 #[serde(skip_serializing_if = "Option::is_none")]
139 truncation: Option<String>,
140
141 #[builder(setter(skip), default)]
142 #[serde(skip_serializing_if = "Option::is_none")]
143 stream: Option<bool>,
144
145 #[builder(setter(strip_option), default)]
146 #[serde(skip)]
147 experimental_metadata: Option<OpenRouterExperimentalMetadata>,
148
149 #[builder(setter(strip_option), default)]
150 #[serde(skip_serializing_if = "Option::is_none")]
151 provider: Option<ProviderPreferences>,
152
153 #[builder(setter(custom), default)]
154 #[serde(skip_serializing_if = "Option::is_none")]
155 plugins: Option<Vec<Plugin>>,
156
157 #[builder(setter(into, strip_option), default)]
158 #[serde(skip_serializing_if = "Option::is_none")]
159 route: Option<String>,
160
161 #[builder(setter(into, strip_option), default)]
162 #[serde(skip_serializing_if = "Option::is_none")]
163 user: Option<String>,
164
165 #[builder(setter(into, strip_option), default)]
166 #[serde(skip_serializing_if = "Option::is_none")]
167 session_id: Option<String>,
168
169 #[builder(setter(strip_option), default)]
170 #[serde(skip_serializing_if = "Option::is_none")]
171 cache_control: Option<CacheControl>,
172
173 #[builder(setter(strip_option), default)]
174 #[serde(skip_serializing_if = "Option::is_none")]
175 trace: Option<TraceOptions>,
176
177 #[builder(setter(strip_option), default)]
178 #[serde(skip_serializing_if = "Option::is_none")]
179 debug: Option<DebugOptions>,
180}
181
182impl ResponsesRequestBuilder {
183 strip_option_map_setter!(metadata, String, String);
184 strip_option_vec_setter!(tools, Value);
185 strip_option_vec_setter!(models, String);
186 strip_option_map_setter!(image_config, String, Value);
187 strip_option_vec_setter!(modalities, String);
188 strip_option_vec_setter!(include, String);
189 strip_option_vec_setter!(plugins, Plugin);
190}
191
192impl ResponsesRequest {
193 pub fn builder() -> ResponsesRequestBuilder {
194 ResponsesRequestBuilder::default()
195 }
196
197 pub fn new(model: impl Into<String>, input: Value) -> Self {
198 Self::builder()
199 .model(model.into())
200 .input(input)
201 .build()
202 .expect("Failed to build ResponsesRequest")
203 }
204
205 fn stream(&self, stream: bool) -> Self {
206 let mut req = self.clone();
207 req.stream = Some(stream);
208 req
209 }
210
211 pub fn experimental_metadata(&self) -> Option<OpenRouterExperimentalMetadata> {
212 self.experimental_metadata
213 }
214}
215
216#[derive(Serialize, Deserialize, Debug, Clone)]
218#[non_exhaustive]
219pub struct ResponsesResponse {
220 pub id: Option<String>,
221 #[serde(rename = "object")]
222 pub object_type: Option<String>,
223 pub created_at: Option<u64>,
224 pub model: Option<String>,
225 pub status: Option<String>,
226 #[serde(skip_serializing_if = "Option::is_none")]
227 pub output: Option<Vec<Value>>,
228 #[serde(skip_serializing_if = "Option::is_none")]
229 pub usage: Option<Value>,
230 #[serde(flatten)]
231 pub extra: HashMap<String, Value>,
232}
233
234#[derive(Serialize, Deserialize, Debug, Clone)]
236#[non_exhaustive]
237pub struct ResponsesStreamEvent {
238 #[serde(rename = "type")]
239 pub event_type: String,
240 #[serde(skip_serializing_if = "Option::is_none")]
241 pub sequence_number: Option<u64>,
242 #[serde(flatten)]
243 pub data: HashMap<String, Value>,
244}
245
246pub async fn create_response(
248 base_url: &str,
249 api_key: &str,
250 x_title: &Option<String>,
251 http_referer: &Option<String>,
252 app_categories: &Option<Vec<String>>,
253 request: &ResponsesRequest,
254) -> Result<ResponsesResponse, OpenRouterError> {
255 let http_client = crate::transport::new_client()?;
256 create_response_with_client(
257 &http_client,
258 base_url,
259 api_key,
260 x_title,
261 http_referer,
262 app_categories,
263 request,
264 )
265 .await
266}
267
268pub(crate) async fn create_response_with_client(
269 http_client: &HttpClient,
270 base_url: &str,
271 api_key: &str,
272 x_title: &Option<String>,
273 http_referer: &Option<String>,
274 app_categories: &Option<Vec<String>>,
275 request: &ResponsesRequest,
276) -> Result<ResponsesResponse, OpenRouterError> {
277 let url = format!("{base_url}/responses");
278 let request = request.stream(false);
279
280 let response = transport_request::with_experimental_metadata_header(
281 transport_request::with_client_request_headers(
282 transport_request::post(http_client, &url),
283 api_key,
284 x_title,
285 http_referer,
286 app_categories,
287 )?,
288 &request.experimental_metadata,
289 )
290 .json(&request)
291 .send()
292 .await?;
293
294 if response.status().is_success() {
295 let response_data: ResponsesResponse =
296 transport_response::parse_json_response(response, "responses API").await?;
297 Ok(response_data)
298 } else {
299 transport_response::handle_error(response).await?;
300 unreachable!()
301 }
302}
303
304pub async fn stream_response(
306 base_url: &str,
307 api_key: &str,
308 x_title: &Option<String>,
309 http_referer: &Option<String>,
310 app_categories: &Option<Vec<String>>,
311 request: &ResponsesRequest,
312) -> Result<BoxStream<'static, Result<ResponsesStreamEvent, OpenRouterError>>, OpenRouterError> {
313 let http_client = crate::transport::new_client()?;
314 stream_response_with_client(
315 &http_client,
316 base_url,
317 api_key,
318 x_title,
319 http_referer,
320 app_categories,
321 request,
322 )
323 .await
324}
325
326pub(crate) async fn stream_response_with_client(
327 http_client: &HttpClient,
328 base_url: &str,
329 api_key: &str,
330 x_title: &Option<String>,
331 http_referer: &Option<String>,
332 app_categories: &Option<Vec<String>>,
333 request: &ResponsesRequest,
334) -> Result<BoxStream<'static, Result<ResponsesStreamEvent, OpenRouterError>>, OpenRouterError> {
335 let url = format!("{base_url}/responses");
336 let request = request.stream(true);
337
338 let response = transport_request::with_experimental_metadata_header(
339 transport_request::with_client_request_headers(
340 transport_request::post(http_client, &url),
341 api_key,
342 x_title,
343 http_referer,
344 app_categories,
345 )?,
346 &request.experimental_metadata,
347 )
348 .json(&request)
349 .send()
350 .await?;
351
352 if response.status().is_success() {
353 let lines = parse_sse_frames(response_lines(response))
354 .filter_map(async |line| match line {
355 Ok(frame) if frame.data == "[DONE]" => None,
356 Ok(frame) => Some(
357 serde_json::from_str::<ResponsesStreamEvent>(&frame.data)
358 .map_err(OpenRouterError::Serialization),
359 ),
360 Err(error) => Some(Err(error)),
361 })
362 .boxed();
363
364 Ok(lines)
365 } else {
366 transport_response::handle_error(response).await?;
367 unreachable!()
368 }
369}