1use serde::{Deserialize, Serialize};
14use serde_json::Value;
15
16use crate::client::Client;
17use crate::error::{Error, Result};
18use crate::request;
19use crate::stream::EventStream;
20use crate::types::Plugin;
21
22pub mod reasoning_effort {
24 pub const MINIMAL: &str = "minimal";
26 pub const LOW: &str = "low";
28 pub const MEDIUM: &str = "medium";
30 pub const HIGH: &str = "high";
32}
33
34#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
36pub struct ResponsesReasoning {
37 pub effort: String,
40}
41
42#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
45pub struct ResponsesInputContent {
46 #[serde(rename = "type")]
48 pub kind: String,
49 pub text: String,
51}
52
53impl ResponsesInputContent {
54 pub fn input_text(text: impl Into<String>) -> Self {
56 Self {
57 kind: "input_text".into(),
58 text: text.into(),
59 }
60 }
61}
62
63#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
66pub struct ResponsesInputItem {
67 #[serde(rename = "type")]
69 pub kind: String,
70 #[serde(skip_serializing_if = "Option::is_none", default)]
72 pub id: Option<String>,
73 #[serde(skip_serializing_if = "Option::is_none", default)]
75 pub status: Option<String>,
76 #[serde(skip_serializing_if = "Option::is_none", default)]
78 pub role: Option<String>,
79 #[serde(skip_serializing_if = "Vec::is_empty", default)]
81 pub content: Vec<ResponsesInputContent>,
82 #[serde(skip_serializing_if = "Option::is_none", default)]
84 pub call_id: Option<String>,
85 #[serde(skip_serializing_if = "Option::is_none", default)]
87 pub output: Option<String>,
88}
89
90impl ResponsesInputItem {
91 pub fn message(role: impl Into<String>, text: impl Into<String>) -> Self {
93 Self {
94 kind: "message".into(),
95 role: Some(role.into()),
96 content: vec![ResponsesInputContent::input_text(text)],
97 ..Default::default()
98 }
99 }
100
101 pub fn user(text: impl Into<String>) -> Self {
103 Self::message("user", text)
104 }
105
106 pub fn assistant(text: impl Into<String>) -> Self {
108 Self::message("assistant", text)
109 }
110
111 pub fn system(text: impl Into<String>) -> Self {
113 Self::message("system", text)
114 }
115
116 pub fn function_call_output(call_id: impl Into<String>, output: impl Into<String>) -> Self {
118 Self {
119 kind: "function_call_output".into(),
120 call_id: Some(call_id.into()),
121 output: Some(output.into()),
122 ..Default::default()
123 }
124 }
125}
126
127#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
130pub struct ResponsesTool {
131 #[serde(rename = "type")]
133 pub kind: String,
134 pub name: String,
136 #[serde(skip_serializing_if = "String::is_empty", default)]
138 pub description: String,
139 pub strict: Option<bool>,
142 #[serde(skip_serializing_if = "Option::is_none", default)]
144 pub parameters: Option<Value>,
145}
146
147impl ResponsesTool {
148 pub fn function(
150 name: impl Into<String>,
151 description: impl Into<String>,
152 parameters: Value,
153 ) -> Self {
154 Self {
155 kind: "function".into(),
156 name: name.into(),
157 description: description.into(),
158 strict: None,
159 parameters: Some(parameters),
160 }
161 }
162}
163
164#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
167#[serde(untagged)]
168pub enum ResponsesInput {
169 Text(String),
171 Items(Vec<ResponsesInputItem>),
173}
174
175impl From<&str> for ResponsesInput {
176 fn from(s: &str) -> Self {
177 ResponsesInput::Text(s.to_string())
178 }
179}
180
181impl From<String> for ResponsesInput {
182 fn from(s: String) -> Self {
183 ResponsesInput::Text(s)
184 }
185}
186
187impl From<Vec<ResponsesInputItem>> for ResponsesInput {
188 fn from(v: Vec<ResponsesInputItem>) -> Self {
189 ResponsesInput::Items(v)
190 }
191}
192
193#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
195pub struct ResponsesRequest {
196 pub model: String,
198 pub input: Option<ResponsesInput>,
200 #[serde(skip_serializing_if = "Option::is_none", default)]
203 pub stream: Option<bool>,
204 #[serde(skip_serializing_if = "Option::is_none", default)]
206 pub max_output_tokens: Option<u32>,
207 #[serde(skip_serializing_if = "Option::is_none", default)]
209 pub temperature: Option<f64>,
210 #[serde(skip_serializing_if = "Option::is_none", default)]
212 pub top_p: Option<f64>,
213 #[serde(skip_serializing_if = "Option::is_none", default)]
215 pub reasoning: Option<ResponsesReasoning>,
216 #[serde(skip_serializing_if = "Vec::is_empty", default)]
218 pub tools: Vec<ResponsesTool>,
219 #[serde(skip_serializing_if = "Option::is_none", default)]
221 pub tool_choice: Option<Value>,
222 #[serde(skip_serializing_if = "Vec::is_empty", default)]
224 pub plugins: Vec<Plugin>,
225}
226
227impl ResponsesRequest {
228 pub fn new(model: impl Into<String>) -> Self {
230 Self {
231 model: model.into(),
232 ..Default::default()
233 }
234 }
235
236 pub fn input(mut self, input: impl Into<ResponsesInput>) -> Self {
238 self.input = Some(input.into());
239 self
240 }
241
242 pub fn max_output_tokens(mut self, n: u32) -> Self {
244 self.max_output_tokens = Some(n);
245 self
246 }
247
248 pub fn temperature(mut self, t: f64) -> Self {
250 self.temperature = Some(t);
251 self
252 }
253
254 pub fn top_p(mut self, p: f64) -> Self {
256 self.top_p = Some(p);
257 self
258 }
259
260 pub fn reasoning_effort(mut self, effort: impl Into<String>) -> Self {
262 self.reasoning = Some(ResponsesReasoning {
263 effort: effort.into(),
264 });
265 self
266 }
267
268 pub fn tools(mut self, tools: impl IntoIterator<Item = ResponsesTool>) -> Self {
270 self.tools = tools.into_iter().collect();
271 self
272 }
273
274 pub fn tool_choice(mut self, choice: Value) -> Self {
276 self.tool_choice = Some(choice);
277 self
278 }
279
280 pub fn plugins(mut self, plugins: impl IntoIterator<Item = Plugin>) -> Self {
282 self.plugins.extend(plugins);
283 self
284 }
285
286 pub fn web_search(mut self, max_results: u32) -> Self {
288 use crate::types::WebPluginConfig;
289 self.plugins.push(Plugin::web_with(
290 WebPluginConfig::new().with_max_results(max_results),
291 ));
292 self
293 }
294
295 fn validate(&self) -> Result<()> {
297 if self.model.is_empty() {
298 return Err(Error::InvalidInput("model is required"));
299 }
300 let input = self
301 .input
302 .as_ref()
303 .ok_or(Error::InvalidInput("input is required"))?;
304 match input {
305 ResponsesInput::Text(s) if s.is_empty() => {
306 return Err(Error::InvalidInput("input string cannot be empty"));
307 }
308 ResponsesInput::Items(v) if v.is_empty() => {
309 return Err(Error::InvalidInput("input array cannot be empty"));
310 }
311 ResponsesInput::Items(items) => {
312 for item in items {
313 if item.kind.is_empty() {
314 return Err(Error::InvalidInput("input item type is required"));
315 }
316 match item.kind.as_str() {
317 "message" => {
318 let role = item
319 .role
320 .as_deref()
321 .ok_or(Error::InvalidInput("message role is required"))?;
322 if !matches!(role, "user" | "assistant" | "system") {
323 return Err(Error::InvalidInput(
324 "message role must be user/assistant/system",
325 ));
326 }
327 }
328 "function_call_output"
329 if item.call_id.as_deref().unwrap_or("").is_empty() =>
330 {
331 return Err(Error::InvalidInput(
332 "function_call_output requires call_id",
333 ));
334 }
335 _ => {}
336 }
337 }
338 }
339 _ => {}
340 }
341 if let Some(r) = &self.reasoning {
342 if !matches!(
343 r.effort.as_str(),
344 reasoning_effort::MINIMAL
345 | reasoning_effort::LOW
346 | reasoning_effort::MEDIUM
347 | reasoning_effort::HIGH
348 ) {
349 return Err(Error::InvalidInput(
350 "reasoning.effort must be minimal/low/medium/high",
351 ));
352 }
353 }
354 for tool in &self.tools {
355 if tool.kind.is_empty() {
356 return Err(Error::InvalidInput("tool type is required"));
357 }
358 if tool.name.is_empty() {
359 return Err(Error::InvalidInput("tool name is required"));
360 }
361 }
362 Ok(())
363 }
364}
365
366#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
368pub struct ResponsesAnnotation {
369 #[serde(rename = "type", default)]
371 pub kind: String,
372 #[serde(default, skip_serializing_if = "String::is_empty")]
374 pub url: String,
375 #[serde(default)]
377 pub start_index: i64,
378 #[serde(default)]
380 pub end_index: i64,
381}
382
383#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
385pub struct ResponsesOutputContent {
386 #[serde(rename = "type", default)]
388 pub kind: String,
389 #[serde(default, skip_serializing_if = "String::is_empty")]
391 pub text: String,
392 #[serde(default, skip_serializing_if = "Vec::is_empty")]
394 pub annotations: Vec<ResponsesAnnotation>,
395 #[serde(default, skip_serializing_if = "String::is_empty")]
397 pub encrypted_content: String,
398 #[serde(default, skip_serializing_if = "Vec::is_empty")]
400 pub summary: Vec<String>,
401}
402
403#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
405pub struct ResponsesOutput {
406 #[serde(rename = "type", default)]
408 pub kind: String,
409 #[serde(default)]
411 pub id: String,
412 #[serde(default, skip_serializing_if = "String::is_empty")]
414 pub status: String,
415 #[serde(default, skip_serializing_if = "String::is_empty")]
417 pub role: String,
418 #[serde(default, skip_serializing_if = "Vec::is_empty")]
420 pub content: Vec<ResponsesOutputContent>,
421 #[serde(default, skip_serializing_if = "String::is_empty")]
423 pub call_id: String,
424 #[serde(default, skip_serializing_if = "String::is_empty")]
426 pub name: String,
427 #[serde(default, skip_serializing_if = "String::is_empty")]
429 pub arguments: String,
430}
431
432#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
434pub struct ResponsesUsage {
435 #[serde(default)]
437 pub input_tokens: u64,
438 #[serde(default)]
440 pub output_tokens: u64,
441 #[serde(default)]
443 pub total_tokens: u64,
444 #[serde(default)]
446 pub reasoning_tokens: u64,
447}
448
449#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
451pub struct ResponsesResponse {
452 #[serde(default)]
454 pub id: String,
455 #[serde(default)]
457 pub object: String,
458 #[serde(default)]
460 pub created_at: i64,
461 #[serde(default)]
463 pub model: String,
464 #[serde(default)]
466 pub output: Vec<ResponsesOutput>,
467 #[serde(default)]
469 pub usage: ResponsesUsage,
470 #[serde(default)]
472 pub status: String,
473 #[serde(default)]
475 pub metadata: Option<Value>,
476}
477
478impl ResponsesResponse {
479 pub fn text_content(&self) -> &str {
481 for o in &self.output {
482 if o.kind == "message" {
483 for c in &o.content {
484 if c.kind == "output_text" && !c.text.is_empty() {
485 return &c.text;
486 }
487 }
488 }
489 }
490 ""
491 }
492
493 pub fn function_calls(&self) -> Vec<&ResponsesOutput> {
495 self.output
496 .iter()
497 .filter(|o| o.kind == "function_call")
498 .collect()
499 }
500
501 pub fn annotations(&self) -> Vec<&ResponsesAnnotation> {
503 self.output
504 .iter()
505 .flat_map(|o| o.content.iter().flat_map(|c| c.annotations.iter()))
506 .collect()
507 }
508
509 pub fn reasoning_summary(&self) -> Option<&[String]> {
511 for o in &self.output {
512 for c in &o.content {
513 if c.kind == "reasoning" && !c.summary.is_empty() {
514 return Some(&c.summary);
515 }
516 }
517 }
518 None
519 }
520}
521
522impl Client {
523 pub async fn create_response(&self, mut req: ResponsesRequest) -> Result<ResponsesResponse> {
528 req.stream = Some(false);
529 req.validate()?;
530 request::execute_json(self, "responses", &req).await
531 }
532
533 pub async fn create_response_stream(
539 &self,
540 mut req: ResponsesRequest,
541 ) -> Result<EventStream<ResponsesResponse>> {
542 req.stream = Some(true);
543 req.validate()?;
544 self.open_event_stream("responses", &req).await
545 }
546}
547
548#[cfg(test)]
549mod tests {
550 use super::*;
551
552 #[test]
553 fn untagged_input_serializes_string() {
554 let req = ResponsesRequest::new("m").input("hello");
555 let v = serde_json::to_value(&req).unwrap();
556 assert_eq!(v["input"], serde_json::json!("hello"));
557 }
558
559 #[test]
560 fn untagged_input_serializes_items() {
561 let req = ResponsesRequest::new("m").input(vec![ResponsesInputItem::user("hi")]);
562 let v = serde_json::to_value(&req).unwrap();
563 assert_eq!(v["input"][0]["type"], "message");
564 assert_eq!(v["input"][0]["role"], "user");
565 assert_eq!(v["input"][0]["content"][0]["type"], "input_text");
566 assert_eq!(v["input"][0]["content"][0]["text"], "hi");
567 }
568
569 #[test]
570 fn validate_rejects_empty_text_input() {
571 let err = ResponsesRequest::new("m").input("").validate().unwrap_err();
572 assert!(matches!(err, Error::InvalidInput(_)));
573 }
574
575 #[test]
576 fn validate_rejects_bad_reasoning_effort() {
577 let mut req = ResponsesRequest::new("m").input("hi");
578 req.reasoning = Some(ResponsesReasoning {
579 effort: "absurd".into(),
580 });
581 let err = req.validate().unwrap_err();
582 assert!(matches!(err, Error::InvalidInput(_)));
583 }
584
585 #[test]
586 fn text_content_extraction() {
587 let r = ResponsesResponse {
588 output: vec![ResponsesOutput {
589 kind: "message".into(),
590 content: vec![ResponsesOutputContent {
591 kind: "output_text".into(),
592 text: "hello world".into(),
593 ..Default::default()
594 }],
595 ..Default::default()
596 }],
597 ..Default::default()
598 };
599 assert_eq!(r.text_content(), "hello world");
600 }
601}