1use std::collections::HashMap;
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use validator::Validate;
9
10use super::{
11 common::{
12 default_model, default_true, validate_stop, ChatLogProbs, Function, GenerationRequest,
13 PromptTokenUsageInfo, StringOrArray, ToolChoice, ToolChoiceValue, ToolReference, UsageInfo,
14 },
15 sampling_params::{validate_top_k_value, validate_top_p_value},
16};
17use crate::{builders::ResponsesResponseBuilder, validated::Normalizable};
18
19#[derive(Debug, Clone, Deserialize, Serialize)]
24pub struct ResponseTool {
25 #[serde(rename = "type")]
26 pub r#type: ResponseToolType,
27 #[serde(flatten)]
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub function: Option<Function>,
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub server_url: Option<String>,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub authorization: Option<String>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub server_label: Option<String>,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub server_description: Option<String>,
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub require_approval: Option<String>,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub allowed_tools: Option<Vec<String>>,
45}
46
47impl Default for ResponseTool {
48 fn default() -> Self {
49 Self {
50 r#type: ResponseToolType::WebSearchPreview,
51 function: None,
52 server_url: None,
53 authorization: None,
54 server_label: None,
55 server_description: None,
56 require_approval: None,
57 allowed_tools: None,
58 }
59 }
60}
61
62#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
63#[serde(rename_all = "snake_case")]
64pub enum ResponseToolType {
65 Function,
66 WebSearchPreview,
67 CodeInterpreter,
68 Mcp,
69}
70
71#[derive(Debug, Clone, Deserialize, Serialize)]
76pub struct ResponseReasoningParam {
77 #[serde(default = "default_reasoning_effort")]
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub effort: Option<ReasoningEffort>,
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub summary: Option<ReasoningSummary>,
82}
83
84fn default_reasoning_effort() -> Option<ReasoningEffort> {
85 Some(ReasoningEffort::Medium)
86}
87
88#[derive(Debug, Clone, Deserialize, Serialize)]
89#[serde(rename_all = "snake_case")]
90pub enum ReasoningEffort {
91 Minimal,
92 Low,
93 Medium,
94 High,
95}
96
97#[derive(Debug, Clone, Deserialize, Serialize)]
98#[serde(rename_all = "snake_case")]
99pub enum ReasoningSummary {
100 Auto,
101 Concise,
102 Detailed,
103}
104
105#[derive(Debug, Clone, Deserialize, Serialize)]
111#[serde(untagged)]
112pub enum StringOrContentParts {
113 String(String),
114 Array(Vec<ResponseContentPart>),
115}
116
117#[derive(Debug, Clone, Deserialize, Serialize)]
118#[serde(tag = "type")]
119#[serde(rename_all = "snake_case")]
120pub enum ResponseInputOutputItem {
121 #[serde(rename = "message")]
122 Message {
123 id: String,
124 role: String,
125 content: Vec<ResponseContentPart>,
126 #[serde(skip_serializing_if = "Option::is_none")]
127 status: Option<String>,
128 },
129 #[serde(rename = "reasoning")]
130 Reasoning {
131 id: String,
132 summary: Vec<String>,
133 #[serde(skip_serializing_if = "Vec::is_empty")]
134 #[serde(default)]
135 content: Vec<ResponseReasoningContent>,
136 #[serde(skip_serializing_if = "Option::is_none")]
137 status: Option<String>,
138 },
139 #[serde(rename = "function_call")]
140 FunctionToolCall {
141 id: String,
142 call_id: String,
143 name: String,
144 arguments: String,
145 #[serde(skip_serializing_if = "Option::is_none")]
146 output: Option<String>,
147 #[serde(skip_serializing_if = "Option::is_none")]
148 status: Option<String>,
149 },
150 #[serde(rename = "function_call_output")]
151 FunctionCallOutput {
152 id: Option<String>,
153 call_id: String,
154 output: String,
155 #[serde(skip_serializing_if = "Option::is_none")]
156 status: Option<String>,
157 },
158 #[serde(untagged)]
159 SimpleInputMessage {
160 content: StringOrContentParts,
161 role: String,
162 #[serde(skip_serializing_if = "Option::is_none")]
163 #[serde(rename = "type")]
164 r#type: Option<String>,
165 },
166}
167
168#[derive(Debug, Clone, Deserialize, Serialize)]
169#[serde(tag = "type")]
170#[serde(rename_all = "snake_case")]
171pub enum ResponseContentPart {
172 #[serde(rename = "output_text")]
173 OutputText {
174 text: String,
175 #[serde(default)]
176 #[serde(skip_serializing_if = "Vec::is_empty")]
177 annotations: Vec<String>,
178 #[serde(skip_serializing_if = "Option::is_none")]
179 logprobs: Option<ChatLogProbs>,
180 },
181 #[serde(rename = "input_text")]
182 InputText { text: String },
183 #[serde(other)]
184 Unknown,
185}
186
187#[derive(Debug, Clone, Deserialize, Serialize)]
188#[serde(tag = "type")]
189#[serde(rename_all = "snake_case")]
190pub enum ResponseReasoningContent {
191 #[serde(rename = "reasoning_text")]
192 ReasoningText { text: String },
193}
194
195#[derive(Debug, Clone, Deserialize, Serialize)]
197pub struct McpToolInfo {
198 pub name: String,
199 #[serde(skip_serializing_if = "Option::is_none")]
200 pub description: Option<String>,
201 pub input_schema: Value,
202 #[serde(skip_serializing_if = "Option::is_none")]
203 pub annotations: Option<Value>,
204}
205
206#[derive(Debug, Clone, Deserialize, Serialize)]
207#[serde(tag = "type")]
208#[serde(rename_all = "snake_case")]
209pub enum ResponseOutputItem {
210 #[serde(rename = "message")]
211 Message {
212 id: String,
213 role: String,
214 content: Vec<ResponseContentPart>,
215 status: String,
216 },
217 #[serde(rename = "reasoning")]
218 Reasoning {
219 id: String,
220 summary: Vec<String>,
221 content: Vec<ResponseReasoningContent>,
222 #[serde(skip_serializing_if = "Option::is_none")]
223 status: Option<String>,
224 },
225 #[serde(rename = "function_call")]
226 FunctionToolCall {
227 id: String,
228 call_id: String,
229 name: String,
230 arguments: String,
231 #[serde(skip_serializing_if = "Option::is_none")]
232 output: Option<String>,
233 status: String,
234 },
235 #[serde(rename = "mcp_list_tools")]
236 McpListTools {
237 id: String,
238 server_label: String,
239 tools: Vec<McpToolInfo>,
240 },
241 #[serde(rename = "mcp_call")]
242 McpCall {
243 id: String,
244 status: String,
245 #[serde(skip_serializing_if = "Option::is_none")]
246 approval_request_id: Option<String>,
247 arguments: String,
248 #[serde(skip_serializing_if = "Option::is_none")]
249 error: Option<String>,
250 name: String,
251 output: String,
252 server_label: String,
253 },
254}
255
256#[derive(Debug, Clone, Deserialize, Serialize, Default)]
261#[serde(rename_all = "snake_case")]
262pub enum ServiceTier {
263 #[default]
264 Auto,
265 Default,
266 Flex,
267 Scale,
268 Priority,
269}
270
271#[derive(Debug, Clone, Deserialize, Serialize, Default)]
272#[serde(rename_all = "snake_case")]
273pub enum Truncation {
274 Auto,
275 #[default]
276 Disabled,
277}
278
279#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
280#[serde(rename_all = "snake_case")]
281pub enum ResponseStatus {
282 Queued,
283 InProgress,
284 Completed,
285 Failed,
286 Cancelled,
287}
288
289#[derive(Debug, Clone, Deserialize, Serialize)]
290pub struct ReasoningInfo {
291 #[serde(skip_serializing_if = "Option::is_none")]
292 pub effort: Option<String>,
293 #[serde(skip_serializing_if = "Option::is_none")]
294 pub summary: Option<String>,
295}
296
297#[derive(Debug, Clone, Deserialize, Serialize)]
303pub struct TextConfig {
304 #[serde(skip_serializing_if = "Option::is_none")]
305 pub format: Option<TextFormat>,
306}
307
308#[derive(Debug, Clone, Deserialize, Serialize)]
310#[serde(tag = "type")]
311pub enum TextFormat {
312 #[serde(rename = "text")]
313 Text,
314
315 #[serde(rename = "json_object")]
316 JsonObject,
317
318 #[serde(rename = "json_schema")]
319 JsonSchema {
320 name: String,
321 schema: Value,
322 #[serde(skip_serializing_if = "Option::is_none")]
323 description: Option<String>,
324 #[serde(skip_serializing_if = "Option::is_none")]
325 strict: Option<bool>,
326 },
327}
328
329#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
330#[serde(rename_all = "snake_case")]
331pub enum IncludeField {
332 #[serde(rename = "code_interpreter_call.outputs")]
333 CodeInterpreterCallOutputs,
334 #[serde(rename = "computer_call_output.output.image_url")]
335 ComputerCallOutputImageUrl,
336 #[serde(rename = "file_search_call.results")]
337 FileSearchCallResults,
338 #[serde(rename = "message.input_image.image_url")]
339 MessageInputImageUrl,
340 #[serde(rename = "message.output_text.logprobs")]
341 MessageOutputTextLogprobs,
342 #[serde(rename = "reasoning.encrypted_content")]
343 ReasoningEncryptedContent,
344}
345
346#[derive(Debug, Clone, Deserialize, Serialize)]
352pub struct ResponseUsage {
353 pub input_tokens: u32,
354 pub output_tokens: u32,
355 pub total_tokens: u32,
356 #[serde(skip_serializing_if = "Option::is_none")]
357 pub input_tokens_details: Option<InputTokensDetails>,
358 #[serde(skip_serializing_if = "Option::is_none")]
359 pub output_tokens_details: Option<OutputTokensDetails>,
360}
361
362#[derive(Debug, Clone, Deserialize, Serialize)]
363#[serde(untagged)]
364pub enum ResponsesUsage {
365 Classic(UsageInfo),
366 Modern(ResponseUsage),
367}
368
369#[derive(Debug, Clone, Deserialize, Serialize)]
370pub struct InputTokensDetails {
371 pub cached_tokens: u32,
372}
373
374#[derive(Debug, Clone, Deserialize, Serialize)]
375pub struct OutputTokensDetails {
376 pub reasoning_tokens: u32,
377}
378
379impl UsageInfo {
380 pub fn to_response_usage(&self) -> ResponseUsage {
382 ResponseUsage {
383 input_tokens: self.prompt_tokens,
384 output_tokens: self.completion_tokens,
385 total_tokens: self.total_tokens,
386 input_tokens_details: self.prompt_tokens_details.as_ref().map(|details| {
387 InputTokensDetails {
388 cached_tokens: details.cached_tokens,
389 }
390 }),
391 output_tokens_details: self.reasoning_tokens.map(|tokens| OutputTokensDetails {
392 reasoning_tokens: tokens,
393 }),
394 }
395 }
396}
397
398impl From<UsageInfo> for ResponseUsage {
399 fn from(usage: UsageInfo) -> Self {
400 usage.to_response_usage()
401 }
402}
403
404impl ResponseUsage {
405 pub fn to_usage_info(&self) -> UsageInfo {
407 UsageInfo {
408 prompt_tokens: self.input_tokens,
409 completion_tokens: self.output_tokens,
410 total_tokens: self.total_tokens,
411 reasoning_tokens: self
412 .output_tokens_details
413 .as_ref()
414 .map(|details| details.reasoning_tokens),
415 prompt_tokens_details: self.input_tokens_details.as_ref().map(|details| {
416 PromptTokenUsageInfo {
417 cached_tokens: details.cached_tokens,
418 }
419 }),
420 }
421 }
422}
423
424#[derive(Debug, Clone, Default, Deserialize, Serialize)]
425pub struct ResponsesGetParams {
426 #[serde(default)]
427 pub include: Vec<String>,
428 #[serde(default)]
429 pub include_obfuscation: Option<bool>,
430 #[serde(default)]
431 pub starting_after: Option<i64>,
432 #[serde(default)]
433 pub stream: Option<bool>,
434}
435
436impl ResponsesUsage {
437 pub fn to_response_usage(&self) -> ResponseUsage {
438 match self {
439 ResponsesUsage::Classic(usage) => usage.to_response_usage(),
440 ResponsesUsage::Modern(usage) => usage.clone(),
441 }
442 }
443
444 pub fn to_usage_info(&self) -> UsageInfo {
445 match self {
446 ResponsesUsage::Classic(usage) => usage.clone(),
447 ResponsesUsage::Modern(usage) => usage.to_usage_info(),
448 }
449 }
450}
451
452fn default_top_k() -> i32 {
457 -1
458}
459
460fn default_repetition_penalty() -> f32 {
461 1.0
462}
463
464fn default_temperature() -> Option<f32> {
465 Some(1.0)
466}
467
468fn default_top_p() -> Option<f32> {
469 Some(1.0)
470}
471
472#[derive(Debug, Clone, Deserialize, Serialize, Validate)]
477#[validate(schema(function = "validate_responses_cross_parameters"))]
478pub struct ResponsesRequest {
479 #[serde(skip_serializing_if = "Option::is_none")]
481 pub background: Option<bool>,
482
483 #[serde(skip_serializing_if = "Option::is_none")]
485 pub include: Option<Vec<IncludeField>>,
486
487 #[validate(custom(function = "validate_response_input"))]
489 pub input: ResponseInput,
490
491 #[serde(skip_serializing_if = "Option::is_none")]
493 pub instructions: Option<String>,
494
495 #[serde(skip_serializing_if = "Option::is_none")]
497 #[validate(range(min = 1))]
498 pub max_output_tokens: Option<u32>,
499
500 #[serde(skip_serializing_if = "Option::is_none")]
502 #[validate(range(min = 1))]
503 pub max_tool_calls: Option<u32>,
504
505 #[serde(skip_serializing_if = "Option::is_none")]
507 pub metadata: Option<HashMap<String, Value>>,
508
509 #[serde(default = "default_model")]
511 pub model: String,
512
513 #[serde(skip_serializing_if = "Option::is_none")]
515 #[validate(custom(function = "validate_conversation_id"))]
516 pub conversation: Option<String>,
517
518 #[serde(skip_serializing_if = "Option::is_none")]
520 pub parallel_tool_calls: Option<bool>,
521
522 #[serde(skip_serializing_if = "Option::is_none")]
524 pub previous_response_id: Option<String>,
525
526 #[serde(skip_serializing_if = "Option::is_none")]
528 pub reasoning: Option<ResponseReasoningParam>,
529
530 #[serde(skip_serializing_if = "Option::is_none")]
532 pub service_tier: Option<ServiceTier>,
533
534 #[serde(skip_serializing_if = "Option::is_none")]
536 pub store: Option<bool>,
537
538 #[serde(default)]
540 pub stream: Option<bool>,
541
542 #[serde(
544 default = "default_temperature",
545 skip_serializing_if = "Option::is_none"
546 )]
547 #[validate(range(min = 0.0, max = 2.0))]
548 pub temperature: Option<f32>,
549
550 #[serde(skip_serializing_if = "Option::is_none")]
552 pub tool_choice: Option<ToolChoice>,
553
554 #[serde(skip_serializing_if = "Option::is_none")]
556 #[validate(custom(function = "validate_response_tools"))]
557 pub tools: Option<Vec<ResponseTool>>,
558
559 #[serde(skip_serializing_if = "Option::is_none")]
561 #[validate(range(min = 0, max = 20))]
562 pub top_logprobs: Option<u32>,
563
564 #[serde(default = "default_top_p", skip_serializing_if = "Option::is_none")]
566 #[validate(custom(function = "validate_top_p_value"))]
567 pub top_p: Option<f32>,
568
569 #[serde(skip_serializing_if = "Option::is_none")]
571 pub truncation: Option<Truncation>,
572
573 #[serde(skip_serializing_if = "Option::is_none")]
575 #[validate(custom(function = "validate_text_format"))]
576 pub text: Option<TextConfig>,
577
578 #[serde(skip_serializing_if = "Option::is_none")]
580 pub user: Option<String>,
581
582 #[serde(skip_serializing_if = "Option::is_none")]
584 pub request_id: Option<String>,
585
586 #[serde(default)]
588 pub priority: i32,
589
590 #[serde(skip_serializing_if = "Option::is_none")]
592 #[validate(range(min = -2.0, max = 2.0))]
593 pub frequency_penalty: Option<f32>,
594
595 #[serde(skip_serializing_if = "Option::is_none")]
597 #[validate(range(min = -2.0, max = 2.0))]
598 pub presence_penalty: Option<f32>,
599
600 #[serde(skip_serializing_if = "Option::is_none")]
602 #[validate(custom(function = "validate_stop"))]
603 pub stop: Option<StringOrArray>,
604
605 #[serde(default = "default_top_k")]
607 #[validate(custom(function = "validate_top_k_value"))]
608 pub top_k: i32,
609
610 #[serde(default)]
612 #[validate(range(min = 0.0, max = 1.0))]
613 pub min_p: f32,
614
615 #[serde(default = "default_repetition_penalty")]
617 #[validate(range(min = 0.0, max = 2.0))]
618 pub repetition_penalty: f32,
619}
620
621#[derive(Debug, Clone, Deserialize, Serialize)]
622#[serde(untagged)]
623pub enum ResponseInput {
624 Items(Vec<ResponseInputOutputItem>),
625 Text(String),
626}
627
628impl Default for ResponsesRequest {
629 fn default() -> Self {
630 Self {
631 background: None,
632 include: None,
633 input: ResponseInput::Text(String::new()),
634 instructions: None,
635 max_output_tokens: None,
636 max_tool_calls: None,
637 metadata: None,
638 model: default_model(),
639 conversation: None,
640 parallel_tool_calls: None,
641 previous_response_id: None,
642 reasoning: None,
643 service_tier: None,
644 store: None,
645 stream: None,
646 temperature: None,
647 tool_choice: None,
648 tools: None,
649 top_logprobs: None,
650 top_p: None,
651 truncation: None,
652 text: None,
653 user: None,
654 request_id: None,
655 priority: 0,
656 frequency_penalty: None,
657 presence_penalty: None,
658 stop: None,
659 top_k: default_top_k(),
660 min_p: 0.0,
661 repetition_penalty: default_repetition_penalty(),
662 }
663 }
664}
665
666impl Normalizable for ResponsesRequest {
667 fn normalize(&mut self) {
672 if self.tool_choice.is_none() {
674 if let Some(tools) = &self.tools {
675 let choice_value = if !tools.is_empty() {
676 ToolChoiceValue::Auto
677 } else {
678 ToolChoiceValue::None
679 };
680 self.tool_choice = Some(ToolChoice::Value(choice_value));
681 }
682 }
684
685 if self.parallel_tool_calls.is_none() && self.tools.is_some() {
687 self.parallel_tool_calls = Some(true);
688 }
689
690 if self.store.is_none() {
692 self.store = Some(true);
693 }
694 }
695}
696
697impl GenerationRequest for ResponsesRequest {
698 fn is_stream(&self) -> bool {
699 self.stream.unwrap_or(false)
700 }
701
702 fn get_model(&self) -> Option<&str> {
703 Some(self.model.as_str())
704 }
705
706 fn extract_text_for_routing(&self) -> String {
707 match &self.input {
708 ResponseInput::Text(text) => text.clone(),
709 ResponseInput::Items(items) => items
710 .iter()
711 .filter_map(|item| match item {
712 ResponseInputOutputItem::Message { content, .. } => {
713 let texts: Vec<String> = content
714 .iter()
715 .filter_map(|part| match part {
716 ResponseContentPart::OutputText { text, .. } => Some(text.clone()),
717 ResponseContentPart::InputText { text } => Some(text.clone()),
718 ResponseContentPart::Unknown => None,
719 })
720 .collect();
721 if texts.is_empty() {
722 None
723 } else {
724 Some(texts.join(" "))
725 }
726 }
727 ResponseInputOutputItem::SimpleInputMessage { content, .. } => {
728 match content {
729 StringOrContentParts::String(s) => Some(s.clone()),
730 StringOrContentParts::Array(parts) => {
731 let texts: Vec<String> = parts
733 .iter()
734 .filter_map(|part| match part {
735 ResponseContentPart::InputText { text } => {
736 Some(text.clone())
737 }
738 _ => None,
739 })
740 .collect();
741 if texts.is_empty() {
742 None
743 } else {
744 Some(texts.join(" "))
745 }
746 }
747 }
748 }
749 ResponseInputOutputItem::Reasoning { content, .. } => {
750 let texts: Vec<String> = content
751 .iter()
752 .map(|part| match part {
753 ResponseReasoningContent::ReasoningText { text } => text.clone(),
754 })
755 .collect();
756 if texts.is_empty() {
757 None
758 } else {
759 Some(texts.join(" "))
760 }
761 }
762 ResponseInputOutputItem::FunctionToolCall { arguments, .. } => {
763 Some(arguments.clone())
764 }
765 ResponseInputOutputItem::FunctionCallOutput { output, .. } => {
766 Some(output.clone())
767 }
768 })
769 .collect::<Vec<String>>()
770 .join(" "),
771 }
772 }
773}
774
775pub fn validate_conversation_id(conv_id: &str) -> Result<(), validator::ValidationError> {
777 if !conv_id.starts_with("conv_") {
778 let mut error = validator::ValidationError::new("invalid_conversation_id");
779 error.message = Some(std::borrow::Cow::Owned(format!(
780 "Invalid 'conversation': '{}'. Expected an ID that begins with 'conv_'.",
781 conv_id
782 )));
783 return Err(error);
784 }
785
786 let is_valid = conv_id
788 .chars()
789 .all(|c| c.is_alphanumeric() || c == '_' || c == '-');
790
791 if !is_valid {
792 let mut error = validator::ValidationError::new("invalid_conversation_id");
793 error.message = Some(std::borrow::Cow::Owned(format!(
794 "Invalid 'conversation': '{}'. Expected an ID that contains letters, numbers, underscores, or dashes, but this value contained additional characters.",
795 conv_id
796 )));
797 return Err(error);
798 }
799 Ok(())
800}
801
802fn validate_tool_choice_with_tools(
804 request: &ResponsesRequest,
805) -> Result<(), validator::ValidationError> {
806 let Some(tool_choice) = &request.tool_choice else {
807 return Ok(());
808 };
809
810 let has_tools = request.tools.as_ref().is_some_and(|t| !t.is_empty());
811 let is_some_choice = !matches!(tool_choice, ToolChoice::Value(ToolChoiceValue::None));
812
813 if is_some_choice && !has_tools {
815 let mut e = validator::ValidationError::new("tool_choice_requires_tools");
816 e.message = Some("Invalid value for 'tool_choice': 'tool_choice' is only allowed when 'tools' are specified.".into());
817 return Err(e);
818 }
819
820 if !has_tools {
822 return Ok(());
823 }
824
825 let tools = request.tools.as_ref().unwrap();
827 let function_tool_names: Vec<&str> = tools
828 .iter()
829 .filter_map(|t| match t.r#type {
830 ResponseToolType::Function => t.function.as_ref().map(|f| f.name.as_str()),
831 _ => None,
832 })
833 .collect();
834
835 match tool_choice {
837 ToolChoice::Function { function, .. } => {
838 if !function_tool_names.contains(&function.name.as_str()) {
839 let mut e = validator::ValidationError::new("tool_choice_function_not_found");
840 e.message = Some(
841 format!(
842 "Invalid value for 'tool_choice': function '{}' not found in 'tools'.",
843 function.name
844 )
845 .into(),
846 );
847 return Err(e);
848 }
849 }
850 ToolChoice::AllowedTools {
851 mode,
852 tools: allowed_tools,
853 ..
854 } => {
855 if mode != "auto" && mode != "required" {
857 let mut e = validator::ValidationError::new("tool_choice_invalid_mode");
858 e.message = Some(
859 format!(
860 "Invalid value for 'tool_choice.mode': must be 'auto' or 'required', got '{}'.",
861 mode
862 )
863 .into(),
864 );
865 return Err(e);
866 }
867
868 for tool_ref in allowed_tools {
870 if let ToolReference::Function { name } = tool_ref {
871 if !function_tool_names.contains(&name.as_str()) {
872 let mut e = validator::ValidationError::new("tool_choice_tool_not_found");
873 e.message = Some(
874 format!(
875 "Invalid value for 'tool_choice.tools': tool '{}' not found in 'tools'.",
876 name
877 )
878 .into(),
879 );
880 return Err(e);
881 }
882 }
883 }
886 }
887 _ => {}
888 }
889
890 Ok(())
891}
892
893fn validate_responses_cross_parameters(
895 request: &ResponsesRequest,
896) -> Result<(), validator::ValidationError> {
897 validate_tool_choice_with_tools(request)?;
899
900 if request.top_logprobs.is_some() {
902 let has_logprobs_include = request
903 .include
904 .as_ref()
905 .is_some_and(|inc| inc.contains(&IncludeField::MessageOutputTextLogprobs));
906
907 if !has_logprobs_include {
908 let mut e = validator::ValidationError::new("top_logprobs_requires_include");
909 e.message = Some(
910 "top_logprobs requires include field with 'message.output_text.logprobs'".into(),
911 );
912 return Err(e);
913 }
914 }
915
916 if request.background == Some(true) && request.stream == Some(true) {
918 let mut e = validator::ValidationError::new("background_conflicts_with_stream");
919 e.message = Some("Cannot use background mode with streaming".into());
920 return Err(e);
921 }
922
923 if request.conversation.is_some() && request.previous_response_id.is_some() {
925 let mut e = validator::ValidationError::new("mutually_exclusive_parameters");
926 e.message = Some("Mutually exclusive parameters. Ensure you are only providing one of: 'previous_response_id' or 'conversation'.".into());
927 return Err(e);
928 }
929
930 if let ResponseInput::Items(items) = &request.input {
932 let has_valid_input = items.iter().any(|item| {
934 matches!(
935 item,
936 ResponseInputOutputItem::Message { .. }
937 | ResponseInputOutputItem::SimpleInputMessage { .. }
938 )
939 });
940
941 if !has_valid_input {
942 let mut e = validator::ValidationError::new("input_missing_user_message");
943 e.message = Some("Input items must contain at least one message".into());
944 return Err(e);
945 }
946 }
947
948 Ok(())
953}
954
955fn validate_response_input(input: &ResponseInput) -> Result<(), validator::ValidationError> {
961 match input {
962 ResponseInput::Text(text) => {
963 if text.is_empty() {
964 let mut e = validator::ValidationError::new("input_text_empty");
965 e.message = Some("Input text cannot be empty".into());
966 return Err(e);
967 }
968 }
969 ResponseInput::Items(items) => {
970 if items.is_empty() {
971 let mut e = validator::ValidationError::new("input_items_empty");
972 e.message = Some("Input items cannot be empty".into());
973 return Err(e);
974 }
975 for item in items {
977 validate_input_item(item)?;
978 }
979 }
980 }
981 Ok(())
982}
983
984fn validate_input_item(item: &ResponseInputOutputItem) -> Result<(), validator::ValidationError> {
986 match item {
987 ResponseInputOutputItem::Message { content, .. } => {
988 if content.is_empty() {
989 let mut e = validator::ValidationError::new("message_content_empty");
990 e.message = Some("Message content cannot be empty".into());
991 return Err(e);
992 }
993 }
994 ResponseInputOutputItem::SimpleInputMessage { content, .. } => match content {
995 StringOrContentParts::String(s) if s.is_empty() => {
996 let mut e = validator::ValidationError::new("message_content_empty");
997 e.message = Some("Message content cannot be empty".into());
998 return Err(e);
999 }
1000 StringOrContentParts::Array(parts) if parts.is_empty() => {
1001 let mut e = validator::ValidationError::new("message_content_empty");
1002 e.message = Some("Message content parts cannot be empty".into());
1003 return Err(e);
1004 }
1005 _ => {}
1006 },
1007 ResponseInputOutputItem::Reasoning { .. } => {
1008 }
1010 ResponseInputOutputItem::FunctionCallOutput { output, .. } => {
1011 if output.is_empty() {
1012 let mut e = validator::ValidationError::new("function_output_empty");
1013 e.message = Some("Function call output cannot be empty".into());
1014 return Err(e);
1015 }
1016 }
1017 _ => {}
1018 }
1019 Ok(())
1020}
1021
1022fn validate_response_tools(tools: &[ResponseTool]) -> Result<(), validator::ValidationError> {
1024 for tool in tools {
1025 match tool.r#type {
1026 ResponseToolType::Function => {
1027 if tool.function.is_none() {
1028 let mut e = validator::ValidationError::new("function_tool_missing_function");
1029 e.message = Some("Function tool must have a function definition".into());
1030 return Err(e);
1031 }
1032 }
1033 ResponseToolType::Mcp => {
1034 if tool.server_url.is_none() {
1035 let mut e = validator::ValidationError::new("mcp_tool_missing_server_url");
1036 e.message = Some("MCP tool must have a server_url".into());
1037 return Err(e);
1038 }
1039 }
1040 _ => {}
1041 }
1042 }
1043 Ok(())
1044}
1045
1046fn validate_text_format(text: &TextConfig) -> Result<(), validator::ValidationError> {
1048 if let Some(TextFormat::JsonSchema { name, .. }) = &text.format {
1049 if name.is_empty() {
1050 let mut e = validator::ValidationError::new("json_schema_name_empty");
1051 e.message = Some("JSON schema name cannot be empty".into());
1052 return Err(e);
1053 }
1054 }
1055 Ok(())
1056}
1057
1058pub fn normalize_input_item(item: &ResponseInputOutputItem) -> ResponseInputOutputItem {
1072 match item {
1073 ResponseInputOutputItem::SimpleInputMessage { content, role, .. } => {
1074 let content_vec = match content {
1075 StringOrContentParts::String(s) => {
1076 vec![ResponseContentPart::InputText { text: s.clone() }]
1077 }
1078 StringOrContentParts::Array(parts) => parts.clone(),
1079 };
1080
1081 ResponseInputOutputItem::Message {
1082 id: generate_id("msg"),
1083 role: role.clone(),
1084 content: content_vec,
1085 status: Some("completed".to_string()),
1086 }
1087 }
1088 _ => item.clone(),
1089 }
1090}
1091
1092pub fn generate_id(prefix: &str) -> String {
1093 use rand::RngCore;
1094 let mut rng = rand::rng();
1095 let mut bytes = [0u8; 25];
1097 rng.fill_bytes(&mut bytes);
1098 let hex_string: String = bytes.iter().map(|b| format!("{:02x}", b)).collect();
1099 format!("{}_{}", prefix, hex_string)
1100}
1101
1102#[derive(Debug, Clone, Deserialize, Serialize)]
1103pub struct ResponsesResponse {
1104 pub id: String,
1106
1107 #[serde(default = "default_object_type")]
1109 pub object: String,
1110
1111 pub created_at: i64,
1113
1114 pub status: ResponseStatus,
1116
1117 #[serde(skip_serializing_if = "Option::is_none")]
1119 pub error: Option<Value>,
1120
1121 #[serde(skip_serializing_if = "Option::is_none")]
1123 pub incomplete_details: Option<Value>,
1124
1125 #[serde(skip_serializing_if = "Option::is_none")]
1127 pub instructions: Option<String>,
1128
1129 #[serde(skip_serializing_if = "Option::is_none")]
1131 pub max_output_tokens: Option<u32>,
1132
1133 pub model: String,
1135
1136 #[serde(default)]
1138 pub output: Vec<ResponseOutputItem>,
1139
1140 #[serde(default = "default_true")]
1142 pub parallel_tool_calls: bool,
1143
1144 #[serde(skip_serializing_if = "Option::is_none")]
1146 pub previous_response_id: Option<String>,
1147
1148 #[serde(skip_serializing_if = "Option::is_none")]
1150 pub reasoning: Option<ReasoningInfo>,
1151
1152 #[serde(default = "default_true")]
1154 pub store: bool,
1155
1156 #[serde(skip_serializing_if = "Option::is_none")]
1158 pub temperature: Option<f32>,
1159
1160 #[serde(skip_serializing_if = "Option::is_none")]
1162 pub text: Option<TextConfig>,
1163
1164 #[serde(default = "default_tool_choice")]
1166 pub tool_choice: String,
1167
1168 #[serde(default)]
1170 pub tools: Vec<ResponseTool>,
1171
1172 #[serde(skip_serializing_if = "Option::is_none")]
1174 pub top_p: Option<f32>,
1175
1176 #[serde(skip_serializing_if = "Option::is_none")]
1178 pub truncation: Option<String>,
1179
1180 #[serde(skip_serializing_if = "Option::is_none")]
1182 pub usage: Option<ResponsesUsage>,
1183
1184 #[serde(skip_serializing_if = "Option::is_none")]
1186 pub user: Option<String>,
1187
1188 #[serde(skip_serializing_if = "Option::is_none")]
1190 pub safety_identifier: Option<String>,
1191
1192 #[serde(default)]
1194 pub metadata: HashMap<String, Value>,
1195}
1196
1197fn default_object_type() -> String {
1198 "response".to_string()
1199}
1200
1201fn default_tool_choice() -> String {
1202 "auto".to_string()
1203}
1204
1205impl ResponsesResponse {
1206 pub fn builder(id: impl Into<String>, model: impl Into<String>) -> ResponsesResponseBuilder {
1208 ResponsesResponseBuilder::new(id, model)
1209 }
1210
1211 pub fn is_complete(&self) -> bool {
1213 matches!(self.status, ResponseStatus::Completed)
1214 }
1215
1216 pub fn is_in_progress(&self) -> bool {
1218 matches!(self.status, ResponseStatus::InProgress)
1219 }
1220
1221 pub fn is_failed(&self) -> bool {
1223 matches!(self.status, ResponseStatus::Failed)
1224 }
1225}
1226
1227impl ResponseOutputItem {
1228 pub fn new_message(
1230 id: String,
1231 role: String,
1232 content: Vec<ResponseContentPart>,
1233 status: String,
1234 ) -> Self {
1235 Self::Message {
1236 id,
1237 role,
1238 content,
1239 status,
1240 }
1241 }
1242
1243 pub fn new_reasoning(
1245 id: String,
1246 summary: Vec<String>,
1247 content: Vec<ResponseReasoningContent>,
1248 status: Option<String>,
1249 ) -> Self {
1250 Self::Reasoning {
1251 id,
1252 summary,
1253 content,
1254 status,
1255 }
1256 }
1257
1258 pub fn new_function_tool_call(
1260 id: String,
1261 call_id: String,
1262 name: String,
1263 arguments: String,
1264 output: Option<String>,
1265 status: String,
1266 ) -> Self {
1267 Self::FunctionToolCall {
1268 id,
1269 call_id,
1270 name,
1271 arguments,
1272 output,
1273 status,
1274 }
1275 }
1276}
1277
1278impl ResponseContentPart {
1279 pub fn new_text(
1281 text: String,
1282 annotations: Vec<String>,
1283 logprobs: Option<ChatLogProbs>,
1284 ) -> Self {
1285 Self::OutputText {
1286 text,
1287 annotations,
1288 logprobs,
1289 }
1290 }
1291}
1292
1293impl ResponseReasoningContent {
1294 pub fn new_reasoning_text(text: String) -> Self {
1296 Self::ReasoningText { text }
1297 }
1298}